diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt index 8bdf3b21ce1..ce4a44f9fbe 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt @@ -1299,6 +1299,47 @@ class CocoaPodsIT : BaseGradleIT() { } } + @Test + fun `hierarchy of dependant pods compiles successfully`() = with(getProjectByName("native-cocoapods-dependant-pods")) { + build( + ":compileKotlinIosX64", + "-Pkotlin.native.cocoapods.generate.wrapper=true", + ) { + assertSuccessful() + } + } + + @Test + fun `configuration fails when trying to depend on non-declared pod`() = with(getProjectByName("native-cocoapods-dependant-pods")) { + gradleBuildScript().appendToCocoapodsBlock(""" + pod("Foo") { useInteropBindingFrom("JBNonExistent") } + """.trimIndent()) + + build( + ":help", + "-Pkotlin.native.cocoapods.generate.wrapper=true", + ) { + assertFailed() + assertContains("Couldn't find declaration of pod 'JBNonExistent' (interop-binding dependency of pod 'Foo')") + } + } + + @Test + fun `configuration fails when dependant pods are in the wrong order`() = with(getProjectByName("native-cocoapods-dependant-pods")) { + gradleBuildScript().appendToCocoapodsBlock(""" + pod("Foo") { useInteropBindingFrom("Bar") } + pod("Bar") + """.trimIndent()) + + build( + ":help", + "-Pkotlin.native.cocoapods.generate.wrapper=true", + ) { + assertFailed() + assertContains("Couldn't find declaration of pod 'Bar' (interop-binding dependency of pod 'Foo')") + } + } + // test configuration phase private class CustomHooks { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/build.gradle.kts new file mode 100644 index 00000000000..7547313e1f1 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + kotlin("multiplatform") version "" + kotlin("native.cocoapods") version "" +} + +group = "org.jetbrains.kotlin.sample.native" +version = "1.0" + +repositories { + mavenLocal() + mavenCentral() +} + +kotlin { + iosX64() + + cocoapods { + summary = "Some description for the Shared Module" + homepage = "Link to the Shared Module homepage" + version = "1.0" + ios.deploymentTarget = "14.1" + framework { + baseName = "shared" + } + + pod("pod1", path = project.file("pod1")) + + pod("pod2") { + source = path(project.file("pod2")) + + useInteropBindingFrom("pod1") + + extraOpts = listOf("-compiler-option", "-fmodules") + } + + pod("pod3") { + source = path(project.file("pod3")) + + interopBindingDependencies.add("pod1") + } + + pod("pod4") { + source = path(project.file("pod4")) + + useInteropBindingFrom("pod2") + useInteropBindingFrom("pod3") + + extraOpts = listOf("-compiler-option", "-fmodules") + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod1/pod1.h b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod1/pod1.h new file mode 100644 index 00000000000..e1ed3f2d1be --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod1/pod1.h @@ -0,0 +1,4 @@ +@interface Pod1 : NSObject +-(int)pod1; +-(void)name: (NSString *) firstName secondName: (NSString *) secondName; +@end diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod1/pod1.m b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod1/pod1.m new file mode 100644 index 00000000000..4c21eb99cfa --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod1/pod1.m @@ -0,0 +1,13 @@ +#include + +@implementation Pod1 + +-(int)pod1 { + return 42; +} + +-(void)name: (NSString *) firstName secondName: (NSString *) secondName { + +} + +@end diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod1/pod1.podspec b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod1/pod1.podspec new file mode 100644 index 00000000000..6fe7624abca --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod1/pod1.podspec @@ -0,0 +1,12 @@ +Pod::Spec.new do |spec| + spec.name = 'pod1' + spec.version = '1.0' + spec.homepage = 'repro' + spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } + spec.authors = '' + spec.license = '' + spec.summary = 'repro' + + spec.source_files = '*.{h,m}' + spec.public_header_files = '*.h' +end \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod2/pod2.podspec b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod2/pod2.podspec new file mode 100644 index 00000000000..56183931e7a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod2/pod2.podspec @@ -0,0 +1,14 @@ +Pod::Spec.new do |spec| + spec.name = 'pod2' + spec.version = '1.0' + spec.homepage = 'repro' + spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } + spec.authors = '' + spec.license = '' + spec.summary = 'repro' + + spec.dependency 'pod1' + + spec.swift_version = '5.0' + spec.source_files = '*.swift' +end diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod2/pod2.swift b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod2/pod2.swift new file mode 100644 index 00000000000..9b85ba9e112 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod2/pod2.swift @@ -0,0 +1,9 @@ +import pod1 + +@objc +open class Pod2: Pod1 { + @objc + public func pod2() -> Int32 { + return 33 + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod3/pod3.h b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod3/pod3.h new file mode 100644 index 00000000000..f84bd8dbbc7 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod3/pod3.h @@ -0,0 +1,5 @@ +#import "pod1/pod1.h" + +@interface Pod3 : Pod1 +-(int)pod3; +@end diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod3/pod3.m b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod3/pod3.m new file mode 100644 index 00000000000..f12da79917c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod3/pod3.m @@ -0,0 +1,9 @@ +#include + +@implementation Pod3 + +-(int)pod3 { + return 42; +} + +@end diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod3/pod3.podspec b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod3/pod3.podspec new file mode 100644 index 00000000000..42542887db9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod3/pod3.podspec @@ -0,0 +1,14 @@ +Pod::Spec.new do |spec| + spec.name = 'pod3' + spec.version = '1.0' + spec.homepage = 'repro' + spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } + spec.authors = '' + spec.license = '' + spec.summary = 'repro' + + spec.source_files = '*.{h,m}' + spec.public_header_files = '*.h' + + spec.dependency 'pod1' +end \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod4/pod4.podspec b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod4/pod4.podspec new file mode 100644 index 00000000000..8e6b6760594 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod4/pod4.podspec @@ -0,0 +1,15 @@ +Pod::Spec.new do |spec| + spec.name = 'pod4' + spec.version = '1.0' + spec.homepage = 'repro' + spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } + spec.authors = '' + spec.license = '' + spec.summary = 'repro' + + spec.dependency 'pod2' + spec.dependency 'pod3' + + spec.swift_version = '5.0' + spec.source_files = '*.swift' +end diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod4/pod4.swift b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod4/pod4.swift new file mode 100644 index 00000000000..b224f2a054f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/pod4/pod4.swift @@ -0,0 +1,18 @@ +import pod2 +import pod3 + +@objc +public class Pod42: Pod2 { + @objc + public func pod4() -> String { + return "Hello" + } +} + +@objc +public class Pod43: Pod3 { + @objc + public func pod4() -> String { + return "Hello" + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/settings.gradle.kts new file mode 100644 index 00000000000..a7865dce8bc --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "cocoapods" diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/src/iosMain/kotlin/main.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/src/iosMain/kotlin/main.kt new file mode 100644 index 00000000000..4ded833d317 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cocoapods-dependant-pods/src/iosMain/kotlin/main.kt @@ -0,0 +1,30 @@ +import cocoapods.pod1.Pod1 +import cocoapods.pod2.Pod2 +import cocoapods.pod3.Pod3 +import cocoapods.pod4.Pod42 +import cocoapods.pod4.Pod43 + +fun printPod1(p: Pod1) { + println(p.pod1()) +} + +fun printPod2(p: Pod2) { + println("Hi from Kt!") + println(p.pod2()) + printPod1(p) +} + +fun printPod3(p: Pod3) { + printPod1(p) +} + +fun printPods4(p42: Pod42, p43: Pod43) { + printPod2(p42) + printPod3(p43) + printPod1(p42) + printPod1(p43) +} + +fun main() { + printPods4(Pod42(), Pod43()) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt index 070d50e9ad4..242f26e5b79 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt @@ -20,7 +20,7 @@ import java.io.File import java.net.URI import javax.inject.Inject -@Suppress("unused") // Public API +@Suppress("unused", "MemberVisibilityCanBePrivate") // Public API abstract class CocoapodsExtension @Inject constructor(private val project: Project) { /** * Configure version of the pod @@ -292,6 +292,27 @@ abstract class CocoapodsExtension @Inject constructor(private val project: Proje @get:Input var linkOnly: Boolean = false + /** + * Contains a list of dependencies to other pods. This list will be used while building an interop Kotlin-binding for the pod. + * + * @see useInteropBindingFrom + */ + @get:Input + val interopBindingDependencies: MutableList = mutableListOf() + + /** + * Specify that the pod depends on another pod **podName** and a Kotlin-binding for **podName** should be used while building + * a binding for the pod. This is necessary if you need to operate entities from **podName** and from the pod together, for + * instance pass an object from **podName** to the pod in Kotlin. + * + * A pod with the exact name must be declared before calling this function. + * + * @see interopBindingDependencies + */ + fun useInteropBindingFrom(podName: String) { + interopBindingDependencies.add(podName) + } + @Input override fun getName(): String = name 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 10f6b8404a1..d019e8c3646 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 @@ -7,6 +7,7 @@ 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.plugins.ExtensionAware @@ -326,12 +327,17 @@ open class KotlinCocoapodsPlugin : Plugin { } kotlinExtension.supportedTargets().all { target -> - target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).cinterops.create(pod.moduleName) { interop -> + val cinterops = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).cinterops + cinterops.create(pod.moduleName) { interop -> - val interopTask = project.tasks.getByPath(interop.interopProcessingTaskName) + val interopTask = project.tasks.named(interop.interopProcessingTaskName).get() interopTask.dependsOn(defTask) + pod.interopBindingDependencies.forEach { dependencyName -> + addPodDependencyToInterop(project, cocoapodsExtension, pod, cinterops, interop, dependencyName) + } + with(interop) { defFileProperty.set(defTask.map { it.outputFile }) _packageNameProp.set(project.provider { pod.packageName }) @@ -364,6 +370,27 @@ open class KotlinCocoapodsPlugin : Plugin { } } + private fun addPodDependencyToInterop( + project: Project, + cocoapodsExtension: CocoapodsExtension, + pod: CocoapodsDependency, + cinterops: NamedDomainObjectContainer, + interop: DefaultCInteropSettings, + dependencyName: String, + ) { + val dependencyPod = cocoapodsExtension.pods.findByName(dependencyName) + ?: error("Couldn't find declaration of pod '$dependencyName' (interop-binding dependency of pod '${pod.name}')") + + 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