From 7aeca2fda08c3bac7f3adba4ed3dd958b4cccac1 Mon Sep 17 00:00:00 2001 From: Artem Daugel-Dauge Date: Thu, 30 Mar 2023 14:54:54 +0200 Subject: [PATCH] [Gradle] Split CocoapodsTasks and AdvancedCocoapodsTasks into separate files (2/2) --- .../cocoapods/PodBuildSettingsProperties.kt | 448 +--------------- .../cocoapods/tasks/AbstractPodInstallTask.kt | 437 +-------------- .../native/cocoapods/tasks/CocoapodsTask.kt | 505 +----------------- .../native/cocoapods/tasks/DefFileTask.kt | 304 +---------- .../cocoapods/tasks/DummyFrameworkTask.kt | 258 +-------- .../native/cocoapods/tasks/PodBuildTask.kt | 449 +--------------- .../native/cocoapods/tasks/PodGenTask.kt | 410 +------------- .../tasks/PodInstallSyntheticTask.kt | 441 +-------------- .../native/cocoapods/tasks/PodInstallTask.kt | 447 +--------------- .../cocoapods/tasks/PodSetupBuildTask.kt | 468 +--------------- .../native/cocoapods/tasks/PodspecTask.kt | 134 +---- 11 files changed, 24 insertions(+), 4277 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/PodBuildSettingsProperties.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/PodBuildSettingsProperties.kt index 8a04a217427..bfad1221659 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/PodBuildSettingsProperties.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/PodBuildSettingsProperties.kt @@ -3,434 +3,14 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle +@file:Suppress("PackageDirectoryMismatch") // Old package for compatibility package org.jetbrains.kotlin.gradle.targets.native.tasks -import org.gradle.api.DefaultTask -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.utils.runCommand -import org.jetbrains.kotlin.konan.target.Family -import org.jetbrains.kotlin.konan.target.HostManager import java.io.File -import java.io.IOException import java.io.Reader import java.util.* -val CocoapodsDependency.schemeName: String - get() = name.split("/")[0] - - -open class CocoapodsTask : DefaultTask() { - init { - onlyIf { - HostManager.hostIsMac - } - } -} - - -/** - * 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:Optional - @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 - } - - @get:OutputDirectory - internal val podsDir: Provider = workingDir.map { it.resolve("Pods") } - - @get:Internal - internal val podsXcodeProjDirProvider: Provider = podsDir.map { it.resolve("Pods.xcodeproj") } - - @TaskAction - open fun doPodInstall() { - val podInstallCommand = listOf("pod", "install") - - runCommand(podInstallCommand, - logger, - errorHandler = ::handleError, - exceptionHandler = { e: IOException -> - CocoapodsErrorHandlingUtil.handle(e, podInstallCommand) - }, - processConfiguration = { - directory(workingDir.get()) - }) - - 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 - - @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 } - - 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 - } - } -} - -/** - * The task generates a synthetic project with all cocoapods dependencies - */ -abstract class PodGenTask : CocoapodsTask() { - - init { - onlyIf { - pods.get().isNotEmpty() - } - } - - @get:InputFile - internal abstract val podspec: Property - - @get:Input - internal abstract val podName: Property - - @get:Input - internal abstract val useLibraries: Property - - @get:Input - internal abstract val family: Property - - @get:Nested - internal abstract val platformSettings: Property - - @get:Nested - internal abstract val specRepos: Property - - @get:Nested - internal abstract val pods: ListProperty - - @get:OutputFile - val podfile: Provider = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") } - - @TaskAction - fun generate() { - val specRepos = specRepos.get().getAll() - - val podfile = this.podfile.get() - podfile.createNewFile() - - val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral) - podfile.writeText(podfileContent) - } - - private fun getPodfileContent(specRepos: Collection, xcodeTarget: String) = - buildString { - - specRepos.forEach { - appendLine("source '$it'") - } - - appendLine("target '$xcodeTarget' do") - if (useLibraries.get().not()) { - appendLine("\tuse_frameworks!") - } - val settings = platformSettings.get() - val deploymentTarget = settings.deploymentTarget - if (deploymentTarget != null) { - appendLine("\tplatform :${settings.name}, '$deploymentTarget'") - } else { - appendLine("\tplatform :${settings.name}") - } - pods.get().mapNotNull { - buildString { - append("pod '${it.name}'") - - val version = it.version - val source = it.source - - if (source != null) { - append(", ${source.getPodSourcePath()}") - } else if (version != null) { - append(", '$version'") - } - - } - }.forEach { appendLine("\t$it") } - appendLine("end\n") - //disable signing for all synthetic pods KT-54314 - append( - """ - post_install do |installer| - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = "" - config.build_settings['CODE_SIGNING_REQUIRED'] = "NO" - config.build_settings['CODE_SIGNING_ALLOWED'] = "NO" - end - end - end - """.trimIndent() - ) - appendLine() - } -} - - -open class PodSetupBuildTask : CocoapodsTask() { - - @get:Input - lateinit var frameworkName: Provider - - @get:Input - internal lateinit var sdk: Provider - - @get:Nested - lateinit var pod: Provider - - @get:OutputFile - val buildSettingsFile: Provider = project.provider { - project.cocoapodsBuildDirs - .buildSettings - .resolve(getBuildSettingFileName(pod.get(), sdk.get())) - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun setupBuild() { - val podsXcodeProjDir = podsXcodeProjDir.get() - - val buildSettingsReceivingCommand = listOf( - "xcodebuild", "-showBuildSettings", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get() - ) - - val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - - val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader()) - buildSettingsFile.get().let { bsf -> - buildSettingsProperties.writeSettings(bsf) - } - } -} - -private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String = - "build-settings-$sdk-${pod.schemeName}.properties" - -/** - * The task compiles external cocoa pods sources. - */ -open class PodBuildTask : CocoapodsTask() { - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:InputFile - lateinit var buildSettingsFile: Provider - internal set - - @get:Nested - internal lateinit var pod: Provider - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:IgnoreEmptyDirectories - @get:InputFiles - internal val srcDir: FileTree - get() = project.fileTree( - buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot } - ) - - @get:Internal - internal var buildDir: Provider = project.provider { - project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir) - } - - @get:Input - internal lateinit var sdk: Provider - - @Suppress("unused") // declares an ouptut - @get:OutputFiles - internal val buildResult: Provider = project.provider { - project.fileTree(buildDir.get()) { - it.include("**/${pod.get().schemeName}.*/") - it.include("**/${pod.get().schemeName}/") - } - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun buildDependencies() { - val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()) - - val podsXcodeProjDir = podsXcodeProjDir.get() - - val podXcodeBuildCommand = listOf( - "xcodebuild", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get(), - "-configuration", podBuildSettings.configuration - ) - - runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - } -} - - data class PodBuildSettingsProperties( internal val buildDir: String, internal val configuration: String, @@ -475,7 +55,6 @@ 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), @@ -497,28 +76,3 @@ data class PodBuildSettingsProperties( getProperty(propertyName) } } - -private object CocoapodsErrorHandlingUtil { - fun handle(e: IOException, command: List) { - if (e.message?.contains("No such file or directory") == true) { - val message = """ - |'${command.take(2).joinToString(" ")}' command failed with an exception: - | ${e.message} - | - | Full command: ${command.joinToString(" ")} - | - | Possible reason: CocoaPods is not installed - | Please check that CocoaPods v1.10 or above is installed. - | - | To check CocoaPods version type 'pod --version' in the terminal - | - | To install CocoaPods execute 'sudo gem install cocoapods' - | - """.trimMargin() - throw IllegalStateException(message) - } else { - throw e - } - } - -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/AbstractPodInstallTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/AbstractPodInstallTask.kt index 8a04a217427..28070194a29 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/AbstractPodInstallTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/AbstractPodInstallTask.kt @@ -3,44 +3,16 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle +@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility package org.jetbrains.kotlin.gradle.targets.native.tasks -import org.gradle.api.DefaultTask -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.utils.runCommand -import org.jetbrains.kotlin.konan.target.Family -import org.jetbrains.kotlin.konan.target.HostManager import java.io.File import java.io.IOException -import java.io.Reader -import java.util.* - -val CocoapodsDependency.schemeName: String - get() = name.split("/")[0] - - -open class CocoapodsTask : DefaultTask() { - init { - onlyIf { - HostManager.hostIsMac - } - } -} - /** * The task takes the path to the Podfile and calls `pod install` @@ -91,413 +63,6 @@ abstract class AbstractPodInstallTask : CocoapodsTask() { 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 - - @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 } - - 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 - } - } -} - -/** - * The task generates a synthetic project with all cocoapods dependencies - */ -abstract class PodGenTask : CocoapodsTask() { - - init { - onlyIf { - pods.get().isNotEmpty() - } - } - - @get:InputFile - internal abstract val podspec: Property - - @get:Input - internal abstract val podName: Property - - @get:Input - internal abstract val useLibraries: Property - - @get:Input - internal abstract val family: Property - - @get:Nested - internal abstract val platformSettings: Property - - @get:Nested - internal abstract val specRepos: Property - - @get:Nested - internal abstract val pods: ListProperty - - @get:OutputFile - val podfile: Provider = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") } - - @TaskAction - fun generate() { - val specRepos = specRepos.get().getAll() - - val podfile = this.podfile.get() - podfile.createNewFile() - - val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral) - podfile.writeText(podfileContent) - } - - private fun getPodfileContent(specRepos: Collection, xcodeTarget: String) = - buildString { - - specRepos.forEach { - appendLine("source '$it'") - } - - appendLine("target '$xcodeTarget' do") - if (useLibraries.get().not()) { - appendLine("\tuse_frameworks!") - } - val settings = platformSettings.get() - val deploymentTarget = settings.deploymentTarget - if (deploymentTarget != null) { - appendLine("\tplatform :${settings.name}, '$deploymentTarget'") - } else { - appendLine("\tplatform :${settings.name}") - } - pods.get().mapNotNull { - buildString { - append("pod '${it.name}'") - - val version = it.version - val source = it.source - - if (source != null) { - append(", ${source.getPodSourcePath()}") - } else if (version != null) { - append(", '$version'") - } - - } - }.forEach { appendLine("\t$it") } - appendLine("end\n") - //disable signing for all synthetic pods KT-54314 - append( - """ - post_install do |installer| - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = "" - config.build_settings['CODE_SIGNING_REQUIRED'] = "NO" - config.build_settings['CODE_SIGNING_ALLOWED'] = "NO" - end - end - end - """.trimIndent() - ) - appendLine() - } -} - - -open class PodSetupBuildTask : CocoapodsTask() { - - @get:Input - lateinit var frameworkName: Provider - - @get:Input - internal lateinit var sdk: Provider - - @get:Nested - lateinit var pod: Provider - - @get:OutputFile - val buildSettingsFile: Provider = project.provider { - project.cocoapodsBuildDirs - .buildSettings - .resolve(getBuildSettingFileName(pod.get(), sdk.get())) - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun setupBuild() { - val podsXcodeProjDir = podsXcodeProjDir.get() - - val buildSettingsReceivingCommand = listOf( - "xcodebuild", "-showBuildSettings", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get() - ) - - val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - - val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader()) - buildSettingsFile.get().let { bsf -> - buildSettingsProperties.writeSettings(bsf) - } - } -} - -private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String = - "build-settings-$sdk-${pod.schemeName}.properties" - -/** - * The task compiles external cocoa pods sources. - */ -open class PodBuildTask : CocoapodsTask() { - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:InputFile - lateinit var buildSettingsFile: Provider - internal set - - @get:Nested - internal lateinit var pod: Provider - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:IgnoreEmptyDirectories - @get:InputFiles - internal val srcDir: FileTree - get() = project.fileTree( - buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot } - ) - - @get:Internal - internal var buildDir: Provider = project.provider { - project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir) - } - - @get:Input - internal lateinit var sdk: Provider - - @Suppress("unused") // declares an ouptut - @get:OutputFiles - internal val buildResult: Provider = project.provider { - project.fileTree(buildDir.get()) { - it.include("**/${pod.get().schemeName}.*/") - it.include("**/${pod.get().schemeName}/") - } - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun buildDependencies() { - val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()) - - val podsXcodeProjDir = podsXcodeProjDir.get() - - val podXcodeBuildCommand = listOf( - "xcodebuild", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get(), - "-configuration", podBuildSettings.configuration - ) - - runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - } -} - - -data class PodBuildSettingsProperties( - internal val buildDir: String, - internal val configuration: String, - val configurationBuildDir: String, - internal val podsTargetSrcRoot: String, - internal val cflags: String? = null, - internal val headerPaths: String? = null, - internal val publicHeadersFolderPath: String? = null, - internal val frameworkPaths: String? = null -) { - - fun writeSettings( - buildSettingsFile: File - ) { - buildSettingsFile.parentFile.mkdirs() - buildSettingsFile.delete() - buildSettingsFile.createNewFile() - - check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" } - - 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") } - publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") } - frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") } - } - } - - companion object { - 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 PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH" - const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS" - - fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties { - with(Properties()) { - @Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here - load(reader) - return PodBuildSettingsProperties( - readProperty(BUILD_DIR), - readProperty(CONFIGURATION), - readProperty(CONFIGURATION_BUILD_DIR), - readProperty(PODS_TARGET_SRCROOT), - readNullableProperty(OTHER_CFLAGS), - readNullableProperty(HEADER_SEARCH_PATHS), - readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH), - 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) - } -} - private object CocoapodsErrorHandlingUtil { fun handle(e: IOException, command: List) { if (e.message?.contains("No such file or directory") == true) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/CocoapodsTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/CocoapodsTask.kt index 8a04a217427..b976e61295d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/CocoapodsTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/CocoapodsTask.kt @@ -3,36 +3,17 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle +@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility package org.jetbrains.kotlin.gradle.targets.native.tasks import org.gradle.api.DefaultTask -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.utils.runCommand -import org.jetbrains.kotlin.konan.target.Family +import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency import org.jetbrains.kotlin.konan.target.HostManager -import java.io.File -import java.io.IOException -import java.io.Reader -import java.util.* val CocoapodsDependency.schemeName: String get() = name.split("/")[0] - open class CocoapodsTask : DefaultTask() { init { onlyIf { @@ -40,485 +21,3 @@ open class CocoapodsTask : DefaultTask() { } } } - - -/** - * 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:Optional - @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 - } - - @get:OutputDirectory - internal val podsDir: Provider = workingDir.map { it.resolve("Pods") } - - @get:Internal - internal val podsXcodeProjDirProvider: Provider = podsDir.map { it.resolve("Pods.xcodeproj") } - - @TaskAction - open fun doPodInstall() { - val podInstallCommand = listOf("pod", "install") - - runCommand(podInstallCommand, - logger, - errorHandler = ::handleError, - exceptionHandler = { e: IOException -> - CocoapodsErrorHandlingUtil.handle(e, podInstallCommand) - }, - processConfiguration = { - directory(workingDir.get()) - }) - - 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 - - @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 } - - 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 - } - } -} - -/** - * The task generates a synthetic project with all cocoapods dependencies - */ -abstract class PodGenTask : CocoapodsTask() { - - init { - onlyIf { - pods.get().isNotEmpty() - } - } - - @get:InputFile - internal abstract val podspec: Property - - @get:Input - internal abstract val podName: Property - - @get:Input - internal abstract val useLibraries: Property - - @get:Input - internal abstract val family: Property - - @get:Nested - internal abstract val platformSettings: Property - - @get:Nested - internal abstract val specRepos: Property - - @get:Nested - internal abstract val pods: ListProperty - - @get:OutputFile - val podfile: Provider = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") } - - @TaskAction - fun generate() { - val specRepos = specRepos.get().getAll() - - val podfile = this.podfile.get() - podfile.createNewFile() - - val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral) - podfile.writeText(podfileContent) - } - - private fun getPodfileContent(specRepos: Collection, xcodeTarget: String) = - buildString { - - specRepos.forEach { - appendLine("source '$it'") - } - - appendLine("target '$xcodeTarget' do") - if (useLibraries.get().not()) { - appendLine("\tuse_frameworks!") - } - val settings = platformSettings.get() - val deploymentTarget = settings.deploymentTarget - if (deploymentTarget != null) { - appendLine("\tplatform :${settings.name}, '$deploymentTarget'") - } else { - appendLine("\tplatform :${settings.name}") - } - pods.get().mapNotNull { - buildString { - append("pod '${it.name}'") - - val version = it.version - val source = it.source - - if (source != null) { - append(", ${source.getPodSourcePath()}") - } else if (version != null) { - append(", '$version'") - } - - } - }.forEach { appendLine("\t$it") } - appendLine("end\n") - //disable signing for all synthetic pods KT-54314 - append( - """ - post_install do |installer| - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = "" - config.build_settings['CODE_SIGNING_REQUIRED'] = "NO" - config.build_settings['CODE_SIGNING_ALLOWED'] = "NO" - end - end - end - """.trimIndent() - ) - appendLine() - } -} - - -open class PodSetupBuildTask : CocoapodsTask() { - - @get:Input - lateinit var frameworkName: Provider - - @get:Input - internal lateinit var sdk: Provider - - @get:Nested - lateinit var pod: Provider - - @get:OutputFile - val buildSettingsFile: Provider = project.provider { - project.cocoapodsBuildDirs - .buildSettings - .resolve(getBuildSettingFileName(pod.get(), sdk.get())) - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun setupBuild() { - val podsXcodeProjDir = podsXcodeProjDir.get() - - val buildSettingsReceivingCommand = listOf( - "xcodebuild", "-showBuildSettings", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get() - ) - - val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - - val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader()) - buildSettingsFile.get().let { bsf -> - buildSettingsProperties.writeSettings(bsf) - } - } -} - -private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String = - "build-settings-$sdk-${pod.schemeName}.properties" - -/** - * The task compiles external cocoa pods sources. - */ -open class PodBuildTask : CocoapodsTask() { - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:InputFile - lateinit var buildSettingsFile: Provider - internal set - - @get:Nested - internal lateinit var pod: Provider - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:IgnoreEmptyDirectories - @get:InputFiles - internal val srcDir: FileTree - get() = project.fileTree( - buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot } - ) - - @get:Internal - internal var buildDir: Provider = project.provider { - project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir) - } - - @get:Input - internal lateinit var sdk: Provider - - @Suppress("unused") // declares an ouptut - @get:OutputFiles - internal val buildResult: Provider = project.provider { - project.fileTree(buildDir.get()) { - it.include("**/${pod.get().schemeName}.*/") - it.include("**/${pod.get().schemeName}/") - } - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun buildDependencies() { - val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()) - - val podsXcodeProjDir = podsXcodeProjDir.get() - - val podXcodeBuildCommand = listOf( - "xcodebuild", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get(), - "-configuration", podBuildSettings.configuration - ) - - runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - } -} - - -data class PodBuildSettingsProperties( - internal val buildDir: String, - internal val configuration: String, - val configurationBuildDir: String, - internal val podsTargetSrcRoot: String, - internal val cflags: String? = null, - internal val headerPaths: String? = null, - internal val publicHeadersFolderPath: String? = null, - internal val frameworkPaths: String? = null -) { - - fun writeSettings( - buildSettingsFile: File - ) { - buildSettingsFile.parentFile.mkdirs() - buildSettingsFile.delete() - buildSettingsFile.createNewFile() - - check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" } - - 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") } - publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") } - frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") } - } - } - - companion object { - 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 PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH" - const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS" - - fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties { - with(Properties()) { - @Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here - load(reader) - return PodBuildSettingsProperties( - readProperty(BUILD_DIR), - readProperty(CONFIGURATION), - readProperty(CONFIGURATION_BUILD_DIR), - readProperty(PODS_TARGET_SRCROOT), - readNullableProperty(OTHER_CFLAGS), - readNullableProperty(HEADER_SEARCH_PATHS), - readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH), - 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) - } -} - -private object CocoapodsErrorHandlingUtil { - fun handle(e: IOException, command: List) { - if (e.message?.contains("No such file or directory") == true) { - val message = """ - |'${command.take(2).joinToString(" ")}' command failed with an exception: - | ${e.message} - | - | Full command: ${command.joinToString(" ")} - | - | Possible reason: CocoaPods is not installed - | Please check that CocoaPods v1.10 or above is installed. - | - | To check CocoaPods version type 'pod --version' in the terminal - | - | To install CocoaPods execute 'sudo gem install cocoapods' - | - """.trimMargin() - throw IllegalStateException(message) - } else { - throw e - } - } - -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/DefFileTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/DefFileTask.kt index 7dbbb3c5f83..dea206fdad9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/DefFileTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/DefFileTask.kt @@ -7,310 +7,16 @@ package org.jetbrains.kotlin.gradle.tasks import org.gradle.api.DefaultTask -import org.gradle.api.Project -import org.gradle.api.plugins.ExtensionAware import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.* -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 -import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.SYNC_TASK_NAME +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs import org.jetbrains.kotlin.gradle.utils.appendLine import java.io.File -/** - * The task generates a podspec file which allows a user to - * integrate a Kotlin/Native framework into a CocoaPods project. - */ -open class PodspecTask : DefaultTask() { - - @get:Input - internal val specName = project.objects.property(String::class.java) - - @get:Internal - internal val outputDir = project.objects.property(File::class.java) - - @get:OutputFile - val outputFile: File - get() = outputDir.get().resolve("${specName.get()}.podspec") - - @get:Input - internal lateinit var needPodspec: Provider - - @get:Nested - val pods = project.objects.listProperty(CocoapodsDependency::class.java) - - @get:Input - internal val version = project.objects.property(String::class.java) - - @get:Input - internal val publishing = project.objects.property(Boolean::class.java) - - @get:Input - @get:Optional - internal val source = project.objects.property(String::class.java) - - @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 - @get:Optional - internal val extraSpecAttributes = project.objects.mapProperty(String::class.java, 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 { needPodspec.get() } - } - - @TaskAction - fun generate() { - - check(version.get() != Project.DEFAULT_VERSION) { - """ - Cocoapods Integration requires pod version to be specified. - Please specify pod version by adding 'version = ""' to the cocoapods block. - Alternatively, specify the version for the entire project explicitly. - Pod version format has to conform podspec syntax requirements: https://guides.cocoapods.org/syntax/podspec.html#version - """.trimIndent() - } - - val gradleWrapper = (project.rootProject.tasks.getByName("wrapper") as? Wrapper)?.scriptFile - require(gradleWrapper != null && gradleWrapper.exists()) { - """ - The Gradle wrapper is required to run the build from Xcode. - - Please run the same command with `-P$GENERATE_WRAPPER_PROPERTY=true` or run the `:wrapper` task to generate the wrapper manually. - - See details about the wrapper at https://docs.gradle.org/current/userguide/gradle_wrapper.html - """.trimIndent() - } - - val deploymentTargets = run { - listOf(ios, osx, tvos, watchos).map { it.get() }.filter { it.deploymentTarget != null }.joinToString("\n") { - if (extraSpecAttributes.get().containsKey("${it.name}.deployment_target")) "" else "| spec.${it.name}.deployment_target = '${it.deploymentTarget}'" - } - } - - val dependencies = pods.get().map { pod -> - val versionSuffix = if (pod.version != null) ", '${pod.version}'" else "" - "| spec.dependency '${pod.name}'$versionSuffix" - }.joinToString(separator = "\n") - - val frameworkDir = project.cocoapodsBuildDirs.framework.relativeTo(outputFile.parentFile) - val vendoredFramework = if (publishing.get()) "${frameworkName.get()}.xcframework" else frameworkDir.resolve("${frameworkName.get()}.framework").invariantSeparatorsPath - val vendoredFrameworks = if (extraSpecAttributes.get().containsKey("vendored_frameworks")) "" else "| spec.vendored_frameworks = '$vendoredFramework'" - - val libraries = if (extraSpecAttributes.get().containsKey("libraries")) "" else "| spec.libraries = 'c++'" - - val xcConfig = if (publishing.get() || extraSpecAttributes.get().containsKey("pod_target_xcconfig")) "" else - """ | - | spec.pod_target_xcconfig = { - | 'KOTLIN_PROJECT_PATH' => '${if (project.depth != 0) project.path else ""}', - | 'PRODUCT_MODULE_NAME' => '${frameworkName.get()}', - | } - """.trimMargin() - - val gradleCommand = "\$REPO_ROOT/${gradleWrapper.relativeTo(project.projectDir).invariantSeparatorsPath}" - val scriptPhase = if (publishing.get() || extraSpecAttributes.get().containsKey("script_phases")) "" else - """ | - | spec.script_phases = [ - | { - | :name => 'Build ${specName.get()}', - | :execution_position => :before_compile, - | :shell_path => '/bin/sh', - | :script => <<-SCRIPT - | if [ "YES" = "${'$'}OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then - | echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" - | exit 0 - | fi - | set -ev - | REPO_ROOT="${'$'}PODS_TARGET_SRCROOT" - | "$gradleCommand" -p "${'$'}REPO_ROOT" ${'$'}KOTLIN_PROJECT_PATH:$SYNC_TASK_NAME \ - | -P${KotlinCocoapodsPlugin.PLATFORM_PROPERTY}=${'$'}PLATFORM_NAME \ - | -P${KotlinCocoapodsPlugin.ARCHS_PROPERTY}="${'$'}ARCHS" \ - | -P${KotlinCocoapodsPlugin.CONFIGURATION_PROPERTY}="${'$'}CONFIGURATION" - | SCRIPT - | } - | ] - """.trimMargin() - - val customSpec = extraSpecAttributes.get().map { "| spec.${it.key} = ${it.value}" }.joinToString("\n") - - with(outputFile) { - writeText( - """ - |Pod::Spec.new do |spec| - | spec.name = '${specName.get()}' - | spec.version = '${version.get()}' - | spec.homepage = ${homepage.getOrEmpty().surroundWithSingleQuotesIfNeeded()} - | spec.source = ${source.getOrElse("{ :http=> ''}")} - | spec.authors = ${authors.getOrEmpty().surroundWithSingleQuotesIfNeeded()} - | spec.license = ${license.getOrEmpty().surroundWithSingleQuotesIfNeeded()} - | spec.summary = '${summary.getOrEmpty()}' - $vendoredFrameworks - $libraries - $deploymentTargets - $dependencies - $xcConfig - $scriptPhase - $customSpec - |end - """.trimMargin() - ) - - if (hasPodfileOwnOrParent(project) && publishing.get().not()) { - logger.quiet( - """ - Generated a podspec file at: ${absolutePath}. - To include it in your Xcode project, check that the following dependency snippet exists in your Podfile: - - pod '${specName.get()}', :path => '${parentFile.absolutePath}' - - """.trimIndent() - ) - } - - } - } - - private fun Provider.getOrEmpty(): String = getOrElse("") - - private fun String.surroundWithSingleQuotesIfNeeded(): String = - if (startsWith("{") || startsWith("<<-") || startsWith("'")) this else "'$this'" - - companion object { - private val KotlinMultiplatformExtension?.cocoapodsExtensionOrNull: CocoapodsExtension? - get() = (this as? ExtensionAware)?.extensions?.findByName(COCOAPODS_EXTENSION_NAME) as? CocoapodsExtension - - private fun hasPodfileOwnOrParent(project: Project): Boolean = - if (project.rootProject == project) project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null - else project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null - || (project.parent?.let { hasPodfileOwnOrParent(it) } ?: false) - - } -} - - -/** - * Creates a dummy framework in the target directory. - * - * We represent a Kotlin/Native module to CocoaPods as a vendored framework. - * CocoaPods needs access to such frameworks during installation process to obtain - * their type (static or dynamic) and configure the Xcode project accordingly. - * But we cannot build the real framework before installation because it may - * depend on CocoaPods libraries which are not downloaded and built at this stage. - * 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. - */ -abstract class DummyFrameworkTask : DefaultTask() { - - @get:Input - abstract val frameworkName: Property - - @get:Input - abstract val useStaticFramework: Property - - @get:OutputDirectory - val outputFramework: Provider = project.provider { project.cocoapodsBuildDirs.dummyFramework } - - private val dummyFrameworkResource: String - get() { - 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 -> - resource.copyTo(file) - } - } - } - - private fun copyTextResource(from: String, to: File, transform: (String) -> String = { it }) { - to.parentFile.mkdirs() - to.printWriter().use { file -> - javaClass.getResourceAsStream(from)!!.use { - it.reader().forEachLine { str -> - file.println(transform(str)) - } - } - } - } - - private fun copyFrameworkFile(relativeFrom: String, relativeTo: String = relativeFrom) = - copyResource( - "$dummyFrameworkResource$relativeFrom", - outputFramework.get().resolve(relativeTo) - ) - - private fun copyFrameworkTextFile( - relativeFrom: String, - relativeTo: String = relativeFrom, - transform: (String) -> String = { it } - ) = copyTextResource( - "$dummyFrameworkResource$relativeFrom", - outputFramework.get().resolve(relativeTo), - transform - ) - - @TaskAction - fun create() { - // Reset the destination directory - with(outputFramework.get()) { - deleteRecursively() - mkdirs() - } - - // Copy files for the dummy framework. - copyFrameworkFile("Info.plist") - copyFrameworkFile("dummy", frameworkName.get()) - copyFrameworkFile("Headers/placeholder.h") - copyFrameworkTextFile("Modules/module.modulemap") { - if (it == "framework module dummy {") { - it.replace("dummy", frameworkName.get()) - } else { - it - } - } - } -} - /** * Generates a def-file for the given CocoaPods dependency. */ diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/DummyFrameworkTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/DummyFrameworkTask.kt index 7dbbb3c5f83..95672b4ba84 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/DummyFrameworkTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/DummyFrameworkTask.kt @@ -7,224 +7,14 @@ package org.jetbrains.kotlin.gradle.tasks import org.gradle.api.DefaultTask -import org.gradle.api.Project -import org.gradle.api.plugins.ExtensionAware import org.gradle.api.provider.Property import org.gradle.api.provider.Provider -import org.gradle.api.tasks.* -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 -import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.SYNC_TASK_NAME +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs -import org.jetbrains.kotlin.gradle.utils.appendLine import java.io.File -/** - * The task generates a podspec file which allows a user to - * integrate a Kotlin/Native framework into a CocoaPods project. - */ -open class PodspecTask : DefaultTask() { - - @get:Input - internal val specName = project.objects.property(String::class.java) - - @get:Internal - internal val outputDir = project.objects.property(File::class.java) - - @get:OutputFile - val outputFile: File - get() = outputDir.get().resolve("${specName.get()}.podspec") - - @get:Input - internal lateinit var needPodspec: Provider - - @get:Nested - val pods = project.objects.listProperty(CocoapodsDependency::class.java) - - @get:Input - internal val version = project.objects.property(String::class.java) - - @get:Input - internal val publishing = project.objects.property(Boolean::class.java) - - @get:Input - @get:Optional - internal val source = project.objects.property(String::class.java) - - @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 - @get:Optional - internal val extraSpecAttributes = project.objects.mapProperty(String::class.java, 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 { needPodspec.get() } - } - - @TaskAction - fun generate() { - - check(version.get() != Project.DEFAULT_VERSION) { - """ - Cocoapods Integration requires pod version to be specified. - Please specify pod version by adding 'version = ""' to the cocoapods block. - Alternatively, specify the version for the entire project explicitly. - Pod version format has to conform podspec syntax requirements: https://guides.cocoapods.org/syntax/podspec.html#version - """.trimIndent() - } - - val gradleWrapper = (project.rootProject.tasks.getByName("wrapper") as? Wrapper)?.scriptFile - require(gradleWrapper != null && gradleWrapper.exists()) { - """ - The Gradle wrapper is required to run the build from Xcode. - - Please run the same command with `-P$GENERATE_WRAPPER_PROPERTY=true` or run the `:wrapper` task to generate the wrapper manually. - - See details about the wrapper at https://docs.gradle.org/current/userguide/gradle_wrapper.html - """.trimIndent() - } - - val deploymentTargets = run { - listOf(ios, osx, tvos, watchos).map { it.get() }.filter { it.deploymentTarget != null }.joinToString("\n") { - if (extraSpecAttributes.get().containsKey("${it.name}.deployment_target")) "" else "| spec.${it.name}.deployment_target = '${it.deploymentTarget}'" - } - } - - val dependencies = pods.get().map { pod -> - val versionSuffix = if (pod.version != null) ", '${pod.version}'" else "" - "| spec.dependency '${pod.name}'$versionSuffix" - }.joinToString(separator = "\n") - - val frameworkDir = project.cocoapodsBuildDirs.framework.relativeTo(outputFile.parentFile) - val vendoredFramework = if (publishing.get()) "${frameworkName.get()}.xcframework" else frameworkDir.resolve("${frameworkName.get()}.framework").invariantSeparatorsPath - val vendoredFrameworks = if (extraSpecAttributes.get().containsKey("vendored_frameworks")) "" else "| spec.vendored_frameworks = '$vendoredFramework'" - - val libraries = if (extraSpecAttributes.get().containsKey("libraries")) "" else "| spec.libraries = 'c++'" - - val xcConfig = if (publishing.get() || extraSpecAttributes.get().containsKey("pod_target_xcconfig")) "" else - """ | - | spec.pod_target_xcconfig = { - | 'KOTLIN_PROJECT_PATH' => '${if (project.depth != 0) project.path else ""}', - | 'PRODUCT_MODULE_NAME' => '${frameworkName.get()}', - | } - """.trimMargin() - - val gradleCommand = "\$REPO_ROOT/${gradleWrapper.relativeTo(project.projectDir).invariantSeparatorsPath}" - val scriptPhase = if (publishing.get() || extraSpecAttributes.get().containsKey("script_phases")) "" else - """ | - | spec.script_phases = [ - | { - | :name => 'Build ${specName.get()}', - | :execution_position => :before_compile, - | :shell_path => '/bin/sh', - | :script => <<-SCRIPT - | if [ "YES" = "${'$'}OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then - | echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" - | exit 0 - | fi - | set -ev - | REPO_ROOT="${'$'}PODS_TARGET_SRCROOT" - | "$gradleCommand" -p "${'$'}REPO_ROOT" ${'$'}KOTLIN_PROJECT_PATH:$SYNC_TASK_NAME \ - | -P${KotlinCocoapodsPlugin.PLATFORM_PROPERTY}=${'$'}PLATFORM_NAME \ - | -P${KotlinCocoapodsPlugin.ARCHS_PROPERTY}="${'$'}ARCHS" \ - | -P${KotlinCocoapodsPlugin.CONFIGURATION_PROPERTY}="${'$'}CONFIGURATION" - | SCRIPT - | } - | ] - """.trimMargin() - - val customSpec = extraSpecAttributes.get().map { "| spec.${it.key} = ${it.value}" }.joinToString("\n") - - with(outputFile) { - writeText( - """ - |Pod::Spec.new do |spec| - | spec.name = '${specName.get()}' - | spec.version = '${version.get()}' - | spec.homepage = ${homepage.getOrEmpty().surroundWithSingleQuotesIfNeeded()} - | spec.source = ${source.getOrElse("{ :http=> ''}")} - | spec.authors = ${authors.getOrEmpty().surroundWithSingleQuotesIfNeeded()} - | spec.license = ${license.getOrEmpty().surroundWithSingleQuotesIfNeeded()} - | spec.summary = '${summary.getOrEmpty()}' - $vendoredFrameworks - $libraries - $deploymentTargets - $dependencies - $xcConfig - $scriptPhase - $customSpec - |end - """.trimMargin() - ) - - if (hasPodfileOwnOrParent(project) && publishing.get().not()) { - logger.quiet( - """ - Generated a podspec file at: ${absolutePath}. - To include it in your Xcode project, check that the following dependency snippet exists in your Podfile: - - pod '${specName.get()}', :path => '${parentFile.absolutePath}' - - """.trimIndent() - ) - } - - } - } - - private fun Provider.getOrEmpty(): String = getOrElse("") - - private fun String.surroundWithSingleQuotesIfNeeded(): String = - if (startsWith("{") || startsWith("<<-") || startsWith("'")) this else "'$this'" - - companion object { - private val KotlinMultiplatformExtension?.cocoapodsExtensionOrNull: CocoapodsExtension? - get() = (this as? ExtensionAware)?.extensions?.findByName(COCOAPODS_EXTENSION_NAME) as? CocoapodsExtension - - private fun hasPodfileOwnOrParent(project: Project): Boolean = - if (project.rootProject == project) project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null - else project.multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null - || (project.parent?.let { hasPodfileOwnOrParent(it) } ?: false) - - } -} - - /** * Creates a dummy framework in the target directory. * @@ -310,45 +100,3 @@ abstract class DummyFrameworkTask : DefaultTask() { } } } - -/** - * Generates a def-file for the given CocoaPods dependency. - */ -abstract class DefFileTask : DefaultTask() { - - @get:Nested - abstract val pod: Property - - @get:Input - abstract val useLibraries: Property - - @get:OutputFile - val outputFile: File - get() = project.cocoapodsBuildDirs.defs.resolve("${pod.get().moduleName}.def") - - @TaskAction - fun generate() { - outputFile.parentFile.mkdirs() - outputFile.writeText(buildString { - appendLine("language = Objective-C") - with(pod.get()) { - when { - headers != null -> appendLine("headers = $headers") - useLibraries.get() -> logger.warn( - """ - w: Pod '$moduleName' should have 'headers' property specified when using 'useLibraries()'. - Otherwise code from this pod won't be accessible from Kotlin. - """.trimIndent() - ) - else -> { - appendLine("modules = $moduleName") - - // Linker opt with framework name is added so produced cinterop klib would have this flag inside its manifest - // This way error will be more obvious when someone will try to depend on a library with this cinterop - appendLine("linkerOpts = -framework $moduleName") - } - } - } - }) - } -} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodBuildTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodBuildTask.kt index 8a04a217427..825d157e065 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodBuildTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodBuildTask.kt @@ -3,374 +3,20 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle +@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility package org.jetbrains.kotlin.gradle.targets.native.tasks -import org.gradle.api.DefaultTask 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.utils.runCommand -import org.jetbrains.kotlin.konan.target.Family -import org.jetbrains.kotlin.konan.target.HostManager import java.io.File -import java.io.IOException -import java.io.Reader import java.util.* -val CocoapodsDependency.schemeName: String - get() = name.split("/")[0] - - -open class CocoapodsTask : DefaultTask() { - init { - onlyIf { - HostManager.hostIsMac - } - } -} - - -/** - * 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:Optional - @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 - } - - @get:OutputDirectory - internal val podsDir: Provider = workingDir.map { it.resolve("Pods") } - - @get:Internal - internal val podsXcodeProjDirProvider: Provider = podsDir.map { it.resolve("Pods.xcodeproj") } - - @TaskAction - open fun doPodInstall() { - val podInstallCommand = listOf("pod", "install") - - runCommand(podInstallCommand, - logger, - errorHandler = ::handleError, - exceptionHandler = { e: IOException -> - CocoapodsErrorHandlingUtil.handle(e, podInstallCommand) - }, - processConfiguration = { - directory(workingDir.get()) - }) - - 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 - - @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 } - - 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 - } - } -} - -/** - * The task generates a synthetic project with all cocoapods dependencies - */ -abstract class PodGenTask : CocoapodsTask() { - - init { - onlyIf { - pods.get().isNotEmpty() - } - } - - @get:InputFile - internal abstract val podspec: Property - - @get:Input - internal abstract val podName: Property - - @get:Input - internal abstract val useLibraries: Property - - @get:Input - internal abstract val family: Property - - @get:Nested - internal abstract val platformSettings: Property - - @get:Nested - internal abstract val specRepos: Property - - @get:Nested - internal abstract val pods: ListProperty - - @get:OutputFile - val podfile: Provider = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") } - - @TaskAction - fun generate() { - val specRepos = specRepos.get().getAll() - - val podfile = this.podfile.get() - podfile.createNewFile() - - val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral) - podfile.writeText(podfileContent) - } - - private fun getPodfileContent(specRepos: Collection, xcodeTarget: String) = - buildString { - - specRepos.forEach { - appendLine("source '$it'") - } - - appendLine("target '$xcodeTarget' do") - if (useLibraries.get().not()) { - appendLine("\tuse_frameworks!") - } - val settings = platformSettings.get() - val deploymentTarget = settings.deploymentTarget - if (deploymentTarget != null) { - appendLine("\tplatform :${settings.name}, '$deploymentTarget'") - } else { - appendLine("\tplatform :${settings.name}") - } - pods.get().mapNotNull { - buildString { - append("pod '${it.name}'") - - val version = it.version - val source = it.source - - if (source != null) { - append(", ${source.getPodSourcePath()}") - } else if (version != null) { - append(", '$version'") - } - - } - }.forEach { appendLine("\t$it") } - appendLine("end\n") - //disable signing for all synthetic pods KT-54314 - append( - """ - post_install do |installer| - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = "" - config.build_settings['CODE_SIGNING_REQUIRED'] = "NO" - config.build_settings['CODE_SIGNING_ALLOWED'] = "NO" - end - end - end - """.trimIndent() - ) - appendLine() - } -} - - -open class PodSetupBuildTask : CocoapodsTask() { - - @get:Input - lateinit var frameworkName: Provider - - @get:Input - internal lateinit var sdk: Provider - - @get:Nested - lateinit var pod: Provider - - @get:OutputFile - val buildSettingsFile: Provider = project.provider { - project.cocoapodsBuildDirs - .buildSettings - .resolve(getBuildSettingFileName(pod.get(), sdk.get())) - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun setupBuild() { - val podsXcodeProjDir = podsXcodeProjDir.get() - - val buildSettingsReceivingCommand = listOf( - "xcodebuild", "-showBuildSettings", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get() - ) - - val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - - val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader()) - buildSettingsFile.get().let { bsf -> - buildSettingsProperties.writeSettings(bsf) - } - } -} - -private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String = - "build-settings-$sdk-${pod.schemeName}.properties" - /** * The task compiles external cocoa pods sources. */ @@ -429,96 +75,3 @@ open class PodBuildTask : CocoapodsTask() { runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } } } - - -data class PodBuildSettingsProperties( - internal val buildDir: String, - internal val configuration: String, - val configurationBuildDir: String, - internal val podsTargetSrcRoot: String, - internal val cflags: String? = null, - internal val headerPaths: String? = null, - internal val publicHeadersFolderPath: String? = null, - internal val frameworkPaths: String? = null -) { - - fun writeSettings( - buildSettingsFile: File - ) { - buildSettingsFile.parentFile.mkdirs() - buildSettingsFile.delete() - buildSettingsFile.createNewFile() - - check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" } - - 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") } - publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") } - frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") } - } - } - - companion object { - 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 PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH" - const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS" - - fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties { - with(Properties()) { - @Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here - load(reader) - return PodBuildSettingsProperties( - readProperty(BUILD_DIR), - readProperty(CONFIGURATION), - readProperty(CONFIGURATION_BUILD_DIR), - readProperty(PODS_TARGET_SRCROOT), - readNullableProperty(OTHER_CFLAGS), - readNullableProperty(HEADER_SEARCH_PATHS), - readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH), - 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) - } -} - -private object CocoapodsErrorHandlingUtil { - fun handle(e: IOException, command: List) { - if (e.message?.contains("No such file or directory") == true) { - val message = """ - |'${command.take(2).joinToString(" ")}' command failed with an exception: - | ${e.message} - | - | Full command: ${command.joinToString(" ")} - | - | Possible reason: CocoaPods is not installed - | Please check that CocoaPods v1.10 or above is installed. - | - | To check CocoaPods version type 'pod --version' in the terminal - | - | To install CocoaPods execute 'sudo gem install cocoapods' - | - """.trimMargin() - throw IllegalStateException(message) - } else { - throw e - } - } - -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodGenTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodGenTask.kt index 8a04a217427..ef530bd6584 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodGenTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodGenTask.kt @@ -3,230 +3,19 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle +@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility package org.jetbrains.kotlin.gradle.targets.native.tasks -import org.gradle.api.DefaultTask -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.utils.runCommand import org.jetbrains.kotlin.konan.target.Family -import org.jetbrains.kotlin.konan.target.HostManager import java.io.File -import java.io.IOException -import java.io.Reader -import java.util.* - -val CocoapodsDependency.schemeName: String - get() = name.split("/")[0] - - -open class CocoapodsTask : DefaultTask() { - init { - onlyIf { - HostManager.hostIsMac - } - } -} - - -/** - * 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:Optional - @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 - } - - @get:OutputDirectory - internal val podsDir: Provider = workingDir.map { it.resolve("Pods") } - - @get:Internal - internal val podsXcodeProjDirProvider: Provider = podsDir.map { it.resolve("Pods.xcodeproj") } - - @TaskAction - open fun doPodInstall() { - val podInstallCommand = listOf("pod", "install") - - runCommand(podInstallCommand, - logger, - errorHandler = ::handleError, - exceptionHandler = { e: IOException -> - CocoapodsErrorHandlingUtil.handle(e, podInstallCommand) - }, - processConfiguration = { - directory(workingDir.get()) - }) - - 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 - - @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 } - - 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 - } - } -} /** * The task generates a synthetic project with all cocoapods dependencies @@ -325,200 +114,3 @@ abstract class PodGenTask : CocoapodsTask() { appendLine() } } - - -open class PodSetupBuildTask : CocoapodsTask() { - - @get:Input - lateinit var frameworkName: Provider - - @get:Input - internal lateinit var sdk: Provider - - @get:Nested - lateinit var pod: Provider - - @get:OutputFile - val buildSettingsFile: Provider = project.provider { - project.cocoapodsBuildDirs - .buildSettings - .resolve(getBuildSettingFileName(pod.get(), sdk.get())) - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun setupBuild() { - val podsXcodeProjDir = podsXcodeProjDir.get() - - val buildSettingsReceivingCommand = listOf( - "xcodebuild", "-showBuildSettings", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get() - ) - - val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - - val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader()) - buildSettingsFile.get().let { bsf -> - buildSettingsProperties.writeSettings(bsf) - } - } -} - -private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String = - "build-settings-$sdk-${pod.schemeName}.properties" - -/** - * The task compiles external cocoa pods sources. - */ -open class PodBuildTask : CocoapodsTask() { - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:InputFile - lateinit var buildSettingsFile: Provider - internal set - - @get:Nested - internal lateinit var pod: Provider - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:IgnoreEmptyDirectories - @get:InputFiles - internal val srcDir: FileTree - get() = project.fileTree( - buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot } - ) - - @get:Internal - internal var buildDir: Provider = project.provider { - project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir) - } - - @get:Input - internal lateinit var sdk: Provider - - @Suppress("unused") // declares an ouptut - @get:OutputFiles - internal val buildResult: Provider = project.provider { - project.fileTree(buildDir.get()) { - it.include("**/${pod.get().schemeName}.*/") - it.include("**/${pod.get().schemeName}/") - } - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun buildDependencies() { - val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()) - - val podsXcodeProjDir = podsXcodeProjDir.get() - - val podXcodeBuildCommand = listOf( - "xcodebuild", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get(), - "-configuration", podBuildSettings.configuration - ) - - runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - } -} - - -data class PodBuildSettingsProperties( - internal val buildDir: String, - internal val configuration: String, - val configurationBuildDir: String, - internal val podsTargetSrcRoot: String, - internal val cflags: String? = null, - internal val headerPaths: String? = null, - internal val publicHeadersFolderPath: String? = null, - internal val frameworkPaths: String? = null -) { - - fun writeSettings( - buildSettingsFile: File - ) { - buildSettingsFile.parentFile.mkdirs() - buildSettingsFile.delete() - buildSettingsFile.createNewFile() - - check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" } - - 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") } - publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") } - frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") } - } - } - - companion object { - 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 PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH" - const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS" - - fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties { - with(Properties()) { - @Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here - load(reader) - return PodBuildSettingsProperties( - readProperty(BUILD_DIR), - readProperty(CONFIGURATION), - readProperty(CONFIGURATION_BUILD_DIR), - readProperty(PODS_TARGET_SRCROOT), - readNullableProperty(OTHER_CFLAGS), - readNullableProperty(HEADER_SEARCH_PATHS), - readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH), - 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) - } -} - -private object CocoapodsErrorHandlingUtil { - fun handle(e: IOException, command: List) { - if (e.message?.contains("No such file or directory") == true) { - val message = """ - |'${command.take(2).joinToString(" ")}' command failed with an exception: - | ${e.message} - | - | Full command: ${command.joinToString(" ")} - | - | Possible reason: CocoaPods is not installed - | Please check that CocoaPods v1.10 or above is installed. - | - | To check CocoaPods version type 'pod --version' in the terminal - | - | To install CocoaPods execute 'sudo gem install cocoapods' - | - """.trimMargin() - throw IllegalStateException(message) - } else { - throw e - } - } - -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodInstallSyntheticTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodInstallSyntheticTask.kt index 8a04a217427..2ce33d93987 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodInstallSyntheticTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodInstallSyntheticTask.kt @@ -3,157 +3,17 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle +@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility package org.jetbrains.kotlin.gradle.targets.native.tasks -import org.gradle.api.DefaultTask -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.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputDirectory 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.utils.runCommand import org.jetbrains.kotlin.konan.target.Family -import org.jetbrains.kotlin.konan.target.HostManager import java.io.File -import java.io.IOException -import java.io.Reader -import java.util.* - -val CocoapodsDependency.schemeName: String - get() = name.split("/")[0] - - -open class CocoapodsTask : DefaultTask() { - init { - onlyIf { - HostManager.hostIsMac - } - } -} - - -/** - * 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:Optional - @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 - } - - @get:OutputDirectory - internal val podsDir: Provider = workingDir.map { it.resolve("Pods") } - - @get:Internal - internal val podsXcodeProjDirProvider: Provider = podsDir.map { it.resolve("Pods.xcodeproj") } - - @TaskAction - open fun doPodInstall() { - val podInstallCommand = listOf("pod", "install") - - runCommand(podInstallCommand, - logger, - errorHandler = ::handleError, - exceptionHandler = { e: IOException -> - CocoapodsErrorHandlingUtil.handle(e, podInstallCommand) - }, - processConfiguration = { - directory(workingDir.get()) - }) - - 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 - - @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 } - - 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() { @@ -227,298 +87,3 @@ abstract class PodInstallSyntheticTask : AbstractPodInstallTask() { } } } - -/** - * The task generates a synthetic project with all cocoapods dependencies - */ -abstract class PodGenTask : CocoapodsTask() { - - init { - onlyIf { - pods.get().isNotEmpty() - } - } - - @get:InputFile - internal abstract val podspec: Property - - @get:Input - internal abstract val podName: Property - - @get:Input - internal abstract val useLibraries: Property - - @get:Input - internal abstract val family: Property - - @get:Nested - internal abstract val platformSettings: Property - - @get:Nested - internal abstract val specRepos: Property - - @get:Nested - internal abstract val pods: ListProperty - - @get:OutputFile - val podfile: Provider = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") } - - @TaskAction - fun generate() { - val specRepos = specRepos.get().getAll() - - val podfile = this.podfile.get() - podfile.createNewFile() - - val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral) - podfile.writeText(podfileContent) - } - - private fun getPodfileContent(specRepos: Collection, xcodeTarget: String) = - buildString { - - specRepos.forEach { - appendLine("source '$it'") - } - - appendLine("target '$xcodeTarget' do") - if (useLibraries.get().not()) { - appendLine("\tuse_frameworks!") - } - val settings = platformSettings.get() - val deploymentTarget = settings.deploymentTarget - if (deploymentTarget != null) { - appendLine("\tplatform :${settings.name}, '$deploymentTarget'") - } else { - appendLine("\tplatform :${settings.name}") - } - pods.get().mapNotNull { - buildString { - append("pod '${it.name}'") - - val version = it.version - val source = it.source - - if (source != null) { - append(", ${source.getPodSourcePath()}") - } else if (version != null) { - append(", '$version'") - } - - } - }.forEach { appendLine("\t$it") } - appendLine("end\n") - //disable signing for all synthetic pods KT-54314 - append( - """ - post_install do |installer| - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = "" - config.build_settings['CODE_SIGNING_REQUIRED'] = "NO" - config.build_settings['CODE_SIGNING_ALLOWED'] = "NO" - end - end - end - """.trimIndent() - ) - appendLine() - } -} - - -open class PodSetupBuildTask : CocoapodsTask() { - - @get:Input - lateinit var frameworkName: Provider - - @get:Input - internal lateinit var sdk: Provider - - @get:Nested - lateinit var pod: Provider - - @get:OutputFile - val buildSettingsFile: Provider = project.provider { - project.cocoapodsBuildDirs - .buildSettings - .resolve(getBuildSettingFileName(pod.get(), sdk.get())) - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun setupBuild() { - val podsXcodeProjDir = podsXcodeProjDir.get() - - val buildSettingsReceivingCommand = listOf( - "xcodebuild", "-showBuildSettings", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get() - ) - - val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - - val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader()) - buildSettingsFile.get().let { bsf -> - buildSettingsProperties.writeSettings(bsf) - } - } -} - -private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String = - "build-settings-$sdk-${pod.schemeName}.properties" - -/** - * The task compiles external cocoa pods sources. - */ -open class PodBuildTask : CocoapodsTask() { - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:InputFile - lateinit var buildSettingsFile: Provider - internal set - - @get:Nested - internal lateinit var pod: Provider - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:IgnoreEmptyDirectories - @get:InputFiles - internal val srcDir: FileTree - get() = project.fileTree( - buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot } - ) - - @get:Internal - internal var buildDir: Provider = project.provider { - project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir) - } - - @get:Input - internal lateinit var sdk: Provider - - @Suppress("unused") // declares an ouptut - @get:OutputFiles - internal val buildResult: Provider = project.provider { - project.fileTree(buildDir.get()) { - it.include("**/${pod.get().schemeName}.*/") - it.include("**/${pod.get().schemeName}/") - } - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun buildDependencies() { - val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()) - - val podsXcodeProjDir = podsXcodeProjDir.get() - - val podXcodeBuildCommand = listOf( - "xcodebuild", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get(), - "-configuration", podBuildSettings.configuration - ) - - runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - } -} - - -data class PodBuildSettingsProperties( - internal val buildDir: String, - internal val configuration: String, - val configurationBuildDir: String, - internal val podsTargetSrcRoot: String, - internal val cflags: String? = null, - internal val headerPaths: String? = null, - internal val publicHeadersFolderPath: String? = null, - internal val frameworkPaths: String? = null -) { - - fun writeSettings( - buildSettingsFile: File - ) { - buildSettingsFile.parentFile.mkdirs() - buildSettingsFile.delete() - buildSettingsFile.createNewFile() - - check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" } - - 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") } - publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") } - frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") } - } - } - - companion object { - 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 PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH" - const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS" - - fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties { - with(Properties()) { - @Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here - load(reader) - return PodBuildSettingsProperties( - readProperty(BUILD_DIR), - readProperty(CONFIGURATION), - readProperty(CONFIGURATION_BUILD_DIR), - readProperty(PODS_TARGET_SRCROOT), - readNullableProperty(OTHER_CFLAGS), - readNullableProperty(HEADER_SEARCH_PATHS), - readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH), - 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) - } -} - -private object CocoapodsErrorHandlingUtil { - fun handle(e: IOException, command: List) { - if (e.message?.contains("No such file or directory") == true) { - val message = """ - |'${command.take(2).joinToString(" ")}' command failed with an exception: - | ${e.message} - | - | Full command: ${command.joinToString(" ")} - | - | Possible reason: CocoaPods is not installed - | Please check that CocoaPods v1.10 or above is installed. - | - | To check CocoaPods version type 'pod --version' in the terminal - | - | To install CocoaPods execute 'sudo gem install cocoapods' - | - """.trimMargin() - throw IllegalStateException(message) - } else { - throw e - } - } - -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodInstallTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodInstallTask.kt index 8a04a217427..0ced95e7493 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodInstallTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodInstallTask.kt @@ -3,93 +3,20 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle +@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility package org.jetbrains.kotlin.gradle.targets.native.tasks -import org.gradle.api.DefaultTask -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.CocoapodsExtension.CocoapodsDependency +import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.SpecRepos 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.utils.runCommand -import org.jetbrains.kotlin.konan.target.Family -import org.jetbrains.kotlin.konan.target.HostManager import java.io.File -import java.io.IOException -import java.io.Reader -import java.util.* - -val CocoapodsDependency.schemeName: String - get() = name.split("/")[0] - - -open class CocoapodsTask : DefaultTask() { - init { - onlyIf { - HostManager.hostIsMac - } - } -} - - -/** - * 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:Optional - @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 - } - - @get:OutputDirectory - internal val podsDir: Provider = workingDir.map { it.resolve("Pods") } - - @get:Internal - internal val podsXcodeProjDirProvider: Provider = podsDir.map { it.resolve("Pods.xcodeproj") } - - @TaskAction - open fun doPodInstall() { - val podInstallCommand = listOf("pod", "install") - - runCommand(podInstallCommand, - logger, - errorHandler = ::handleError, - exceptionHandler = { e: IOException -> - CocoapodsErrorHandlingUtil.handle(e, podInstallCommand) - }, - processConfiguration = { - directory(workingDir.get()) - }) - - 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() { @@ -154,371 +81,3 @@ abstract class PodInstallTask : AbstractPodInstallTask() { ).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 - } - } -} - -/** - * The task generates a synthetic project with all cocoapods dependencies - */ -abstract class PodGenTask : CocoapodsTask() { - - init { - onlyIf { - pods.get().isNotEmpty() - } - } - - @get:InputFile - internal abstract val podspec: Property - - @get:Input - internal abstract val podName: Property - - @get:Input - internal abstract val useLibraries: Property - - @get:Input - internal abstract val family: Property - - @get:Nested - internal abstract val platformSettings: Property - - @get:Nested - internal abstract val specRepos: Property - - @get:Nested - internal abstract val pods: ListProperty - - @get:OutputFile - val podfile: Provider = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") } - - @TaskAction - fun generate() { - val specRepos = specRepos.get().getAll() - - val podfile = this.podfile.get() - podfile.createNewFile() - - val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral) - podfile.writeText(podfileContent) - } - - private fun getPodfileContent(specRepos: Collection, xcodeTarget: String) = - buildString { - - specRepos.forEach { - appendLine("source '$it'") - } - - appendLine("target '$xcodeTarget' do") - if (useLibraries.get().not()) { - appendLine("\tuse_frameworks!") - } - val settings = platformSettings.get() - val deploymentTarget = settings.deploymentTarget - if (deploymentTarget != null) { - appendLine("\tplatform :${settings.name}, '$deploymentTarget'") - } else { - appendLine("\tplatform :${settings.name}") - } - pods.get().mapNotNull { - buildString { - append("pod '${it.name}'") - - val version = it.version - val source = it.source - - if (source != null) { - append(", ${source.getPodSourcePath()}") - } else if (version != null) { - append(", '$version'") - } - - } - }.forEach { appendLine("\t$it") } - appendLine("end\n") - //disable signing for all synthetic pods KT-54314 - append( - """ - post_install do |installer| - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = "" - config.build_settings['CODE_SIGNING_REQUIRED'] = "NO" - config.build_settings['CODE_SIGNING_ALLOWED'] = "NO" - end - end - end - """.trimIndent() - ) - appendLine() - } -} - - -open class PodSetupBuildTask : CocoapodsTask() { - - @get:Input - lateinit var frameworkName: Provider - - @get:Input - internal lateinit var sdk: Provider - - @get:Nested - lateinit var pod: Provider - - @get:OutputFile - val buildSettingsFile: Provider = project.provider { - project.cocoapodsBuildDirs - .buildSettings - .resolve(getBuildSettingFileName(pod.get(), sdk.get())) - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun setupBuild() { - val podsXcodeProjDir = podsXcodeProjDir.get() - - val buildSettingsReceivingCommand = listOf( - "xcodebuild", "-showBuildSettings", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get() - ) - - val outputText = runCommand(buildSettingsReceivingCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - - val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromReader(outputText.reader()) - buildSettingsFile.get().let { bsf -> - buildSettingsProperties.writeSettings(bsf) - } - } -} - -private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String = - "build-settings-$sdk-${pod.schemeName}.properties" - -/** - * The task compiles external cocoa pods sources. - */ -open class PodBuildTask : CocoapodsTask() { - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:InputFile - lateinit var buildSettingsFile: Provider - internal set - - @get:Nested - internal lateinit var pod: Provider - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:IgnoreEmptyDirectories - @get:InputFiles - internal val srcDir: FileTree - get() = project.fileTree( - buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot } - ) - - @get:Internal - internal var buildDir: Provider = project.provider { - project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir) - } - - @get:Input - internal lateinit var sdk: Provider - - @Suppress("unused") // declares an ouptut - @get:OutputFiles - internal val buildResult: Provider = project.provider { - project.fileTree(buildDir.get()) { - it.include("**/${pod.get().schemeName}.*/") - it.include("**/${pod.get().schemeName}/") - } - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun buildDependencies() { - val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()) - - val podsXcodeProjDir = podsXcodeProjDir.get() - - val podXcodeBuildCommand = listOf( - "xcodebuild", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get(), - "-configuration", podBuildSettings.configuration - ) - - runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - } -} - - -data class PodBuildSettingsProperties( - internal val buildDir: String, - internal val configuration: String, - val configurationBuildDir: String, - internal val podsTargetSrcRoot: String, - internal val cflags: String? = null, - internal val headerPaths: String? = null, - internal val publicHeadersFolderPath: String? = null, - internal val frameworkPaths: String? = null -) { - - fun writeSettings( - buildSettingsFile: File - ) { - buildSettingsFile.parentFile.mkdirs() - buildSettingsFile.delete() - buildSettingsFile.createNewFile() - - check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" } - - 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") } - publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") } - frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") } - } - } - - companion object { - 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 PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH" - const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS" - - fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties { - with(Properties()) { - @Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here - load(reader) - return PodBuildSettingsProperties( - readProperty(BUILD_DIR), - readProperty(CONFIGURATION), - readProperty(CONFIGURATION_BUILD_DIR), - readProperty(PODS_TARGET_SRCROOT), - readNullableProperty(OTHER_CFLAGS), - readNullableProperty(HEADER_SEARCH_PATHS), - readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH), - 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) - } -} - -private object CocoapodsErrorHandlingUtil { - fun handle(e: IOException, command: List) { - if (e.message?.contains("No such file or directory") == true) { - val message = """ - |'${command.take(2).joinToString(" ")}' command failed with an exception: - | ${e.message} - | - | Full command: ${command.joinToString(" ")} - | - | Possible reason: CocoaPods is not installed - | Please check that CocoaPods v1.10 or above is installed. - | - | To check CocoaPods version type 'pod --version' in the terminal - | - | To install CocoaPods execute 'sudo gem install cocoapods' - | - """.trimMargin() - throw IllegalStateException(message) - } else { - throw e - } - } - -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodSetupBuildTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodSetupBuildTask.kt index 8a04a217427..21fd984292a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodSetupBuildTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodSetupBuildTask.kt @@ -3,329 +3,16 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -@file:Suppress("LeakingThis") // All tasks should be inherited only by Gradle +@file:Suppress("LeakingThis", "PackageDirectoryMismatch") // All tasks should be inherited only by Gradle, Old package for compatibility package org.jetbrains.kotlin.gradle.targets.native.tasks -import org.gradle.api.DefaultTask -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.CocoapodsExtension.CocoapodsDependency 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.utils.runCommand -import org.jetbrains.kotlin.konan.target.Family -import org.jetbrains.kotlin.konan.target.HostManager import java.io.File -import java.io.IOException -import java.io.Reader -import java.util.* - -val CocoapodsDependency.schemeName: String - get() = name.split("/")[0] - - -open class CocoapodsTask : DefaultTask() { - init { - onlyIf { - HostManager.hostIsMac - } - } -} - - -/** - * 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:Optional - @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 - } - - @get:OutputDirectory - internal val podsDir: Provider = workingDir.map { it.resolve("Pods") } - - @get:Internal - internal val podsXcodeProjDirProvider: Provider = podsDir.map { it.resolve("Pods.xcodeproj") } - - @TaskAction - open fun doPodInstall() { - val podInstallCommand = listOf("pod", "install") - - runCommand(podInstallCommand, - logger, - errorHandler = ::handleError, - exceptionHandler = { e: IOException -> - CocoapodsErrorHandlingUtil.handle(e, podInstallCommand) - }, - processConfiguration = { - directory(workingDir.get()) - }) - - 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 - - @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 } - - 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 - } - } -} - -/** - * The task generates a synthetic project with all cocoapods dependencies - */ -abstract class PodGenTask : CocoapodsTask() { - - init { - onlyIf { - pods.get().isNotEmpty() - } - } - - @get:InputFile - internal abstract val podspec: Property - - @get:Input - internal abstract val podName: Property - - @get:Input - internal abstract val useLibraries: Property - - @get:Input - internal abstract val family: Property - - @get:Nested - internal abstract val platformSettings: Property - - @get:Nested - internal abstract val specRepos: Property - - @get:Nested - internal abstract val pods: ListProperty - - @get:OutputFile - val podfile: Provider = family.map { project.cocoapodsBuildDirs.synthetic(it).resolve("Podfile") } - - @TaskAction - fun generate() { - val specRepos = specRepos.get().getAll() - - val podfile = this.podfile.get() - podfile.createNewFile() - - val podfileContent = getPodfileContent(specRepos, family.get().platformLiteral) - podfile.writeText(podfileContent) - } - - private fun getPodfileContent(specRepos: Collection, xcodeTarget: String) = - buildString { - - specRepos.forEach { - appendLine("source '$it'") - } - - appendLine("target '$xcodeTarget' do") - if (useLibraries.get().not()) { - appendLine("\tuse_frameworks!") - } - val settings = platformSettings.get() - val deploymentTarget = settings.deploymentTarget - if (deploymentTarget != null) { - appendLine("\tplatform :${settings.name}, '$deploymentTarget'") - } else { - appendLine("\tplatform :${settings.name}") - } - pods.get().mapNotNull { - buildString { - append("pod '${it.name}'") - - val version = it.version - val source = it.source - - if (source != null) { - append(", ${source.getPodSourcePath()}") - } else if (version != null) { - append(", '$version'") - } - - } - }.forEach { appendLine("\t$it") } - appendLine("end\n") - //disable signing for all synthetic pods KT-54314 - append( - """ - post_install do |installer| - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = "" - config.build_settings['CODE_SIGNING_REQUIRED'] = "NO" - config.build_settings['CODE_SIGNING_ALLOWED'] = "NO" - end - end - end - """.trimIndent() - ) - appendLine() - } -} - open class PodSetupBuildTask : CocoapodsTask() { @@ -371,154 +58,3 @@ open class PodSetupBuildTask : CocoapodsTask() { private fun getBuildSettingFileName(pod: CocoapodsDependency, sdk: String): String = "build-settings-$sdk-${pod.schemeName}.properties" -/** - * The task compiles external cocoa pods sources. - */ -open class PodBuildTask : CocoapodsTask() { - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:InputFile - lateinit var buildSettingsFile: Provider - internal set - - @get:Nested - internal lateinit var pod: Provider - - @get:PathSensitive(PathSensitivity.ABSOLUTE) - @get:IgnoreEmptyDirectories - @get:InputFiles - internal val srcDir: FileTree - get() = project.fileTree( - buildSettingsFile.map { PodBuildSettingsProperties.readSettingsFromReader(it.reader()).podsTargetSrcRoot } - ) - - @get:Internal - internal var buildDir: Provider = project.provider { - project.file(PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()).buildDir) - } - - @get:Input - internal lateinit var sdk: Provider - - @Suppress("unused") // declares an ouptut - @get:OutputFiles - internal val buildResult: Provider = project.provider { - project.fileTree(buildDir.get()) { - it.include("**/${pod.get().schemeName}.*/") - it.include("**/${pod.get().schemeName}/") - } - } - - @get:Internal - internal lateinit var podsXcodeProjDir: Provider - - @TaskAction - fun buildDependencies() { - val podBuildSettings = PodBuildSettingsProperties.readSettingsFromReader(buildSettingsFile.get().reader()) - - val podsXcodeProjDir = podsXcodeProjDir.get() - - val podXcodeBuildCommand = listOf( - "xcodebuild", - "-project", podsXcodeProjDir.name, - "-scheme", pod.get().schemeName, - "-sdk", sdk.get(), - "-configuration", podBuildSettings.configuration - ) - - runCommand(podXcodeBuildCommand, project.logger) { directory(podsXcodeProjDir.parentFile) } - } -} - - -data class PodBuildSettingsProperties( - internal val buildDir: String, - internal val configuration: String, - val configurationBuildDir: String, - internal val podsTargetSrcRoot: String, - internal val cflags: String? = null, - internal val headerPaths: String? = null, - internal val publicHeadersFolderPath: String? = null, - internal val frameworkPaths: String? = null -) { - - fun writeSettings( - buildSettingsFile: File - ) { - buildSettingsFile.parentFile.mkdirs() - buildSettingsFile.delete() - buildSettingsFile.createNewFile() - - check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" } - - 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") } - publicHeadersFolderPath?.let { appendText("$PUBLIC_HEADERS_FOLDER_PATH=$it\n") } - frameworkPaths?.let { appendText("$FRAMEWORK_SEARCH_PATHS=$it") } - } - } - - companion object { - 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 PUBLIC_HEADERS_FOLDER_PATH = "PUBLIC_HEADERS_FOLDER_PATH" - const val FRAMEWORK_SEARCH_PATHS = "FRAMEWORK_SEARCH_PATHS" - - fun readSettingsFromReader(reader: Reader): PodBuildSettingsProperties { - with(Properties()) { - @Suppress("BlockingMethodInNonBlockingContext") // It's ok to do blocking call here - load(reader) - return PodBuildSettingsProperties( - readProperty(BUILD_DIR), - readProperty(CONFIGURATION), - readProperty(CONFIGURATION_BUILD_DIR), - readProperty(PODS_TARGET_SRCROOT), - readNullableProperty(OTHER_CFLAGS), - readNullableProperty(HEADER_SEARCH_PATHS), - readNullableProperty(PUBLIC_HEADERS_FOLDER_PATH), - 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) - } -} - -private object CocoapodsErrorHandlingUtil { - fun handle(e: IOException, command: List) { - if (e.message?.contains("No such file or directory") == true) { - val message = """ - |'${command.take(2).joinToString(" ")}' command failed with an exception: - | ${e.message} - | - | Full command: ${command.joinToString(" ")} - | - | Possible reason: CocoaPods is not installed - | Please check that CocoaPods v1.10 or above is installed. - | - | To check CocoaPods version type 'pod --version' in the terminal - | - | To install CocoaPods execute 'sudo gem install cocoapods' - | - """.trimMargin() - throw IllegalStateException(message) - } else { - throw e - } - } - -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodspecTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodspecTask.kt index 7dbbb3c5f83..3a6aaf93e86 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodspecTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/tasks/PodspecTask.kt @@ -9,20 +9,19 @@ package org.jetbrains.kotlin.gradle.tasks import org.gradle.api.DefaultTask import org.gradle.api.Project import org.gradle.api.plugins.ExtensionAware -import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.* 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.CocoapodsExtension.CocoapodsDependency +import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.PodspecPlatformSettings 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 import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.SYNC_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs -import org.jetbrains.kotlin.gradle.utils.appendLine import java.io.File /** @@ -223,132 +222,3 @@ open class PodspecTask : DefaultTask() { } } - - -/** - * Creates a dummy framework in the target directory. - * - * We represent a Kotlin/Native module to CocoaPods as a vendored framework. - * CocoaPods needs access to such frameworks during installation process to obtain - * their type (static or dynamic) and configure the Xcode project accordingly. - * But we cannot build the real framework before installation because it may - * depend on CocoaPods libraries which are not downloaded and built at this stage. - * 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. - */ -abstract class DummyFrameworkTask : DefaultTask() { - - @get:Input - abstract val frameworkName: Property - - @get:Input - abstract val useStaticFramework: Property - - @get:OutputDirectory - val outputFramework: Provider = project.provider { project.cocoapodsBuildDirs.dummyFramework } - - private val dummyFrameworkResource: String - get() { - 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 -> - resource.copyTo(file) - } - } - } - - private fun copyTextResource(from: String, to: File, transform: (String) -> String = { it }) { - to.parentFile.mkdirs() - to.printWriter().use { file -> - javaClass.getResourceAsStream(from)!!.use { - it.reader().forEachLine { str -> - file.println(transform(str)) - } - } - } - } - - private fun copyFrameworkFile(relativeFrom: String, relativeTo: String = relativeFrom) = - copyResource( - "$dummyFrameworkResource$relativeFrom", - outputFramework.get().resolve(relativeTo) - ) - - private fun copyFrameworkTextFile( - relativeFrom: String, - relativeTo: String = relativeFrom, - transform: (String) -> String = { it } - ) = copyTextResource( - "$dummyFrameworkResource$relativeFrom", - outputFramework.get().resolve(relativeTo), - transform - ) - - @TaskAction - fun create() { - // Reset the destination directory - with(outputFramework.get()) { - deleteRecursively() - mkdirs() - } - - // Copy files for the dummy framework. - copyFrameworkFile("Info.plist") - copyFrameworkFile("dummy", frameworkName.get()) - copyFrameworkFile("Headers/placeholder.h") - copyFrameworkTextFile("Modules/module.modulemap") { - if (it == "framework module dummy {") { - it.replace("dummy", frameworkName.get()) - } else { - it - } - } - } -} - -/** - * Generates a def-file for the given CocoaPods dependency. - */ -abstract class DefFileTask : DefaultTask() { - - @get:Nested - abstract val pod: Property - - @get:Input - abstract val useLibraries: Property - - @get:OutputFile - val outputFile: File - get() = project.cocoapodsBuildDirs.defs.resolve("${pod.get().moduleName}.def") - - @TaskAction - fun generate() { - outputFile.parentFile.mkdirs() - outputFile.writeText(buildString { - appendLine("language = Objective-C") - with(pod.get()) { - when { - headers != null -> appendLine("headers = $headers") - useLibraries.get() -> logger.warn( - """ - w: Pod '$moduleName' should have 'headers' property specified when using 'useLibraries()'. - Otherwise code from this pod won't be accessible from Kotlin. - """.trimIndent() - ) - else -> { - appendLine("modules = $moduleName") - - // Linker opt with framework name is added so produced cinterop klib would have this flag inside its manifest - // This way error will be more obvious when someone will try to depend on a library with this cinterop - appendLine("linkerOpts = -framework $moduleName") - } - } - } - }) - } -} \ No newline at end of file