From c2e092fe4acc7b861a2239a04455f1e4c4ac2608 Mon Sep 17 00:00:00 2001 From: Artem Daugel-Dauge Date: Wed, 18 Jan 2023 21:59:38 +0100 Subject: [PATCH] [Gradle] Fix invalidating iOS framework after import ^KT-49430 Verification Pending --- .../kotlin/gradle/native/CocoaPodsIT.kt | 94 ++++++++++++++++++- .../native/cocoapods/CocoapodsExtension.kt | 2 +- .../native/cocoapods/KotlinCocoapodsPlugin.kt | 26 ++--- .../native/tasks/AdvancedCocoapodsTasks.kt | 28 +++++- .../targets/native/tasks/CocoapodsTasks.kt | 35 ++++--- 5 files changed, 150 insertions(+), 35 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt index d226f4ad2c0..628c5abf862 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt @@ -397,6 +397,75 @@ class CocoaPodsIT : BaseGradleIT() { project.testWithWrapper(dummyTaskName) } + @Test + fun testImportUTDAfterLinkingFramework() { + val linkTaskName = ":linkPodDebugFrameworkIOS" + project.gradleBuildScript().appendToCocoapodsBlock(""" + framework { + baseName = "kotlin-library" + } + name = "kotlin-library" + podfile = project.file("ios-app/Podfile") + """.trimIndent()) + + + hooks.addHook { + assertTasksExecuted(dummyTaskName) + assertTasksExecuted(podInstallTaskName) + } + project.testImport() + + hooks.rewriteHooks { + assertTasksExecuted(linkTaskName) + } + project.testWithWrapper(linkTaskName) + + hooks.rewriteHooks { + assertTasksUpToDate(dummyTaskName) + assertTasksUpToDate(podInstallTaskName) + } + project.testImport() + } + + @Test + fun testChangeFrameworkTypeUTD() { + project.gradleBuildScript().appendToCocoapodsBlock(""" + framework { + baseName = "kotlin-library" + } + name = "kotlin-library" + podfile = project.file("ios-app/Podfile") + """.trimIndent()) + + hooks.addHook { + assertTasksExecuted(dummyTaskName) + assertTasksExecuted(podInstallTaskName) + } + project.testImport() + + hooks.rewriteHooks { + assertTasksUpToDate(dummyTaskName) + assertTasksUpToDate(podInstallTaskName) + } + project.testImport() + + project.gradleBuildScript().appendToFrameworkBlock("isStatic = true") + + hooks.rewriteHooks { + assertTasksExecuted(dummyTaskName) + assertTasksExecuted(podInstallTaskName) + } + project.testImport() + + hooks.rewriteHooks { + assertTasksUpToDate(dummyTaskName) + assertTasksUpToDate(podInstallTaskName) + } + project.testImport() + } + + + @Test fun basicUTDTest() { val tasks = listOf( @@ -771,10 +840,10 @@ class CocoaPodsIT : BaseGradleIT() { fun testUseDynamicFramework() { with(project) { gradleBuildScript().addPod(defaultPodName, produceGitBlock(defaultPodRepo)) - gradleBuildScript().appendToFrameworkBlock("isStatic=false") + gradleBuildScript().appendToFrameworkBlock("isStatic = false") hooks.addHook { // Check that an output framework is a dynamic framework - val framework = fileInWorkingDir("build/cocoapods/framework/cocoapods.framework/cocoapods") + val framework = fileInWorkingDir("build/bin/iOS/podDebugFramework/cocoapods.framework/cocoapods") with(runProcess(listOf("file", framework.absolutePath), projectDir, environmentVariables = getEnvs())) { assertTrue(isSuccessful) assertTrue(output.contains("dynamically linked shared library")) @@ -788,6 +857,27 @@ class CocoaPodsIT : BaseGradleIT() { } } + @Test + fun testUseStaticFramework() { + with(project) { + gradleBuildScript().addPod(defaultPodName, produceGitBlock(defaultPodRepo)) + gradleBuildScript().appendToFrameworkBlock("isStatic = true") + hooks.addHook { + // Check that an output framework is a static framework + val framework = fileInWorkingDir("build/bin/iOS/podDebugFramework/cocoapods.framework/cocoapods") + with(runProcess(listOf("file", framework.absolutePath), projectDir, environmentVariables = getEnvs())) { + assertTrue(isSuccessful) + assertTrue(output.contains("current ar archive random library")) + } + } + + test( + "linkPodDebugFrameworkIOS", + "-Pkotlin.native.cocoapods.generate.wrapper=true" + ) + } + } + @Test fun testCocoapodsWithRegularFrameworkDefinition() { with(project) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt index 7598444875e..070d50e9ad4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt @@ -121,7 +121,7 @@ abstract class CocoapodsExtension @Inject constructor(private val project: Proje anyFramework } - internal val podFrameworkName = anyPodFramework.map { it.baseName } + internal val podFrameworkName = anyPodFramework.map { it.baseName.asValidFrameworkName() } internal val podFrameworkIsStatic = anyPodFramework.map { it.isStatic } /** diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt index 87df2100766..046045a1d3e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt @@ -53,6 +53,9 @@ internal class CocoapodsBuildDirs(val project: Project) { val framework: File get() = root.resolve("framework") + val dummyFramework: File + get() = root.resolve("dummy.framework") + val defs: File get() = root.resolve("defs") @@ -365,9 +368,9 @@ open class KotlinCocoapodsPlugin : Plugin { project: Project, cocoapodsExtension: CocoapodsExtension ) { - project.tasks.register(DUMMY_FRAMEWORK_TASK_NAME, DummyFrameworkTask::class.java) { - it.frameworkName = cocoapodsExtension.podFrameworkName - it.useDynamicFramework = cocoapodsExtension.podFrameworkIsStatic.map { isStatic -> !isStatic } + project.registerTask(DUMMY_FRAMEWORK_TASK_NAME) { task -> + task.frameworkName.set(cocoapodsExtension.podFrameworkName) + task.useStaticFramework.set(cocoapodsExtension.podFrameworkIsStatic) } } @@ -375,8 +378,6 @@ open class KotlinCocoapodsPlugin : Plugin { project: Project, cocoapodsExtension: CocoapodsExtension ) { - val dummyFrameworkTaskProvider = project.tasks.named(DUMMY_FRAMEWORK_TASK_NAME) - project.tasks.register(POD_SPEC_TASK_NAME, PodspecTask::class.java) { it.group = TASK_GROUP it.description = "Generates a podspec file for CocoaPods import" @@ -396,7 +397,6 @@ open class KotlinCocoapodsPlugin : Plugin { 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) { it.dependsOn(":wrapper") @@ -461,6 +461,7 @@ open class KotlinCocoapodsPlugin : Plugin { cocoapodsExtension: CocoapodsExtension ) { val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME) + val dummyFrameworkTaskProvider = project.tasks.named(DUMMY_FRAMEWORK_TASK_NAME) project.registerTask(POD_INSTALL_TASK_NAME) { task -> task.group = TASK_GROUP task.description = "Invokes `pod install` call within Podfile location directory" @@ -469,6 +470,7 @@ open class KotlinCocoapodsPlugin : Plugin { task.frameworkName.set(cocoapodsExtension.podFrameworkName) task.specRepos.set(project.provider { cocoapodsExtension.specRepos }) task.pods.set(cocoapodsExtension.pods) + task.dummyFramework.set(dummyFrameworkTaskProvider.map { it.outputFramework.get() }) task.dependsOn(podspecTaskProvider) } } @@ -496,12 +498,12 @@ open class KotlinCocoapodsPlugin : Plugin { val podGenTask = project.registerTask(family.toPodGenTaskName) { task -> task.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies" - task.podspec = podspecTaskProvider.map { it.outputFile } - task.podName = project.provider { cocoapodsExtension.name } - task.useLibraries = project.provider { cocoapodsExtension.useLibraries } - task.specRepos = project.provider { cocoapodsExtension.specRepos } - task.family = family - task.platformSettings = platformSettings + task.podspec.set(podspecTaskProvider.map { it.outputFile }) + task.podName.set(project.provider { cocoapodsExtension.name }) + task.useLibraries.set(project.provider { cocoapodsExtension.useLibraries }) + task.specRepos.set(project.provider { cocoapodsExtension.specRepos }) + task.family.set(family) + task.platformSettings.set(platformSettings) task.pods.set(cocoapodsExtension.pods) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/AdvancedCocoapodsTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/AdvancedCocoapodsTasks.kt index 2c9438e8dbb..8a04a217427 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/AdvancedCocoapodsTasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/AdvancedCocoapodsTasks.kt @@ -56,6 +56,7 @@ abstract class AbstractPodInstallTask : CocoapodsTask() { @get:InputFile abstract val podfile: Property + @get:Internal protected val workingDir: Provider = podfile.map { file: File? -> requireNotNull(file) { "Task outputs shouldn't be queried if it's skipped" }.parentFile } @@ -92,7 +93,6 @@ abstract class AbstractPodInstallTask : CocoapodsTask() { abstract class PodInstallTask : AbstractPodInstallTask() { - @get:Optional @get:InputFile abstract val podspec: Property @@ -106,6 +106,32 @@ abstract class PodInstallTask : AbstractPodInstallTask() { @get:Nested abstract val pods: ListProperty + @get:InputDirectory + abstract val dummyFramework: Property + + private val framework = project.provider { project.cocoapodsBuildDirs.framework.resolve("${frameworkName.get()}.framework") } + private val tmpFramework = dummyFramework.map { dummy -> dummy.parentFile.resolve("tmp.framework").also { it.deleteOnExit() } } + + override fun doPodInstall() { + // We always need to execute 'pod install' with the dummy framework because the one left from a previous build + // may have a wrong linkage type. So we temporarily swap them, run 'pod install' and then swap them back + framework.rename(tmpFramework) + dummyFramework.rename(framework) + super.doPodInstall() + framework.rename(dummyFramework) + tmpFramework.rename(framework) + } + + private fun Provider.rename(dest: Provider) = get().rename(dest.get()) + + private fun File.rename(dest: File) { + if (!exists()) { + mkdirs() + } + + check(renameTo(dest)) { "Can't rename '${this}' to '${dest}'" } + } + override fun handleError(retCode: Int, error: String, process: Process): String? { val specReposMessages = MissingSpecReposMessage(specRepos.get()).missingMessage val cocoapodsMessages = pods.get().map { MissingCocoapodsMessage(it).missingMessage } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt index 863eecc7799..7dbbb3c5f83 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt @@ -236,30 +236,27 @@ open class PodspecTask : DefaultTask() { * So we create a dummy static framework to allow CocoaPods install our pod correctly * and then replace it with the real one during a real build process. */ -open class DummyFrameworkTask : DefaultTask() { +abstract class DummyFrameworkTask : DefaultTask() { - @OutputDirectory - val destinationDir = project.cocoapodsBuildDirs.framework + @get:Input + abstract val frameworkName: Property - @Input - lateinit var frameworkName: Provider + @get:Input + abstract val useStaticFramework: Property - @Input - lateinit var useDynamicFramework: Provider + @get:OutputDirectory + val outputFramework: Provider = project.provider { project.cocoapodsBuildDirs.dummyFramework } - private val frameworkDir: File - get() = destinationDir.resolve("${frameworkName.get()}.framework") - - private val dummyFrameworkPath: String + private val dummyFrameworkResource: String get() { - val staticOrDynamic = if (useDynamicFramework.get()) "dynamic" else "static" + val staticOrDynamic = if (!useStaticFramework.get()) "dynamic" else "static" return "/cocoapods/$staticOrDynamic/dummy.framework/" } private fun copyResource(from: String, to: File) { to.parentFile.mkdirs() to.outputStream().use { file -> - javaClass.getResourceAsStream(from).use { resource -> + javaClass.getResourceAsStream(from)!!.use { resource -> resource.copyTo(file) } } @@ -268,7 +265,7 @@ open class DummyFrameworkTask : DefaultTask() { private fun copyTextResource(from: String, to: File, transform: (String) -> String = { it }) { to.parentFile.mkdirs() to.printWriter().use { file -> - javaClass.getResourceAsStream(from).use { + javaClass.getResourceAsStream(from)!!.use { it.reader().forEachLine { str -> file.println(transform(str)) } @@ -278,8 +275,8 @@ open class DummyFrameworkTask : DefaultTask() { private fun copyFrameworkFile(relativeFrom: String, relativeTo: String = relativeFrom) = copyResource( - "$dummyFrameworkPath$relativeFrom", - frameworkDir.resolve(relativeTo) + "$dummyFrameworkResource$relativeFrom", + outputFramework.get().resolve(relativeTo) ) private fun copyFrameworkTextFile( @@ -287,15 +284,15 @@ open class DummyFrameworkTask : DefaultTask() { relativeTo: String = relativeFrom, transform: (String) -> String = { it } ) = copyTextResource( - "$dummyFrameworkPath$relativeFrom", - frameworkDir.resolve(relativeTo), + "$dummyFrameworkResource$relativeFrom", + outputFramework.get().resolve(relativeTo), transform ) @TaskAction fun create() { // Reset the destination directory - with(destinationDir) { + with(outputFramework.get()) { deleteRecursively() mkdirs() }