Fat frameworks: Support for watchOS and tvOS
Issue #KT-34259 fixed
This commit is contained in:
+72
-34
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.gradle
|
||||
|
||||
import org.jetbrains.kotlin.gradle.util.checkedReplace
|
||||
import org.jetbrains.kotlin.gradle.util.modify
|
||||
import org.jetbrains.kotlin.gradle.util.runProcess
|
||||
import org.junit.Test
|
||||
@@ -13,39 +14,12 @@ import kotlin.test.assertTrue
|
||||
class FatFrameworkIT : BaseGradleIT() {
|
||||
|
||||
@Test
|
||||
fun smoke() = with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "new-mpp-fat-framework")) {
|
||||
fun smokeIos() = with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "new-mpp-fat-framework")) {
|
||||
build("fat") {
|
||||
assertSuccessful()
|
||||
val archs = listOf("x64", "arm64", "arm32")
|
||||
val linkTasks = archs.map { ":linkDebugFrameworkIos${it.capitalize()}" }
|
||||
|
||||
assertTasksExecuted(linkTasks)
|
||||
assertTasksExecuted(":fat")
|
||||
|
||||
assertFileExists("build/fat-framework/smoke.framework/smoke")
|
||||
assertFileExists("build/fat-framework/smoke.framework/Headers/smoke.h")
|
||||
assertFileExists("build/fat-framework/smoke.framework.dSYM/Contents/Resources/DWARF/smoke")
|
||||
|
||||
val headerContent = fileInWorkingDir("build/fat-framework/smoke.framework/Headers/smoke.h").readText()
|
||||
assertTrue(
|
||||
headerContent.contains("+ (int32_t)foo __attribute__((swift_name(\"foo()\")));"),
|
||||
"Unexpected header content:\n$headerContent"
|
||||
)
|
||||
|
||||
val plistContent = fileInWorkingDir("build/fat-framework/smoke.framework/Info.plist")
|
||||
.readLines()
|
||||
.joinToString(separator = "\n") { it.trim() }
|
||||
|
||||
assertTrue(
|
||||
plistContent.contains(
|
||||
"""
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
""".trimIndent()
|
||||
),
|
||||
"Unexpected Info.plist content:\n$plistContent"
|
||||
checkSmokeBuild(
|
||||
archs = listOf("x64", "arm64", "arm32"),
|
||||
targetPrefix = "ios",
|
||||
expectedPlistPlatform = "iPhoneOS"
|
||||
)
|
||||
|
||||
val binary = fileInWorkingDir("build/fat-framework/smoke.framework/smoke")
|
||||
@@ -58,6 +32,70 @@ class FatFrameworkIT : BaseGradleIT() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun smokeWatchos() = with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "new-mpp-fat-framework")) {
|
||||
gradleBuildScript().modify {
|
||||
it.checkedReplace("iosArm32()", "watchosArm32()")
|
||||
.checkedReplace("iosArm64()", "watchosArm64()")
|
||||
.checkedReplace("iosX64()", "watchosX86()")
|
||||
}
|
||||
|
||||
|
||||
build("fat") {
|
||||
checkSmokeBuild(
|
||||
archs = listOf("x86", "arm64", "arm32"),
|
||||
targetPrefix = "watchos",
|
||||
expectedPlistPlatform = "WatchOS"
|
||||
)
|
||||
|
||||
val binary = fileInWorkingDir("build/fat-framework/smoke.framework/smoke")
|
||||
with(runProcess(listOf("file", binary.absolutePath), projectDir)) {
|
||||
assertTrue(isSuccessful)
|
||||
assertTrue(output.contains("\\(for architecture i386\\):\\s+Mach-O dynamically linked shared library i386".toRegex()))
|
||||
assertTrue(output.contains("\\(for architecture armv7k\\):\\s+Mach-O dynamically linked shared library arm_v7k".toRegex()))
|
||||
assertTrue(output.contains("\\(for architecture arm64_32\\):\\s+Mach-O dynamically linked shared library arm64_32_v8".toRegex()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CompiledProject.checkSmokeBuild(
|
||||
archs: List<String>,
|
||||
targetPrefix: String,
|
||||
expectedPlistPlatform: String
|
||||
) {
|
||||
assertSuccessful()
|
||||
val linkTasks = archs.map { ":linkDebugFramework${targetPrefix.capitalize()}${it.capitalize()}" }
|
||||
|
||||
assertTasksExecuted(linkTasks)
|
||||
assertTasksExecuted(":fat")
|
||||
|
||||
assertFileExists("build/fat-framework/smoke.framework/smoke")
|
||||
assertFileExists("build/fat-framework/smoke.framework/Headers/smoke.h")
|
||||
assertFileExists("build/fat-framework/smoke.framework.dSYM/Contents/Resources/DWARF/smoke")
|
||||
|
||||
val headerContent = fileInWorkingDir("build/fat-framework/smoke.framework/Headers/smoke.h").readText()
|
||||
assertTrue(
|
||||
headerContent.contains("+ (int32_t)foo __attribute__((swift_name(\"foo()\")));"),
|
||||
"Unexpected header content:\n$headerContent"
|
||||
)
|
||||
|
||||
val plistContent = fileInWorkingDir("build/fat-framework/smoke.framework/Info.plist")
|
||||
.readLines()
|
||||
.joinToString(separator = "\n") { it.trim() }
|
||||
|
||||
assertTrue(
|
||||
plistContent.contains(
|
||||
"""
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>$expectedPlistPlatform</string>
|
||||
</array>
|
||||
""".trimIndent()
|
||||
),
|
||||
"Unexpected Info.plist content:\n$plistContent"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDuplicatedArchitecture()= with(
|
||||
transformProjectWithPluginsDsl("smoke", directoryPrefix = "new-mpp-fat-framework")
|
||||
@@ -77,7 +115,7 @@ class FatFrameworkIT : BaseGradleIT() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIncorrectTarget() = with(
|
||||
fun testIncorrectFamily() = with(
|
||||
transformProjectWithPluginsDsl("smoke", directoryPrefix = "new-mpp-fat-framework")
|
||||
) {
|
||||
gradleBuildScript().modify {
|
||||
@@ -90,7 +128,7 @@ class FatFrameworkIT : BaseGradleIT() {
|
||||
}
|
||||
build("fat") {
|
||||
assertFailed()
|
||||
assertContains("Cannot add a framework with target 'macos_x64' to the fat framework")
|
||||
assertContains("Cannot add a binary with platform family 'osx' to the fat framework")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
-25
@@ -13,8 +13,8 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
|
||||
import org.jetbrains.kotlin.konan.target.Architecture
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget.*
|
||||
import org.jetbrains.kotlin.konan.util.visibleName
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
|
||||
@@ -131,12 +131,15 @@ open class FatFrameworkTask: DefaultTask() {
|
||||
*/
|
||||
fun from(frameworks: Iterable<Framework>) {
|
||||
frameworks.forEach {
|
||||
val konanTarget = it.konanTarget
|
||||
val arch = konanTarget.architecture
|
||||
require(konanTarget.family == Family.IOS) {
|
||||
"Cannot add a framework with target '${konanTarget.visibleName}' to the fat framework: " +
|
||||
"fat frameworks are available only for iOS binaries."
|
||||
val arch = it.konanTarget.architecture
|
||||
val family = it.konanTarget.family
|
||||
val fatFrameworkFamily = getFatFrameworkFamily()
|
||||
require(fatFrameworkFamily == null || family == fatFrameworkFamily) {
|
||||
"Cannot add a binary with platform family '${family.visibleName}' to the fat framework:\n" +
|
||||
"A fat framework must include binaries with the same platform family " +
|
||||
"while this framework already includes binaries with family '${fatFrameworkFamily!!.visibleName}'"
|
||||
}
|
||||
|
||||
require(!archToFramework.containsKey(arch)) {
|
||||
val alreadyAdded = archToFramework.getValue(arch)
|
||||
"This fat framework already has a binary for architecture `${arch.name.toLowerCase()}` " +
|
||||
@@ -144,21 +147,19 @@ open class FatFrameworkTask: DefaultTask() {
|
||||
}
|
||||
archToFramework[arch] = it
|
||||
dependsOn(it.linkTask)
|
||||
// Framework generating task may stop with NO-SOURCE result. We should track it.
|
||||
|
||||
}
|
||||
}
|
||||
// endregion.
|
||||
|
||||
private val Architecture.lipoArg: String
|
||||
get() = when(this) {
|
||||
Architecture.X64 -> "x86_64"
|
||||
Architecture.ARM32 -> "armv7"
|
||||
Architecture.ARM64 -> "arm64"
|
||||
else -> error("Fat frameworks are not supported for architecture `$name`")
|
||||
}
|
||||
private fun getFatFrameworkFamily(): Family? {
|
||||
assert(archToFramework.values.distinctBy { it.konanTarget.family }.size <= 1)
|
||||
return archToFramework.values.firstOrNull()?.konanTarget?.family
|
||||
}
|
||||
|
||||
private val Architecture.clangMacro: String
|
||||
get() = when(this) {
|
||||
Architecture.X86 -> "__i386__"
|
||||
Architecture.X64 -> "__x86_64__"
|
||||
Architecture.ARM32 -> "__arm__"
|
||||
Architecture.ARM64 -> "__aarch64__"
|
||||
@@ -203,19 +204,18 @@ open class FatFrameworkTask: DefaultTask() {
|
||||
fun delete(entry: String) = commands.add("Delete \"$entry\"")
|
||||
}
|
||||
|
||||
private fun runLipo(inputFiles: Map<Architecture, File>, outputFile: File) =
|
||||
project.exec {
|
||||
it.executable = "/usr/bin/lipo"
|
||||
it.args = mutableListOf("-create").apply {
|
||||
inputFiles.forEach { (arch, binary) ->
|
||||
addAll(listOf("-arch", arch.lipoArg, binary.absolutePath))
|
||||
}
|
||||
addArg("-output", outputFile.absolutePath)
|
||||
}
|
||||
private fun runLipo(inputFiles: Collection<File>, outputFile: File) =
|
||||
project.exec { exec ->
|
||||
exec.executable = "/usr/bin/lipo"
|
||||
exec.args = listOf(
|
||||
"-create",
|
||||
*inputFiles.map { it.absolutePath }.toTypedArray(),
|
||||
"-output", outputFile.absolutePath
|
||||
)
|
||||
}
|
||||
|
||||
private fun mergeBinaries(outputFile: File) =
|
||||
runLipo(archToFramework.mapValues { (_, framework) -> framework.files.binary }, outputFile)
|
||||
runLipo(archToFramework.values.map { it.files.binary }, outputFile)
|
||||
|
||||
private fun mergeHeaders(outputFile: File) = outputFile.writer().use { writer ->
|
||||
|
||||
@@ -311,7 +311,7 @@ open class FatFrameworkTask: DefaultTask() {
|
||||
fatDsym.mkdirs()
|
||||
|
||||
// Merge dSYM binary.
|
||||
runLipo(dsymInputs.mapValues { (_, dsym) -> dsym.binary }, fatDsym.binary)
|
||||
runLipo(dsymInputs.values.map { it.binary }, fatDsym.binary)
|
||||
|
||||
// Copy dSYM's Info.plist.
|
||||
// It doesn't contain target-specific info or framework names except the bundle id so there is no need to edit it.
|
||||
|
||||
Reference in New Issue
Block a user