From 8a34d1f430ad7c594ae51cb91f7315ef02b5daad Mon Sep 17 00:00:00 2001 From: Artem Daugel-Dauge Date: Tue, 10 Jan 2023 13:12:13 +0100 Subject: [PATCH] Separate podgen & podinstall tasks ^KT-54161 Verification Pending --- .../kotlin/gradle/native/CocoaPodsIT.kt | 49 ++- .../native/cocoapods/CocoapodsExtension.kt | 7 +- .../native/cocoapods/KotlinCocoapodsPlugin.kt | 87 +++-- .../cocoapods/missingPodfileInfoUtils.kt | 4 +- .../native/tasks/AdvancedCocoapodsTasks.kt | 350 ++++++++---------- .../targets/native/tasks/CocoapodsTasks.kt | 2 - 6 files changed, 253 insertions(+), 246 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 0f07d712629..d226f4ad2c0 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 @@ -70,9 +70,10 @@ class CocoaPodsIT : BaseGradleIT() { private val defaultPodRepo = "https://github.com/AFNetworking/AFNetworking" private val defaultPodName = "AFNetworking" private val defaultTarget = "IOS" - private val defaultFamily = "IOS" + private val defaultFamily = "ios" private val defaultSDK = "iphonesimulator" private val defaultPodGenTaskName = podGenFullTaskName() + private val defaultPodInstallSyntheticTaskName = ":podInstallSyntheticIos" private val defaultBuildTaskName = podBuildFullTaskName() private val defaultSetupBuildTaskName = podSetupBuildFullTaskName() private val defaultCinteropTaskName = cinteropTaskName + defaultPodName + defaultTarget @@ -245,7 +246,7 @@ class CocoaPodsIT : BaseGradleIT() { } @Test - fun testSyntheticProjectPodspecGeneration() { + fun testSyntheticProjectPodfileGeneration() { val gradleProject = transformProjectWithPluginsDsl(cocoapodsSingleKtPod, gradleVersion) gradleProject.gradleBuildScript().appendToCocoapodsBlock(""" ios.deploymentTarget = "14.1" @@ -257,9 +258,11 @@ class CocoaPodsIT : BaseGradleIT() { } } """.trimIndent()) - gradleProject.build("podGenIOS", "-Pkotlin.native.cocoapods.generate.wrapper=true") { + gradleProject.build("podInstallSyntheticIos", "-Pkotlin.native.cocoapods.generate.wrapper=true") { assertSuccessful() - val podfileText = gradleProject.projectDir.resolve("build/cocoapods/synthetic/IOS/Podfile").readText().trim() + assertTasksExecuted(":podGenIos") + + val podfileText = gradleProject.projectDir.resolve("build/cocoapods/synthetic/ios/Podfile").readText().trim() assertTrue(podfileText.contains("platform :ios, '14.1'")) assertTrue(podfileText.contains("pod 'SSZipArchive'")) assertTrue(podfileText.contains("pod 'AFNetworking', '~> 4.0.1'")) @@ -270,6 +273,27 @@ class CocoaPodsIT : BaseGradleIT() { } } + @Test + fun testSyntheticProjectPodfilePostprocessing() { + project.gradleBuildScript().apply { + appendToCocoapodsBlock("""pod("AWSMobileClient", version = "2.29.1")""") + + appendText(""" + + tasks.withType().configureEach { + doLast { + podfile.get().appendText("ENV['SWIFT_VERSION'] = '5'") + } + } + """.trimIndent()) + } + + project.build("podInstallSyntheticIos", "-Pkotlin.native.cocoapods.generate.wrapper=true") { + assertSuccessful() + assertFileContains(path = "build/cocoapods/synthetic/ios/Podfile", "ENV['SWIFT_VERSION'] = '5'") + } + } + @Test fun testPodDownloadGitNoTagNorCommit() { doTestGit() @@ -378,9 +402,10 @@ class CocoaPodsIT : BaseGradleIT() { val tasks = listOf( podspecTaskName, defaultPodGenTaskName, + defaultPodInstallSyntheticTaskName, defaultSetupBuildTaskName, defaultBuildTaskName, - defaultCinteropTaskName + defaultCinteropTaskName, ) with(project.gradleBuildScript()) { addPod(defaultPodName, produceGitBlock(defaultPodRepo)) @@ -416,21 +441,21 @@ class CocoaPodsIT : BaseGradleIT() { } @Test - fun testPodGenInvalidatesUTD() { + fun testPodInstallInvalidatesUTD() { with(project.gradleBuildScript()) { addPod("AFNetworking") } hooks.addHook { - assertTasksExecuted(defaultPodGenTaskName) - assertTrue { fileInWorkingDir("build/cocoapods/synthetic/IOS/Pods/AFNetworking").deleteRecursively() } + assertTasksExecuted(defaultPodInstallSyntheticTaskName) + assertTrue { fileInWorkingDir("build/cocoapods/synthetic/ios/Pods/AFNetworking").deleteRecursively() } } - project.testSynthetic(defaultPodGenTaskName) + project.testSynthetic(defaultPodInstallSyntheticTaskName) hooks.rewriteHooks { - assertTasksExecuted(defaultPodGenTaskName) + assertTasksExecuted(defaultPodInstallSyntheticTaskName) } - project.testSynthetic(defaultPodGenTaskName) + project.testSynthetic(defaultPodInstallSyntheticTaskName) } @Test @@ -499,7 +524,7 @@ class CocoaPodsIT : BaseGradleIT() { val anotherTarget = "MacosX64" val anotherSdk = "macosx" - val anotherFamily = "OSX" + val anotherFamily = "macos" with(project.gradleBuildScript()) { appendToKotlinBlock(anotherTarget.replaceFirstChar { it.lowercase(Locale.getDefault()) } + "()") } 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 652d728390e..7598444875e 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 @@ -10,22 +10,17 @@ import org.gradle.api.Action import org.gradle.api.Named import org.gradle.api.NamedDomainObjectSet import org.gradle.api.Project -import org.gradle.api.provider.Provider import org.gradle.api.tasks.* import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.* import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_FRAMEWORK_PREFIX import org.jetbrains.kotlin.gradle.plugin.mpp.Framework -import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBinary import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType -import org.jetbrains.kotlin.gradle.tasks.addArg -import org.jetbrains.kotlin.gradle.tasks.addArgs -import org.jetbrains.kotlin.gradle.utils.relativeToRoot -import org.jetbrains.kotlin.konan.target.HostManager import java.io.File import java.net.URI import javax.inject.Inject +@Suppress("unused") // Public API abstract class CocoapodsExtension @Inject constructor(private val project: Project) { /** * Configure version of the pod 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 f6b0e672116..87df2100766 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 @@ -62,7 +62,7 @@ internal class CocoapodsBuildDirs(val project: Project) { val synthetic: File get() = root.resolve("synthetic") - fun synthetic(family: Family) = synthetic.resolve(family.name) + fun synthetic(family: Family) = synthetic.resolve(family.platformLiteral) val publish: File = root.resolve("publish") @@ -72,11 +72,20 @@ internal class CocoapodsBuildDirs(val project: Project) { internal fun String.asValidFrameworkName() = replace('-', '_') +internal val Family.platformLiteral: String + get() = when (this) { + Family.OSX -> "macos" + Family.IOS -> "ios" + Family.TVOS -> "tvos" + Family.WATCHOS -> "watchos" + else -> throw IllegalArgumentException("Bad family ${this.name}") + } + private val Family.toPodGenTaskName: String - get() = lowerCamelCaseName( - KotlinCocoapodsPlugin.POD_GEN_TASK_NAME, - name - ) + get() = lowerCamelCaseName(KotlinCocoapodsPlugin.POD_GEN_TASK_NAME, platformLiteral) + +private val Family.toPodInstallSyntheticTaskName: String + get() = lowerCamelCaseName(KotlinCocoapodsPlugin.POD_INSTALL_TASK_NAME, "synthetic", platformLiteral) private fun String.toSetupBuildTaskName(pod: CocoapodsDependency): String = lowerCamelCaseName( KotlinCocoapodsPlugin.POD_SETUP_BUILD_TASK_NAME, @@ -451,23 +460,25 @@ open class KotlinCocoapodsPlugin : Plugin { project: Project, cocoapodsExtension: CocoapodsExtension ) { - val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME, PodspecTask::class.java) - 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.podfile.set(cocoapodsExtension.podfile) - it.podspec.set(podspecTaskProvider.map { podspecTask -> podspecTask.outputFile }) - it.frameworkName = cocoapodsExtension.podFrameworkName - it.dependsOn(podspecTaskProvider) + val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME) + project.registerTask(POD_INSTALL_TASK_NAME) { task -> + task.group = TASK_GROUP + task.description = "Invokes `pod install` call within Podfile location directory" + task.podfile.set(project.provider { cocoapodsExtension.podfile }) + task.podspec.set(podspecTaskProvider.map { it.outputFile }) + task.frameworkName.set(cocoapodsExtension.podFrameworkName) + task.specRepos.set(project.provider { cocoapodsExtension.specRepos }) + task.pods.set(cocoapodsExtension.pods) + task.dependsOn(podspecTaskProvider) } } - private fun registerPodGenTask( + private fun registerSyntheticPodTasks( project: Project, kotlinExtension: KotlinMultiplatformExtension, cocoapodsExtension: CocoapodsExtension ) { val families = mutableSetOf() - val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME, PodspecTask::class.java) + val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME) kotlinExtension.supportedTargets().all { target -> val family = target.konanTarget.family if (family in families) { @@ -483,15 +494,23 @@ open class KotlinCocoapodsPlugin : Plugin { else -> error("Unknown cocoapods platform: $family") } - project.tasks.register(family.toPodGenTaskName, PodGenTask::class.java) { - it.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies" - it.podspec = podspecTaskProvider.map { task -> task.outputFile } - it.podName = project.provider { cocoapodsExtension.name } - it.useLibraries = project.provider { cocoapodsExtension.useLibraries } - it.specRepos = project.provider { cocoapodsExtension.specRepos } - it.family = family - it.platformSettings = platformSettings - it.pods.set(cocoapodsExtension.pods) + 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.pods.set(cocoapodsExtension.pods) + } + + project.registerTask(family.toPodInstallSyntheticTaskName) { task -> + task.description = "Invokes `pod install` for synthetic project" + task.podfile.set(podGenTask.map { it.podfile.get() }) + task.family.set(family) + task.podName.set(cocoapodsExtension.name) + task.dependsOn(podGenTask) } } } @@ -520,15 +539,15 @@ open class KotlinCocoapodsPlugin : Plugin { } sdks += sdk - 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 = cocoapodsExtension.podFrameworkName - it.dependsOn(podGenTaskProvider) + val podInstallTask = project.tasks.named(target.konanTarget.family.toPodInstallSyntheticTaskName) + project.registerTask(sdk.toSetupBuildTaskName(pod)) { task -> + task.group = TASK_GROUP + task.description = "Collect environment variables from .xcworkspace file" + task.pod = project.provider { pod } + task.sdk = project.provider { sdk } + task.podsXcodeProjDir = podInstallTask.map { it.podsXcodeProjDirProvider.get() } + task.frameworkName = cocoapodsExtension.podFrameworkName + task.dependsOn(podInstallTask) } } } @@ -772,7 +791,7 @@ open class KotlinCocoapodsPlugin : Plugin { injectPodspecExtensionToArtifacts(project, kotlinArtifactsExtension, cocoapodsExtension) registerPodspecTask(project, cocoapodsExtension) - registerPodGenTask(project, kotlinExtension, cocoapodsExtension) + registerSyntheticPodTasks(project, kotlinExtension, cocoapodsExtension) registerPodInstallTask(project, cocoapodsExtension) registerPodSetupBuildTasks(project, kotlinExtension, cocoapodsExtension) registerPodBuildTasks(project, kotlinExtension, cocoapodsExtension) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/missingPodfileInfoUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/missingPodfileInfoUtils.kt index a7441b8d0ea..9d884a9b74c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/missingPodfileInfoUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/missingPodfileInfoUtils.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.gradle.targets.native.cocoapods -import org.gradle.api.Project import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.SpecRepos @@ -20,8 +19,7 @@ class MissingSpecReposMessage(override val missingInfo: SpecRepos) : MissingInfo } class MissingCocoapodsMessage( - override val missingInfo: CocoapodsDependency, - private val project: Project + override val missingInfo: CocoapodsDependency ) : MissingInfoMessage { override val missingMessage: String get() = "pod '${missingInfo.name}'${missingInfo.source?.let { ", ${it.getPodSourcePath()}" }.orEmpty()}" 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 fee5fabc73e..2c9438e8dbb 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 @@ -8,46 +8,30 @@ package org.jetbrains.kotlin.gradle.targets.native.tasks import org.gradle.api.DefaultTask -import org.gradle.api.Project import org.gradle.api.file.FileCollection import org.gradle.api.file.FileTree import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.* import org.gradle.api.tasks.Optional 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.cocoapodsBuildDirs +import org.jetbrains.kotlin.gradle.plugin.cocoapods.platformLiteral import org.jetbrains.kotlin.gradle.targets.native.cocoapods.MissingCocoapodsMessage import org.jetbrains.kotlin.gradle.targets.native.cocoapods.MissingSpecReposMessage -import org.jetbrains.kotlin.gradle.tasks.PodspecTask.Companion.retrievePods -import org.jetbrains.kotlin.gradle.tasks.PodspecTask.Companion.retrieveSpecRepos import org.jetbrains.kotlin.gradle.utils.runCommand import org.jetbrains.kotlin.konan.target.Family import org.jetbrains.kotlin.konan.target.HostManager -import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly import java.io.File import java.io.IOException import java.io.Reader import java.util.* -private val Family.platformLiteral: String - get() = when (this) { - Family.OSX -> "macos" - Family.IOS -> "ios" - Family.TVOS -> "tvos" - Family.WATCHOS -> "watchos" - 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. - * This task is a part of CocoaPods integration infrastructure. - */ open class CocoapodsTask : DefaultTask() { init { @@ -58,61 +42,164 @@ open class CocoapodsTask : DefaultTask() { } -open class PodInstallTask : CocoapodsTask() { +/** + * The task takes the path to the Podfile and calls `pod install` + * to obtain sources or artifacts for the declared dependencies. + * This task is a part of CocoaPods integration infrastructure. + */ +abstract class AbstractPodInstallTask : CocoapodsTask() { init { onlyIf { podfile.isPresent } } - @get:Internal - internal lateinit var frameworkName: Provider - @get:Optional - @get:PathSensitive(PathSensitivity.ABSOLUTE) @get:InputFile - internal val podfile = project.objects.property(File::class.java) + abstract val podfile: Property - @get:Optional - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:InputFile - internal val podspec = project.objects.property(File::class.java) + protected val workingDir: Provider = podfile.map { file: File? -> + requireNotNull(file) { "Task outputs shouldn't be queried if it's skipped" }.parentFile + } - @get:Optional @get:OutputDirectory - internal val podsXcodeProjDirProvider: Provider? - get() = podfile.orNull?.let { - project.provider { it.parentFile.resolve("Pods").resolve("Pods.xcodeproj") } - } + internal val podsDir: Provider = workingDir.map { it.resolve("Pods") } + + @get:Internal + internal val podsXcodeProjDirProvider: Provider = podsDir.map { it.resolve("Pods.xcodeproj") } @TaskAction - fun doPodInstall() { - podfile.orNull?.parentFile?.also { podfileDir -> - val podInstallCommand = listOf("pod", "install") + open fun doPodInstall() { + val podInstallCommand = listOf("pod", "install") - runCommand(podInstallCommand, - logger, - errorHandler = { returnCode, output, _ -> - CocoapodsErrorHandlingUtil.handlePodInstallError( - podInstallCommand.joinToString(" "), - returnCode, - output, - project, - frameworkName.get() - ) - }, - exceptionHandler = { e: IOException -> - CocoapodsErrorHandlingUtil.handle(e, podInstallCommand) - }, - processConfiguration = { - directory(podfileDir) - }) + runCommand(podInstallCommand, + logger, + errorHandler = ::handleError, + exceptionHandler = { e: IOException -> + CocoapodsErrorHandlingUtil.handle(e, podInstallCommand) + }, + processConfiguration = { + directory(workingDir.get()) + }) - with(podsXcodeProjDirProvider) { - check(this != null && get().exists() && get().isDirectory) { - "The directory 'Pods/Pods.xcodeproj' was not created as a result of the `pod install` call." - } + with(podsXcodeProjDirProvider.get()) { + check(exists() && isDirectory) { + "The directory 'Pods/Pods.xcodeproj' was not created as a result of the `pod install` call." } } } + + abstract fun handleError(retCode: Int, error: String, process: Process): String? +} + +abstract class PodInstallTask : AbstractPodInstallTask() { + + + @get:Optional + @get:InputFile + abstract val podspec: Property + + @get:Input + abstract val frameworkName: Property + + @get:Nested + abstract val specRepos: Property + + @get:Nested + abstract val pods: ListProperty + + override fun handleError(retCode: Int, error: String, process: Process): String? { + val specReposMessages = MissingSpecReposMessage(specRepos.get()).missingMessage + val cocoapodsMessages = pods.get().map { MissingCocoapodsMessage(it).missingMessage } + + return listOfNotNull( + "'pod install' command failed with code $retCode.", + "Error message:", + error.lines().filter { it.isNotBlank() }.joinToString("\n"), + """ + | Please, check that podfile contains following lines in header: + | $specReposMessages + | + """.trimMargin(), + """ + | Please, check that each target depended on ${frameworkName.get()} contains following dependencies: + | ${cocoapodsMessages.joinToString("\n")} + | + """.trimMargin() + + ).joinToString("\n") + } +} + +abstract class PodInstallSyntheticTask : AbstractPodInstallTask() { + + @get:Input + abstract val family: Property + + @get:Input + abstract val podName: Property + + @get:OutputDirectory + internal val syntheticXcodeProject: Provider = workingDir.map { it.resolve("synthetic.xcodeproj") } + + override fun doPodInstall() { + val projResource = "/cocoapods/project.pbxproj" + val projDestination = syntheticXcodeProject.get().resolve("project.pbxproj") + + syntheticXcodeProject.get().mkdirs() + projDestination.outputStream().use { file -> + javaClass.getResourceAsStream(projResource)!!.use { resource -> + resource.copyTo(file) + } + } + + super.doPodInstall() + } + + override fun handleError(retCode: Int, error: String, process: Process): String? { + var message = """ + |'pod install' command on the synthetic project failed with return code: $retCode + | + | Error: ${error.lines().filter { it.contains("[!]") }.joinToString("\n")} + | + """.trimMargin() + + if ( + error.contains("deployment target") || + error.contains("no platform was specified") || + error.contains(Regex("The platform of the target .+ is not compatible with `${podName.get()}")) + ) { + message += """ + | + | Possible reason: ${family.get().platformLiteral} deployment target is not configured + | Configure deployment_target for ALL targets as follows: + | cocoapods { + | ... + | ${family.get().platformLiteral}.deploymentTarget = "..." + | ... + | } + | + """.trimMargin() + return message + } else if ( + error.contains("Unable to add a source with url") || + error.contains("Couldn't determine repo name for URL") || + error.contains("Unable to find a specification") + ) { + message += """ + | + | Possible reason: spec repos are not configured correctly. + | Ensure that spec repos are correctly configured for all private pod dependencies: + | cocoapods { + | specRepos { + | url("") + | } + | } + | + """.trimMargin() + return message + } else { + return null + } + } } /** @@ -126,82 +213,39 @@ abstract class PodGenTask : CocoapodsTask() { } } - @get:PathSensitive(PathSensitivity.ABSOLUTE) @get:InputFile - internal lateinit var podspec: Provider + internal abstract val podspec: Property @get:Input - internal lateinit var podName: Provider + internal abstract val podName: Property @get:Input - internal lateinit var useLibraries: Provider + internal abstract val useLibraries: Property - @get:Internal - lateinit var family: Family + @get:Input + internal abstract val family: Property @get:Nested - internal lateinit var platformSettings: PodspecPlatformSettings + internal abstract val platformSettings: Property @get:Nested - internal lateinit var specRepos: Provider + internal abstract val specRepos: Property @get:Nested - abstract val pods: ListProperty + internal abstract val pods: ListProperty - @get:OutputDirectory - internal val outputDir: Provider = project.provider { project.cocoapodsBuildDirs.synthetic(family) } - - @get:Internal - internal val podsXcodeProjDir: Provider = outputDir.map { it.resolve("Pods").resolve("Pods.xcodeproj") } - - @get:Internal - val podfile: Provider = outputDir.map { it.resolve("Podfile") } + @get:OutputFile + val podfile: Provider = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") } @TaskAction fun generate() { - val syntheticDir = outputDir.get().apply { mkdirs() } val specRepos = specRepos.get().getAll() - val projResource = "/cocoapods/project.pbxproj" - val projDestination = syntheticDir.resolve("synthetic.xcodeproj").resolve("project.pbxproj") - - projDestination.parentFile.mkdirs() - projDestination.outputStream().use { file -> - javaClass.getResourceAsStream(projResource)!!.use { resource -> - resource.copyTo(file) - } - } - val podfile = this.podfile.get() podfile.createNewFile() - val podfileContent = getPodfileContent(specRepos, family.platformLiteral) + val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral) podfile.writeText(podfileContent) - val podInstallCommand = listOf("pod", "install") - - runCommand( - podInstallCommand, - project.logger, - exceptionHandler = { e: IOException -> - CocoapodsErrorHandlingUtil.handle(e, podInstallCommand) - }, - errorHandler = { retCode, output, _ -> - CocoapodsErrorHandlingUtil.handlePodInstallSyntheticError( - podInstallCommand.joinToString(" "), - retCode, - output, - family, - podName.get() - ) - }, - processConfiguration = { - directory(syntheticDir) - }) - - val podsXcprojFile = podsXcodeProjDir.get() - check(podsXcprojFile.exists() && podsXcprojFile.isDirectory) { - "Synthetic project '${podsXcprojFile.path}' was not created." - } } private fun getPodfileContent(specRepos: Collection, xcodeTarget: String) = @@ -215,11 +259,12 @@ abstract class PodGenTask : CocoapodsTask() { if (useLibraries.get().not()) { appendLine("\tuse_frameworks!") } - val deploymentTarget = platformSettings.deploymentTarget + val settings = platformSettings.get() + val deploymentTarget = settings.deploymentTarget if (deploymentTarget != null) { - appendLine("\tplatform :${platformSettings.name}, '$deploymentTarget'") + appendLine("\tplatform :${settings.name}, '$deploymentTarget'") } else { - appendLine("\tplatform :${platformSettings.name}") + appendLine("\tplatform :${settings.name}") } pods.get().mapNotNull { buildString { @@ -251,6 +296,7 @@ abstract class PodGenTask : CocoapodsTask() { end """.trimIndent() ) + appendLine() } } @@ -403,6 +449,7 @@ data class PodBuildSettingsProperties( fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties { with(Properties()) { + @Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here load(reader) return PodBuildSettingsProperties( readProperty(BUILD_DIR), @@ -448,79 +495,4 @@ private object CocoapodsErrorHandlingUtil { } } - fun handlePodInstallError(command: String, retCode: Int, error: String, project: Project, frameworkName: String): String { - val specReposMessages = retrieveSpecRepos(project)?.let { MissingSpecReposMessage(it).missingMessage } - val cocoapodsMessages = retrievePods(project)?.map { MissingCocoapodsMessage(it, project).missingMessage } - - return listOfNotNull( - "'pod install' command failed with code $retCode.", - "Full command: $command", - "Error message:", - error.lines().filter { it.isNotBlank() }.joinToString("\n"), - specReposMessages?.let { - """ - | Please, check that podfile contains following lines in header: - | $it - | - """.trimMargin() - }, - cocoapodsMessages?.let { - """ - | Please, check that each target depended on $frameworkName contains following dependencies: - | ${it.joinToString("\n")} - | - """.trimMargin() - } - - ).joinToString("\n") - } - - fun handlePodInstallSyntheticError(command: String, retCode: Int, error: String, family: Family, podName: String): String? { - var message = """ - |'pod install' command on the synthetic project failed with return code: $retCode - | - | Full command: $command - | - | Error: ${error.lines().filter { it.contains("[!]") }.joinToString("\n")} - | - """.trimMargin() - - if ( - error.contains("deployment target") || - error.contains("no platform was specified") || - error.contains(Regex("The platform of the target .+ is not compatible with `$podName")) - ) { - message += """ - | - | Possible reason: ${family.name.toLowerCaseAsciiOnly()} deployment target is not configured - | Configure deployment_target for ALL targets as follows: - | cocoapods { - | ... - | ${family.name.toLowerCaseAsciiOnly()}.deploymentTarget = "..." - | ... - | } - | - """.trimMargin() - return message - } else if ( - error.contains("Unable to add a source with url") || - error.contains("Couldn't determine repo name for URL") || - error.contains("Unable to find a specification") - ) { - message += """ - | - | Possible reason: spec repos are not configured correctly. - | Ensure that spec repos are correctly configured for all private pod dependencies: - | cocoapods { - | specRepos { - | url("") - | } - | } - | - """.trimMargin() - return message - } - return null - } - } 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 2a14b59fcc4..863eecc7799 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 @@ -221,8 +221,6 @@ open class PodspecTask : DefaultTask() { else project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null || (project.parent?.let { hasPodfileOwnOrParent(it) } ?: false) - internal fun retrieveSpecRepos(project: Project): SpecRepos? = project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.specRepos - internal fun retrievePods(project: Project): List? = project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podsAsTaskInput } }