[Gradle, Native] Advanced CocoaPods integration.

This commit is contained in:
Yaroslav Chernyshev
2020-03-25 11:20:01 +03:00
parent acbe3126b1
commit b13b6d86c7
11 changed files with 646 additions and 155 deletions
@@ -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
}
@@ -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
@@ -1,2 +1,3 @@
org.jetbrains.kotlin.gradle.KotlinGradleModelBuilder
org.jetbrains.kotlin.gradle.KotlinMPPGradleModelBuilder
org.jetbrains.kotlin.gradle.KotlinCocoaPodsModelBuilder
@@ -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.")
}
}
+56 -51
View File
@@ -1,60 +1,65 @@
<idea-plugin>
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
<frameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.GradleKotlinMPPSourceSetsFrameworkSupportProvider"/>
<frameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.GradleKotlinJavaFrameworkSupportProvider"/>
<frameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.GradleKotlinJSBrowserFrameworkSupportProvider"/>
<frameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.GradleKotlinJSNodeFrameworkSupportProvider"/>
<kotlinDslFrameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.KotlinDslGradleKotlinMPPFrameworkSupportProvider"/>
<kotlinDslFrameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.KotlinDslGradleKotlinJavaFrameworkSupportProvider"/>
<kotlinDslFrameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.KotlinDslGradleKotlinJSBrowserFrameworkSupportProvider"/>
<kotlinDslFrameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.KotlinDslGradleKotlinJSNodeFrameworkSupportProvider"/>
<pluginDescriptions implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradlePluginDescription"/>
<projectResolve implementation="org.jetbrains.kotlin.idea.configuration.KotlinNonJvmGutterConfigurator" />
<projectResolve implementation="org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModelResolver" order="first"/>
<projectResolve implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleProjectResolverExtension" order="first"/>
<projectResolve implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleCoroutineDebugProjectResolver" order="last"/>
<projectResolve implementation="org.jetbrains.kotlin.kapt.idea.KaptProjectResolverExtension" order="last"/>
<projectResolve implementation="org.jetbrains.kotlin.allopen.ide.AllOpenProjectResolverExtension" order="last"/>
<projectResolve implementation="org.jetbrains.kotlin.noarg.ide.NoArgProjectResolverExtension" order="last"/>
<projectResolve implementation="org.jetbrains.kotlin.samWithReceiver.ide.SamWithReceiverProjectResolverExtension" order="last"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
<frameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.GradleKotlinMPPSourceSetsFrameworkSupportProvider"/>
<frameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.GradleKotlinJavaFrameworkSupportProvider"/>
<frameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.GradleKotlinJSBrowserFrameworkSupportProvider"/>
<frameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.GradleKotlinJSNodeFrameworkSupportProvider"/>
<kotlinDslFrameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.KotlinDslGradleKotlinMPPFrameworkSupportProvider"/>
<kotlinDslFrameworkSupport implementation="org.jetbrains.kotlin.idea.configuration.KotlinDslGradleKotlinJavaFrameworkSupportProvider"/>
<kotlinDslFrameworkSupport
implementation="org.jetbrains.kotlin.idea.configuration.KotlinDslGradleKotlinJSBrowserFrameworkSupportProvider"/>
<kotlinDslFrameworkSupport
implementation="org.jetbrains.kotlin.idea.configuration.KotlinDslGradleKotlinJSNodeFrameworkSupportProvider"/>
<pluginDescriptions implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradlePluginDescription"/>
<projectResolve implementation="org.jetbrains.kotlin.idea.configuration.KotlinNonJvmGutterConfigurator"/>
<projectResolve implementation="org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModelResolver" order="first"/>
<projectResolve implementation="org.jetbrains.kotlin.idea.cocoapods.KotlinCocoaPodsModelResolver" order="first"/>
<projectResolve implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleProjectResolverExtension" order="first"/>
<projectResolve implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleCoroutineDebugProjectResolver" order="last"/>
<projectResolve implementation="org.jetbrains.kotlin.kapt.idea.KaptProjectResolverExtension" order="last"/>
<projectResolve implementation="org.jetbrains.kotlin.allopen.ide.AllOpenProjectResolverExtension" order="last"/>
<projectResolve implementation="org.jetbrains.kotlin.noarg.ide.NoArgProjectResolverExtension" order="last"/>
<projectResolve implementation="org.jetbrains.kotlin.samWithReceiver.ide.SamWithReceiverProjectResolverExtension" order="last"/>
</extensions>
<extensions defaultExtensionNs="com.intellij">
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleSourceSetDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleProjectDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleLibraryDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinTargetDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.KotlinJavaMPPSourceSetDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryDataService"/>
<externalSystemTaskNotificationListener implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptDefinitionsUpdater"/>
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptOutOfSourceNotificationProvider"/>
<extensions defaultExtensionNs="com.intellij">
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleSourceSetDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleProjectDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleLibraryDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinTargetDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.KotlinJavaMPPSourceSetDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryDataService"/>
<externalSystemTaskNotificationListener implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptDefinitionsUpdater"/>
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptOutOfSourceNotificationProvider"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinJvmTestClassGradleConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinMultiplatformJvmTestClassGradleConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinJvmTestMethodGradleConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinMultiplatformJvmTestMethodGradleConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinJvmTestClassGradleConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinMultiplatformJvmTestClassGradleConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinJvmTestMethodGradleConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinMultiplatformJvmTestMethodGradleConfigurationProducer"/>
</extensions>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.kotlin">
<gradleProjectImportHandler implementation="org.jetbrains.kotlin.allopen.ide.AllOpenGradleProjectImportHandler"/>
<gradleProjectImportHandler implementation="org.jetbrains.kotlin.scripting.idea.plugin.ScriptingGradleProjectImportHandler"/>
<gradleProjectImportHandler implementation="org.jetbrains.kotlin.kapt.idea.KaptGradleProjectImportHandler"/>
<gradleProjectImportHandler implementation="org.jetbrains.kotlin.noarg.ide.NoArgGradleProjectImportHandler"/>
<gradleProjectImportHandler implementation="org.jetbrains.kotlin.samWithReceiver.ide.SamWithReceiverGradleProjectImportHandler"/>
<gradleProjectImportHandler implementation="org.jetbrains.kotlinx.serialization.idea.KotlinSerializationGradleImportHandler"/>
<extensions defaultExtensionNs="org.jetbrains.kotlin">
<gradleProjectImportHandler implementation="org.jetbrains.kotlin.allopen.ide.AllOpenGradleProjectImportHandler"/>
<gradleProjectImportHandler implementation="org.jetbrains.kotlin.scripting.idea.plugin.ScriptingGradleProjectImportHandler"/>
<gradleProjectImportHandler implementation="org.jetbrains.kotlin.kapt.idea.KaptGradleProjectImportHandler"/>
<gradleProjectImportHandler implementation="org.jetbrains.kotlin.noarg.ide.NoArgGradleProjectImportHandler"/>
<gradleProjectImportHandler implementation="org.jetbrains.kotlin.samWithReceiver.ide.SamWithReceiverGradleProjectImportHandler"/>
<gradleProjectImportHandler implementation="org.jetbrains.kotlinx.serialization.idea.KotlinSerializationGradleImportHandler"/>
<projectConfigurator implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleModuleConfigurator"/>
<projectConfigurator implementation="org.jetbrains.kotlin.idea.configuration.KotlinJsGradleModuleConfigurator"/>
<gradleModelFacade implementation="org.jetbrains.kotlin.idea.inspections.gradle.DefaultGradleModelFacade"/>
<projectConfigurator implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleModuleConfigurator"/>
<projectConfigurator implementation="org.jetbrains.kotlin.idea.configuration.KotlinJsGradleModuleConfigurator"/>
<gradleModelFacade implementation="org.jetbrains.kotlin.idea.inspections.gradle.DefaultGradleModelFacade"/>
<scriptDefinitionContributor implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptDefinitionsContributor" order="first"/>
<scriptAdditionalIdeaDependenciesProvider implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptAdditionalIdeaDependenciesProvider"/>
<scriptDefinitionContributor implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptDefinitionsContributor"
order="first"/>
<scriptAdditionalIdeaDependenciesProvider
implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptAdditionalIdeaDependenciesProvider"/>
<moduleBuilder implementation="org.jetbrains.kotlin.ide.konan.gradle.KotlinGradleNativeMultiplatformModuleBuilder"/>
<moduleBuilder implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleSharedMultiplatformModuleBuilder"/>
<moduleBuilder implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleWebMultiplatformModuleBuilder"/>
<moduleBuilder implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleMobileMultiplatformModuleBuilder"/>
<moduleBuilder implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleMobileSharedMultiplatformModuleBuilder"/>
</extensions>
<moduleBuilder implementation="org.jetbrains.kotlin.ide.konan.gradle.KotlinGradleNativeMultiplatformModuleBuilder"/>
<moduleBuilder implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleSharedMultiplatformModuleBuilder"/>
<moduleBuilder implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleWebMultiplatformModuleBuilder"/>
<moduleBuilder implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleMobileMultiplatformModuleBuilder"/>
<moduleBuilder implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleMobileSharedMultiplatformModuleBuilder"/>
</extensions>
</idea-plugin>
@@ -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.
*/
@@ -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<Project> {
private fun KotlinMultiplatformExtension.supportedTargets() = targets
@@ -70,8 +84,9 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
}
}
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<Project> {
it.from(originalDirectory)
it.destinationDir = cocoapodsBuildDirs.framework
}
}
private fun createSyncForFatFramework(
project: Project,
@@ -153,32 +169,14 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
}
}
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<String>()
cocoapodsExtension.pods.all { pod ->
if (moduleNames.contains(pod.moduleName)) {
return@all
@@ -199,10 +197,19 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
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<Project> {
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<Project> {
}
}
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<Project> {
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<Project> {
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.
@@ -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<File?> = project.provider { cocoapodsExtension.podfile }
@get:OutputDirectory
@get:Optional
internal val podsXcodeProjDirProvider: Provider<File?> = 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<File?>
@Internal
lateinit var kotlinNativeTarget: KotlinNativeTarget
@get:OutputFile
@get:Optional
internal val buildSettingsFileProvider: Provider<File?> =
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<File?>
@get:InputFile
@get:Optional
internal lateinit var buildSettingsFileProvider: Provider<File?>
@Internal
lateinit var kotlinNativeTarget: KotlinNativeTarget
@get:OutputFile
@get:Optional
internal val buildDirHashFileProvider: Provider<File?> =
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)
)
}
}
}
}
@@ -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<String> = project.provider { settings.frameworkName }
@get:OutputFile
internal val outputFileProvider: Provider<File> = 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.
*