Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jetbrains.kotlin.gradle.targets.native.cocoapods.KotlinCocoapodsPlugin.kt Maven / Gradle / Ivy
/*
* Copyright 2010-2019 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.
*/
@file:Suppress("PackageDirectoryMismatch") // Old package for compatibility
package org.jetbrains.kotlin.gradle.plugin.cocoapods
import org.gradle.api.DefaultTask
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFile
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.wrapper.Wrapper
import org.jetbrains.kotlin.daemon.common.trimQuotes
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.addExtension
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnostic
import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnosticOncePerProject
import org.jetbrains.kotlin.gradle.plugin.ide.Idea222Api
import org.jetbrains.kotlin.gradle.plugin.ide.ideaImportDependsOn
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.*
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AppleSdk
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AppleTarget
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.CocoapodsPluginDiagnostics
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.KotlinArtifactsPodspecExtension
import org.jetbrains.kotlin.gradle.targets.native.cocoapods.kotlinArtifactsPodspecExtension
import org.jetbrains.kotlin.gradle.targets.native.tasks.*
import org.jetbrains.kotlin.gradle.targets.native.tasks.artifact.kotlinArtifactsExtension
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.gradle.utils.asValidTaskName
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.gradle.utils.newInstance
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.KonanTarget.*
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
import org.jetbrains.kotlin.utils.addToStdlib.cast
import java.io.File
internal val ProjectLayout.cocoapodsBuildDirs: CocoapodsBuildDirs
get() = CocoapodsBuildDirs(this)
internal class CocoapodsBuildDirs(private val layout: ProjectLayout) {
val root: Provider
get() = layout.buildDirectory.dir("cocoapods")
val framework: Provider
get() = dir("framework")
val defs: Provider
get() = dir("defs")
val publish: Provider
get() = dir("publish")
fun synthetic(family: Provider): Provider {
return dir("synthetic").map { it.dir(family.get().platformLiteral) }
}
fun fatFramework(buildType: NativeBuildType): Provider {
return root.map { it.dir("fat-frameworks/${buildType.getName()}") }
}
fun buildSettings(pod: Provider, sdk: Provider): Provider {
return dir("buildSettings").map { it.file("build-settings-${sdk.get()}-${pod.get().schemeName}.properties") }
}
private fun dir(pathFromRoot: String): Provider = root.map { it.dir(pathFromRoot) }
}
internal fun String.asValidFrameworkName() = replace('-', '_')
internal val Family.platformLiteral: String
get() = when (this) {
Family.OSX -> "macos"
Family.IOS -> "ios"
Family.TVOS -> "tvos"
Family.WATCHOS -> "watchos"
else -> throw IllegalArgumentException("Bad family ${this.name}")
}
private val Family.toPodGenTaskName: String
get() = lowerCamelCaseName(KotlinCocoapodsPlugin.POD_GEN_TASK_NAME, platformLiteral)
private val Family.toPodInstallSyntheticTaskName: String
get() = lowerCamelCaseName(KotlinCocoapodsPlugin.POD_INSTALL_TASK_NAME, "synthetic", platformLiteral)
private fun String.toSetupBuildTaskName(pod: CocoapodsDependency): String = lowerCamelCaseName(
KotlinCocoapodsPlugin.POD_SETUP_BUILD_TASK_NAME,
pod.schemeName.asValidTaskName(),
this
)
private fun String.toBuildDependenciesTaskName(pod: CocoapodsDependency): String = lowerCamelCaseName(
KotlinCocoapodsPlugin.POD_BUILD_TASK_NAME,
pod.schemeName.asValidTaskName(),
this
)
private val KotlinNativeTarget.toValidSDK: String
get() = when (konanTarget) {
IOS_X64, IOS_SIMULATOR_ARM64 -> "iphonesimulator"
IOS_ARM32, IOS_ARM64 -> "iphoneos"
WATCHOS_X86, WATCHOS_X64, WATCHOS_SIMULATOR_ARM64 -> "watchsimulator"
WATCHOS_ARM32, WATCHOS_ARM64, WATCHOS_DEVICE_ARM64 -> "watchos"
TVOS_X64, TVOS_SIMULATOR_ARM64 -> "appletvsimulator"
TVOS_ARM64 -> "appletvos"
MACOS_X64, MACOS_ARM64 -> "macosx"
else -> throw IllegalArgumentException("Bad target ${konanTarget.name}.")
}
private fun Project.getPodBuildTaskProvider(
target: KotlinNativeTarget,
pod: CocoapodsDependency
): TaskProvider {
return tasks.named(target.toValidSDK.toBuildDependenciesTaskName(pod), PodBuildTask::class.java)
}
internal val PodBuildSettingsProperties.frameworkSearchPaths: List
get() {
val frameworkPathsSelfIncluding = mutableListOf()
frameworkPathsSelfIncluding += configurationBuildDir.trimQuotes()
frameworkPaths?.let { frameworkPathsSelfIncluding.addAll(it.splitQuotedArgs()) }
return frameworkPathsSelfIncluding
}
//Make frameworks headers discoverable with any syntax (quotes, brackets, @import, etc.)
//https://github.com/CocoaPods/CocoaPods/blob/d18f49392c5e9ed9a2cdcb2ee89391cf7690ee5d/lib/cocoapods/target/build_settings.rb#L1188
private val PodBuildSettingsProperties.frameworkHeadersSearchPaths: List
get() = mutableListOf().apply {
headerPaths?.let { addAll(it.splitQuotedArgs()) }
publicHeadersFolderPath?.let { add("${configurationBuildDir.trimQuotes()}/${it.trimQuotes()}") }
}
/**
* Splits a string using a whitespace characters as delimiters.
* Ignores whitespaces in quotes and drops quotes, e.g. a string
* `foo "bar baz" qux="quux"` will be split into ["foo", "bar baz", "qux=quux"].
*/
@Suppress("RegExpUnnecessaryNonCapturingGroup")
internal fun String.splitQuotedArgs(): List =
Regex("""(?:[^\s"]|(?:"[^"]*"))+""").findAll(this).map {
it.value.replace("\"", "")
}.toList()
internal fun KotlinMultiplatformExtension.supportedTargets() = targets
.withType(KotlinNativeTarget::class.java)
.matching { it.konanTarget.family.isAppleFamily }
open class KotlinCocoapodsPlugin : Plugin {
private fun KotlinMultiplatformExtension.targetsForPlatform(requestedPlatform: KonanTarget) =
supportedTargets().matching { it.konanTarget == requestedPlatform }
private fun createDefaultFrameworks(kotlinExtension: KotlinMultiplatformExtension) {
kotlinExtension.supportedTargets().all { target ->
target.binaries.framework(POD_FRAMEWORK_PREFIX) {
baseName = project.name.asValidFrameworkName()
}
}
}
private fun Project.createCopyFrameworkTask(
frameworkFile: Provider,
buildingTask: TaskProvider<*>
) = registerTask(SYNC_TASK_NAME) {
it.group = TASK_GROUP
it.description = "Copies a framework for given platform and build type into the CocoaPods build directory"
it.sourceFramework.fileProvider(frameworkFile)
it.dependsOn(buildingTask)
it.destinationDirectory.set(layout.cocoapodsBuildDirs.framework)
}
private fun createSyncForFatFramework(
project: Project,
kotlinExtension: KotlinMultiplatformExtension,
requestedBuildType: NativeBuildType,
requestedPlatforms: List
) {
val fatTargets = requestedPlatforms.associateWith { kotlinExtension.targetsForPlatform(it) }
check(fatTargets.values.any { it.isNotEmpty() }) {
"The project must have a target for at least one of the following platforms: " +
"${requestedPlatforms.joinToString { it.visibleName }}."
}
fatTargets.forEach { (platform, targets) ->
check(targets.size <= 1) {
"The project has more than one target for the requested platform: `${platform.visibleName}`"
}
}
val fatFrameworkTask = project.registerTask("fatFramework") { task ->
task.group = TASK_GROUP
task.description = "Creates a fat framework for requested architectures"
task.destinationDir = project.layout.cocoapodsBuildDirs.fatFramework(requestedBuildType).getFile()
fatTargets.forEach { (_, targets) ->
targets.singleOrNull()?.let {
val framework = it.binaries.getFramework(POD_FRAMEWORK_PREFIX, requestedBuildType)
task.baseName = framework.baseName //all frameworks should have same names
task.from(framework)
}
}
}
project.createCopyFrameworkTask(fatFrameworkTask.map { it.fatFramework }, fatFrameworkTask)
}
private fun createSyncForRegularFramework(
project: Project,
kotlinExtension: KotlinMultiplatformExtension,
requestedBuildType: NativeBuildType,
requestedPlatform: KonanTarget
) {
val targets = kotlinExtension.targetsForPlatform(requestedPlatform)
check(targets.isNotEmpty()) { "The project doesn't contain a target for the requested platform: `${requestedPlatform.visibleName}`" }
check(targets.size == 1) { "The project has more than one target for the requested platform: `${requestedPlatform.visibleName}`" }
val frameworkLinkTask = targets.single().binaries.getFramework(POD_FRAMEWORK_PREFIX, requestedBuildType).linkTaskProvider
project.createCopyFrameworkTask(frameworkLinkTask.flatMap { it.outputFile }, frameworkLinkTask)
}
private fun createSyncTask(
project: Project,
kotlinExtension: KotlinMultiplatformExtension,
cocoapodsExtension: CocoapodsExtension
) = project.whenEvaluated {
val xcodeConfiguration = project.findProperty(CONFIGURATION_PROPERTY)?.toString() ?: return@whenEvaluated
val platforms = project.findProperty(PLATFORM_PROPERTY)?.toString()?.split(",", " ")?.filter { it.isNotBlank() }
val archs = project.findProperty(ARCHS_PROPERTY)?.toString()?.split(",", " ")?.filter { it.isNotBlank() }
if (platforms == null || archs == null) {
check(project.findProperty(TARGET_PROPERTY) == null) {
"""
$TARGET_PROPERTY property was dropped in favor of $PLATFORM_PROPERTY and $ARCHS_PROPERTY.
Podspec file might be outdated. Sync project with Gradle files or run the 'podspec' task manually to regenerate it.
""".trimIndent()
}
return@whenEvaluated
}
check(platforms.size == 1) {
"$PLATFORM_PROPERTY has to contain a single value only. If building for multiple platforms is required, consider using XCFrameworks"
}
val platform = platforms.first()
val nativeTargets = AppleSdk.defineNativeTargets(platform, archs)
check(nativeTargets.isNotEmpty()) { "Could not identify native targets for platform: '$platform' and architectures: '$archs'" }
val requestedBuildType = cocoapodsExtension.xcodeConfigurationToNativeBuildType[xcodeConfiguration]
check(requestedBuildType != null) {
"""
Could not identify build type for Kotlin framework '${cocoapodsExtension.podFrameworkName.get()}' built via cocoapods plugin with CONFIGURATION=$xcodeConfiguration.
Add xcodeConfigurationToNativeBuildType["$xcodeConfiguration"]=NativeBuildType.DEBUG or xcodeConfigurationToNativeBuildType["$xcodeConfiguration"]=NativeBuildType.RELEASE to cocoapods plugin configuration
""".trimIndent()
}
val frameworkTargets = nativeTargets.flatMap { kotlinExtension.targetsForPlatform(it) }
if (frameworkTargets.size == 1) {
// Fast path: there is only one device target. There is no need to build a fat framework.
createSyncForRegularFramework(project, kotlinExtension, requestedBuildType, frameworkTargets.single().konanTarget)
} else {
// There are several device targets so we need to build a fat framework.
createSyncForFatFramework(project, kotlinExtension, requestedBuildType, nativeTargets)
}
}
private fun reportDeprecatedPropertiesUsage(project: Project) {
listOf(CFLAGS_PROPERTY, FRAMEWORK_PATHS_PROPERTY, HEADER_PATHS_PROPERTY)
.filter { project.findProperty(it) != null }
.takeIf { it.isNotEmpty() }
?.let {
project.reportDiagnostic(CocoapodsPluginDiagnostics.DeprecatedPropertiesUsed(it))
}
}
private fun createInterops(
project: Project,
kotlinExtension: KotlinMultiplatformExtension,
cocoapodsExtension: CocoapodsExtension
) {
val moduleNames = mutableSetOf()
cocoapodsExtension.pods.all { pod ->
if (pod.linkOnly || moduleNames.contains(pod.moduleName)) {
return@all
}
moduleNames.add(pod.moduleName)
val defTask = project.registerTask(
lowerCamelCaseName("generateDef", pod.moduleName).asValidTaskName()
) {
it.pod.set(pod)
it.description = "Generates a def file for CocoaPods dependencies with module ${pod.moduleName}"
// This task is an implementation detail so we don't add it in any group
// to avoid showing it in the `tasks` output.
}
kotlinExtension.supportedTargets().all { target ->
val cinterops = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).cinterops
cinterops.create(pod.moduleName) { interop ->
val interopTask = project.tasks.named(interop.interopProcessingTaskName).get()
interopTask.onlyIf { HostManager.hostIsMac }
interopTask.dependsOn(defTask)
pod.interopBindingDependencies.forEach { dependencyName ->
addPodDependencyToInterop(project, cocoapodsExtension, pod, cinterops, interop, dependencyName)
}
with(interop) {
defFileProperty.set(defTask.flatMap { it.defFile.mapToFile() })
_packageNameProp.set(project.provider { pod.packageName })
_extraOptsProp.addAll(project.provider { pod.extraOpts })
}
val podBuildTaskProvider = project.getPodBuildTaskProvider(target, pod)
val buildSettingsFileProvider = project.buildSettingsFileProvider(pod, target)
interopTask.inputs.file(buildSettingsFileProvider)
// Since we can't properly declare frameworks as inputs (see below) it's the best approximation
interopTask.inputs.files(podBuildTaskProvider.flatMap { it.srcDir })
interopTask.dependsOn(podBuildTaskProvider)
interopTask.doFirst { _ ->
// Since we cannot expand the configuration phase of interop tasks
// receiving the required environment variables happens on execution phase.
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromFile(buildSettingsFileProvider.getFile())
podBuildSettings.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())
}
interop.compilerOpts.addAll(podBuildSettings.frameworkHeadersSearchPaths.map { "-I$it" })
interop.compilerOpts.addAll(podBuildSettings.frameworkSearchPaths.map { "-F$it" })
}
}
}
}
}
private fun addPodDependencyToInterop(
project: Project,
cocoapodsExtension: CocoapodsExtension,
pod: CocoapodsDependency,
cinterops: NamedDomainObjectContainer,
interop: DefaultCInteropSettings,
dependencyName: String,
) {
if (pod.name == dependencyName) {
project.reportDiagnosticOncePerProject(CocoapodsPluginDiagnostics.InteropBindingSelfDependency(pod.name))
return
}
val dependencyPod = cocoapodsExtension.pods.findByName(dependencyName)
?: run {
project.reportDiagnosticOncePerProject(CocoapodsPluginDiagnostics.InteropBindingUnknownDependency(pod.name, dependencyName))
return
}
val dependencyTaskName = cinterops.getByName(dependencyPod.moduleName).interopProcessingTaskName
val dependencyTask = project.tasks.named(dependencyTaskName)
interop.dependencyFiles += project.files(dependencyTask.map { it.outputFile }).builtBy(dependencyTask)
dependencyPod.interopBindingDependencies.forEach { transitiveDependency ->
addPodDependencyToInterop(project, cocoapodsExtension, pod, cinterops, interop, transitiveDependency)
}
}
private fun registerDummyFrameworkTask(
project: Project,
cocoapodsExtension: CocoapodsExtension
) {
project.registerTask(DUMMY_FRAMEWORK_TASK_NAME) { task ->
task.frameworkName.convention(cocoapodsExtension.podFrameworkName)
task.useStaticFramework.convention(cocoapodsExtension.podFrameworkIsStatic)
task.outputFramework.convention(project.layout.cocoapodsBuildDirs.framework.map { it.dir(task.frameworkName.get() + ".framework") })
}
}
private fun registerPodspecTask(
project: Project,
cocoapodsExtension: CocoapodsExtension
) {
project.registerTask(POD_SPEC_TASK_NAME) { task ->
task.group = TASK_GROUP
task.description = "Generates a podspec file for CocoaPods import"
task.outputDir.set(project.projectDir)
task.needPodspec.set(project.provider { cocoapodsExtension.needPodspec })
task.publishing.set(false)
task.configure(cocoapodsExtension, project)
task.gradleWrapperPath.set(project.gradleWrapperPath())
val generateWrapper = project.findProperty(GENERATE_WRAPPER_PROPERTY)?.toString()?.toBoolean() ?: false
if (generateWrapper) {
task.dependsOn(":wrapper")
}
}
}
private fun registerPodspecTask(
project: Project,
artifact: KotlinNativeArtifact,
podspecExtension: KotlinArtifactsPodspecExtension,
cocoapodsExtension: CocoapodsExtension,
) {
val artifactName = artifact.artifactName
val assembleTask = project.tasks.named(artifact.taskName)
val podspecTaskName = lowerCamelCaseName("generate", artifact.name, "podspec")
val artifactType = when (artifact) {
is KotlinNativeLibrary -> when {
artifact.isStatic -> GenerateArtifactPodspecTask.ArtifactType.StaticLibrary
else -> GenerateArtifactPodspecTask.ArtifactType.DynamicLibrary
}
is KotlinNativeFramework -> GenerateArtifactPodspecTask.ArtifactType.Framework
is KotlinNativeFatFramework -> GenerateArtifactPodspecTask.ArtifactType.FatFramework
is KotlinNativeXCFramework -> GenerateArtifactPodspecTask.ArtifactType.XCFramework
else -> error("Podspec can only be generated for Library, Framework, FatFramework or XCFramework")
}
val podspecTask = project.tasks.register(podspecTaskName, GenerateArtifactPodspecTask::class.java) { task ->
task.group = TASK_GROUP
task.description = "Generates a podspec file for '$artifactName' artifact"
task.specName.set(artifactName)
task.specVersion.set(project.version.takeIf { it != Project.DEFAULT_VERSION }.toString())
task.destinationDir.set(project.buildDir.resolve(artifact.outDir))
task.attributes.set(podspecExtension.attributes)
task.rawStatements.set(podspecExtension.rawStatements)
task.dependencies.set(cocoapodsExtension.pods)
task.artifactType.set(artifactType)
}
assembleTask.dependsOn(podspecTask)
}
private fun injectPodspecExtensionToArtifacts(
project: Project,
artifactsExtension: KotlinArtifactsExtension,
cocoapodsExtension: CocoapodsExtension,
) {
artifactsExtension.artifactConfigs.withType(KotlinNativeArtifactConfig::class.java) { artifactConfig ->
val podspecExtension = project.objects.newInstance()
artifactConfig.addExtension(ARTIFACTS_PODSPEC_EXTENSION_NAME, podspecExtension)
}
artifactsExtension.artifacts.withType(KotlinNativeArtifact::class.java) { artifact ->
val podspecExtension = requireNotNull(artifact.kotlinArtifactsPodspecExtension)
registerPodspecTask(project, artifact, podspecExtension, cocoapodsExtension)
}
}
private fun registerPodInstallTask(
project: Project,
cocoapodsExtension: CocoapodsExtension
) {
val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME)
val dummyFrameworkTaskProvider = project.tasks.named(DUMMY_FRAMEWORK_TASK_NAME)
project.registerTask(POD_INSTALL_TASK_NAME) { task ->
task.group = TASK_GROUP
task.description = "Invokes `pod install` call within Podfile location directory"
task.podfile.set(project.provider { cocoapodsExtension.podfile })
task.podspec.set(podspecTaskProvider.map { it.outputFile })
task.useStaticFramework.set(cocoapodsExtension.podFrameworkIsStatic)
task.frameworkName.set(cocoapodsExtension.podFrameworkName)
task.specRepos.set(project.provider { cocoapodsExtension.specRepos })
task.pods.set(cocoapodsExtension.pods)
task.dependsOn(podspecTaskProvider)
task.dependsOn(dummyFrameworkTaskProvider)
}
}
private fun registerSyntheticPodTasks(
project: Project, kotlinExtension: KotlinMultiplatformExtension, cocoapodsExtension: CocoapodsExtension
) {
val families = mutableSetOf()
val podspecTaskProvider = project.tasks.named(POD_SPEC_TASK_NAME)
kotlinExtension.supportedTargets().all { target ->
val family = target.konanTarget.family
if (family in families) {
return@all
}
families += family
val platformSettings = when (family) {
Family.IOS -> cocoapodsExtension.ios
Family.OSX -> cocoapodsExtension.osx
Family.TVOS -> cocoapodsExtension.tvos
Family.WATCHOS -> cocoapodsExtension.watchos
else -> error("Unknown cocoapods platform: $family")
}
val xcodeVersionTask = XcodeVersionTask.locateOrRegister(project)
val podGenTask = project.registerTask(family.toPodGenTaskName) { task ->
task.description = "Сreates a synthetic Xcode project to retrieve CocoaPods dependencies"
task.podspec.set(podspecTaskProvider.map { it.outputFile })
task.podName.set(project.provider { cocoapodsExtension.name })
task.specRepos.set(project.provider { cocoapodsExtension.specRepos })
task.family.set(family)
task.platformSettings.set(platformSettings)
task.pods.set(cocoapodsExtension.pods)
task.xcodeVersion.set(xcodeVersionTask.version)
}
project.registerTask(family.toPodInstallSyntheticTaskName) { task ->
task.description = "Invokes `pod install` for synthetic project"
task.podfile.set(podGenTask.map { it.podfile.get() })
task.family.set(family)
task.podName.set(cocoapodsExtension.name)
task.dependsOn(podGenTask)
}
}
}
private fun registerPodSetupBuildTasks(
project: Project,
kotlinExtension: KotlinMultiplatformExtension,
cocoapodsExtension: CocoapodsExtension
) {
val schemeNames = mutableSetOf()
cocoapodsExtension.pods.all { pod ->
if (schemeNames.contains(pod.schemeName)) {
return@all
}
schemeNames.add(pod.schemeName)
val sdks = mutableSetOf()
kotlinExtension.supportedTargets().all loop@{ target ->
val sdk = target.toValidSDK
if (sdk in sdks) {
return@loop
}
sdks += sdk
val podInstallTask = project.tasks.named(target.konanTarget.family.toPodInstallSyntheticTaskName)
project.registerTask(sdk.toSetupBuildTaskName(pod)) { task ->
task.group = TASK_GROUP
task.description = "Collect environment variables from .xcworkspace file"
task.pod.set(pod)
task.sdk.set(sdk)
task.podsXcodeProjDir.set(podInstallTask.map { it.podsXcodeProjDirProvider.get() })
task.frameworkName.set(cocoapodsExtension.podFrameworkName)
task.dependsOn(podInstallTask)
}
}
}
}
private fun registerPodBuildTasks(
project: Project,
kotlinExtension: KotlinMultiplatformExtension,
cocoapodsExtension: CocoapodsExtension
) {
val schemeNames = mutableSetOf()
cocoapodsExtension.pods.all { pod ->
if (schemeNames.contains(pod.schemeName)) {
return@all
}
schemeNames.add(pod.schemeName)
val sdks = mutableSetOf()
kotlinExtension.supportedTargets().all loop@{ target ->
val sdk = target.toValidSDK
if (sdk in sdks) {
return@loop
}
sdks += sdk
val podSetupBuildTaskProvider =
project.tasks.named(sdk.toSetupBuildTaskName(pod), PodSetupBuildTask::class.java)
project.tasks.register(sdk.toBuildDependenciesTaskName(pod), PodBuildTask::class.java) { task ->
task.group = TASK_GROUP
task.description = "Calls `xcodebuild` on xcworkspace for the pod scheme"
task.buildSettingsFile.set(podSetupBuildTaskProvider.flatMap { it.buildSettingsFile })
task.pod.set(pod)
task.sdk.set(sdk)
task.family.set(target.konanTarget.family)
task.podsXcodeProjDir.fileProvider(podSetupBuildTaskProvider.flatMap { it.podsXcodeProjDir })
task.dependsOn(podSetupBuildTaskProvider)
}
}
}
}
private fun registerPodImportTask(
project: Project,
kotlinExtension: KotlinMultiplatformExtension
) {
val podInstallTaskProvider = project.tasks.named(POD_INSTALL_TASK_NAME, PodInstallTask::class.java)
val podImportTaskProvider = 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"
it.dependsOn(podInstallTaskProvider)
kotlinExtension.supportedTargets().all { target ->
target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).cinterops.all { interop ->
val interopTaskProvider = project.tasks.named(interop.interopProcessingTaskName)
it.dependsOn(interopTaskProvider)
}
}
}
/* Older IDEs will explicitly call 'podImport' instead */
@OptIn(Idea222Api::class)
project.ideaImportDependsOn(podImportTaskProvider)
}
private fun configureLinkingOptions(project: Project, cocoapodsExtension: CocoapodsExtension) {
project.multiplatformExtension.supportedTargets().all { target ->
target.binaries.all { binary ->
val testExecutable = binary is TestExecutable
val podFramework = binary is Framework && binary.name.startsWith(POD_FRAMEWORK_PREFIX)
if (testExecutable || podFramework) {
configureLinkingOptions(project, cocoapodsExtension, binary)
}
}
}
}
private fun configureLinkingOptions(project: Project, cocoapodsExtension: CocoapodsExtension, nativeBinary: NativeBinary) {
cocoapodsExtension.pods.all { pod ->
nativeBinary.linkTaskProvider.configure { task ->
task.onlyIf { HostManager.hostIsMac }
val binary = task.binary
val podBuildTaskProvider = project.getPodBuildTaskProvider(binary.target, pod)
val buildSettingsFileProvider = project.buildSettingsFileProvider(pod, binary.target)
task.inputs.file(buildSettingsFileProvider)
task.dependsOn(podBuildTaskProvider)
val isExecutable = binary is AbstractExecutable
val isDynamicFramework = project.provider { binary is Framework && !binary.isStatic }
task.doFirst {
val podBuildSettings = PodBuildSettingsProperties.readSettingsFromFile(buildSettingsFileProvider.getFile())
val frameworkFileName = pod.moduleName + ".framework"
val frameworkSearchPaths = podBuildSettings.frameworkSearchPaths
val linkerOpts = task.additionalLinkerOpts
if (isExecutable || isDynamicFramework.get()) {
val frameworkFileExists = frameworkSearchPaths.any { dir -> File(dir, frameworkFileName).exists() }
if (frameworkFileExists) linkerOpts.addArg("-framework", pod.moduleName)
linkerOpts.addAll(frameworkSearchPaths.map { "-F$it" })
}
if (isExecutable) linkerOpts.addArgs("-rpath", frameworkSearchPaths)
}
}
}
}
private fun registerPodXCFrameworkTask(
project: Project,
cocoapodsExtension: CocoapodsExtension,
buildType: NativeBuildType
): TaskProvider =
with(project) {
registerTask(lowerCamelCaseName(POD_FRAMEWORK_PREFIX, "publish", buildType.getName(), "XCFramework")) { task ->
multiplatformExtension.supportedTargets().all { target ->
target.binaries.matching { it.buildType == buildType && it.name.startsWith(POD_FRAMEWORK_PREFIX) }
.withType(Framework::class.java) { framework ->
task.from(framework)
}
}
task.outputDir = cocoapodsExtension.publishDir
task.buildType = buildType
task.baseName = cocoapodsExtension.podFrameworkName
task.description = "Produces ${buildType.getName().capitalizeAsciiOnly()} XCFramework for all requested targets"
task.group = TASK_GROUP
}
}
private fun registerPodspecPublishTask(
project: Project,
cocoapodsExtension: CocoapodsExtension,
xcFrameworkTask: TaskProvider,
buildType: NativeBuildType
): TaskProvider {
val task = project.registerTask(lowerCamelCaseName(POD_FRAMEWORK_PREFIX, "spec", buildType.getName())) { task ->
task.description = "Generates podspec for ${buildType.getName().capitalizeAsciiOnly()} XCFramework publishing"
task.outputDir.set(xcFrameworkTask.map { it.outputDir.resolve(it.buildType.getName()) })
task.needPodspec.set(true)
task.publishing.set(true)
task.source.set(project.provider { cocoapodsExtension.source })
task.configure(cocoapodsExtension, project)
}
xcFrameworkTask.dependsOn(task)
return task
}
private fun PodspecTask.configure(cocoapodsExtension: CocoapodsExtension, project: Project) {
fun Property.setProvider(provider: () -> T?) = set(project.provider(provider))
pods.set(cocoapodsExtension.pods)
specName.setProvider { cocoapodsExtension.name }
version.setProvider { cocoapodsExtension.version ?: project.version.toString() }
extraSpecAttributes.set(project.provider { cocoapodsExtension.extraSpecAttributes })
homepage.setProvider { cocoapodsExtension.homepage }
license.setProvider { cocoapodsExtension.license }
authors.setProvider { cocoapodsExtension.authors }
summary.setProvider { cocoapodsExtension.summary }
frameworkName.set(cocoapodsExtension.podFrameworkName)
ios.set(cocoapodsExtension.ios)
osx.set(cocoapodsExtension.osx)
tvos.set(cocoapodsExtension.tvos)
watchos.set(cocoapodsExtension.watchos)
projectPath.set(project.taskProjectPath())
hasPodfile.set(project.hasPodfileOwnOrParent())
}
private fun registerPodPublishFatFrameworkTasks(
project: Project,
xcFrameworkTask: TaskProvider,
buildType: NativeBuildType
) =
with(project) {
multiplatformExtension.supportedTargets().all { target ->
target.binaries.matching { it.buildType == buildType && it.name.startsWith(POD_FRAMEWORK_PREFIX) }
.withType(Framework::class.java) { framework ->
val appleTarget = AppleTarget.values().firstOrNull { it.targets.contains(target.konanTarget) } ?: return@withType
val fatFrameworkTaskName =
lowerCamelCaseName(POD_FRAMEWORK_PREFIX, buildType.getName(), appleTarget.targetName, "FatFramework")
val fatFrameworkTask = locateOrRegisterTask(fatFrameworkTaskName) { fatTask ->
fatTask.baseName = framework.baseName
fatTask.destinationDir =
XCFrameworkTask.fatFrameworkDir(this, fatTask.fatFrameworkName, buildType, appleTarget)
fatTask.onlyIf {
fatTask.frameworks.size > 1
}
}
fatFrameworkTask.configure {
it.from(framework)
}
xcFrameworkTask.dependsOn(fatFrameworkTask)
}
}
}
private fun registerPodPublishTasks(project: Project, cocoapodsExtension: CocoapodsExtension) {
val xcFrameworkTasks = NativeBuildType.values().map { buildType ->
val xcFrameworkTask = registerPodXCFrameworkTask(project, cocoapodsExtension, buildType)
registerPodPublishFatFrameworkTasks(project, xcFrameworkTask, buildType)
registerPodspecPublishTask(project, cocoapodsExtension, xcFrameworkTask, buildType)
xcFrameworkTask
}
project.registerTask("podPublishXCFramework", DefaultTask::class.java) { task ->
task.description = "Produces Release and Debug XCFrameworks with respective podspecs"
task.dependsOn(xcFrameworkTasks)
task.group = TASK_GROUP
}
}
private fun checkLinkOnlyNotUsedWithStaticFramework(project: Project, cocoapodsExtension: CocoapodsExtension) {
project.runProjectConfigurationHealthCheckWhenEvaluated {
cocoapodsExtension.pods.all { pod ->
if (pod.linkOnly && cocoapodsExtension.podFrameworkIsStatic.get()) {
project.reportDiagnostic(CocoapodsPluginDiagnostics.LinkOnlyUsedWithStaticFramework(pod.name))
}
}
}
}
private fun Project.gradleWrapperPath(): Provider {
return provider { rootProject.tasks.locateTask("wrapper")?.get()?.scriptFile?.absolutePath }
}
private fun Project.taskProjectPath(): String {
return if (project.depth != 0) project.path else ""
}
private fun Project.buildSettingsFileProvider(pod: CocoapodsDependency, target: KotlinNativeTarget): Provider {
return layout.cocoapodsBuildDirs.buildSettings(provider { pod }, provider { target.toValidSDK })
}
private val KotlinMultiplatformExtension?.cocoapodsExtensionOrNull: CocoapodsExtension?
get() = (this as? ExtensionAware)?.extensions?.findByName(COCOAPODS_EXTENSION_NAME) as? CocoapodsExtension
private fun Project.hasPodfileOwnOrParent(): Provider {
return provider {
multiplatformExtensionOrNull?.cocoapodsExtensionOrNull?.podfile != null
|| (parent?.hasPodfileOwnOrParent()?.get() ?: false)
}
}
// Enable cinterop commonization if it is not explicitly specified
private fun Project.enableCInteropCommonizationSetByExternalPlugin() {
kotlinPropertiesProvider.enableCInteropCommonizationSetByExternalPlugin = true
}
override fun apply(project: Project): Unit = with(project) {
pluginManager.withPlugin("kotlin-multiplatform") {
enableCInteropCommonizationSetByExternalPlugin()
val kotlinExtension = project.multiplatformExtension
val kotlinArtifactsExtension = project.kotlinArtifactsExtension
val cocoapodsExtension = project.objects.newInstance(CocoapodsExtension::class.java, this)
kotlinExtension.addExtension(COCOAPODS_EXTENSION_NAME, cocoapodsExtension)
createDefaultFrameworks(kotlinExtension)
registerDummyFrameworkTask(project, cocoapodsExtension)
createSyncTask(project, kotlinExtension, cocoapodsExtension)
injectPodspecExtensionToArtifacts(project, kotlinArtifactsExtension, cocoapodsExtension)
registerPodspecTask(project, cocoapodsExtension)
registerSyntheticPodTasks(project, kotlinExtension, cocoapodsExtension)
registerPodInstallTask(project, cocoapodsExtension)
registerPodSetupBuildTasks(project, kotlinExtension, cocoapodsExtension)
registerPodBuildTasks(project, kotlinExtension, cocoapodsExtension)
registerPodImportTask(project, kotlinExtension)
registerPodPublishTasks(project, cocoapodsExtension)
if (!HostManager.hostIsMac) {
reportDiagnostic(CocoapodsPluginDiagnostics.UnsupportedOs())
}
reportDeprecatedPropertiesUsage(project)
createInterops(project, kotlinExtension, cocoapodsExtension)
configureLinkingOptions(project, cocoapodsExtension)
checkLinkOnlyNotUsedWithStaticFramework(project, cocoapodsExtension)
}
}
companion object {
const val COCOAPODS_EXTENSION_NAME = "cocoapods"
const val TASK_GROUP = "CocoaPods"
const val POD_FRAMEWORK_PREFIX = "pod"
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_GEN_TASK_NAME = "podGen"
const val POD_SETUP_BUILD_TASK_NAME = "podSetupBuild"
const val POD_BUILD_TASK_NAME = "podBuild"
const val POD_IMPORT_TASK_NAME = "podImport"
const val ARTIFACTS_PODSPEC_EXTENSION_NAME = "withPodspec"
// We don't move these properties in PropertiesProvider because
// they are not intended to be overridden in local.properties.
const val PLATFORM_PROPERTY = "kotlin.native.cocoapods.platform"
const val TARGET_PROPERTY = "kotlin.native.cocoapods.target"
const val ARCHS_PROPERTY = "kotlin.native.cocoapods.archs"
const val CONFIGURATION_PROPERTY = "kotlin.native.cocoapods.configuration"
const val CFLAGS_PROPERTY = "kotlin.native.cocoapods.cflags"
const val HEADER_PATHS_PROPERTY = "kotlin.native.cocoapods.paths.headers"
const val FRAMEWORK_PATHS_PROPERTY = "kotlin.native.cocoapods.paths.frameworks"
const val GENERATE_WRAPPER_PROPERTY = "kotlin.native.cocoapods.generate.wrapper"
}
}
/**
* Extends a KotlinArtifact with a corresponding Podspec
*
* Only needed in *.kts build files. In Groovy you can use the same syntax but without explicit extension import
*/
fun KotlinNativeArtifactConfig.withPodspec(configure: KotlinArtifactsPodspecExtension.() -> Unit) {
val extension = cast().kotlinArtifactsPodspecExtension
checkNotNull(extension) { "CocoaPods plugin should be applied before using `${KotlinCocoapodsPlugin.ARTIFACTS_PODSPEC_EXTENSION_NAME}` extension" }
extension.configure()
}