M1 support for cocoapods

#KT-47078
This commit is contained in:
Viacheslav Kormushkin
2021-07-05 16:44:22 +03:00
committed by Space
parent 455fee29e4
commit 8c021af646
5 changed files with 218 additions and 93 deletions
@@ -763,43 +763,6 @@ class CocoaPodsIT : BaseGradleIT() {
}
}
@Test
fun testCommaSeparatedTargets() {
with(project) {
gradleBuildScript().modify {
// Replace a single target with a pair (iosX64 + iosArm64) to test building a fat framework.
it.replace("iosX64(\"iOS\")", "ios()")
}
hooks.addHook {
// Check that a built universal framework includes both device and simulator architectures.
val framework = fileInWorkingDir("build/cocoapods/framework/cocoapods.framework/cocoapods")
with(runProcess(listOf("file", framework.absolutePath), projectDir, environmentVariables = getPathEnvs())) {
assertTrue(isSuccessful)
assertTrue(output.contains("\\(for architecture x86_64\\):\\s+current ar archive".toRegex()))
assertTrue(output.contains("\\(for architecture arm64\\):\\s+current ar archive".toRegex()))
}
}
// Run the build.
test(
"syncFramework",
"-Pkotlin.native.cocoapods.target=ios_x64,ios_arm64",
"-Pkotlin.native.cocoapods.configuration=Debug"
)
}
}
@Test
fun testCustomXcodeConfiguration() {
with(project) {
gradleBuildScript().appendToCocoapodsBlock("xcodeConfigurationToNativeBuildType[\"CUSTOM\"] = org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.DEBUG\n")
test(
"syncFramework",
"-Pkotlin.native.cocoapods.target=ios_x64",
"-Pkotlin.native.cocoapods.configuration=CUSTOM"
)
}
}
@Test
fun testUseDynamicFramework() {
with(project) {
@@ -808,7 +771,7 @@ class CocoaPodsIT : BaseGradleIT() {
hooks.addHook {
// Check that an output framework is a dynamic framework
val framework = fileInWorkingDir("build/cocoapods/framework/cocoapods.framework/cocoapods")
with(runProcess(listOf("file", framework.absolutePath), projectDir)) {
with(runProcess(listOf("file", framework.absolutePath), projectDir, environmentVariables = getPathEnvs())) {
assertTrue(isSuccessful)
assertTrue(output.contains("dynamically linked shared library"))
}
@@ -829,6 +792,95 @@ class CocoaPodsIT : BaseGradleIT() {
}
}
@Test
fun testSyncFramework() {
with(project) {
hooks.addHook {
assertTasksExecuted(":linkPodDebugFrameworkIOS")
assertTrue(fileInWorkingDir("build/cocoapods/framework/cocoapods.framework/cocoapods").exists())
}
test(
"syncFramework",
"-Pkotlin.native.cocoapods.platform=iphonesimulator",
"-Pkotlin.native.cocoapods.archs=x86_64",
"-Pkotlin.native.cocoapods.configuration=Debug"
)
}
}
@Test
fun testSyncFrameworkCustomXcodeConfiguration() {
with(project) {
gradleBuildScript().appendToCocoapodsBlock("xcodeConfigurationToNativeBuildType[\"CUSTOM\"] = org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.DEBUG\n")
hooks.addHook {
assertTasksExecuted(":linkPodDebugFrameworkIOS")
assertTrue(fileInWorkingDir("build/cocoapods/framework/cocoapods.framework/cocoapods").exists())
}
test(
"syncFramework",
"-Pkotlin.native.cocoapods.platform=iphonesimulator",
"-Pkotlin.native.cocoapods.archs=x86_64",
"-Pkotlin.native.cocoapods.configuration=CUSTOM"
)
}
}
@Test
fun testSyncFrameworkInvalidArch() {
with(project) {
build(
"syncFramework",
"-Pkotlin.native.cocoapods.platform=iphoneos",
"-Pkotlin.native.cocoapods.archs=x86_64",
"-Pkotlin.native.cocoapods.configuration=Debug"
) {
assertFailed()
assertContains("Architecture x86_64 is not supported for platform iphoneos")
}
}
}
@Test
fun testSyncFrameworkMultiplePlatforms() {
with(project) {
build(
"syncFramework",
"-Pkotlin.native.cocoapods.platform=iphoneos iphonesimulator",
"-Pkotlin.native.cocoapods.archs=arm64",
"-Pkotlin.native.cocoapods.configuration=Debug"
) {
assertFailed()
assertContains("kotlin.native.cocoapods.platform has to contain a single value only.")
}
}
}
@Test
fun testSyncFrameworkMultipleArchitectures() {
with(project) {
gradleBuildScript().appendToKotlinBlock("iosArm64()")
gradleBuildScript().appendToKotlinBlock("iosArm32()")
hooks.addHook {
// Check that an output framework is a dynamic framework
val framework = fileInWorkingDir("build/cocoapods/framework/cocoapods.framework/cocoapods")
with(runProcess(listOf("file", framework.absolutePath), projectDir)) {
assertTrue(isSuccessful)
assertTrue(output.contains("\\(for architecture armv7\\):\\s+current ar archive random librar".toRegex()))
assertTrue(output.contains("\\(for architecture arm64\\):\\s+current ar archive random library".toRegex()))
}
}
test(
"syncFramework",
"-Pkotlin.native.cocoapods.platform=iphoneos",
"-Pkotlin.native.cocoapods.archs=arm64 armv7",
"-Pkotlin.native.cocoapods.configuration=Debug",
"-Pkotlin.native.cocoapods.generate.wrapper=true"
)
}
}
// paths
private fun CompiledProject.url() = externalSources().resolve("url")
@@ -1324,15 +1376,6 @@ class CocoaPodsIT : BaseGradleIT() {
spec.module_name = "#{spec.name}_umbrella"
spec.dependency 'pod_dependency', '1.0'
spec.dependency 'subspec_dependency/Core', '1.0'
spec.pod_target_xcconfig = {
'KOTLIN_TARGET[sdk=iphonesimulator*]' => 'ios_x64',
'KOTLIN_TARGET[sdk=iphoneos*]' => 'ios_arm',
'KOTLIN_TARGET[sdk=watchsimulator*]' => 'watchos_x64',
'KOTLIN_TARGET[sdk=watchos*]' => 'watchos_arm',
'KOTLIN_TARGET[sdk=appletvsimulator*]' => 'tvos_x64',
'KOTLIN_TARGET[sdk=appletvos*]' => 'tvos_arm64',
'KOTLIN_TARGET[sdk=macosx*]' => 'macos_x64'
}
spec.script_phases = [
{
:name => 'Build kotlin_library',
@@ -1342,7 +1385,8 @@ class CocoaPodsIT : BaseGradleIT() {
set -ev
REPO_ROOT="${'$'}PODS_TARGET_SRCROOT"
"${'$'}REPO_ROOT/../gradlew" -p "${'$'}REPO_ROOT" :kotlin-library:syncFramework \
-Pkotlin.native.cocoapods.target=${'$'}KOTLIN_TARGET \
-Pkotlin.native.cocoapods.platform=${'$'}PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="${'$'}ARCHS" \
-Pkotlin.native.cocoapods.configuration=${'$'}CONFIGURATION \
-Pkotlin.native.cocoapods.cflags="${'$'}OTHER_CFLAGS" \
-Pkotlin.native.cocoapods.paths.headers="${'$'}HEADER_SEARCH_PATHS" \
@@ -1365,15 +1409,6 @@ class CocoaPodsIT : BaseGradleIT() {
spec.vendored_frameworks = "build/cocoapods/framework/${frameworkName ?: "second_library"}.framework"
spec.libraries = "c++"
spec.module_name = "#{spec.name}_umbrella"
spec.pod_target_xcconfig = {
'KOTLIN_TARGET[sdk=iphonesimulator*]' => 'ios_x64',
'KOTLIN_TARGET[sdk=iphoneos*]' => 'ios_arm',
'KOTLIN_TARGET[sdk=watchsimulator*]' => 'watchos_x64',
'KOTLIN_TARGET[sdk=watchos*]' => 'watchos_arm',
'KOTLIN_TARGET[sdk=appletvsimulator*]' => 'tvos_x64',
'KOTLIN_TARGET[sdk=appletvos*]' => 'tvos_arm64',
'KOTLIN_TARGET[sdk=macosx*]' => 'macos_x64'
}
spec.script_phases = [
{
:name => 'Build second_library',
@@ -1383,7 +1418,8 @@ class CocoaPodsIT : BaseGradleIT() {
set -ev
REPO_ROOT="${'$'}PODS_TARGET_SRCROOT"
"${'$'}REPO_ROOT/../gradlew" -p "${'$'}REPO_ROOT" :second-library:syncFramework \
-Pkotlin.native.cocoapods.target=${'$'}KOTLIN_TARGET \
-Pkotlin.native.cocoapods.platform=${'$'}PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="${'$'}ARCHS" \
-Pkotlin.native.cocoapods.configuration=${'$'}CONFIGURATION \
-Pkotlin.native.cocoapods.cflags="${'$'}OTHER_CFLAGS" \
-Pkotlin.native.cocoapods.paths.headers="${'$'}HEADER_SEARCH_PATHS" \
@@ -15,6 +15,9 @@ group = "org.jetbrains.kotlin.sample.native"
version = "1.0"
kotlin {
sourceSets["commonMain"].dependencies {
implementation(kotlin("stdlib-common"))
}
iosX64("iOS")
cocoapods {
homepage = "https://github.com/JetBrains/kotlin"
@@ -0,0 +1,5 @@
package testProject.`new-mpp-cocoapods-template`.src.commonMain.kotlin
fun foo() {
println("hi!")
}
@@ -18,6 +18,9 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.addExtension
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.ARCHS_PROPERTY
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.CONFIGURATION_PROPERTY
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.PLATFORM_PROPERTY
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
@@ -88,8 +91,9 @@ private val CocoapodsDependency.toPodDownloadTaskName: String
)
internal val Project.shouldUseSyntheticProjectSettings: Boolean
get() = (findProperty(KotlinCocoapodsPlugin.TARGET_PROPERTY) == null &&
findProperty(KotlinCocoapodsPlugin.CONFIGURATION_PROPERTY) == null)
get() = (project.findProperty(PLATFORM_PROPERTY) == null &&
project.findProperty(ARCHS_PROPERTY) == null &&
project.findProperty(CONFIGURATION_PROPERTY) == null)
private val KotlinNativeTarget.toValidSDK: String
get() = when (konanTarget) {
@@ -174,7 +178,7 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
requestedBuildType: NativeBuildType,
requestedPlatforms: List<KonanTarget>
) {
val fatTargets = requestedPlatforms.associate { it to kotlinExtension.targetsForPlatform(it) }
val fatTargets = requestedPlatforms.associateWith { kotlinExtension.targetsForPlatform(it) }
check(fatTargets.values.any { it.isNotEmpty() }) {
"The project must have a target for at least one of the following platforms: " +
@@ -188,10 +192,10 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
val fatFrameworkTask = project.registerTask<FatFrameworkTask>("fatFramework") { task ->
task.group = TASK_GROUP
task.description = "Creates a fat framework for ARM32 and ARM64 architectures"
task.description = "Creates a fat framework for requested architectures"
task.destinationDir = project.cocoapodsBuildDirs.fatFramework(requestedBuildType)
fatTargets.forEach { _, targets ->
fatTargets.forEach { (_, targets) ->
targets.singleOrNull()?.let {
task.from(it.binaries.getFramework(POD_FRAMEWORK_PREFIX, requestedBuildType))
}
@@ -221,8 +225,29 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
kotlinExtension: KotlinMultiplatformExtension,
cocoapodsExtension: CocoapodsExtension
) = project.whenEvaluated {
val requestedTargetName = project.findProperty(TARGET_PROPERTY)?.toString() ?: return@whenEvaluated
val xcodeConfiguration = project.findProperty(CONFIGURATION_PROPERTY)?.toString() ?: return@whenEvaluated
val platforms = project.findProperty(PLATFORM_PROPERTY)?.toString()?.split(",", " ")?.filter { it.isNotBlank() }
val archs = project.findProperty(ARCHS_PROPERTY)?.toString()?.split(",", " ")?.filter { it.isNotBlank() }
if (platforms == null || archs == null) {
check(project.findProperty(TARGET_PROPERTY) == null) {
"""
$TARGET_PROPERTY property was dropped in favor of $PLATFORM_PROPERTY and $ARCHS_PROPERTY.
Podspec file might be outdated. Sync project with Gradle files or run the 'podspec' task manually to regenerate it.
""".trimIndent()
}
return@whenEvaluated
}
check(platforms.size == 1) {
"$PLATFORM_PROPERTY has to contain a single value only. If building for multiple platforms is required, consider using XCFrameworks"
}
val platform = platforms.first()
val nativeTargets = getNativeTargets(platform, archs)
check(nativeTargets.isNotEmpty()) { "Could not identify native targets for platform: '$platform' and architectures: '$archs'" }
val requestedBuildType = cocoapodsExtension.xcodeConfigurationToNativeBuildType[xcodeConfiguration]
@@ -233,25 +258,95 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
""".trimIndent()
}
// We create a fat framework only for device platforms which have several
// device architectures: iosArm64, iosArm32, watchosArm32 and watchosArm64.
val frameworkPlatforms: List<KonanTarget> = when (requestedTargetName) {
KOTLIN_TARGET_FOR_IOS_DEVICE -> listOf(IOS_ARM64, IOS_ARM32)
KOTLIN_TARGET_FOR_WATCHOS_DEVICE -> listOf(WATCHOS_ARM32, WATCHOS_ARM64)
// A request parameter can be comma separated list of targets.
else -> requestedTargetName.split(",").map { HostManager().targetByName(it) }.toList()
}
val frameworkTargets = frameworkPlatforms.flatMap { kotlinExtension.targetsForPlatform(it) }
val frameworkTargets = nativeTargets.flatMap { kotlinExtension.targetsForPlatform(it) }
if (frameworkTargets.size == 1) {
// Fast path: there is only one device target. There is no need to build a fat framework.
createSyncForRegularFramework(project, kotlinExtension, requestedBuildType, frameworkTargets.single().konanTarget)
} else {
// There are several device targets so we need to build a fat framework.
createSyncForFatFramework(project, kotlinExtension, requestedBuildType, frameworkPlatforms)
createSyncForFatFramework(project, kotlinExtension, requestedBuildType, nativeTargets)
}
}
private fun getNativeTargets(platform: String, archs: List<String>): List<KonanTarget> {
class UnknownArchitectureException(platform: String, arch: String) :
IllegalArgumentException("Architecture $arch is not supported for platform $platform")
val targets: MutableSet<KonanTarget> = mutableSetOf()
when (platform) {
"iphoneos" -> {
targets.addAll(archs.map { arch ->
when (arch) {
"arm64", "arm64e" -> IOS_ARM64
"armv7", "armv7s" -> IOS_ARM32
else -> throw UnknownArchitectureException(platform, arch)
}
})
}
"iphonesimulator" -> {
targets.addAll(archs.map { arch ->
when (arch) {
"arm64", "arm64e" -> IOS_SIMULATOR_ARM64
"x86_64" -> IOS_X64
else -> throw UnknownArchitectureException(platform, arch)
}
})
}
"watchos" -> {
targets.addAll(archs.map { arch ->
when (arch) {
"armv7k" -> WATCHOS_ARM32
"arm64_32" -> WATCHOS_ARM64
else -> throw UnknownArchitectureException(platform, arch)
}
})
}
"watchsimulator" -> {
targets.addAll(archs.map { arch ->
when (arch) {
"arm64", "arm64e" -> WATCHOS_SIMULATOR_ARM64
"i386" -> WATCHOS_X86
"x86_64" -> WATCHOS_X64
else -> throw UnknownArchitectureException(platform, arch)
}
})
}
"appletvos" -> {
targets.addAll(archs.map { arch ->
when (arch) {
"arm64", "arm64e" -> TVOS_ARM64
else -> throw UnknownArchitectureException(platform, arch)
}
})
}
"appletvsimulator" -> {
targets.addAll(archs.map { arch ->
when (arch) {
"arm64", "arm64e" -> TVOS_SIMULATOR_ARM64
"x86_64" -> TVOS_X64
else -> throw UnknownArchitectureException(platform, arch)
}
})
}
"macosx" -> {
targets.addAll(archs.map { arch ->
when (arch) {
"arm64" -> MACOS_ARM64
"x86_64" -> MACOS_X64
else -> throw UnknownArchitectureException(platform, arch)
}
})
}
else -> throw IllegalArgumentException("Platform $platform is not supported")
}
return targets.toList()
}
private fun createInterops(
project: Project,
kotlinExtension: KotlinMultiplatformExtension,
@@ -590,7 +685,9 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
// We don't move these properties in PropertiesProvider because
// they are not intended to be overridden in local.properties.
const val PLATFORM_PROPERTY = "kotlin.native.cocoapods.platform"
const val TARGET_PROPERTY = "kotlin.native.cocoapods.target"
const val ARCHS_PROPERTY = "kotlin.native.cocoapods.archs"
const val CONFIGURATION_PROPERTY = "kotlin.native.cocoapods.configuration"
const val CFLAGS_PROPERTY = "kotlin.native.cocoapods.cflags"
@@ -599,11 +696,6 @@ open class KotlinCocoapodsPlugin : Plugin<Project> {
const val GENERATE_WRAPPER_PROPERTY = "kotlin.native.cocoapods.generate.wrapper"
// Used in Xcode script phase to indicate that the framework is being built for a device
// so we should generate a fat framework with arm32 and arm64 binaries.
const val KOTLIN_TARGET_FOR_IOS_DEVICE = "ios_arm"
const val KOTLIN_TARGET_FOR_WATCHOS_DEVICE = "watchos_arm"
val isAvailableToProduceSynthetic: Boolean by lazy {
if (!HostManager.hostIsMac) {
return@lazy false
@@ -19,8 +19,6 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.COCOAPODS_EXTENSION_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.GENERATE_WRAPPER_PROPERTY
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.KOTLIN_TARGET_FOR_IOS_DEVICE
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.KOTLIN_TARGET_FOR_WATCHOS_DEVICE
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.SYNC_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
@@ -132,16 +130,6 @@ open class PodspecTask : DefaultTask() {
|
$dependencies
|
| spec.pod_target_xcconfig = {
| 'KOTLIN_TARGET[sdk=iphonesimulator*]' => 'ios_x64',
| 'KOTLIN_TARGET[sdk=iphoneos*]' => '$KOTLIN_TARGET_FOR_IOS_DEVICE',
| 'KOTLIN_TARGET[sdk=watchsimulator*]' => 'watchos_x64',
| 'KOTLIN_TARGET[sdk=watchos*]' => '$KOTLIN_TARGET_FOR_WATCHOS_DEVICE',
| 'KOTLIN_TARGET[sdk=appletvsimulator*]' => 'tvos_x64',
| 'KOTLIN_TARGET[sdk=appletvos*]' => 'tvos_arm64',
| 'KOTLIN_TARGET[sdk=macosx*]' => 'macos_x64'
| }
|
| spec.script_phases = [
| {
| :name => 'Build $specName',
@@ -151,7 +139,8 @@ open class PodspecTask : DefaultTask() {
| set -ev
| REPO_ROOT="${'$'}PODS_TARGET_SRCROOT"
| "$gradleCommand" -p "${'$'}REPO_ROOT" $syncTask \
| -P${KotlinCocoapodsPlugin.TARGET_PROPERTY}=${'$'}KOTLIN_TARGET \
| -P${KotlinCocoapodsPlugin.PLATFORM_PROPERTY}=${'$'}PLATFORM_NAME \
| -P${KotlinCocoapodsPlugin.ARCHS_PROPERTY}="${'$'}ARCHS" \
| -P${KotlinCocoapodsPlugin.CONFIGURATION_PROPERTY}=${'$'}CONFIGURATION \
| -P${KotlinCocoapodsPlugin.CFLAGS_PROPERTY}="${'$'}OTHER_CFLAGS" \
| -P${KotlinCocoapodsPlugin.HEADER_PATHS_PROPERTY}="${'$'}HEADER_SEARCH_PATHS" \