diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/cocoapods/KotlinCocoaPodsModelResolver.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/cocoapods/KotlinCocoaPodsModelResolver.kt
new file mode 100644
index 00000000000..7853746a00b
--- /dev/null
+++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/cocoapods/KotlinCocoaPodsModelResolver.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.idea.cocoapods
+
+import org.jetbrains.kotlin.gradle.EnablePodImportTask
+import org.jetbrains.plugins.gradle.model.ClassSetProjectImportModelProvider
+import org.jetbrains.plugins.gradle.model.ProjectImportModelProvider
+import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension
+
+class KotlinCocoaPodsModelResolver : AbstractProjectResolverExtension() {
+
+ override fun getProjectsLoadedModelProvider(): ProjectImportModelProvider {
+ return ClassSetProjectImportModelProvider(
+ setOf(EnablePodImportTask::class.java)
+ )
+ }
+
+ override fun requiresTaskRunning(): Boolean = true
+}
+
+
diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/cocoapods/KotlinCocoaPodsModelResolver.kt.191 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/cocoapods/KotlinCocoaPodsModelResolver.kt.191
new file mode 100644
index 00000000000..d165935db5b
--- /dev/null
+++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/cocoapods/KotlinCocoaPodsModelResolver.kt.191
@@ -0,0 +1,8 @@
+/*
+ * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.idea.cocoapods
+
+class KotlinCocoaPodsModelResolver
\ No newline at end of file
diff --git a/idea/kotlin-gradle-tooling/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService b/idea/kotlin-gradle-tooling/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService
index b918a8c27ab..d35ac4b4278 100644
--- a/idea/kotlin-gradle-tooling/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService
+++ b/idea/kotlin-gradle-tooling/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService
@@ -1,2 +1,3 @@
org.jetbrains.kotlin.gradle.KotlinGradleModelBuilder
org.jetbrains.kotlin.gradle.KotlinMPPGradleModelBuilder
+org.jetbrains.kotlin.gradle.KotlinCocoaPodsModelBuilder
diff --git a/idea/kotlin-gradle-tooling/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService.191 b/idea/kotlin-gradle-tooling/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService.191
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/idea/kotlin-gradle-tooling/src/KotlinCocoaPodsModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinCocoaPodsModelBuilder.kt
new file mode 100644
index 00000000000..d33c732ce20
--- /dev/null
+++ b/idea/kotlin-gradle-tooling/src/KotlinCocoaPodsModelBuilder.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.gradle
+
+import org.gradle.api.Project
+import org.jetbrains.plugins.gradle.tooling.AbstractModelBuilderService
+import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder
+import org.jetbrains.plugins.gradle.tooling.ModelBuilderContext
+
+private const val POD_IMPORT_TASK_NAME = "podImport"
+
+interface EnablePodImportTask
+
+class KotlinCocoaPodsModelBuilder : AbstractModelBuilderService() {
+ override fun canBuild(modelName: String?): Boolean {
+ return EnablePodImportTask::class.java.name == modelName
+ }
+
+ override fun buildAll(modelName: String, project: Project, context: ModelBuilderContext): Any? {
+ val startParameter = project.gradle.startParameter
+ val taskNames = startParameter.taskNames
+
+ if (project.tasks.findByPath(POD_IMPORT_TASK_NAME) != null && POD_IMPORT_TASK_NAME !in taskNames) {
+ taskNames.add(POD_IMPORT_TASK_NAME)
+ startParameter.setTaskNames(taskNames)
+ }
+ return null
+ }
+
+ override fun getErrorMessageBuilder(project: Project, e: Exception): ErrorMessageBuilder {
+ return ErrorMessageBuilder.create(
+ project, e, "EnablePodImportTask error"
+ ).withDescription("Unable to create $POD_IMPORT_TASK_NAME task.")
+ }
+}
\ No newline at end of file
diff --git a/idea/kotlin-gradle-tooling/src/KotlinCocoaPodsModelBuilder.kt.191 b/idea/kotlin-gradle-tooling/src/KotlinCocoaPodsModelBuilder.kt.191
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/idea/resources/META-INF/gradle-java.xml b/idea/resources/META-INF/gradle-java.xml
index 8f920688c46..e48bcd17ea8 100644
--- a/idea/resources/META-INF/gradle-java.xml
+++ b/idea/resources/META-INF/gradle-java.xml
@@ -1,60 +1,65 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt
index 0bd73bb2bf1..cfe63fd8ea2 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt
@@ -9,16 +9,25 @@ package org.jetbrains.kotlin.gradle.plugin.cocoapods
import org.gradle.api.Named
import org.gradle.api.NamedDomainObjectSet
import org.gradle.api.Project
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.Internal
-import org.gradle.api.tasks.Nested
-import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.*
+import java.io.File
open class CocoapodsExtension(private val project: Project) {
@get:Input
val version: String
get() = project.version.toString()
+ @Optional
+ @InputFile
+ var podfile: File? = null
+
+ /**
+ * Configure path to the Podfile.
+ */
+ fun podfile(path: String) {
+ podfile = project.file(path)
+ }
+
/**
* Configure authors of the pod built from this project.
*/
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt
index 7e200046921..67a1708ea4b 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt
@@ -16,12 +16,20 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.addExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
-import org.jetbrains.kotlin.gradle.tasks.*
+import org.jetbrains.kotlin.gradle.targets.native.tasks.PodBuildSettingsProperties
+import org.jetbrains.kotlin.gradle.targets.native.tasks.PodBuildTask
+import org.jetbrains.kotlin.gradle.targets.native.tasks.PodInstallTask
+import org.jetbrains.kotlin.gradle.targets.native.tasks.PodSetupBuildTask
+import org.jetbrains.kotlin.gradle.tasks.DefFileTask
+import org.jetbrains.kotlin.gradle.tasks.DummyFrameworkTask
+import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
+import org.jetbrains.kotlin.gradle.tasks.PodspecTask
import org.jetbrains.kotlin.gradle.utils.asValidTaskName
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
+import java.io.FileInputStream
internal val Project.cocoapodsBuildDirs: CocoapodsBuildDirs
get() = CocoapodsBuildDirs(this)
@@ -42,6 +50,12 @@ internal class CocoapodsBuildDirs(val project: Project) {
internal fun String.asValidFrameworkName() = replace('-', '_')
+internal val KotlinNativeTarget.toBuildSetupTaskName: String
+ get() = lowerCamelCaseName(KotlinCocoapodsPlugin.POD_SETUP_BUILD_TASK_NAME, disambiguationClassifier)
+
+internal val KotlinNativeTarget.toBuildDependenciesTaskName: String
+ get() = lowerCamelCaseName(KotlinCocoapodsPlugin.POD_BUILD_DEPENDENCIES_TASK_NAME, disambiguationClassifier)
+
open class KotlinCocoapodsPlugin : Plugin {
private fun KotlinMultiplatformExtension.supportedTargets() = targets
@@ -70,8 +84,9 @@ open class KotlinCocoapodsPlugin : Plugin {
}
}
- private fun Project.createSyncFrameworkTask(originalDirectory: File, buildingTask: Task) =
- tasks.create(SYNC_TASK_NAME, Sync::class.java) {
+ private fun Project.createSyncFrameworkTask(originalDirectory: File, buildingTask: Task): Sync? {
+
+ return tasks.create(SYNC_TASK_NAME, Sync::class.java) {
it.group = TASK_GROUP
it.description = "Copies a framework for given platform and build type into the CocoaPods build directory"
@@ -79,6 +94,7 @@ open class KotlinCocoapodsPlugin : Plugin {
it.from(originalDirectory)
it.destinationDir = cocoapodsBuildDirs.framework
}
+ }
private fun createSyncForFatFramework(
project: Project,
@@ -153,32 +169,14 @@ open class KotlinCocoapodsPlugin : Plugin {
}
}
- private fun createPodspecGenerationTask(
- project: Project,
- cocoapodsExtension: CocoapodsExtension
- ) {
- val dummyFrameworkTask = project.tasks.create("generateDummyFramework", DummyFrameworkTask::class.java) {
- it.settings = cocoapodsExtension
- }
-
- project.tasks.create("podspec", PodspecTask::class.java) {
- it.group = TASK_GROUP
- it.description = "Generates a podspec file for CocoaPods import"
- it.settings = cocoapodsExtension
- it.dependsOn(dummyFrameworkTask)
- val generateWrapper = project.findProperty(GENERATE_WRAPPER_PROPERTY)?.toString()?.toBoolean() ?: false
- if (generateWrapper) {
- it.dependsOn(":wrapper")
- }
- }
- }
-
private fun createInterops(
project: Project,
kotlinExtension: KotlinMultiplatformExtension,
cocoapodsExtension: CocoapodsExtension
) {
val moduleNames = mutableSetOf()
+
+
cocoapodsExtension.pods.all { pod ->
if (moduleNames.contains(pod.moduleName)) {
return@all
@@ -199,10 +197,19 @@ open class KotlinCocoapodsPlugin : Plugin {
target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).cinterops.create(pod.moduleName) { interop ->
val interopTask = project.tasks.getByPath(interop.interopProcessingTaskName)
+
interopTask.dependsOn(defTask)
+
interop.defFile = defTask.outputFile
interop.packageName = "cocoapods.${pod.moduleName}"
+
+ if (project.findProperty(TARGET_PROPERTY) == null && project.findProperty(CONFIGURATION_PROPERTY) == null) {
+ val podBuildTaskProvider = project.tasks.named(target.toBuildDependenciesTaskName, PodBuildTask::class.java)
+ interopTask.inputs.file(podBuildTaskProvider.get().buildSettingsFileProvider)
+ interopTask.dependsOn(podBuildTaskProvider)
+ }
+
project.findProperty(CFLAGS_PROPERTY)?.toString()?.let { args ->
// Xcode quotes around paths with spaces.
// Here and below we need to split such paths taking this into account.
@@ -215,23 +222,30 @@ open class KotlinCocoapodsPlugin : Plugin {
interop.compilerOpts.addAll(args.splitQuotedArgs().map { "-F$it" })
}
- // Show a human-readable error messages if the interop is created
- // but there are no parameters set by Xcode or manually by user (KT-31062).
interopTask.doFirst { _ ->
- val hasCompilerOpts = interop.compilerOpts.isNotEmpty()
- val hasHeaderSearchPath = interop.includeDirs.let {
- !it.headerFilterDirs.isEmpty || !it.allHeadersDirs.isEmpty
- }
+ // Since we cannot expand the configuration phase of interop tasks
+ // receiving the required environment variables happens on execution phase.
+ // TODO This needs to be fixed to improve UP-TO-DATE checks.
+ if (project.findProperty(TARGET_PROPERTY) == null && project.findProperty(CONFIGURATION_PROPERTY) == null) {
+ val podBuildTaskProvider = project.tasks.named(target.toBuildDependenciesTaskName, PodBuildTask::class.java)
+ val buildSettings =
+ podBuildTaskProvider.get().buildSettingsFileProvider.get()
+ ?.inputStream()
+ ?.use {
+ PodBuildSettingsProperties.readSettingsFromStream(it)
+ }
- check(hasCompilerOpts || hasHeaderSearchPath) {
- """
- |Cannot perform cinterop processing for module ${pod.moduleName}: cannot determine headers location.
- |
- |Probably the build is executed from command line.
- |Note that a Kotlin/Native module using CocoaPods dependencies can be built only from Xcode.
- |
- |See details at https://kotlinlang.org/docs/reference/native/cocoapods.html#interoperability.
- """.trimMargin()
+ buildSettings?.cflags?.let { args ->
+ // Xcode quotes around paths with spaces.
+ // Here and below we need to split such paths taking this into account.
+ interop.compilerOpts.addAll(args.splitQuotedArgs())
+ }
+ buildSettings?.headerPaths?.let { args ->
+ interop.compilerOpts.addAll(args.splitQuotedArgs().map { "-I$it" })
+ }
+ buildSettings?.frameworkPaths?.let { args ->
+ interop.compilerOpts.addAll(args.splitQuotedArgs().map { "-F$it" })
+ }
}
}
}
@@ -239,6 +253,109 @@ open class KotlinCocoapodsPlugin : Plugin {
}
}
+ private fun registerDummyFrameworkTask(
+ project: Project,
+ cocoapodsExtension: CocoapodsExtension
+ ) {
+ project.tasks.register(DUMMY_FRAMEWORK_TASK_NAME, DummyFrameworkTask::class.java) {
+ it.settings = cocoapodsExtension
+ }
+ }
+
+ private fun registerPodspecTask(
+ project: Project,
+ cocoapodsExtension: CocoapodsExtension
+ ) {
+ val dummyFrameworkTaskProvider = project.tasks.named(DUMMY_FRAMEWORK_TASK_NAME)
+
+ project.tasks.register(POD_SPEC_TASK_NAME, PodspecTask::class.java) {
+ it.group = TASK_GROUP
+ it.description = "Generates a podspec file for CocoaPods import"
+ it.cocoapodsExtension = cocoapodsExtension
+ it.dependsOn(dummyFrameworkTaskProvider)
+ val generateWrapper = project.findProperty(GENERATE_WRAPPER_PROPERTY)?.toString()?.toBoolean() ?: false
+ if (generateWrapper) {
+ it.dependsOn(":wrapper")
+ }
+ }
+ }
+
+ private fun registerPodInstallTask(
+ project: Project,
+ cocoapodsExtension: CocoapodsExtension
+ ) {
+
+ val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME, PodspecTask::class.java)
+
+ project.tasks.register(POD_INSTALL_TASK_NAME, PodInstallTask::class.java) {
+ it.group = TASK_GROUP
+ it.description = "Invokes `pod install` call within Podfile location directory"
+ it.cocoapodsExtension = cocoapodsExtension
+ it.dependsOn(podspecTaskProvider)
+ }
+ }
+
+ private fun registerPodSetupBuildTasks(
+ project: Project,
+ kotlinExtension: KotlinMultiplatformExtension,
+ cocoapodsExtension: CocoapodsExtension
+ ) {
+
+ val podInstallTaskProvider = project.tasks.named(POD_INSTALL_TASK_NAME, PodInstallTask::class.java)
+
+ kotlinExtension.supportedTargets().all { target ->
+ project.tasks.register(target.toBuildSetupTaskName, PodSetupBuildTask::class.java) {
+ it.group = TASK_GROUP
+ it.kotlinNativeTarget = target
+ it.description = "Collect environment variables from .xcworkspace file"
+ it.cocoapodsExtension = cocoapodsExtension
+ it.podsXcodeProjDirProvider = podInstallTaskProvider.get().podsXcodeProjDirProvider
+ it.dependsOn(podInstallTaskProvider)
+ }
+ }
+ }
+
+ private fun registerPodBuildTasks(
+ project: Project,
+ kotlinExtension: KotlinMultiplatformExtension,
+ cocoapodsExtension: CocoapodsExtension
+ ) {
+
+ kotlinExtension.supportedTargets().all { target ->
+
+ val podSetupBuildTaskProvider = project.tasks.named(target.toBuildSetupTaskName, PodSetupBuildTask::class.java)
+
+ project.tasks.register(target.toBuildDependenciesTaskName, PodBuildTask::class.java) {
+ it.group = TASK_GROUP
+ it.description = "Calls `xcodebuild` on xcworkspace for the pod scheme"
+ it.kotlinNativeTarget = target
+ it.cocoapodsExtension = cocoapodsExtension
+ it.podsXcodeProjDirProvider = (podSetupBuildTaskProvider.get()).podsXcodeProjDirProvider
+ it.buildSettingsFileProvider = (podSetupBuildTaskProvider.get()).buildSettingsFileProvider
+ it.dependsOn(podSetupBuildTaskProvider)
+ }
+ }
+ }
+
+ private fun registerPodImportTask(
+ project: Project,
+ kotlinExtension: KotlinMultiplatformExtension
+ ) {
+
+ project.tasks.register(POD_IMPORT_TASK_NAME) {
+ it.group = TASK_GROUP
+ it.description = "Called on Gradle sync, depends on Cinterop tasks for every used pod"
+
+ kotlinExtension.supportedTargets().all { target ->
+ target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).cinterops.all { interop ->
+ val interopTaskProvider = project.tasks.named(interop.interopProcessingTaskName)
+ it.dependsOn(interopTaskProvider)
+ }
+ }
+
+ }
+ }
+
override fun apply(project: Project): Unit = with(project) {
pluginManager.withPlugin("kotlin-multiplatform") {
@@ -247,8 +364,13 @@ open class KotlinCocoapodsPlugin : Plugin {
kotlinExtension.addExtension(EXTENSION_NAME, cocoapodsExtension)
createDefaultFrameworks(kotlinExtension, cocoapodsExtension)
+ registerDummyFrameworkTask(project, cocoapodsExtension)
createSyncTask(project, kotlinExtension)
- createPodspecGenerationTask(project, cocoapodsExtension)
+ registerPodspecTask(project, cocoapodsExtension)
+ registerPodInstallTask(project, cocoapodsExtension)
+ registerPodSetupBuildTasks(project, kotlinExtension, cocoapodsExtension)
+ registerPodBuildTasks(project, kotlinExtension, cocoapodsExtension)
+ registerPodImportTask(project, kotlinExtension)
createInterops(project, kotlinExtension, cocoapodsExtension)
}
}
@@ -257,6 +379,12 @@ open class KotlinCocoapodsPlugin : Plugin {
const val EXTENSION_NAME = "cocoapods"
const val TASK_GROUP = "CocoaPods"
const val SYNC_TASK_NAME = "syncFramework"
+ const val POD_SPEC_TASK_NAME = "podspec"
+ const val DUMMY_FRAMEWORK_TASK_NAME = "generateDummyFramework"
+ const val POD_INSTALL_TASK_NAME = "podInstall"
+ const val POD_SETUP_BUILD_TASK_NAME = "podSetupBuild"
+ const val POD_BUILD_DEPENDENCIES_TASK_NAME = "podBuildDependencies"
+ const val POD_IMPORT_TASK_NAME = "podImport"
// We don't move these properties in PropertiesProvider because
// they are not intended to be overridden in local.properties.
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/AdvancedCocoapodsTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/AdvancedCocoapodsTasks.kt
new file mode 100644
index 00000000000..32881ae9cfe
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/AdvancedCocoapodsTasks.kt
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.gradle.targets.native.tasks
+
+import org.gradle.api.DefaultTask
+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.cocoapodsBuildDirs
+import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
+import org.jetbrains.kotlin.gradle.targets.native.tasks.PodBuildTask.Companion.toValidSDK
+import org.jetbrains.kotlin.konan.target.KonanTarget
+import java.io.File
+import java.io.FileInputStream
+import java.io.InputStream
+import java.math.BigInteger
+import java.security.MessageDigest
+import java.util.*
+
+abstract class AdvancedCocoapodsTask : DefaultTask() {
+ @get:Nested
+ lateinit var cocoapodsExtension: CocoapodsExtension
+
+ @TaskAction
+ fun commonInvoke() {
+ podfileCheck(cocoapodsExtension)
+ specificInvoke()
+ }
+
+ abstract fun specificInvoke()
+
+ private fun podfileCheck(cocoapodsExtension: CocoapodsExtension) {
+ check(cocoapodsExtension.podfile != null) {
+ "Execution of task '$name' requires the path to the Podfile. Specify it in the file ${project.buildFile.absolutePath}."
+ }
+ }
+
+}
+
+/**
+ * The task takes the path to the Podfile and calls `pod install`
+ * to obtain sources or artifacts for the declared dependencies.
+ * This task is a part of CocoaPods integration infrastructure.
+ */
+open class PodInstallTask : AdvancedCocoapodsTask() {
+
+ @get:InputFile
+ @get:Optional
+ internal val podfileProvider: Provider = project.provider { cocoapodsExtension.podfile }
+
+ @get:OutputDirectory
+ @get:Optional
+ internal val podsXcodeProjDirProvider: Provider = project.provider {
+ cocoapodsExtension.podfile
+ ?.parentFile
+ ?.resolve("Pods")
+ ?.resolve("Pods.xcodeproj")
+ }
+
+ override fun specificInvoke() {
+ val podfileDir = podfileProvider.get()!!.parentFile
+ val podInstallProcess = ProcessBuilder("pod", "install").apply {
+ directory(podfileDir)
+ inheritIO()
+ }.start()
+ val podInstallRetCode = podInstallProcess.waitFor()
+ check(podInstallRetCode == 0) { "Unable to run 'pod install', return code $podInstallRetCode" }
+
+ val podsXcprojFile = podsXcodeProjDirProvider.get()
+ check(podsXcprojFile != null && podsXcprojFile.exists() && podsXcprojFile.isDirectory) {
+ "The directory 'Pods/Pods.xcodeproj' was not created as a result of the `pod install` call."
+ }
+ }
+}
+
+open class PodSetupBuildTask : AdvancedCocoapodsTask() {
+ @get:InputDirectory
+ @get:Optional
+ internal lateinit var podsXcodeProjDirProvider: Provider
+
+ @Internal
+ lateinit var kotlinNativeTarget: KotlinNativeTarget
+
+ @get:OutputFile
+ @get:Optional
+ internal val buildSettingsFileProvider: Provider =
+ project.provider {
+ project
+ .cocoapodsBuildDirs
+ .root
+ .resolve("buildSettings")
+ .resolve(kotlinNativeTarget.toBuildSettingsFileName)
+ }
+
+ override fun specificInvoke() {
+ val podsXcodeProjDir = podsXcodeProjDirProvider.get()
+
+ val buildSettingsReceivingCommand = listOf(
+ "xcodebuild", "-showBuildSettings",
+ "-project", podsXcodeProjDir!!.name,
+ "-scheme", cocoapodsExtension.frameworkName,
+ "-sdk", kotlinNativeTarget.toValidSDK
+ )
+
+ val buildSettingsProcess = ProcessBuilder(buildSettingsReceivingCommand)
+ .apply {
+ directory(podsXcodeProjDir!!.parentFile)
+ }.start()
+
+ val buildSettingsRetCode = buildSettingsProcess.waitFor()
+ check(buildSettingsRetCode == 0) {
+ "Unable to run '${buildSettingsReceivingCommand.joinToString(" ")}' return code $buildSettingsRetCode"
+ }
+
+
+ val stdOut = buildSettingsProcess.inputStream
+
+ val buildSettingsProperties = PodBuildSettingsProperties.readSettingsFromStream(stdOut)
+ buildSettingsProperties.writeSettings(buildSettingsFileProvider.get()!!)
+ }
+
+ companion object {
+ private val KotlinNativeTarget.toBuildSettingsFileName: String
+ get() = "build-settings-$disambiguationClassifier.properties"
+ }
+}
+
+/**
+ * The task compiles external cocoa pods sources.
+ */
+open class PodBuildTask : AdvancedCocoapodsTask() {
+ @get:InputDirectory
+ @get:Optional
+ internal lateinit var podsXcodeProjDirProvider: Provider
+
+ @get:InputFile
+ @get:Optional
+ internal lateinit var buildSettingsFileProvider: Provider
+
+ @Internal
+ lateinit var kotlinNativeTarget: KotlinNativeTarget
+
+ @get:OutputFile
+ @get:Optional
+ internal val buildDirHashFileProvider: Provider =
+ project.provider {
+ project
+ .cocoapodsBuildDirs
+ .root
+ .resolve("buildDirHashSums")
+ .resolve(kotlinNativeTarget.toBuildDirHashSumFileName)
+ }
+
+ override fun specificInvoke() {
+ val podBuildSettings = PodBuildSettingsProperties.readSettingsFromStream(
+ FileInputStream(buildSettingsFileProvider.get())
+ )
+
+ val podsXcodeProjDir = podsXcodeProjDirProvider.get()
+
+ cocoapodsExtension.pods.all {
+
+ val podXcodeBuildCommand = listOf(
+ "xcodebuild",
+ "-project", podsXcodeProjDir!!.name,
+ "-scheme", it.moduleName,
+ "-sdk", kotlinNativeTarget.toValidSDK,
+ "-configuration", podBuildSettings.configuration
+ )
+
+ val podBuildProcess = ProcessBuilder(podXcodeBuildCommand)
+ .apply {
+ directory(podsXcodeProjDir!!.parentFile)
+ inheritIO()
+ }.start()
+
+ val podBuildRetCode = podBuildProcess.waitFor()
+ check(podBuildRetCode == 0) {
+ "Unable to run '${podXcodeBuildCommand.joinToString(" ")}' return code $podBuildRetCode"
+ }
+ }
+ val buildDirHashSumFile = buildDirHashFileProvider.get()!!
+ buildDirHashSumFile!!.parentFile.mkdirs()
+ buildDirHashSumFile!!.createNewFile()
+ check(buildDirHashSumFile!!.exists()) {
+ "Unable to create file ${buildDirHashSumFile.toRelativeString(project.projectDir)}!"
+ }
+
+ buildDirHashSumFile.writeText(getFileChecksumStr(project.file(podBuildSettings.buildDir)))
+ }
+
+ companion object {
+
+ private fun getFileChecksumStr(file: File): String {
+ val digest = MessageDigest.getInstance("SHA-1")
+ file.walkTopDown().forEach {
+ val byteArray = ByteArray(1024)
+ var bytesCount = 0
+ if (it.isFile) {
+ val inputStream = it.inputStream()
+ while (inputStream.read(byteArray).also { bt -> bytesCount = bt } != -1) {
+ digest.update(byteArray, 0, bytesCount)
+ }
+ }
+ }
+ val buildDirHashSumStr = BigInteger(1, digest.digest()).toString(16).padStart(32, '0')
+ return buildDirHashSumStr
+ }
+
+ internal val KotlinNativeTarget.toBuildDirHashSumFileName: String
+ get() = "build-dir-hash-$disambiguationClassifier.txt"
+
+ internal val KotlinNativeTarget.toValidSDK: String
+ get() {
+ return when (konanTarget) {
+ KonanTarget.IOS_X64 -> "iphonesimulator"
+ KonanTarget.IOS_ARM32, KonanTarget.IOS_ARM64 -> "iphoneos"
+ KonanTarget.WATCHOS_X86, KonanTarget.WATCHOS_X64 -> "watchsimulator"
+ KonanTarget.WATCHOS_ARM32, KonanTarget.WATCHOS_ARM64 -> "watchos"
+ KonanTarget.TVOS_X64 -> "appletvsimulator"
+ KonanTarget.TVOS_ARM64 -> "appletvos"
+ KonanTarget.MACOS_X64 -> "macos"
+ else -> throw Error("Bad target ${konanTarget.name}")
+ }
+ }
+ }
+}
+
+internal data class PodBuildSettingsProperties(
+ internal val buildDir: String,
+ internal val configuration: String,
+ internal val cflags: String? = null,
+ internal val headerPaths: String? = null,
+ internal val frameworkPaths: String? = null
+) {
+
+ fun writeSettings(buildSettingsFile: File) {
+ buildSettingsFile.parentFile.mkdirs()
+ buildSettingsFile.createNewFile()
+
+ check(buildSettingsFile.exists()) { "Unable to create file ${buildSettingsFile.path}!" }
+
+ with(Properties()) {
+ setProperty(BUILD_DIR, buildDir)
+ setProperty(CONFIGURATION, configuration)
+ cflags?.let { setProperty(OTHER_CFLAGS, it) }
+ headerPaths?.let { setProperty(HEADER_SEARCH_PATHS, it) }
+ frameworkPaths?.let { setProperty(FRAMEWORK_SEARCH_PATHS, it) }
+ buildSettingsFile.outputStream().use {
+ store(it, null)
+ }
+ }
+ }
+
+ companion object {
+ const val BUILD_DIR: String = "BUILD_DIR"
+ const val CONFIGURATION: String = "CONFIGURATION"
+ const val OTHER_CFLAGS: String = "OTHER_CFLAGS"
+ const val HEADER_SEARCH_PATHS: String = "HEADER_SEARCH_PATHS"
+ const val FRAMEWORK_SEARCH_PATHS: String = "FRAMEWORK_SEARCH_PATHS"
+
+ fun readSettingsFromStream(inputStream: InputStream): PodBuildSettingsProperties {
+ with(Properties()) {
+ load(inputStream)
+ return PodBuildSettingsProperties(
+ getProperty(BUILD_DIR),
+ getProperty(CONFIGURATION),
+ getProperty(OTHER_CFLAGS),
+ getProperty(HEADER_SEARCH_PATHS),
+ getProperty(FRAMEWORK_SEARCH_PATHS)
+ )
+ }
+ }
+ }
+}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt
index 9c60400d498..afe42c8e3b8 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt
@@ -28,20 +28,16 @@ open class PodspecTask : DefaultTask() {
private val specName = project.name.asValidFrameworkName()
- @OutputFile
- val outputFile: File = project.projectDir.resolve("$specName.podspec")
-
- @Input
- val frameworkNameProvider: Provider = project.provider { settings.frameworkName }
+ @get:OutputFile
+ internal val outputFileProvider: Provider = project.provider { project.file("$specName.podspec") }
@get:Nested
- internal lateinit var settings: CocoapodsExtension
+ internal lateinit var cocoapodsExtension: CocoapodsExtension
- // TODO: Handle Framework name customization - rename the framework during sync process.
@TaskAction
fun generate() {
- val frameworkDir = project.cocoapodsBuildDirs.framework.relativeTo(outputFile.parentFile).path
- val dependencies = settings.pods.map { pod ->
+ val frameworkDir = project.cocoapodsBuildDirs.framework.relativeTo(outputFileProvider.get().parentFile).path
+ val dependencies = cocoapodsExtension.pods.map { pod ->
val versionSuffix = if (pod.version != null) ", '${pod.version}'" else ""
"| spec.dependency '${pod.name}'$versionSuffix"
}.joinToString(separator = "\n")
@@ -61,67 +57,70 @@ open class PodspecTask : DefaultTask() {
val syncTask = "${project.path}:$SYNC_TASK_NAME"
- outputFile.writeText(
- """
- |Pod::Spec.new do |spec|
- | spec.name = '$specName'
- | spec.version = '${settings.version}'
- | spec.homepage = '${settings.homepage.orEmpty()}'
- | spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
- | spec.authors = '${settings.authors.orEmpty()}'
- | spec.license = '${settings.license.orEmpty()}'
- | spec.summary = '${settings.summary.orEmpty()}'
- |
- | spec.static_framework = true
- | spec.vendored_frameworks = "$frameworkDir/${frameworkNameProvider.get()}.framework"
- | spec.libraries = "c++"
- | spec.module_name = "#{spec.name}_umbrella"
- |
- $dependencies
- |
- | spec.pod_target_xcconfig = {
- | 'KOTLIN_TARGET[sdk=iphonesimulator*]' => 'ios_x64',
- | 'KOTLIN_TARGET[sdk=iphoneos*]' => '$KOTLIN_TARGET_FOR_IOS_DEVICE',
- | 'KOTLIN_TARGET[sdk=watchsimulator*]' => 'watchos_x86',
- | 'KOTLIN_TARGET[sdk=watchos*]' => '$KOTLIN_TARGET_FOR_WATCHOS_DEVICE',
- | 'KOTLIN_TARGET[sdk=appletvsimulator*]' => 'tvos_x64',
- | 'KOTLIN_TARGET[sdk=appletvos*]' => 'tvos_arm64',
- | 'KOTLIN_TARGET[sdk=macosx*]' => 'macos_x64'
- | }
- |
- | spec.script_phases = [
- | {
- | :name => 'Build $specName',
- | :execution_position => :before_compile,
- | :shell_path => '/bin/sh',
- | :script => <<-SCRIPT
- | set -ev
- | REPO_ROOT="${'$'}PODS_TARGET_SRCROOT"
- | "$gradleCommand" -p "${'$'}REPO_ROOT" $syncTask \
- | -P${KotlinCocoapodsPlugin.TARGET_PROPERTY}=${'$'}KOTLIN_TARGET \
- | -P${KotlinCocoapodsPlugin.CONFIGURATION_PROPERTY}=${'$'}CONFIGURATION \
- | -P${KotlinCocoapodsPlugin.CFLAGS_PROPERTY}="${'$'}OTHER_CFLAGS" \
- | -P${KotlinCocoapodsPlugin.HEADER_PATHS_PROPERTY}="${'$'}HEADER_SEARCH_PATHS" \
- | -P${KotlinCocoapodsPlugin.FRAMEWORK_PATHS_PROPERTY}="${'$'}FRAMEWORK_SEARCH_PATHS"
- | SCRIPT
- | }
- | ]
- |end
+ with(outputFileProvider.get()) {
+ writeText(
+ """
+ |Pod::Spec.new do |spec|
+ | spec.name = '$specName'
+ | spec.version = '${cocoapodsExtension.version}'
+ | spec.homepage = '${cocoapodsExtension.homepage.orEmpty()}'
+ | spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
+ | spec.authors = '${cocoapodsExtension.authors.orEmpty()}'
+ | spec.license = '${cocoapodsExtension.license.orEmpty()}'
+ | spec.summary = '${cocoapodsExtension.summary.orEmpty()}'
+ |
+ | spec.static_framework = true
+ | spec.vendored_frameworks = "$frameworkDir/${cocoapodsExtension.frameworkName}.framework"
+ | spec.libraries = "c++"
+ | spec.module_name = "#{spec.name}_umbrella"
+ |
+ $dependencies
+ |
+ | spec.pod_target_xcconfig = {
+ | 'KOTLIN_TARGET[sdk=iphonesimulator*]' => 'ios_x64',
+ | 'KOTLIN_TARGET[sdk=iphoneos*]' => '$KOTLIN_TARGET_FOR_IOS_DEVICE',
+ | 'KOTLIN_TARGET[sdk=watchsimulator*]' => 'watchos_x86',
+ | 'KOTLIN_TARGET[sdk=watchos*]' => '$KOTLIN_TARGET_FOR_WATCHOS_DEVICE',
+ | 'KOTLIN_TARGET[sdk=appletvsimulator*]' => 'tvos_x64',
+ | 'KOTLIN_TARGET[sdk=appletvos*]' => 'tvos_arm64',
+ | 'KOTLIN_TARGET[sdk=macosx*]' => 'macos_x64'
+ | }
+ |
+ | spec.script_phases = [
+ | {
+ | :name => 'Build $specName',
+ | :execution_position => :before_compile,
+ | :shell_path => '/bin/sh',
+ | :script => <<-SCRIPT
+ | set -ev
+ | REPO_ROOT="${'$'}PODS_TARGET_SRCROOT/../"
+ | "$gradleCommand" -p "${'$'}REPO_ROOT" $syncTask \
+ | -P${KotlinCocoapodsPlugin.TARGET_PROPERTY}=${'$'}KOTLIN_TARGET \
+ | -P${KotlinCocoapodsPlugin.CONFIGURATION_PROPERTY}=${'$'}CONFIGURATION \
+ | -P${KotlinCocoapodsPlugin.CFLAGS_PROPERTY}="${'$'}OTHER_CFLAGS" \
+ | -P${KotlinCocoapodsPlugin.HEADER_PATHS_PROPERTY}="${'$'}HEADER_SEARCH_PATHS" \
+ | -P${KotlinCocoapodsPlugin.FRAMEWORK_PATHS_PROPERTY}="${'$'}FRAMEWORK_SEARCH_PATHS"
+ | SCRIPT
+ | }
+ | ]
+ |end
""".trimMargin()
- )
+ )
- logger.quiet(
- """
- Generated a podspec file at: ${outputFile.absolutePath}.
+ logger.quiet(
+ """
+ Generated a podspec file at: ${absolutePath}.
To include it in your Xcode project, add the following dependency snippet in your Podfile:
- pod '$specName', :path => '${outputFile.parentFile.absolutePath}'
+ pod '$specName', :path => '${parentFile.absolutePath}'
""".trimIndent()
- )
+ )
+ }
}
}
+
/**
* Creates a dummy framework in the target directory.
*