diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CocoaPodsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CocoaPodsIT.kt index d6356b7b9e7..676081119ed 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CocoaPodsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CocoaPodsIT.kt @@ -22,7 +22,12 @@ class CocoaPodsIT : BaseGradleIT() { val PODFILE_IMPORT_DIRECTIVE_PLACEHOLDER = "" @Test - fun testPodspec() { + fun testPodspec() = doTestPodspec() + + @Test + fun testPodspecCustomFrameworkName() = doTestPodspec("MultiPlatformLibrary") + + private fun doTestPodspec(frameworkName: String? = null) { assumeTrue(HostManager.hostIsMac) val gradleProject = transformProjectWithPluginsDsl("new-mpp-cocoapods", gradleVersion) @@ -30,8 +35,10 @@ class CocoaPodsIT : BaseGradleIT() { gradleProject.build(":kotlin-library:podspec") { assertFailed() assertContains("The Gradle wrapper is required to run the build from Xcode.") - assertContains("Please run the same command with `-Pkotlin.native.cocoapods.generate.wrapper=true` " + - "or run the `:wrapper` task to generate the wrapper manually.") + assertContains( + "Please run the same command with `-Pkotlin.native.cocoapods.generate.wrapper=true` " + + "or run the `:wrapper` task to generate the wrapper manually." + ) } // Check that we can generate the wrapper along with the podspec if the corresponding property specified @@ -42,6 +49,14 @@ class CocoaPodsIT : BaseGradleIT() { // Check that the podspec file is correctly generated. val podspecFileName = "kotlin-library/kotlin_library.podspec" + + + frameworkName?.let { + fileInWorkingDir(podspecFileName).modify { + it.replace("build/cocoapods/framework/kotlin_library.framework", "build/cocoapods/framework/$frameworkName.framework") + } + } + val expectedPodspecContent = """ Pod::Spec.new do |spec| spec.name = 'kotlin_library' @@ -53,7 +68,7 @@ class CocoaPodsIT : BaseGradleIT() { spec.summary = 'CocoaPods test library' spec.static_framework = true - spec.vendored_frameworks = "build/cocoapods/framework/#{spec.name}.framework" + spec.vendored_frameworks = "build/cocoapods/framework/${frameworkName ?: "kotlin_library"}.framework" spec.libraries = "c++" spec.module_name = "#{spec.name}_umbrella" @@ -159,11 +174,34 @@ class CocoaPodsIT : BaseGradleIT() { CommandResult(process.exitValue(), stdOut, stdErr).block() } - private fun doTestXcode(mode: ImportMode) { + private fun doTestXcode(mode: ImportMode, isCustomFrameworkName: Boolean) { assumeTrue(HostManager.hostIsMac) val gradleProject = transformProjectWithPluginsDsl("new-mpp-cocoapods", gradleVersion) with(gradleProject) { + setupWorkingDir() + + // Add property with custom framework name + if (isCustomFrameworkName) { + gradleBuildScript("kotlin-library").appendText( + """ + kotlin { + cocoapods { + frameworkName = "MultiPlatformLibrary" + } + } + """.trimIndent() + ) + + // Change swift sources import + val iosAppDir = projectDir.resolve("ios-app") + iosAppDir.resolve("ios-app/ViewController.swift").modify { + it.replace("import kotlin_library", "import MultiPlatformLibrary") + } + + } + + // Generate podspec. gradleProject.build(":kotlin-library:podspec", "-Pkotlin.native.cocoapods.generate.wrapper=true") { assertSuccessful() @@ -178,7 +216,8 @@ class CocoaPodsIT : BaseGradleIT() { // Install pods. runCommand(iosAppDir, "pod", "install") { - assertEquals(0, exitCode, """ + assertEquals( + 0, exitCode, """ |Exit code mismatch for `pod install`. |stdout: |$stdOut @@ -190,15 +229,17 @@ class CocoaPodsIT : BaseGradleIT() { } // Run Xcode build. - runCommand(iosAppDir, "xcodebuild", - "-sdk", "iphonesimulator", - "-arch", "arm64", - "-configuration", "Release", - "-workspace", "ios-app.xcworkspace", - "-scheme", "ios-app", - inheritIO = true // Xcode doesn't finish the process if the PIPE redirect is used. + runCommand( + iosAppDir, "xcodebuild", + "-sdk", "iphonesimulator", + "-arch", "arm64", + "-configuration", "Release", + "-workspace", "ios-app.xcworkspace", + "-scheme", "ios-app", + inheritIO = true // Xcode doesn't finish the process if the PIPE redirect is used. ) { - assertEquals(0, exitCode, """ + assertEquals( + 0, exitCode, """ |Exit code mismatch for `xcodebuild`. |stdout: |$stdOut @@ -212,8 +253,12 @@ class CocoaPodsIT : BaseGradleIT() { } @Test - fun testXcodeUseFrameworks() = doTestXcode(ImportMode.FRAMEWORKS) + fun testXcodeUseFrameworks() = doTestXcode(ImportMode.FRAMEWORKS, false) @Test - fun testXcodeUseModularHeaders() = doTestXcode(ImportMode.MODULAR_HEADERS) + fun testXcodeUseModularHeaders() = doTestXcode(ImportMode.MODULAR_HEADERS, false) + + @Test + fun testXcodeWithCustomFrameworkName() = doTestXcode(ImportMode.FRAMEWORKS, true) + } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/NativeBinaries.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/NativeBinaries.kt index dd7b53d4cd8..ec3b538595a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/NativeBinaries.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/NativeBinaries.kt @@ -11,6 +11,7 @@ import org.gradle.api.Action import org.gradle.api.Named import org.gradle.api.Project import org.gradle.api.artifacts.Dependency +import org.gradle.api.provider.Provider import org.gradle.api.tasks.AbstractExecTask import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName @@ -27,10 +28,16 @@ import java.io.File */ sealed class NativeBinary( private val name: String, - open var baseName: String, + baseNameProvided: String, val buildType: NativeBuildType, var compilation: KotlinNativeCompilation ) : Named { + open var baseName: String + get() = baseNameProvider.get() + set(value) { + baseNameProvider = project.provider { value } + } + internal var baseNameProvider: Provider = project.provider { baseNameProvided } internal val konanTarget: KonanTarget get() = compilation.konanTarget @@ -247,8 +254,10 @@ class Framework( enum class BitcodeEmbeddingMode { /** Don't embed LLVM IR bitcode. */ DISABLE, + /** Embed LLVM IR bitcode as data. */ BITCODE, + /** Embed placeholder LLVM IR data as a marker. */ MARKER, } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt index 210bb06559f..0bd73bb2bf1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/CocoapodsExtension.kt @@ -47,6 +47,12 @@ open class CocoapodsExtension(private val project: Project) { @Input var homepage: String? = null + /** + * Configure framework name of the pod built from this project. + */ + @Input + var frameworkName: String = project.name.asValidFrameworkName() + private val _pods = project.container(CocoapodsDependency::class.java) // For some reason Gradle doesn't consume the @Nested annotation on NamedDomainObjectContainer. diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt index 94d116bc3dd..7e200046921 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt @@ -61,9 +61,10 @@ open class KotlinCocoapodsPlugin : Plugin { private fun KotlinMultiplatformExtension.targetsForPlatform(requestedPlatform: KonanTarget) = supportedTargets().matching { it.konanTarget == requestedPlatform } - private fun createDefaultFrameworks(kotlinExtension: KotlinMultiplatformExtension) { + private fun createDefaultFrameworks(kotlinExtension: KotlinMultiplatformExtension, cocoapodsExtension: CocoapodsExtension) { kotlinExtension.supportedTargets().all { target -> target.binaries.framework { + baseNameProvider = project.provider { cocoapodsExtension.frameworkName } isStatic = true } } @@ -156,7 +157,9 @@ open class KotlinCocoapodsPlugin : Plugin { project: Project, cocoapodsExtension: CocoapodsExtension ) { - val dummyFrameworkTask = project.tasks.create("generateDummyFramework", DummyFrameworkTask::class.java) + val dummyFrameworkTask = project.tasks.create("generateDummyFramework", DummyFrameworkTask::class.java) { + it.settings = cocoapodsExtension + } project.tasks.create("podspec", PodspecTask::class.java) { it.group = TASK_GROUP @@ -237,12 +240,13 @@ open class KotlinCocoapodsPlugin : Plugin { } override fun apply(project: Project): Unit = with(project) { + pluginManager.withPlugin("kotlin-multiplatform") { val kotlinExtension = project.multiplatformExtension val cocoapodsExtension = CocoapodsExtension(this) kotlinExtension.addExtension(EXTENSION_NAME, cocoapodsExtension) - createDefaultFrameworks(kotlinExtension) + createDefaultFrameworks(kotlinExtension, cocoapodsExtension) createSyncTask(project, kotlinExtension) createPodspecGenerationTask(project, cocoapodsExtension) createInterops(project, kotlinExtension, cocoapodsExtension) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt index 70f80bf8b83..9c60400d498 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/CocoapodsTasks.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.gradle.tasks import org.gradle.api.DefaultTask +import org.gradle.api.provider.Provider import org.gradle.api.tasks.* import org.gradle.api.tasks.wrapper.Wrapper import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension @@ -30,8 +31,11 @@ open class PodspecTask : DefaultTask() { @OutputFile val outputFile: File = project.projectDir.resolve("$specName.podspec") - @Nested - lateinit var settings: CocoapodsExtension + @Input + val frameworkNameProvider: Provider = project.provider { settings.frameworkName } + + @get:Nested + internal lateinit var settings: CocoapodsExtension // TODO: Handle Framework name customization - rename the framework during sync process. @TaskAction @@ -69,7 +73,7 @@ open class PodspecTask : DefaultTask() { | spec.summary = '${settings.summary.orEmpty()}' | | spec.static_framework = true - | spec.vendored_frameworks = "$frameworkDir/#{spec.name}.framework" + | spec.vendored_frameworks = "$frameworkDir/${frameworkNameProvider.get()}.framework" | spec.libraries = "c++" | spec.module_name = "#{spec.name}_umbrella" | @@ -133,12 +137,14 @@ open class DummyFrameworkTask : DefaultTask() { @OutputDirectory val destinationDir = project.cocoapodsBuildDirs.framework - @get:Input - val frameworkName - get() = project.name.asValidFrameworkName() + @Input + val frameworkNameProvider: Provider = project.provider { settings.frameworkName } + + @get:Nested + internal lateinit var settings: CocoapodsExtension private val frameworkDir: File - get() = destinationDir.resolve("$frameworkName.framework") + get() = destinationDir.resolve("${frameworkNameProvider.get()}.framework") private fun copyResource(from: String, to: File) { to.parentFile.mkdirs() @@ -184,11 +190,11 @@ open class DummyFrameworkTask : DefaultTask() { // Copy files for the dummy framework. copyFrameworkFile("Info.plist") - copyFrameworkFile("dummy", frameworkName) + copyFrameworkFile("dummy", frameworkNameProvider.get()) copyFrameworkFile("Headers/dummy.h") copyFrameworkTextFile("Modules/module.modulemap") { if (it == "framework module dummy {") { - it.replace("dummy", frameworkName) + it.replace("dummy", frameworkNameProvider.get()) } else { it }