From ef57c625762b1337a4c50d437c6ad05a76625c32 Mon Sep 17 00:00:00 2001 From: "Andrey.Lozhkin" Date: Fri, 28 Aug 2020 19:33:36 +0300 Subject: [PATCH] [Gradle, Cocoapods] refactor I/O, improve Up-To-Date all synthetic tasks are per pod tasks receive parts of extension as inputs --- .../jetbrains/kotlin/gradle/CocoaPodsIT.kt | 388 ++++++++++++++++-- .../native/cocoapods/KotlinCocoapodsPlugin.kt | 152 ++++--- .../native/tasks/AdvancedCocoapodsTasks.kt | 242 ++++++----- .../targets/native/tasks/CocoapodsTasks.kt | 84 ++-- 4 files changed, 637 insertions(+), 229 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CocoaPodsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CocoaPodsIT.kt index c1bded7b3c0..10e844fce10 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CocoaPodsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CocoaPodsIT.kt @@ -7,7 +7,7 @@ package org.jetbrains.kotlin.gradle import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.DUMMY_FRAMEWORK_TASK_NAME -import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_BUILD_DEPENDENCIES_TASK_NAME +import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_BUILD_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_DOWNLOAD_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_GEN_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_IMPORT_TASK_NAME @@ -16,10 +16,8 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Compan import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SPEC_TASK_NAME import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.konan.target.HostManager -import org.junit.Assume.assumeFalse import org.junit.Assume.assumeTrue import org.junit.Before -import org.junit.Ignore import org.junit.Test import java.io.File import java.util.concurrent.TimeUnit @@ -46,7 +44,7 @@ class CocoaPodsIT : BaseGradleIT() { private val podspecTaskName = ":$POD_SPEC_TASK_NAME" private val podDownloadTaskName = ":$POD_DOWNLOAD_TASK_NAME" private val podGenTaskName = ":$POD_GEN_TASK_NAME" - private val podBuildTaskName = ":$POD_BUILD_DEPENDENCIES_TASK_NAME" + private val podBuildTaskName = ":$POD_BUILD_TASK_NAME" private val podSetupBuildTaskName = ":$POD_SETUP_BUILD_TASK_NAME" private val podImportTaskName = ":$POD_IMPORT_TASK_NAME" private val podInstallTaskName = ":$POD_INSTALL_TASK_NAME" @@ -54,14 +52,30 @@ class CocoaPodsIT : BaseGradleIT() { private val defaultPodRepo = "https://github.com/AFNetworking/AFNetworking" private val defaultPodName = "AFNetworking" + private val downloadUrlPodName = "podspecWithFilesExample" + private val downloadUrlRepoName = "https://github.com/alozhkin/podspecWithFilesExample/raw/master" private val defaultTarget = "IOS" private val defaultFamily = "IOS" private val defaultSDK = "iphonesimulator" - private val defaultPodDownloadTaskName = podDownloadTaskName + defaultPodName - private val defaultPodGenTaskName = podGenTaskName + defaultFamily - private val defaultBuildTaskName = podBuildTaskName + defaultSDK.capitalize() - private val defaultSetupBuildTaskName = podSetupBuildTaskName + defaultSDK.capitalize() + private val defaultPodDownloadTaskName = podDownloadFullTaskName() + private val defaultPodGenTaskName = podGenFullTaskName() + private val defaultBuildTaskName = podBuildFullTaskName() + private val defaultSetupBuildTaskName = podSetupBuildFullTaskName() private val defaultCinteropTaskName = cinteropTaskName + defaultPodName + defaultTarget + private val downloadUrlTaskName = podDownloadTaskName + downloadUrlPodName.capitalize() + + private fun podDownloadFullTaskName(podName: String = defaultPodName) = podDownloadTaskName + podName.capitalize() + + private fun podGenFullTaskName(familyName: String = defaultFamily) = podGenTaskName + familyName.capitalize() + + private fun podSetupBuildFullTaskName(podName: String = defaultPodName, sdkName: String = defaultSDK) = + podSetupBuildTaskName + podName.capitalize() + sdkName.capitalize() + + private fun podBuildFullTaskName(podName: String = defaultPodName, sdkName: String = defaultSDK) = + podBuildTaskName + podName.capitalize() + sdkName.capitalize() + + private fun cinteropFullTaskName(podName: String = defaultPodName, targetName: String = defaultTarget) = + cinteropTaskName + podName.capitalize() + targetName.capitalize() private lateinit var hooks: CustomHooks private lateinit var project: BaseGradleIT.Project @@ -118,10 +132,10 @@ class CocoaPodsIT : BaseGradleIT() { ) @Test - fun testPodImportUseFrameworksSingle() = doTestImportSingle(ImportMode.FRAMEWORKS) + fun testPodImportSingle() = doTestImportSingle() @Test - fun testPodImportUseModularHeadersSingle() = doTestImportSingle(ImportMode.MODULAR_HEADERS) + fun testPodImportMultiple() = doTestImportMultiple() @Test fun testPodspecMultiple() = doTestPodspec( @@ -173,20 +187,14 @@ class CocoaPodsIT : BaseGradleIT() { ) @Test - fun testPodImportUseFrameworksMultiple() = doTestImportMultiple(ImportMode.FRAMEWORKS) - - @Test - fun testPodImportUseModularHeadersMultiple() = doTestImportMultiple(ImportMode.MODULAR_HEADERS) - - @Test - fun testSpecReposDownloads() { + fun testSpecReposImport() { val podName = "example" - val podRepo = "https://github.com/alozhkin/spec_repo_example" + val podRepo = "https://github.com/alozhkin/spec_repo" with(project.gradleBuildScript()) { addPod(podName) addSpecRepo(podRepo) } - project.testDownload(listOf(podRepo)) + project.testImportWithAsserts(listOf(podRepo)) } @Test @@ -275,19 +283,6 @@ class CocoaPodsIT : BaseGradleIT() { project.test(":kotlin-library:podDownload") } - @Ignore - @Test - fun testSubspecWithModuleName() { - with(project.gradleBuildScript()) { - addPod("AFNetworking/Reachability", "moduleName = \"CustomName\"") - addPod("AFNetworking/Security") - } - hooks.addHook { - assertTasksExecuted(defaultCinteropTaskName, cinteropTaskName + "AFNetworking/Security" + defaultTarget) - } - project.testImportWithAsserts() - } - // up-to-date tests @@ -355,6 +350,282 @@ class CocoaPodsIT : BaseGradleIT() { project.testSynthetic(defaultPodGenTaskName) } + @Test + fun testUTDPodAdded() { + with(project.gradleBuildScript()) { + addPod(defaultPodName, produceGitBlock(defaultPodRepo)) + } + project.testImport(listOf(defaultPodRepo)) + + val anotherPodName = "Alamofire" + val anotherPodRepo = "https://github.com/Alamofire/Alamofire" + with(project.gradleBuildScript()) { + addPod(anotherPodName, produceGitBlock(anotherPodRepo)) + } + hooks.rewriteHooks { + assertTasksExecuted( + podspecTaskName, + podDownloadFullTaskName(anotherPodName), + defaultPodGenTaskName, + podSetupBuildFullTaskName(anotherPodName), + podBuildFullTaskName(anotherPodName), + cinteropFullTaskName(anotherPodName) + ) + assertTasksUpToDate( + defaultPodDownloadTaskName, + defaultSetupBuildTaskName, + defaultBuildTaskName, + defaultCinteropTaskName + ) + } + project.testImport(listOf(defaultPodRepo, anotherPodRepo)) + + with(project.gradleBuildScript()) { + removePod(anotherPodName) + } + hooks.rewriteHooks { + assertTasksNotRegisteredByPrefix( + listOf( + podDownloadFullTaskName(anotherPodName), + podBuildFullTaskName(anotherPodName), + cinteropFullTaskName(anotherPodName) + ) + ) + assertTasksUpToDate( + defaultBuildTaskName, + defaultPodDownloadTaskName, + defaultSetupBuildTaskName, + defaultCinteropTaskName + ) + } + project.testImport(listOf(defaultPodRepo)) + } + + @Test + fun testImportSubspecs() { + with(project.gradleBuildScript()) { + addPod("SDWebImage/Core") + addPod("SDWebImage/MapKit") + } + project.testImport(listOf(defaultPodRepo)) + } + + @Test + fun testUTDTargetAdded() { + with(project.gradleBuildScript()) { + addPod(defaultPodName, produceGitBlock(defaultPodRepo)) + appendToCocoapodsBlock("osx.deploymentTarget = \"13.5\"") + } + project.testImport(listOf(defaultPodRepo)) + + val anotherTarget = "MacosX64" + val anotherSdk = "macosx" + val anotherFamily = "OSX" + with(project.gradleBuildScript()) { + appendToKotlinBlock(anotherTarget.decapitalize() + "()") + } + hooks.rewriteHooks { + assertTasksExecuted( + podGenFullTaskName(anotherFamily), + podSetupBuildFullTaskName(sdkName = anotherSdk), + podBuildFullTaskName(sdkName = anotherSdk), + cinteropFullTaskName(targetName = anotherTarget) + ) + assertTasksUpToDate( + podspecTaskName, + defaultPodDownloadTaskName, + defaultPodGenTaskName, + defaultSetupBuildTaskName, + defaultBuildTaskName, + defaultCinteropTaskName + ) + } + project.testImport(listOf(defaultPodRepo)) + + with(project.gradleBuildScript()) { + var text = readText() + text = text.replace(anotherTarget.decapitalize() + "()", "") + writeText(text) + } + hooks.rewriteHooks { + assertTasksNotRegisteredByPrefix( + listOf( + podGenFullTaskName(anotherFamily), + podSetupBuildFullTaskName(sdkName = anotherSdk), + podBuildFullTaskName(sdkName = anotherSdk), + cinteropFullTaskName(targetName = anotherTarget) + ) + ) + assertTasksUpToDate( + podspecTaskName, + defaultPodDownloadTaskName, + defaultPodGenTaskName, + defaultSetupBuildTaskName, + defaultBuildTaskName, + defaultCinteropTaskName + ) + } + project.testImport(listOf(defaultPodRepo)) + } + + @Test + fun testUTDPodspec() { + project.testWithWrapper(podspecTaskName) + hooks.addHook { + assertTasksExecuted(podspecTaskName) + } + with(project.gradleBuildScript()) { + appendToCocoapodsBlock("license = \"new license name\"") + } + project.testWithWrapper(podspecTaskName) + with(project.gradleBuildScript()) { + appendToCocoapodsBlock("license = \"new license name\"") + } + hooks.rewriteHooks { + assertTasksUpToDate(podspecTaskName) + } + project.testWithWrapper(podspecTaskName) + } + + @Test + fun testUTDPodspecDeploymentTarget() { + project.testWithWrapper(podspecTaskName) + hooks.addHook { + assertTasksExecuted(podspecTaskName) + } + with(project.gradleBuildScript()) { + appendToCocoapodsBlock("ios.deploymentTarget = \"12.5\"") + } + project.testWithWrapper(podspecTaskName) + hooks.rewriteHooks { + assertTasksUpToDate(podspecTaskName) + } + project.testWithWrapper(podspecTaskName) + } + + @Test + fun testUTDDownload() { + val gitRepo = downloadUrlRepoName.substringBeforeLast("/").substringBeforeLast("/") + with(project.gradleBuildScript()) { + addPod(downloadUrlPodName, produceGitBlock(repo = gitRepo)) + } + hooks.addHook { + assertTasksExecuted(downloadUrlTaskName) + } + project.testDownload(listOf(gitRepo)) + with(project.gradleBuildScript()) { + changePod(downloadUrlPodName, produceGitBlock(repo = gitRepo, commitName = "f52f035018b4f3fe253d50ce85a7e0652a62ee9b")) + } + project.testDownload(listOf(gitRepo)) + hooks.rewriteHooks { + assertTasksUpToDate(downloadUrlTaskName) + } + project.testDownload(listOf(gitRepo)) + val podArchivePath = "$downloadUrlRepoName/$downloadUrlPodName.tar.gz" + with(project.gradleBuildScript()) { + changePod(downloadUrlPodName, "source = url(\"$podArchivePath\")") + } + hooks.rewriteHooks { + assertTasksExecuted(downloadUrlTaskName) + } + project.testDownload(listOf(podArchivePath)) + } + + @Test + fun testUTDPodGen() { + with(project.gradleBuildScript()) { + addPod(defaultPodName) + } + val repos = listOf( + "https://github.com/alozhkin/spec_repo_example", + "https://github.com/alozhkin/spec_repo_example_2" + ) + for (repo in repos) { + assumeTrue(isRepoAvailable(repo)) + } + hooks.addHook { + assertTasksExecuted(defaultPodGenTaskName) + } + project.testSynthetic(defaultPodGenTaskName) + with(project.gradleBuildScript()) { + addSpecRepo("https://github.com/alozhkin/spec_repo_example") + } + project.testSynthetic(defaultPodGenTaskName) + with(project.gradleBuildScript()) { + addSpecRepo("https://github.com/alozhkin/spec_repo_example_2") + } + project.testSynthetic(defaultPodGenTaskName) + hooks.rewriteHooks { + assertTasksUpToDate(defaultPodGenTaskName) + } + project.testSynthetic(defaultPodGenTaskName) + } + + @Test + fun testUTDBuild() { + with(project.gradleBuildScript()) { + addPod(defaultPodName, produceGitBlock()) + } + hooks.addHook { + assertTasksExecuted(defaultBuildTaskName) + } + project.testImport() + + val repo = "$downloadUrlRepoName/$downloadUrlPodName.tar.gz" + with(project.gradleBuildScript()) { + addPod(downloadUrlPodName, "source = url(\"$repo\")") + } + val urlTaskName = podBuildFullTaskName(downloadUrlPodName) + hooks.rewriteHooks { + assertTasksUpToDate(defaultBuildTaskName) + assertTasksExecuted(urlTaskName) + } + project.testImport() + + val anotherTarget = "MacosX64" + val anotherSdk = "macosx" + with(project.gradleBuildScript()) { + appendToCocoapodsBlock("osx.deploymentTarget = \"13.5\"") + appendToKotlinBlock(anotherTarget.decapitalize() + "()") + } + val anotherSdkDefaultPodTaskName = podBuildFullTaskName(sdkName = anotherSdk) + val anotherTargetUrlTaskName = podBuildFullTaskName(downloadUrlPodName, anotherSdk) + hooks.rewriteHooks { + assertTasksUpToDate(defaultBuildTaskName, urlTaskName) + assertTasksExecuted(anotherSdkDefaultPodTaskName, anotherTargetUrlTaskName) + } + project.testImport() + + hooks.rewriteHooks { + assertTasksUpToDate(defaultBuildTaskName, urlTaskName, anotherSdkDefaultPodTaskName, anotherTargetUrlTaskName) + } + project.testImport() + } + + @Test + fun testPodBuildUTDClean() { + with(project.gradleBuildScript()) { + addPod(defaultPodName, produceGitBlock()) + } + hooks.addHook { + assertTasksExecuted(defaultBuildTaskName) + } + project.testImport() + + hooks.rewriteHooks {} + project.test(":clean") + + hooks.addHook { + assertTasksExecuted(defaultBuildTaskName) + } + project.testImport() + } + + @Test + fun testPodInstallWithoutPodFile() { + project.testSynthetic(podInstallTaskName) + } + // groovy tests @@ -395,6 +666,13 @@ class CocoaPodsIT : BaseGradleIT() { project.testImportWithAsserts(listOf(repo)) } + @Test + fun supportPodsWithDependencies() { + with(project.gradleBuildScript()) { + addPod("AlamofireImage") + } + project.testImportWithAsserts() + } // paths @@ -408,7 +686,7 @@ class CocoaPodsIT : BaseGradleIT() { // test configuration phase - private class CustomHooks() { + private class CustomHooks { private val hooks = mutableSetOf Unit>() fun addHook(hook: CompiledProject.() -> Unit) { @@ -427,21 +705,21 @@ class CocoaPodsIT : BaseGradleIT() { } } - private fun doTestImportSingle(importMode: ImportMode) { + private fun doTestImportSingle() { val project = getProjectByName(cocoapodsSingleKtPod) val subprojects = listOf("kotlin-library") - doTestPodImport(project, subprojects, importMode) + doTestPodImport(project, subprojects) } - private fun doTestImportMultiple(importMode: ImportMode) { + private fun doTestImportMultiple() { val project = getProjectByName(cocoapodsMultipleKtPods) val subprojects = listOf("kotlin-library", "second-library") - doTestPodImport(project, subprojects, importMode) + doTestPodImport(project, subprojects) } - private fun doTestPodImport(project: BaseGradleIT.Project, subprojects: List, importMode: ImportMode) { + private fun doTestPodImport(project: BaseGradleIT.Project, subprojects: List) { with(project) { - preparePodfile("ios-app", importMode) + preparePodfile("ios-app", ImportMode.FRAMEWORKS) } project.testImportWithAsserts() subprojects.forEach { @@ -468,10 +746,10 @@ class CocoaPodsIT : BaseGradleIT() { project.testDownload(listOf(repo)) } - fun doTestPodDownloadUrl( + private fun doTestPodDownloadUrl( fileExtension: String, - podName: String = "podspecWithFilesExample", - repoPath: String = "https://github.com/alozhkin/podspecWithFilesExample/raw/master", + podName: String = downloadUrlPodName, + repoPath: String = downloadUrlRepoName, archiveName: String = podName, flatten: Boolean = false ) { @@ -554,6 +832,30 @@ class CocoaPodsIT : BaseGradleIT() { appendToCocoapodsBlock(podBlock) } + private fun File.removePod(podName: String) { + val text = readText() + val begin = text.indexOf("""pod("$podName")""") + require(begin != -1) { "Pod doesn't exist in file" } + var index = begin + """pod("$podName")""".length - 1 + if (text.indexOf("""pod("$podName") {""", startIndex = begin) != -1) { + index += 2 + var bracket = 1 + while (bracket != 0) { + if (text[++index] == '{') { + bracket++ + } else if (text[index] == '}') { + bracket-- + } + } + } + writeText(text.removeRange(begin..index)) + } + + private fun File.changePod(podName: String, newConfiguration: String? = null) { + removePod(podName) + addPod(podName, newConfiguration) + } + private fun File.addSpecRepo(specRepo: String) = appendToCocoapodsBlock("url(\"$specRepo\")".wrap("specRepos")) private fun File.appendToKotlinBlock(str: String) = appendLine(str.wrap("kotlin")) @@ -691,7 +993,7 @@ class CocoaPodsIT : BaseGradleIT() { assertTasksExecutedByPrefix(listOf("$taskPrefix:$POD_GEN_TASK_NAME")) } - with(listOf(POD_SETUP_BUILD_TASK_NAME, POD_BUILD_DEPENDENCIES_TASK_NAME).map { "$taskPrefix:$it" }) { + with(listOf(POD_SETUP_BUILD_TASK_NAME, POD_BUILD_TASK_NAME).map { "$taskPrefix:$it" }) { if (buildScriptText.matches("pod\\(.*\\)".toRegex())) { assertTasksRegisteredByPrefix(this) assertTasksExecutedByPrefix(this) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt index ad94b594ef3..bc66c680865 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt @@ -11,6 +11,7 @@ import org.gradle.api.Project import org.gradle.api.provider.Provider import org.gradle.api.tasks.Sync import org.gradle.api.tasks.TaskProvider +import org.jetbrains.kotlin.daemon.common.trimQuotes import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation @@ -29,6 +30,7 @@ import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget.* import java.io.File +import java.util.* internal val Project.cocoapodsBuildDirs: CocoapodsBuildDirs get() = CocoapodsBuildDirs(this) @@ -68,17 +70,17 @@ private val Family.toPodGenTaskName: String name ) -private val String.toSetupBuildTaskName: String - get() = lowerCamelCaseName( - KotlinCocoapodsPlugin.POD_SETUP_BUILD_TASK_NAME, - this - ) +private fun String.toSetupBuildTaskName(pod: CocoapodsDependency): String = lowerCamelCaseName( + KotlinCocoapodsPlugin.POD_SETUP_BUILD_TASK_NAME, + pod.schemeName, + this +) -private val String.toBuildDependenciesTaskName: String - get() = lowerCamelCaseName( - KotlinCocoapodsPlugin.POD_BUILD_DEPENDENCIES_TASK_NAME, - this - ) +private fun String.toBuildDependenciesTaskName(pod: CocoapodsDependency): String = lowerCamelCaseName( + KotlinCocoapodsPlugin.POD_BUILD_TASK_NAME, + pod.schemeName, + this +) private val CocoapodsDependency.toPodDownloadTaskName: String get() = lowerCamelCaseName( @@ -225,7 +227,7 @@ open class KotlinCocoapodsPlugin : Plugin { val defTask = project.registerTask( lowerCamelCaseName("generateDef", pod.moduleName).asValidTaskName() ) { - it.pod = pod + it.pod = project.provider { pod } it.description = "Generates a def file for CocoaPods dependencies with module ${pod.moduleName}" // This task is an implementation detail so we don't add it in any group // to avoid showing it in the `tasks` output. @@ -248,8 +250,8 @@ open class KotlinCocoapodsPlugin : Plugin { && project.findProperty(CONFIGURATION_PROPERTY) == null ) { val podBuildTaskProvider = - project.tasks.named(target.toValidSDK.toBuildDependenciesTaskName, PodBuildTask::class.java) - interopTask.inputs.file(podBuildTaskProvider.get().buildSettingsFileProvider) + project.tasks.named(target.toValidSDK.toBuildDependenciesTaskName(pod), PodBuildTask::class.java) + interopTask.inputs.file(podBuildTaskProvider.get().buildSettingsFile) interopTask.dependsOn(podBuildTaskProvider) } @@ -275,9 +277,9 @@ open class KotlinCocoapodsPlugin : Plugin { && project.findProperty(CONFIGURATION_PROPERTY) == null ) { val podBuildTaskProvider = - project.tasks.named(target.toValidSDK.toBuildDependenciesTaskName, PodBuildTask::class.java) + project.tasks.named(target.toValidSDK.toBuildDependenciesTaskName(pod), PodBuildTask::class.java) val buildSettings = - podBuildTaskProvider.get().buildSettingsFileProvider.get() + podBuildTaskProvider.get().buildSettingsFile.get() .inputStream() .use { PodBuildSettingsProperties.readSettingsFromStream(it) @@ -291,9 +293,14 @@ open class KotlinCocoapodsPlugin : Plugin { buildSettings.headerPaths?.let { args -> interop.compilerOpts.addAll(args.splitQuotedArgs().map { "-I$it" }) } - buildSettings.frameworkPaths?.let { args -> - interop.compilerOpts.addAll(args.splitQuotedArgs().map { "-F$it" }) - } + + val frameworkPaths = buildSettings.frameworkPaths + val configurationBuildDir = buildSettings.configurationBuildDir + val frameworkPathsSelfIncluding = mutableListOf() + frameworkPathsSelfIncluding += configurationBuildDir.trimQuotes() + frameworkPaths?.let { frameworkPathsSelfIncluding.addAll(it.splitQuotedArgs()) } + + interop.compilerOpts.addAll(frameworkPathsSelfIncluding.map { "-F$it" }) } } } @@ -306,7 +313,7 @@ open class KotlinCocoapodsPlugin : Plugin { cocoapodsExtension: CocoapodsExtension ) { project.tasks.register(DUMMY_FRAMEWORK_TASK_NAME, DummyFrameworkTask::class.java) { - it.cocoapodsExtension = cocoapodsExtension + it.frameworkName = project.provider { cocoapodsExtension.frameworkName } } } @@ -319,7 +326,18 @@ open class KotlinCocoapodsPlugin : Plugin { project.tasks.register(POD_SPEC_TASK_NAME, PodspecTask::class.java) { it.group = TASK_GROUP it.description = "Generates a podspec file for CocoaPods import" - it.cocoapodsExtension = cocoapodsExtension + it.needPodspec = project.provider { cocoapodsExtension.needPodspec } + it.pods.set(cocoapodsExtension.pods) + it.version = project.provider { cocoapodsExtension.version } + it.homepage.set(cocoapodsExtension.homepage) + it.license.set(cocoapodsExtension.license) + it.authors.set(cocoapodsExtension.authors) + it.summary.set(cocoapodsExtension.summary) + it.frameworkName = project.provider { cocoapodsExtension.frameworkName } + it.ios = project.provider { cocoapodsExtension.ios } + it.osx = project.provider { cocoapodsExtension.osx } + it.tvos = project.provider { cocoapodsExtension.tvos } + it.watchos = project.provider { cocoapodsExtension.watchos } it.dependsOn(dummyFrameworkTaskProvider) val generateWrapper = project.findProperty(GENERATE_WRAPPER_PROPERTY)?.toString()?.toBoolean() ?: false if (generateWrapper) { @@ -335,13 +353,13 @@ open class KotlinCocoapodsPlugin : Plugin { project.tasks.register(POD_INSTALL_TASK_NAME, PodInstallTask::class.java) { it.group = TASK_GROUP it.description = "Invokes `pod install` call within Podfile location directory" - it.cocoapodsExtension = cocoapodsExtension + it.podfile.set(cocoapodsExtension.podfile) it.onlyIf { isAvailableToProduceSynthetic } //TODO avoid subproject task management here project.allprojects.map { it.tasks.named(POD_SPEC_TASK_NAME, PodspecTask::class.java) } .forEach { podspecTaskProvider -> - podspecTaskProvider.get().takeIf { task -> task.cocoapodsExtension.needPodspec } + podspecTaskProvider.get().takeIf { task -> task.needPodspec.get() } ?.also { task -> it.inputs.file(task.outputFileProvider) } it.dependsOn(podspecTaskProvider) } @@ -361,12 +379,10 @@ open class KotlinCocoapodsPlugin : Plugin { val podSource = pod.source val downloadPodTask = when (podSource) { is Git -> project.tasks.register(pod.toPodDownloadTaskName, PodDownloadGitTask::class.java) { - it.cocoapodsExtension = cocoapodsExtension it.podName = project.provider { pod.name } it.podSource = project.provider { podSource as Git } } is Url -> project.tasks.register(pod.toPodDownloadTaskName, PodDownloadUrlTask::class.java) { - it.cocoapodsExtension = cocoapodsExtension it.podName = project.provider { pod.name } it.podSource = project.provider { podSource as Url } } @@ -394,10 +410,10 @@ open class KotlinCocoapodsPlugin : Plugin { project.tasks.register(family.toPodGenTaskName, PodGenTask::class.java) { it.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies" - it.podspecProvider = podspecTaskProvider.get().outputFileProvider + it.podspec = podspecTaskProvider.map { task -> task.outputFileProvider.get() } + it.specRepos = project.provider { cocoapodsExtension.specRepos } it.family = family - it.cocoapodsExtension = cocoapodsExtension - it.dependsOn(podspecTaskProvider) + it.pods.set(cocoapodsExtension.pods) it.dependsOn(downloadPods) it.onlyIf { isAvailableToProduceSynthetic } } @@ -409,30 +425,36 @@ open class KotlinCocoapodsPlugin : Plugin { kotlinExtension: KotlinMultiplatformExtension, cocoapodsExtension: CocoapodsExtension ) { - val sdks = mutableSetOf() + val schemeNames = mutableSetOf() - kotlinExtension.supportedTargets().all { target -> + cocoapodsExtension.pods.all { pod -> - val sdk = target.toValidSDK - if (sdk in sdks) { + if (schemeNames.contains(pod.schemeName)) { return@all } - sdks += sdk + schemeNames.add(pod.schemeName) - val podGenTaskProvider = project.tasks.named(target.konanTarget.family.toPodGenTaskName, PodGenTask::class.java) + val sdks = mutableSetOf() - project.tasks.register(sdk.toSetupBuildTaskName, PodSetupBuildTask::class.java) { - it.group = TASK_GROUP - it.description = "Collect environment variables from .xcworkspace file" - it.cocoapodsExtension = cocoapodsExtension - it.sdk = project.provider { sdk } + kotlinExtension.supportedTargets().all loop@{ target -> - val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME, PodspecTask::class.java) - it.schemeName = podspecTaskProvider.map { it.specName } + val sdk = target.toValidSDK + if (sdk in sdks) { + return@loop + } + sdks += sdk - it.podsXcodeProjDirProvider = podGenTaskProvider.get().podsXcodeProjDirProvider - it.dependsOn(podGenTaskProvider) - it.onlyIf { isAvailableToProduceSynthetic } + val podGenTaskProvider = project.tasks.named(target.konanTarget.family.toPodGenTaskName, PodGenTask::class.java) + project.tasks.register(sdk.toSetupBuildTaskName(pod), PodSetupBuildTask::class.java) { + it.group = TASK_GROUP + it.description = "Collect environment variables from .xcworkspace file" + it.pod = project.provider { pod } + it.sdk = project.provider { sdk } + it.podsXcodeProjDir = podGenTaskProvider.map { podGen -> podGen.podsXcodeProjDir.get() } + it.frameworkName = project.provider { cocoapodsExtension.frameworkName } + it.dependsOn(podGenTaskProvider) + it.onlyIf { isAvailableToProduceSynthetic } + } } } } @@ -442,27 +464,38 @@ open class KotlinCocoapodsPlugin : Plugin { kotlinExtension: KotlinMultiplatformExtension, cocoapodsExtension: CocoapodsExtension ) { - val sdks = mutableSetOf() + val schemeNames = mutableSetOf() - kotlinExtension.supportedTargets().all { target -> + cocoapodsExtension.pods.all { pod -> - val sdk = target.toValidSDK - if (sdk in sdks) { + if (schemeNames.contains(pod.schemeName)) { return@all } - sdks += sdk + schemeNames.add(pod.schemeName) - val podSetupBuildTaskProvider = project.tasks.named(sdk.toSetupBuildTaskName, PodSetupBuildTask::class.java) + val sdks = mutableSetOf() - project.tasks.register(sdk.toBuildDependenciesTaskName, PodBuildTask::class.java) { - it.group = TASK_GROUP - it.description = "Calls `xcodebuild` on xcworkspace for the pod scheme" - it.sdk = project.provider { sdk } - it.cocoapodsExtension = cocoapodsExtension - it.podsXcodeProjDirProvider = podSetupBuildTaskProvider.get().podsXcodeProjDirProvider - it.buildSettingsFileProvider = podSetupBuildTaskProvider.get().buildSettingsFileProvider - it.dependsOn(podSetupBuildTaskProvider) - it.onlyIf { isAvailableToProduceSynthetic } + kotlinExtension.supportedTargets().all loop@{ target -> + + val sdk = target.toValidSDK + + if (sdk in sdks) { + return@loop + } + sdks += sdk + + val podSetupBuildTaskProvider = + project.tasks.named(sdk.toSetupBuildTaskName(pod), PodSetupBuildTask::class.java) + + project.tasks.register(sdk.toBuildDependenciesTaskName(pod), PodBuildTask::class.java) { + it.group = TASK_GROUP + it.description = "Calls `xcodebuild` on xcworkspace for the pod scheme" + it.sdk = project.provider { sdk } + it.pod = project.provider { pod } + it.podsXcodeProjDir = podSetupBuildTaskProvider.map { task -> task.podsXcodeProjDir.get() } + it.buildSettingsFile = podSetupBuildTaskProvider.map { task -> task.buildSettingsFile.get() } + it.onlyIf { isAvailableToProduceSynthetic } + } } } } @@ -471,7 +504,6 @@ open class KotlinCocoapodsPlugin : Plugin { project: Project, kotlinExtension: KotlinMultiplatformExtension ) { - val podInstallTaskProvider = project.tasks.named(POD_INSTALL_TASK_NAME, PodInstallTask::class.java) project.tasks.register(POD_IMPORT_TASK_NAME) { it.group = TASK_GROUP @@ -531,7 +563,7 @@ open class KotlinCocoapodsPlugin : Plugin { const val POD_DOWNLOAD_TASK_NAME = "podDownload" const val POD_GEN_TASK_NAME = "podGen" const val POD_SETUP_BUILD_TASK_NAME = "podSetupBuild" - const val POD_BUILD_DEPENDENCIES_TASK_NAME = "podBuildDependencies" + const val POD_BUILD_TASK_NAME = "podBuild" const val POD_IMPORT_TASK_NAME = "podImport" // We don't move these properties in PropertiesProvider because diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/AdvancedCocoapodsTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/AdvancedCocoapodsTasks.kt index 0041302e9f8..7308b9b079b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/AdvancedCocoapodsTasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/AdvancedCocoapodsTasks.kt @@ -7,15 +7,14 @@ package org.jetbrains.kotlin.gradle.targets.native.tasks import org.gradle.api.DefaultTask import org.gradle.api.artifacts.repositories.ArtifactRepository +import org.gradle.api.file.FileCollection import org.gradle.api.file.FileTree import org.gradle.api.file.RelativePath import org.gradle.api.provider.Provider import org.gradle.api.tasks.* import org.gradle.api.tasks.Optional -import org.jetbrains.kotlin.gradle.plugin.cocoapods.* +import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.* import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.* -import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension -import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs import org.jetbrains.kotlin.konan.target.Family import java.io.File @@ -34,6 +33,9 @@ private val Family.platformLiteral: String else -> throw IllegalArgumentException("Bad family ${this.name}") } +val CocoapodsDependency.schemeName: String + get() = name.split("/")[0] + /** * The task takes the path to the Podfile and calls `pod install` * to obtain sources or artifacts for the declared dependencies. @@ -41,24 +43,24 @@ private val Family.platformLiteral: String */ open class PodInstallTask : DefaultTask() { init { - onlyIf { cocoapodsExtension?.podfile != null } + onlyIf { podfile.isPresent } } @get:Optional - @get:Nested - internal var cocoapodsExtension: CocoapodsExtension? = null + @get:Input + internal val podfile = project.objects.property(File::class.java) @get:Optional @get:OutputDirectory internal val podsXcodeProjDirProvider: Provider? - get() = cocoapodsExtension?.podfile?.let { + get() = podfile.orNull?.let { project.provider { it.parentFile.resolve("Pods").resolve("Pods.xcodeproj") } } @TaskAction fun doPodInstall() { - cocoapodsExtension?.podfile?.parentFile?.also { podfileDir -> + podfile.orNull?.parentFile?.also { podfileDir -> val podInstallProcess = ProcessBuilder("pod", "install").apply { directory(podfileDir) }.start() @@ -81,25 +83,12 @@ open class PodInstallTask : DefaultTask() { } } - -abstract class CocoapodsWithSyntheticTask : DefaultTask() { - init { - onlyIf { - cocoapodsExtension.pods.isNotEmpty() - } - } - - @get:Nested - internal lateinit var cocoapodsExtension: CocoapodsExtension -} - -abstract class PodDownloadTask : CocoapodsWithSyntheticTask() { +abstract class DownloadCocoapodsTask : DefaultTask() { @get:Input internal lateinit var podName: Provider } - -open class PodDownloadUrlTask : PodDownloadTask() { +open class PodDownloadUrlTask : DownloadCocoapodsTask() { @get:Nested internal lateinit var podSource: Provider @@ -181,8 +170,7 @@ open class PodDownloadUrlTask : PodDownloadTask() { } } - -open class PodDownloadGitTask : PodDownloadTask() { +open class PodDownloadGitTask : DownloadCocoapodsTask() { @get:Nested internal lateinit var podSource: Provider @@ -328,19 +316,31 @@ private fun runCommand( * The task takes the path to the .podspec file and calls `pod gen` * to create synthetic xcode project and workspace. */ -open class PodGenTask : CocoapodsWithSyntheticTask() { +open class PodGenTask : DefaultTask() { + + init { + onlyIf { + pods.get().isNotEmpty() + } + } @get:InputFile - internal lateinit var podspecProvider: Provider + internal lateinit var podspec: Provider @get:Internal lateinit var family: Family + @get:Nested + internal lateinit var specRepos: Provider + + @get:Nested + val pods = project.objects.listProperty(CocoapodsDependency::class.java) + @get:OutputDirectory - internal val podsXcodeProjDirProvider: Provider + internal val podsXcodeProjDir: Provider get() = project.provider { project.cocoapodsBuildDirs.synthetic(family) - .resolve(podspecProvider.get().nameWithoutExtension) + .resolve(podspec.get().nameWithoutExtension) .resolve("Pods") .resolve("Pods.xcodeproj") } @@ -348,18 +348,18 @@ open class PodGenTask : CocoapodsWithSyntheticTask() { @TaskAction fun generate() { val syntheticDir = project.cocoapodsBuildDirs.synthetic(family).apply { mkdirs() } - val localPodspecPaths = cocoapodsExtension.pods.mapNotNull { it.source?.getLocalPath(project, it.name) } + val localPodspecPaths = pods.get().mapNotNull { it.source?.getLocalPath(project, it.name) } - val sources = cocoapodsExtension.specRepos.getAll().toMutableList() - sources += URI("https://cdn.cocoapods.org") + val specRepos = specRepos.get().getAll().toMutableList() + specRepos += URI("https://cdn.cocoapods.org") val podGenProcessArgs = listOfNotNull( "pod", "gen", "--platforms=${family.platformLiteral}", "--gen-directory=${syntheticDir.absolutePath}", localPodspecPaths.takeIf { it.isNotEmpty() }?.joinToString(separator = ",")?.let { "--local-sources=$it" }, - sources.takeIf { it.isNotEmpty() }?.joinToString(separator = ",")?.let { "--sources=$it" }, - podspecProvider.get().absolutePath + specRepos.takeIf { it.isNotEmpty() }?.joinToString(separator = ",")?.let { "--sources=$it" }, + podspec.get().absolutePath ) val podGenProcess = ProcessBuilder(podGenProcessArgs).apply { @@ -388,7 +388,7 @@ open class PodGenTask : CocoapodsWithSyntheticTask() { ).joinToString("\n") } - val podsXcprojFile = podsXcodeProjDirProvider.get() + val podsXcprojFile = podsXcodeProjDir.get() check(podsXcprojFile.exists() && podsXcprojFile.isDirectory) { "The directory '${podsXcprojFile.path}' was not created as a result of the `pod gen` call." } @@ -396,35 +396,35 @@ open class PodGenTask : CocoapodsWithSyntheticTask() { } -open class PodSetupBuildTask : CocoapodsWithSyntheticTask() { +open class PodSetupBuildTask : DefaultTask() { - @get:InputDirectory - internal lateinit var podsXcodeProjDirProvider: Provider + @get:Input + lateinit var frameworkName: Provider @get:Input internal lateinit var sdk: Provider - @Input - lateinit var schemeName: Provider + @get:Nested + lateinit var pod: Provider @get:OutputFile - internal val buildSettingsFileProvider: Provider = project.provider { + internal val buildSettingsFile: Provider = project.provider { project.cocoapodsBuildDirs .buildSettings - .resolve(sdk.get().toBuildSettingsFileName) + .resolve(getBuildSettingFileName(pod.get(), sdk.get())) } - private val String.toBuildSettingsFileName: String - get() = "build-settings-$this.properties" + @get:Internal + internal lateinit var podsXcodeProjDir: Provider @TaskAction fun setupBuild() { - val podsXcodeProjDir = podsXcodeProjDirProvider.get() + val podsXcodeProjDir = podsXcodeProjDir.get() val buildSettingsReceivingCommand = listOf( "xcodebuild", "-showBuildSettings", "-project", podsXcodeProjDir.name, - "-scheme", schemeName.get(), + "-scheme", pod.get().schemeName, "-sdk", sdk.get() ) @@ -442,113 +442,151 @@ open class PodSetupBuildTask : CocoapodsWithSyntheticTask() { } val stdOut = buildSettingsProcess.inputStream - val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromStream(stdOut) - buildSettingsFileProvider.get().let { buildSettingsProperties.writeSettings(it) } + buildSettingsFile.get().let { + buildSettingsProperties.writeSettings(it) + } } } +private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String = + "build-settings-$sdk-${pod.schemeName}.properties" + /** * The task compiles external cocoa pods sources. */ -open class PodBuildTask : CocoapodsWithSyntheticTask() { - - @get:InputDirectory - internal lateinit var podsXcodeProjDirProvider: Provider +open class PodBuildTask : DefaultTask() { @get:InputFile - internal lateinit var buildSettingsFileProvider: Provider + internal lateinit var buildSettingsFile: Provider + + @get:Nested + internal lateinit var pod: Provider + + @get:InputFiles + internal val srcDir: FileTree + get() = project.fileTree( + buildSettingsFile.map { + PodBuildSettingsProperties.readSettingsFromStream( + FileInputStream(it) + ).podsTargetSrcRoot + } + ) + + @get:Internal + internal var buildDir: Provider = project.provider { + project.file( + PodBuildSettingsProperties.readSettingsFromStream( + FileInputStream(buildSettingsFile.get()) + ).buildDir + ) + } @get:Input internal lateinit var sdk: Provider - @get:Optional - @get:OutputDirectory - internal var buildDirProvider: Provider? = null + @get:OutputFiles + internal val buildResult: Provider? = project.provider { + project.fileTree(buildDir.get()) { + it.include("**/${pod.get().schemeName}.*/") + it.include("**/${pod.get().schemeName}/") + } + } - private val CocoapodsExtension.CocoapodsDependency.schemeName: String - get() = name.split("/")[0] + @get:Internal + internal lateinit var podsXcodeProjDir: Provider @TaskAction fun buildDependencies() { val podBuildSettings = PodBuildSettingsProperties.readSettingsFromStream( - FileInputStream(buildSettingsFileProvider.get()) + FileInputStream(buildSettingsFile.get()) ) - val podsXcodeProjDir = podsXcodeProjDirProvider.get() + val podsXcodeProjDir = podsXcodeProjDir.get() - cocoapodsExtension.pods.all { + val podXcodeBuildCommand = listOf( + "xcodebuild", + "-project", podsXcodeProjDir.name, + "-scheme", pod.get().schemeName, + "-sdk", sdk.get(), + "-configuration", podBuildSettings.configuration + ) - val podXcodeBuildCommand = listOf( - "xcodebuild", - "-project", podsXcodeProjDir.name, - "-scheme", it.schemeName, - "-sdk", sdk.get(), - "-configuration", podBuildSettings.configuration - ) + val podBuildProcess = ProcessBuilder(podXcodeBuildCommand) + .apply { + directory(podsXcodeProjDir.parentFile) + inheritIO() + }.start() - val podBuildProcess = ProcessBuilder(podXcodeBuildCommand) - .apply { - directory(podsXcodeProjDir.parentFile) - inheritIO() - }.start() - - val podBuildRetCode = podBuildProcess.waitFor() - check(podBuildRetCode == 0) { - listOf( - "Executing of '${podXcodeBuildCommand.joinToString(" ")}' failed with code $podBuildRetCode and message:", - podBuildProcess.errorStream.use { it.reader().readText() } - ).joinToString("\n") - } + val podBuildRetCode = podBuildProcess.waitFor() + check(podBuildRetCode == 0) { + listOf( + "Executing of '${podXcodeBuildCommand.joinToString(" ")}' failed with code $podBuildRetCode and message:", + podBuildProcess.errorStream.use { it.reader().readText() } + ).joinToString("\n") } - buildDirProvider = project.provider { project.file(podBuildSettings.buildDir) } } } + internal data class PodBuildSettingsProperties( internal val buildDir: String, internal val configuration: String, + internal val configurationBuildDir: String, + internal val podsTargetSrcRoot: String, internal val cflags: String? = null, internal val headerPaths: String? = null, internal val frameworkPaths: String? = null ) { - fun writeSettings(buildSettingsFile: File) { + fun writeSettings( + buildSettingsFile: File + ) { buildSettingsFile.parentFile.mkdirs() + buildSettingsFile.delete() buildSettingsFile.createNewFile() check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" } - with(Properties()) { - setProperty(BUILD_DIR, buildDir) - setProperty(CONFIGURATION, configuration) - cflags?.let { setProperty(OTHER_CFLAGS, it) } - headerPaths?.let { setProperty(HEADER_SEARCH_PATHS, it) } - frameworkPaths?.let { setProperty(FRAMEWORK_SEARCH_PATHS, it) } - buildSettingsFile.outputStream().use { - store(it, null) - } + with(buildSettingsFile) { + appendText("$BUILD_DIR=$buildDir\n") + appendText("$CONFIGURATION=$configuration\n") + appendText("$CONFIGURATION_BUILD_DIR=$configurationBuildDir\n") + appendText("$PODS_TARGET_SRCROOT=$podsTargetSrcRoot\n") + cflags?.let { appendText("$OTHER_CFLAGS=$it\n") } + headerPaths?.let { appendText("$HEADER_SEARCH_PATHS=$it\n") } + frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") } } } companion object { - const val BUILD_DIR: String = "BUILD_DIR" - const val CONFIGURATION: String = "CONFIGURATION" - const val OTHER_CFLAGS: String = "OTHER_CFLAGS" - const val HEADER_SEARCH_PATHS: String = "HEADER_SEARCH_PATHS" - const val FRAMEWORK_SEARCH_PATHS: String = "FRAMEWORK_SEARCH_PATHS" + const val BUILD_DIR = "BUILD_DIR" + const val CONFIGURATION = "CONFIGURATION" + const val CONFIGURATION_BUILD_DIR = "CONFIGURATION_BUILD_DIR" + const val PODS_TARGET_SRCROOT = "PODS_TARGET_SRCROOT" + const val OTHER_CFLAGS = "OTHER_CFLAGS" + const val HEADER_SEARCH_PATHS = "HEADER_SEARCH_PATHS" + const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS" fun readSettingsFromStream(inputStream: InputStream): PodBuildSettingsProperties { with(Properties()) { load(inputStream) return PodBuildSettingsProperties( - getProperty(BUILD_DIR), - getProperty(CONFIGURATION), - getProperty(OTHER_CFLAGS), - getProperty(HEADER_SEARCH_PATHS), - getProperty(FRAMEWORK_SEARCH_PATHS) + readProperty(BUILD_DIR), + readProperty(CONFIGURATION), + readProperty(CONFIGURATION_BUILD_DIR), + readProperty(PODS_TARGET_SRCROOT), + readNullableProperty(OTHER_CFLAGS), + readNullableProperty(HEADER_SEARCH_PATHS), + readNullableProperty(FRAMEWORK_SEARCH_PATHS) ) } } + + private fun Properties.readProperty(propertyName: String) = + readNullableProperty(propertyName) ?: error("$propertyName property is absent") + + private fun Properties.readNullableProperty(propertyName: String) = + getProperty(propertyName) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt index b1d67599b97..4fa31a0e37d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt @@ -15,6 +15,7 @@ import org.gradle.api.tasks.wrapper.Wrapper import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension +import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.* import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.COCOAPODS_EXTENSION_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.GENERATE_WRAPPER_PROPERTY @@ -38,18 +39,55 @@ open class PodspecTask : DefaultTask() { internal val outputFileProvider: Provider get() = project.provider { project.file("$specName.podspec") } + @get:Input + internal lateinit var needPodspec: Provider + @get:Nested - internal lateinit var cocoapodsExtension: CocoapodsExtension + val pods = project.objects.listProperty(CocoapodsDependency::class.java) + + @get:Input + internal lateinit var version: Provider + + @get:Input + @get:Optional + internal val homepage = project.objects.property(String::class.java) + + @get:Input + @get:Optional + internal val license = project.objects.property(String::class.java) + + @get:Input + @get:Optional + internal val authors = project.objects.property(String::class.java) + + @get:Input + @get:Optional + internal val summary = project.objects.property(String::class.java) + + @get:Input + internal lateinit var frameworkName: Provider + + @get:Nested + internal lateinit var ios: Provider + + @get:Nested + internal lateinit var osx: Provider + + @get:Nested + internal lateinit var tvos: Provider + + @get:Nested + internal lateinit var watchos: Provider init { - onlyIf { cocoapodsExtension.needPodspec } + onlyIf { needPodspec.get() } } @TaskAction fun generate() { val frameworkDir = project.cocoapodsBuildDirs.framework.relativeTo(outputFileProvider.get().parentFile).path - val dependencies = cocoapodsExtension.pods.map { pod -> + val dependencies = pods.get().map { pod -> val versionSuffix = if (pod.version != null) ", '${pod.version}'" else "" "| spec.dependency '${pod.name}'$versionSuffix" }.joinToString(separator = "\n") @@ -69,10 +107,8 @@ open class PodspecTask : DefaultTask() { val syncTask = "${project.path}:$SYNC_TASK_NAME" val deploymentTargets = run { - with(cocoapodsExtension) { - listOf(ios, osx, tvos, watchos).filter { it.deploymentTarget != null }.joinToString("\n") { - "| spec.${it.name}.deployment_target = '${it.deploymentTarget}'" - } + listOf(ios, osx, tvos, watchos).map { it.get() }.filter { it.deploymentTarget != null }.joinToString("\n") { + "| spec.${it.name}.deployment_target = '${it.deploymentTarget}'" } } @@ -81,15 +117,15 @@ open class PodspecTask : DefaultTask() { """ |Pod::Spec.new do |spec| | spec.name = '$specName' - | spec.version = '${cocoapodsExtension.version}' - | spec.homepage = '${cocoapodsExtension.homepage.orEmpty()}' + | spec.version = '${version.get()}' + | spec.homepage = '${homepage.getOrEmpty()}' | spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } - | spec.authors = '${cocoapodsExtension.authors.orEmpty()}' - | spec.license = '${cocoapodsExtension.license.orEmpty()}' - | spec.summary = '${cocoapodsExtension.summary.orEmpty()}' + | spec.authors = '${authors.getOrEmpty()}' + | spec.license = '${license.getOrEmpty()}' + | spec.summary = '${summary.getOrEmpty()}' | | spec.static_framework = true - | spec.vendored_frameworks = "$frameworkDir/${cocoapodsExtension.frameworkName}.framework" + | spec.vendored_frameworks = "$frameworkDir/${frameworkName.get()}.framework" | spec.libraries = "c++" | spec.module_name = "#{spec.name}_umbrella" | @@ -143,6 +179,8 @@ open class PodspecTask : DefaultTask() { } } + fun Provider.getOrEmpty() = getOrElse("") + companion object { private val KotlinMultiplatformExtension?.cocoapodsExtensionOrNull: CocoapodsExtension? get() = (this as? ExtensionAware)?.extensions?.findByName(COCOAPODS_EXTENSION_NAME) as? CocoapodsExtension @@ -167,17 +205,15 @@ open class PodspecTask : DefaultTask() { * and then replace it with the real one during a real build process. */ open class DummyFrameworkTask : DefaultTask() { + @OutputDirectory val destinationDir = project.cocoapodsBuildDirs.framework @Input - val frameworkNameProvider: Provider = project.provider { cocoapodsExtension.frameworkName } - - @get:Nested - internal lateinit var cocoapodsExtension: CocoapodsExtension + lateinit var frameworkName: Provider private val frameworkDir: File - get() = destinationDir.resolve("${frameworkNameProvider.get()}.framework") + get() = destinationDir.resolve("${frameworkName.get()}.framework") private fun copyResource(from: String, to: File) { to.parentFile.mkdirs() @@ -225,11 +261,11 @@ open class DummyFrameworkTask : DefaultTask() { // Copy files for the dummy framework. copyFrameworkFile("Info.plist") - copyFrameworkFile("dummy", frameworkNameProvider.get()) + copyFrameworkFile("dummy", frameworkName.get()) copyFrameworkFile("Headers/placeholder.h") copyFrameworkTextFile("Modules/module.modulemap") { if (it == "framework module dummy {") { - it.replace("dummy", frameworkNameProvider.get()) + it.replace("dummy", frameworkName.get()) } else { it } @@ -242,12 +278,12 @@ open class DummyFrameworkTask : DefaultTask() { */ open class DefFileTask : DefaultTask() { - @Nested - lateinit var pod: CocoapodsExtension.CocoapodsDependency + @get:Nested + lateinit var pod: Provider @get:OutputFile val outputFile: File - get() = project.cocoapodsBuildDirs.defs.resolve("${pod.moduleName}.def") + get() = project.cocoapodsBuildDirs.defs.resolve("${pod.get().moduleName}.def") @TaskAction fun generate() { @@ -255,7 +291,7 @@ open class DefFileTask : DefaultTask() { outputFile.writeText( """ language = Objective-C - modules = ${pod.moduleName} + modules = ${pod.get().moduleName} """.trimIndent() ) }