[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>"
@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)
}
@@ -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<String> = 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,
}
@@ -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.
@@ -61,9 +61,10 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
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: 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<Project> {
}
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)
@@ -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<String> = 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<String> = 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
}