diff --git a/.idea/dictionaries/Artem_Daugel_Dauge.xml b/.idea/dictionaries/Artem_Daugel_Dauge.xml
new file mode 100644
index 00000000000..0c7f9c49110
--- /dev/null
+++ b/.idea/dictionaries/Artem_Daugel_Dauge.xml
@@ -0,0 +1,8 @@
+
+
+
+ vendored
+ xcframework
+
+
+
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-api/api/kotlin-gradle-plugin-api.api b/libraries/tools/kotlin-gradle-plugin-api/api/kotlin-gradle-plugin-api.api
index 910fb30b06f..7cf00b3bb4b 100644
--- a/libraries/tools/kotlin-gradle-plugin-api/api/kotlin-gradle-plugin-api.api
+++ b/libraries/tools/kotlin-gradle-plugin-api/api/kotlin-gradle-plugin-api.api
@@ -143,6 +143,7 @@ public abstract interface class org/jetbrains/kotlin/gradle/dsl/KaptJavacOption
public abstract interface class org/jetbrains/kotlin/gradle/dsl/KotlinArtifact : org/gradle/api/Named, org/gradle/api/plugins/ExtensionAware {
public abstract fun getArtifactName ()Ljava/lang/String;
public abstract fun getModules ()Ljava/util/Set;
+ public abstract fun getOutDir ()Ljava/lang/String;
public abstract fun getTaskName ()Ljava/lang/String;
public abstract fun registerAssembleTask (Lorg/gradle/api/Project;)V
}
diff --git a/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinArtifact.kt b/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinArtifact.kt
index 2afa9201807..e8ee60fa203 100644
--- a/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinArtifact.kt
+++ b/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinArtifact.kt
@@ -15,6 +15,7 @@ interface KotlinArtifact : Named, ExtensionAware {
val artifactName: String
val modules: Set
val taskName: String
+ val outDir: String
fun registerAssembleTask(project: Project)
}
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeLibraryDslWithCocoapodsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeLibraryDslWithCocoapodsIT.kt
new file mode 100644
index 00000000000..840c51ecb62
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeLibraryDslWithCocoapodsIT.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2010-2022 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.native
+
+import org.jetbrains.kotlin.gradle.BaseGradleIT
+import org.jetbrains.kotlin.gradle.GradleVersionRequired
+import org.jetbrains.kotlin.gradle.transformProjectWithPluginsDsl
+import org.jetbrains.kotlin.konan.target.HostManager
+import org.junit.Assume
+import org.junit.BeforeClass
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class NativeLibraryDslWithCocoapodsIT : BaseGradleIT() {
+
+ companion object {
+ @BeforeClass
+ @JvmStatic
+ fun assumeMacHost() {
+ Assume.assumeTrue(HostManager.hostIsMac)
+ }
+ }
+
+ override val defaultGradleVersion = GradleVersionRequired.FOR_MPP_SUPPORT
+
+ @Test
+ fun `check registered gradle tasks`() {
+ project {
+ build(":shared:tasks") {
+ assertSuccessful()
+ assertTasksRegistered(
+ ":shared:generateMylibPodspec",
+ ":shared:generateMyslibPodspec",
+ ":shared:generateMyframePodspec",
+ ":shared:generateMyfatframePodspec",
+ ":shared:generateSharedPodspec",
+ ":lib:generateGrooframePodspec",
+ )
+ assertTasksNotRegistered(
+ ":shared:generateMyfatframewithoutpodspecPodspec",
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `generate podspec when assembling lib`() {
+ project {
+ build(":shared:assembleMylibSharedLibraryLinuxX64") {
+ assertSuccessful()
+ assertTasksExecuted(":shared:generateMylibPodspec")
+ assertFilesContentEqual("podspecs/mylib.podspec", "/shared/build/out/dynamic/mylib.podspec")
+ }
+ }
+ }
+
+ @Test
+ fun `generate podspec when assembling shared lib`() {
+ project {
+ build(":shared:assembleMyslibSharedLibraryLinuxX64") {
+ assertSuccessful()
+ assertTasksExecuted(":shared:generateMyslibPodspec")
+ assertFilesContentEqual("podspecs/myslib.podspec", "/shared/build/out/dynamic/myslib.podspec")
+ }
+ }
+ }
+
+ @Test
+ fun `generate podspec when assembling framework`() {
+ project {
+ build(":shared:assembleMyframeFrameworkIosArm64") {
+ assertSuccessful()
+ assertTasksExecuted(":shared:generateMyframePodspec")
+ assertFilesContentEqual("podspecs/myframe.podspec", "/shared/build/out/framework/myframe.podspec")
+ }
+ }
+ }
+
+ @Test
+ fun `generate podspec when assembling fat framework`() {
+ project {
+ build(":shared:assembleMyfatframeFatFramework") {
+ assertSuccessful()
+ assertTasksExecuted(":shared:generateMyfatframePodspec")
+ assertFilesContentEqual("podspecs/myfatframe.podspec", "/shared/build/out/fatframework/myfatframe.podspec")
+ }
+ }
+ }
+
+ @Test
+ fun `generate podspec when assembling xcframework`() {
+ project {
+ build(":shared:assembleSharedXCFramework") {
+ assertSuccessful()
+ assertTasksExecuted(":shared:generateSharedPodspec")
+ assertFilesContentEqual("podspecs/shared.podspec", "/shared/build/out/xcframework/shared.podspec")
+ }
+ }
+ }
+
+ @Test
+ fun `generate podspec when assembling framework from groovy`() {
+ project {
+ build(":lib:assembleGrooframeFrameworkIosArm64") {
+ assertSuccessful()
+ assertTasksExecuted(":lib:generateGrooframePodspec")
+ assertFilesContentEqual("podspecs/grooframe.podspec", "/lib/build/out/framework/grooframe.podspec")
+ }
+ }
+ }
+
+ private fun project(block: Project.() -> Unit) {
+ transformProjectWithPluginsDsl("new-kn-library-dsl-cocoapods").block()
+ }
+
+ private fun CompiledProject.assertFilesContentEqual(expected: String, actual: String) {
+ assertFileExists(expected)
+ assertFileExists(actual)
+ assertEquals(fileInWorkingDir(expected).readText(), fileInWorkingDir(actual).readText())
+ }
+
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/build.gradle.kts
new file mode 100644
index 00000000000..f556befce79
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/build.gradle.kts
@@ -0,0 +1,10 @@
+plugins {
+ kotlin("multiplatform").apply(false)
+}
+
+allprojects {
+ repositories {
+ mavenLocal()
+ mavenCentral()
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/lib/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/lib/build.gradle
new file mode 100644
index 00000000000..54fdd39c2bb
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/lib/build.gradle
@@ -0,0 +1,31 @@
+plugins {
+ id 'org.jetbrains.kotlin.multiplatform'
+ id 'org.jetbrains.kotlin.native.cocoapods'
+}
+
+version '0.2'
+
+kotlin {
+ linuxX64()
+ iosX64()
+ iosArm64()
+ iosSimulatorArm64()
+}
+
+kotlinArtifacts {
+ it.native.Framework("grooframe") {
+ modes(DEBUG, RELEASE)
+ target = iosArm64
+ addModule(project(":shared"))
+
+ withPodspec {
+ attribute('license', 'Apache-2.0')
+ attribute('homepage', 'https://example.com/lib')
+ attribute('ios.deployment_target', '"10.0"')
+ attribute('swift_versions', '[\'4\', \'5\']')
+
+ rawStatement(' # This is raw statement that is appended \'as is\' to the podspec')
+ rawStatement(' spec.frameworks = \'CFNetwork\'')
+ }
+ }
+}
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/lib/src/commonMain/kotlin/org/my/sam/Platform.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/lib/src/commonMain/kotlin/org/my/sam/Platform.kt
new file mode 100644
index 00000000000..b43f95488da
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/lib/src/commonMain/kotlin/org/my/sam/Platform.kt
@@ -0,0 +1,5 @@
+package org.my.sam
+
+class Greeting {
+ fun greeting() = "Hello, lib!"
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/grooframe.podspec b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/grooframe.podspec
new file mode 100644
index 00000000000..787505d3129
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/grooframe.podspec
@@ -0,0 +1,13 @@
+Pod::Spec.new do |spec|
+ spec.name = 'grooframe'
+ spec.version = '0.2'
+ spec.vendored_frameworks = 'grooframe.framework'
+
+ spec.license = 'Apache-2.0'
+ spec.homepage = 'https://example.com/lib'
+ spec.ios.deployment_target = "10.0"
+ spec.swift_versions = ['4', '5']
+
+ # This is raw statement that is appended 'as is' to the podspec
+ spec.frameworks = 'CFNetwork'
+end
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/myfatframe.podspec b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/myfatframe.podspec
new file mode 100644
index 00000000000..678d09b25ab
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/myfatframe.podspec
@@ -0,0 +1,6 @@
+Pod::Spec.new do |spec|
+ spec.version = '0.1'
+ spec.vendored_frameworks = 'myfatframe.framework'
+
+ spec.name = 'custom-podspec-name'
+end
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/myframe.podspec b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/myframe.podspec
new file mode 100644
index 00000000000..aff1b60587d
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/myframe.podspec
@@ -0,0 +1,7 @@
+Pod::Spec.new do |spec|
+ spec.name = 'myframe'
+ spec.version = '0.1'
+ spec.vendored_frameworks = 'myframe.framework'
+
+ spec.prefix_header_file = false
+end
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/mylib.podspec b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/mylib.podspec
new file mode 100644
index 00000000000..214577f4c93
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/mylib.podspec
@@ -0,0 +1,16 @@
+Pod::Spec.new do |spec|
+ spec.name = 'mylib'
+ spec.version = '0.1'
+ spec.vendored_library = 'mylib.dylib'
+
+ spec.description = <<-DESC
+ Computes the meaning of life.
+ Features:
+ 1. Is self aware
+ ...
+ 42. Likes candies
+ DESC
+ spec.static_framework = true
+ spec.requires_arc = 'true'
+ spec.authors = 'Tony O\'Connor'
+end
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/myslib.podspec b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/myslib.podspec
new file mode 100644
index 00000000000..cc94d27b99c
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/myslib.podspec
@@ -0,0 +1,5 @@
+Pod::Spec.new do |spec|
+ spec.name = 'myslib'
+ spec.version = '0.1'
+ spec.vendored_library = 'myslib.dylib'
+end
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/shared.podspec b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/shared.podspec
new file mode 100644
index 00000000000..c11b9fa1eb5
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/podspecs/shared.podspec
@@ -0,0 +1,16 @@
+Pod::Spec.new do |spec|
+ spec.name = 'shared'
+ spec.vendored_frameworks = 'shared.xcframework'
+
+ spec.version = '5.6.2'
+ spec.license = 'MIT'
+ spec.homepage = 'https://github.com/Alpaca/Alpaca'
+ spec.source = { :git => 'https://github.com/Alpaca/Alpaca.git', :tag => spec.version }
+ spec.ios.deployment_target = '10.0'
+ spec.osx.deployment_target = '10.12'
+ spec.watchos.deployment_target = '3.0'
+ spec.swift_versions = ['4', '5']
+
+ # This is raw statement that is appended 'as is' to the podspec
+ spec.frameworks = 'CFNetwork'
+end
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/settings.gradle.kts
new file mode 100644
index 00000000000..beb0025f5d5
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/settings.gradle.kts
@@ -0,0 +1,2 @@
+include(":shared")
+include(":lib")
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/shared/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/shared/build.gradle.kts
new file mode 100644
index 00000000000..e76f6fc925f
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/shared/build.gradle.kts
@@ -0,0 +1,96 @@
+import org.jetbrains.kotlin.gradle.plugin.cocoapods.withPodspec
+
+plugins {
+ kotlin("multiplatform")
+ kotlin("native.cocoapods")
+}
+
+version = "0.1"
+
+kotlin {
+ linuxX64()
+ iosX64()
+ iosArm64()
+ iosSimulatorArm64()
+}
+
+kotlinArtifacts {
+ Native.Library("mylib") {
+ target = linuxX64
+
+ withPodspec {
+ attribute(
+ "description", """ |<<-DESC
+ | Computes the meaning of life.
+ | Features:
+ | 1. Is self aware
+ | ...
+ | 42. Likes candies
+ | DESC
+ """.trimMargin()
+ )
+
+ attribute("static_framework", "true")
+ attribute("requires_arc", "'true'")
+ attribute("authors", "Tony O'Connor")
+ }
+ }
+ Native.Library("myslib") {
+ target = linuxX64
+ isStatic = false
+ modes(DEBUG)
+ addModule(project(":lib"))
+
+ withPodspec {} // intentionally empty
+ }
+ Native.Framework("myframe") {
+ modes(DEBUG, RELEASE)
+ target = iosArm64
+ isStatic = false
+ embedBitcode = EmbedBitcodeMode.MARKER
+
+ withPodspec {
+ attribute("prefix_header_file", "false")
+ }
+ }
+ Native.Framework("myframewihtoutpodspec") {
+ modes(DEBUG, RELEASE)
+ target = iosArm64
+ isStatic = false
+ embedBitcode = EmbedBitcodeMode.MARKER
+ }
+ Native.FatFramework("myfatframe") {
+ targets(iosX64, iosSimulatorArm64)
+ embedBitcode = EmbedBitcodeMode.DISABLE
+ toolOptions {
+ suppressWarnings.set(true)
+ }
+
+ withPodspec {
+ attribute("name", "custom-podspec-name")
+ }
+ }
+ Native.XCFramework {
+ targets(iosX64, iosArm64, iosSimulatorArm64)
+ setModules(
+ project(":shared"),
+ project(":lib")
+ )
+
+ withPodspec {
+ attribute("version", "5.6.2")
+ attribute("license", "MIT")
+ attribute("homepage", "https://github.com/Alpaca/Alpaca")
+ attribute("source", "{ :git => 'https://github.com/Alpaca/Alpaca.git', :tag => spec.version }")
+
+ attribute("ios.deployment_target", "10.0")
+ attribute("osx.deployment_target", "10.12")
+ attribute("watchos.deployment_target", "3.0")
+
+ attribute("swift_versions", "['4', '5']")
+
+ rawStatement(" # This is raw statement that is appended 'as is' to the podspec")
+ rawStatement(" spec.frameworks = 'CFNetwork'")
+ }
+ }
+}
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/shared/src/commonMain/kotlin/org/sample/application/Platform.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/shared/src/commonMain/kotlin/org/sample/application/Platform.kt
new file mode 100644
index 00000000000..da2586c36ac
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl-cocoapods/shared/src/commonMain/kotlin/org/sample/application/Platform.kt
@@ -0,0 +1,5 @@
+package org.sample.application
+
+class Greeting {
+ fun greeting() = "Hello, new DSL!"
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl/shared/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl/shared/build.gradle.kts
index 3a4bdb35369..1d4a465af06 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl/shared/build.gradle.kts
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-kn-library-dsl/shared/build.gradle.kts
@@ -1,3 +1,5 @@
+import org.jetbrains.kotlin.gradle.plugin.cocoapods.withPodspec
+
plugins {
kotlin("multiplatform")
}
@@ -49,4 +51,4 @@ kotlinArtifacts {
project(":lib")
)
}
-}
\ No newline at end of file
+}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinArtifactsPodspecExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinArtifactsPodspecExtension.kt
new file mode 100644
index 00000000000..cf267e95c05
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinArtifactsPodspecExtension.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010-2022 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.cocoapods
+
+import org.gradle.api.*
+import org.gradle.api.plugins.ExtensionAware
+import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin
+import org.jetbrains.kotlin.gradle.plugin.findExtension
+import javax.inject.Inject
+
+abstract class KotlinArtifactsPodspecExtension @Inject constructor(private val project: Project) {
+
+ private val attrs = mutableMapOf()
+ private val statements = mutableListOf()
+
+ internal val attributes: Map
+ get() = attrs
+
+ internal val rawStatements: List
+ get() = statements
+
+ fun attribute(key: String, value: String) {
+ attrs[key] = value
+ }
+
+ fun rawStatement(statement: String) {
+ statements.add(statement)
+ }
+}
+
+val ExtensionAware.kotlinArtifactsPodspecExtension: KotlinArtifactsPodspecExtension?
+ get() = findExtension(KotlinCocoapodsPlugin.ARTIFACTS_PODSPEC_EXTENSION_NAME)
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt
index fb0638e6cec..39756f31be9 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt
@@ -9,11 +9,11 @@ package org.jetbrains.kotlin.gradle.plugin.cocoapods
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
+import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.daemon.common.trimQuotes
-import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
-import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
+import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.addExtension
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
@@ -25,15 +25,20 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AppleTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.FrameworkCopy
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkTask
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
+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.asValidTaskName
import org.jetbrains.kotlin.gradle.utils.filesProvider
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.utils.addToStdlib.cast
import java.io.File
internal val Project.cocoapodsBuildDirs: CocoapodsBuildDirs
@@ -149,7 +154,7 @@ open class KotlinCocoapodsPlugin : Plugin {
private fun KotlinMultiplatformExtension.targetsForPlatform(requestedPlatform: KonanTarget) =
supportedTargets().matching { it.konanTarget == requestedPlatform }
- private fun createDefaultFrameworks(kotlinExtension: KotlinMultiplatformExtension, cocoapodsExtension: CocoapodsExtension) {
+ private fun createDefaultFrameworks(kotlinExtension: KotlinMultiplatformExtension) {
kotlinExtension.supportedTargets().all { target ->
target.binaries.framework(POD_FRAMEWORK_PREFIX) {
baseName = project.name.asValidFrameworkName()
@@ -216,7 +221,7 @@ open class KotlinCocoapodsPlugin : Plugin {
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.destinationDirectory.map { it.asFile } }, frameworkLinkTask)
+ project.createCopyFrameworkTask(frameworkLinkTask.flatMap { it.destinationDirectory.map { dir -> dir.asFile } }, frameworkLinkTask)
}
private fun createSyncTask(
@@ -387,6 +392,58 @@ open class KotlinCocoapodsPlugin : Plugin {
}
}
+ 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", artifactName, "podspec")
+
+ val artifactType = when (artifact) {
+ is KotlinNativeLibrary -> GenerateArtifactPodspecTask.ArtifactType.Library
+ 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(project)
+ artifactConfig.addExtension(ARTIFACTS_PODSPEC_EXTENSION_NAME, podspecExtension)
+ }
+
+ artifactsExtension.artifacts.withType(KotlinNativeArtifact::class.java) { artifact ->
+ val podspecExtension = artifact.kotlinArtifactsPodspecExtension
+
+ if (podspecExtension != null) {
+ registerPodspecTask(project, artifact, podspecExtension, cocoapodsExtension)
+ }
+ }
+ }
+
private fun registerPodInstallTask(
project: Project,
cocoapodsExtension: CocoapodsExtension
@@ -688,11 +745,15 @@ open class KotlinCocoapodsPlugin : Plugin {
pluginManager.withPlugin("kotlin-multiplatform") {
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, cocoapodsExtension)
+
+ createDefaultFrameworks(kotlinExtension)
registerDummyFrameworkTask(project, cocoapodsExtension)
createSyncTask(project, kotlinExtension, cocoapodsExtension)
+ injectPodspecExtensionToArtifacts(project, kotlinArtifactsExtension, cocoapodsExtension)
registerPodspecTask(project, cocoapodsExtension)
registerPodGenTask(project, kotlinExtension, cocoapodsExtension)
@@ -726,6 +787,7 @@ open class KotlinCocoapodsPlugin : Plugin {
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.
@@ -741,3 +803,16 @@ open class KotlinCocoapodsPlugin : Plugin {
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()
+}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/GenerateArtifactPodspecTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/GenerateArtifactPodspecTask.kt
new file mode 100644
index 00000000000..2abbf8e2de5
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/GenerateArtifactPodspecTask.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2010-2022 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.file.DirectoryProperty
+import org.gradle.api.file.RegularFile
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.MapProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.*
+import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
+import org.jetbrains.kotlin.gradle.targets.native.tasks.GenerateArtifactPodspecTask.ArtifactType.*
+import org.jetbrains.kotlin.gradle.utils.appendLine
+
+abstract class GenerateArtifactPodspecTask : DefaultTask() {
+
+ enum class ArtifactType {
+ Library, Framework, FatFramework, XCFramework
+ }
+
+ @get:Input
+ abstract val specName: Property
+
+ @get:Input
+ abstract val specVersion: Property
+
+ @get:OutputDirectory
+ abstract val destinationDir: DirectoryProperty
+
+ @get:Input
+ abstract val attributes: MapProperty
+
+ @get:Input
+ abstract val rawStatements: ListProperty
+
+ @get:Nested
+ abstract val dependencies: ListProperty
+
+ @get:Input
+ abstract val artifactType: Property
+
+ @get:OutputFile
+ @Suppress("LeakingThis") // should be inherited only by gradle machinery
+ val outputFile: Provider = specName.flatMap { specName ->
+ destinationDir.file("$specName.podspec")
+ }
+
+ @TaskAction
+ fun generate() {
+ outputFile.get().asFile.writeText(buildString {
+
+ append("Pod::Spec.new do |spec|")
+
+ appendAttributes(buildAdditionalAttrs())
+ appendAttributes(attributes.get())
+ appendDependencies()
+ appendRawStatements()
+
+ appendLine("end")
+
+ })
+ }
+
+ private fun Appendable.appendAttributes(attributes: Map) {
+ if (attributes.isNotEmpty()) {
+ appendLine()
+ }
+
+ for ((key, value) in attributes) {
+ append(" spec.")
+ append(key)
+
+ repeat(podspecValueIndent - key.length) { append(' ') }
+ append(" = ")
+
+ append(value.wrapInSingleQuotesIfNeeded())
+
+ appendLine()
+ }
+ }
+
+ private fun Appendable.appendDependencies() {
+ if (dependencies.get().isNotEmpty()) {
+ appendLine()
+ }
+
+ for (dependency in dependencies.get()) {
+
+ append(" spec.dependency ")
+ append(dependency.name.wrapInSingleQuotes())
+
+ val version = dependency.version
+ if (version != null) {
+ append(", ")
+ append(version.wrapInSingleQuotes())
+ }
+ appendLine()
+ }
+ }
+
+ private fun Appendable.appendRawStatements() {
+ if (rawStatements.get().isNotEmpty()) {
+ appendLine()
+ }
+
+ for (statement in rawStatements.get()) {
+ appendLine(statement)
+ }
+ }
+
+ private fun buildAdditionalAttrs(): Map {
+ val artifactTypeValue: ArtifactType = artifactType.get()
+
+ return mutableMapOf().apply {
+ if (!attributes.get().containsKey(specNameKey)) {
+ put(specNameKey, specName.get())
+ }
+
+ if (!attributes.get().containsKey(specVersionKey)) {
+ specVersion.get()?.let { put(specVersionKey, it) }
+ }
+
+ if (vendoredKeys.none { attributes.get().containsKey(it) }) {
+ val vendoredKey = when (artifactTypeValue) {
+ Library -> vendoredLibrary
+ Framework, FatFramework, XCFramework -> vendoredFrameworks
+ }
+
+ val vendoredValue = specName.get() + "." + when (artifactTypeValue) {
+ Library -> "dylib"
+ Framework, FatFramework -> "framework"
+ XCFramework -> "xcframework"
+ }
+
+ put(vendoredKey, vendoredValue)
+ }
+ }
+ }
+
+ private fun String.wrapInSingleQuotesIfNeeded(): String {
+ return when {
+ startsWith('{') ||
+ startsWith('[') ||
+ startsWith("<<-") ||
+ startsWith('\'') ||
+ startsWith('"') ||
+ equals("true") ||
+ equals("false") -> this
+
+ else -> wrapInSingleQuotes()
+ }
+ }
+
+ private fun String.wrapInSingleQuotes() = "'" + replace("'", "\\'") + "'"
+
+ companion object {
+ private const val specNameKey = "name"
+ private const val specVersionKey = "version"
+ private const val vendoredLibrary = "vendored_library"
+ private const val vendoredLibraries = "vendored_libraries"
+ private const val vendoredFrameworks = "vendored_frameworks"
+ private const val podspecValueIndent = 24
+
+ private val vendoredKeys = listOf(vendoredLibrary, vendoredLibraries, vendoredFrameworks)
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeFatFramework.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeFatFramework.kt
index 3d91011e1fb..3e80bcbac6c 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeFatFramework.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeFatFramework.kt
@@ -78,6 +78,8 @@ class KotlinNativeFatFrameworkImpl(
) : KotlinNativeFatFramework, ExtensionAware by extensions {
override fun getName() = lowerCamelCaseName(artifactName, "FatFramework")
override val taskName = lowerCamelCaseName("assemble", name)
+ override val outDir
+ get() = "out/fatframework"
override fun registerAssembleTask(project: Project) {
val parentTask = project.registerTask(taskName) {
@@ -91,7 +93,7 @@ class KotlinNativeFatFrameworkImpl(
lowerCamelCaseName("assemble", artifactName, buildType.visibleName, "FatFramework")
) {
it.baseName = artifactName
- it.destinationDir = project.buildDir.resolve("out/fatframework/${buildType.getName()}")
+ it.destinationDir = project.buildDir.resolve("$outDir/${buildType.getName()}")
}
parentTask.dependsOn(fatTask)
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeFramework.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeFramework.kt
index 661992e55f9..d4790bee1ca 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeFramework.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeFramework.kt
@@ -72,6 +72,7 @@ class KotlinNativeFrameworkImpl(
private val kind = NativeOutputKind.FRAMEWORK
override fun getName() = lowerCamelCaseName(artifactName, kind.taskNameClassifier, target.presetName)
override val taskName = lowerCamelCaseName("assemble", name)
+ override val outDir = "out/${kind.visibleName}"
override fun registerAssembleTask(project: Project) {
val resultTask = project.registerTask(taskName) { task ->
@@ -105,11 +106,11 @@ internal fun KotlinNativeArtifact.registerLinkFrameworkTask(
librariesConfigurationName: String,
exportConfigurationName: String,
embedBitcode: BitcodeEmbeddingMode?,
- outDirName: String = "out",
+ outDirName: String = outDir,
taskNameSuffix: String = ""
): TaskProvider {
val kind = NativeOutputKind.FRAMEWORK
- val destinationDir = project.buildDir.resolve("$outDirName/${kind.visibleName}/${target.visibleName}/${buildType.visibleName}")
+ val destinationDir = project.buildDir.resolve("$outDirName/${target.visibleName}/${buildType.visibleName}")
val resultTask = project.registerTask(
lowerCamelCaseName("assemble", name, buildType.visibleName, kind.taskNameClassifier, target.presetName, taskNameSuffix),
listOf(target, kind.compilerOutputKind)
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeLibrary.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeLibrary.kt
index 766df6f6999..2e88a63061e 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeLibrary.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeLibrary.kt
@@ -67,6 +67,7 @@ class KotlinNativeLibraryImpl(
private val kind = if (isStatic) NativeOutputKind.STATIC else NativeOutputKind.DYNAMIC
override fun getName() = lowerCamelCaseName(artifactName, kind.taskNameClassifier, "Library", target.presetName)
override val taskName = lowerCamelCaseName("assemble", name)
+ override val outDir = "out/${kind.visibleName}"
override fun registerAssembleTask(project: Project) {
val resultTask = project.registerTask(taskName) { task ->
@@ -84,6 +85,7 @@ class KotlinNativeLibraryImpl(
listOf(target, kind.compilerOutputKind)
) { task ->
task.description = "Assemble ${kind.description} '$artifactName' for a target '${target.name}'."
+ task.destinationDir.set(project.buildDir.resolve("$outDir/${target.visibleName}/${buildType.visibleName}"))
task.enabled = target.enabledOnCurrentHost
task.baseName.set(artifactName)
task.optimized.set(buildType.optimized)
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeXCFramework.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeXCFramework.kt
index 2c9492aba8b..e848b4bea6d 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeXCFramework.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeXCFramework.kt
@@ -78,6 +78,8 @@ class KotlinNativeXCFrameworkImpl(
) : KotlinNativeXCFramework, ExtensionAware by extensions {
override fun getName() = lowerCamelCaseName(artifactName, "XCFramework")
override val taskName = lowerCamelCaseName("assemble", name)
+ override val outDir: String
+ get() = "out/xcframework"
override fun registerAssembleTask(project: Project) {
val parentTask = project.registerTask(taskName) {
@@ -120,7 +122,7 @@ class KotlinNativeXCFrameworkImpl(
}
holder.task.configure {
it.fromFrameworkDescriptors(frameworkDescriptors)
- it.outputDir = project.buildDir.resolve("out/xcframework")
+ it.outputDir = project.buildDir.resolve(outDir)
}
}
}