[Gradle, Native] Implement custom name for generated framework for XCode

This commit is contained in:
Yaroslav Chernyshev
2020-01-13 19:32:54 +03:00
parent a24e62eaf7
commit 401baa35ab
5 changed files with 99 additions and 29 deletions
@@ -22,7 +22,12 @@ class CocoaPodsIT : BaseGradleIT() {
val PODFILE_IMPORT_DIRECTIVE_PLACEHOLDER = "<import_mode_directive>" val PODFILE_IMPORT_DIRECTIVE_PLACEHOLDER = "<import_mode_directive>"
@Test @Test
fun testPodspec() { fun testPodspec() = doTestPodspec()
@Test
fun testPodspecCustomFrameworkName() = doTestPodspec("MultiPlatformLibrary")
private fun doTestPodspec(frameworkName: String? = null) {
assumeTrue(HostManager.hostIsMac) assumeTrue(HostManager.hostIsMac)
val gradleProject = transformProjectWithPluginsDsl("new-mpp-cocoapods", gradleVersion) val gradleProject = transformProjectWithPluginsDsl("new-mpp-cocoapods", gradleVersion)
@@ -30,8 +35,10 @@ class CocoaPodsIT : BaseGradleIT() {
gradleProject.build(":kotlin-library:podspec") { gradleProject.build(":kotlin-library:podspec") {
assertFailed() assertFailed()
assertContains("The Gradle wrapper is required to run the build from Xcode.") 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` " + assertContains(
"or run the `:wrapper` task to generate the wrapper manually.") "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 // 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. // Check that the podspec file is correctly generated.
val podspecFileName = "kotlin-library/kotlin_library.podspec" 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 = """ val expectedPodspecContent = """
Pod::Spec.new do |spec| Pod::Spec.new do |spec|
spec.name = 'kotlin_library' spec.name = 'kotlin_library'
@@ -53,7 +68,7 @@ class CocoaPodsIT : BaseGradleIT() {
spec.summary = 'CocoaPods test library' spec.summary = 'CocoaPods test library'
spec.static_framework = true 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.libraries = "c++"
spec.module_name = "#{spec.name}_umbrella" spec.module_name = "#{spec.name}_umbrella"
@@ -159,11 +174,34 @@ class CocoaPodsIT : BaseGradleIT() {
CommandResult(process.exitValue(), stdOut, stdErr).block() CommandResult(process.exitValue(), stdOut, stdErr).block()
} }
private fun doTestXcode(mode: ImportMode) { private fun doTestXcode(mode: ImportMode, isCustomFrameworkName: Boolean) {
assumeTrue(HostManager.hostIsMac) assumeTrue(HostManager.hostIsMac)
val gradleProject = transformProjectWithPluginsDsl("new-mpp-cocoapods", gradleVersion) val gradleProject = transformProjectWithPluginsDsl("new-mpp-cocoapods", gradleVersion)
with(gradleProject) { 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. // Generate podspec.
gradleProject.build(":kotlin-library:podspec", "-Pkotlin.native.cocoapods.generate.wrapper=true") { gradleProject.build(":kotlin-library:podspec", "-Pkotlin.native.cocoapods.generate.wrapper=true") {
assertSuccessful() assertSuccessful()
@@ -178,7 +216,8 @@ class CocoaPodsIT : BaseGradleIT() {
// Install pods. // Install pods.
runCommand(iosAppDir, "pod", "install") { runCommand(iosAppDir, "pod", "install") {
assertEquals(0, exitCode, """ assertEquals(
0, exitCode, """
|Exit code mismatch for `pod install`. |Exit code mismatch for `pod install`.
|stdout: |stdout:
|$stdOut |$stdOut
@@ -190,15 +229,17 @@ class CocoaPodsIT : BaseGradleIT() {
} }
// Run Xcode build. // Run Xcode build.
runCommand(iosAppDir, "xcodebuild", runCommand(
"-sdk", "iphonesimulator", iosAppDir, "xcodebuild",
"-arch", "arm64", "-sdk", "iphonesimulator",
"-configuration", "Release", "-arch", "arm64",
"-workspace", "ios-app.xcworkspace", "-configuration", "Release",
"-scheme", "ios-app", "-workspace", "ios-app.xcworkspace",
inheritIO = true // Xcode doesn't finish the process if the PIPE redirect is used. "-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`. |Exit code mismatch for `xcodebuild`.
|stdout: |stdout:
|$stdOut |$stdOut
@@ -212,8 +253,12 @@ class CocoaPodsIT : BaseGradleIT() {
} }
@Test @Test
fun testXcodeUseFrameworks() = doTestXcode(ImportMode.FRAMEWORKS) fun testXcodeUseFrameworks() = doTestXcode(ImportMode.FRAMEWORKS, false)
@Test @Test
fun testXcodeUseModularHeaders() = doTestXcode(ImportMode.MODULAR_HEADERS) fun testXcodeUseModularHeaders() = doTestXcode(ImportMode.MODULAR_HEADERS, false)
@Test
fun testXcodeWithCustomFrameworkName() = doTestXcode(ImportMode.FRAMEWORKS, true)
} }
@@ -11,6 +11,7 @@ import org.gradle.api.Action
import org.gradle.api.Named import org.gradle.api.Named
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.Dependency
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.AbstractExecTask import org.gradle.api.tasks.AbstractExecTask
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
@@ -27,10 +28,16 @@ import java.io.File
*/ */
sealed class NativeBinary( sealed class NativeBinary(
private val name: String, private val name: String,
open var baseName: String, baseNameProvided: String,
val buildType: NativeBuildType, val buildType: NativeBuildType,
var compilation: KotlinNativeCompilation var compilation: KotlinNativeCompilation
) : Named { ) : Named {
open var baseName: String
get() = baseNameProvider.get()
set(value) {
baseNameProvider = project.provider { value }
}
internal var baseNameProvider: Provider<String> = project.provider { baseNameProvided }
internal val konanTarget: KonanTarget internal val konanTarget: KonanTarget
get() = compilation.konanTarget get() = compilation.konanTarget
@@ -247,8 +254,10 @@ class Framework(
enum class BitcodeEmbeddingMode { enum class BitcodeEmbeddingMode {
/** Don't embed LLVM IR bitcode. */ /** Don't embed LLVM IR bitcode. */
DISABLE, DISABLE,
/** Embed LLVM IR bitcode as data. */ /** Embed LLVM IR bitcode as data. */
BITCODE, BITCODE,
/** Embed placeholder LLVM IR data as a marker. */ /** Embed placeholder LLVM IR data as a marker. */
MARKER, MARKER,
} }
@@ -47,6 +47,12 @@ open class CocoapodsExtension(private val project: Project) {
@Input @Input
var homepage: String? = null 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) private val _pods = project.container(CocoapodsDependency::class.java)
// For some reason Gradle doesn't consume the @Nested annotation on NamedDomainObjectContainer. // For some reason Gradle doesn't consume the @Nested annotation on NamedDomainObjectContainer.
@@ -61,9 +61,10 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
private fun KotlinMultiplatformExtension.targetsForPlatform(requestedPlatform: KonanTarget) = private fun KotlinMultiplatformExtension.targetsForPlatform(requestedPlatform: KonanTarget) =
supportedTargets().matching { it.konanTarget == requestedPlatform } supportedTargets().matching { it.konanTarget == requestedPlatform }
private fun createDefaultFrameworks(kotlinExtension: KotlinMultiplatformExtension) { private fun createDefaultFrameworks(kotlinExtension: KotlinMultiplatformExtension, cocoapodsExtension: CocoapodsExtension) {
kotlinExtension.supportedTargets().all { target -> kotlinExtension.supportedTargets().all { target ->
target.binaries.framework { target.binaries.framework {
baseNameProvider = project.provider { cocoapodsExtension.frameworkName }
isStatic = true isStatic = true
} }
} }
@@ -156,7 +157,9 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
project: Project, project: Project,
cocoapodsExtension: CocoapodsExtension 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) { project.tasks.create("podspec", PodspecTask::class.java) {
it.group = TASK_GROUP it.group = TASK_GROUP
@@ -237,12 +240,13 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
} }
override fun apply(project: Project): Unit = with(project) { override fun apply(project: Project): Unit = with(project) {
pluginManager.withPlugin("kotlin-multiplatform") { pluginManager.withPlugin("kotlin-multiplatform") {
val kotlinExtension = project.multiplatformExtension val kotlinExtension = project.multiplatformExtension
val cocoapodsExtension = CocoapodsExtension(this) val cocoapodsExtension = CocoapodsExtension(this)
kotlinExtension.addExtension(EXTENSION_NAME, cocoapodsExtension) kotlinExtension.addExtension(EXTENSION_NAME, cocoapodsExtension)
createDefaultFrameworks(kotlinExtension) createDefaultFrameworks(kotlinExtension, cocoapodsExtension)
createSyncTask(project, kotlinExtension) createSyncTask(project, kotlinExtension)
createPodspecGenerationTask(project, cocoapodsExtension) createPodspecGenerationTask(project, cocoapodsExtension)
createInterops(project, kotlinExtension, cocoapodsExtension) createInterops(project, kotlinExtension, cocoapodsExtension)
@@ -7,6 +7,7 @@
package org.jetbrains.kotlin.gradle.tasks package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.api.tasks.wrapper.Wrapper import org.gradle.api.tasks.wrapper.Wrapper
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension
@@ -30,8 +31,11 @@ open class PodspecTask : DefaultTask() {
@OutputFile @OutputFile
val outputFile: File = project.projectDir.resolve("$specName.podspec") val outputFile: File = project.projectDir.resolve("$specName.podspec")
@Nested @Input
lateinit var settings: CocoapodsExtension val frameworkNameProvider: Provider<String> = project.provider { settings.frameworkName }
@get:Nested
internal lateinit var settings: CocoapodsExtension
// TODO: Handle Framework name customization - rename the framework during sync process. // TODO: Handle Framework name customization - rename the framework during sync process.
@TaskAction @TaskAction
@@ -69,7 +73,7 @@ open class PodspecTask : DefaultTask() {
| spec.summary = '${settings.summary.orEmpty()}' | spec.summary = '${settings.summary.orEmpty()}'
| |
| spec.static_framework = true | spec.static_framework = true
| spec.vendored_frameworks = "$frameworkDir/#{spec.name}.framework" | spec.vendored_frameworks = "$frameworkDir/${frameworkNameProvider.get()}.framework"
| spec.libraries = "c++" | spec.libraries = "c++"
| spec.module_name = "#{spec.name}_umbrella" | spec.module_name = "#{spec.name}_umbrella"
| |
@@ -133,12 +137,14 @@ open class DummyFrameworkTask : DefaultTask() {
@OutputDirectory @OutputDirectory
val destinationDir = project.cocoapodsBuildDirs.framework val destinationDir = project.cocoapodsBuildDirs.framework
@get:Input @Input
val frameworkName val frameworkNameProvider: Provider<String> = project.provider { settings.frameworkName }
get() = project.name.asValidFrameworkName()
@get:Nested
internal lateinit var settings: CocoapodsExtension
private val frameworkDir: File private val frameworkDir: File
get() = destinationDir.resolve("$frameworkName.framework") get() = destinationDir.resolve("${frameworkNameProvider.get()}.framework")
private fun copyResource(from: String, to: File) { private fun copyResource(from: String, to: File) {
to.parentFile.mkdirs() to.parentFile.mkdirs()
@@ -184,11 +190,11 @@ open class DummyFrameworkTask : DefaultTask() {
// Copy files for the dummy framework. // Copy files for the dummy framework.
copyFrameworkFile("Info.plist") copyFrameworkFile("Info.plist")
copyFrameworkFile("dummy", frameworkName) copyFrameworkFile("dummy", frameworkNameProvider.get())
copyFrameworkFile("Headers/dummy.h") copyFrameworkFile("Headers/dummy.h")
copyFrameworkTextFile("Modules/module.modulemap") { copyFrameworkTextFile("Modules/module.modulemap") {
if (it == "framework module dummy {") { if (it == "framework module dummy {") {
it.replace("dummy", frameworkName) it.replace("dummy", frameworkNameProvider.get())
} else { } else {
it it
} }