diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FatFrameworkIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FatFrameworkIT.kt
index d5594146125..5c4831f8ee4 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FatFrameworkIT.kt
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FatFrameworkIT.kt
@@ -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(
- """
- CFBundleSupportedPlatforms
-
- iPhoneOS
-
- """.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,
+ 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(
+ """
+ CFBundleSupportedPlatforms
+
+ $expectedPlistPlatform
+
+ """.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")
}
}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/FatFrameworkTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/FatFrameworkTask.kt
index c7ae776569b..2401c49d86f 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/FatFrameworkTask.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/FatFrameworkTask.kt
@@ -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) {
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, 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, 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.