diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/FatFrameworkIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/FatFrameworkIT.kt index 7693d52c788..ec1da6da0b4 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/FatFrameworkIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/FatFrameworkIT.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.native import org.jetbrains.kotlin.gradle.BaseGradleIT import org.jetbrains.kotlin.gradle.GradleVersionRequired import org.jetbrains.kotlin.gradle.transformProjectWithPluginsDsl +import org.jetbrains.kotlin.gradle.util.addBeforeSubstring import org.jetbrains.kotlin.gradle.util.checkedReplace import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.gradle.util.runProcess @@ -146,6 +147,54 @@ class FatFrameworkIT : BaseGradleIT() { } } + @Test + fun testCustomName() { + with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) { + gradleBuildScript().modify { + it.addBeforeSubstring("baseName = \"custom\"\n","from(frameworksToMerge)") + } + + build("fat") { + val binary = fileInWorkingDir("build/fat-framework/custom.framework/custom") + with(runProcess(listOf("otool", "-D", binary.absolutePath), project.projectDir)) { + assertSuccessful() + assertTrue { output.lines().any { it.contains("@rpath/custom.framework/custom") } } + } + } + } + } + + @Test + fun testDifferentTypes() { + with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) { + gradleBuildScript().modify { + it.checkedReplace("iosArm32()", "iosArm32{binaries.framework {isStatic = true}}") + .checkedReplace("iosArm64()", "iosArm64{binaries.framework {isStatic = false}}") + .checkedReplace("iosX64()", "iosX64{binaries.framework {isStatic = false}}") + .addBeforeSubstring("//", "binaries.framework(listOf(DEBUG))") + } + build("fat") { + assertFailed() + assertContains("All input frameworks must be either static or dynamic") + } + } + } + + @Test + fun testAllStatic() { + with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) { + gradleBuildScript().modify { + it.checkedReplace("iosArm32()", "iosArm32{binaries.framework {isStatic = true}}") + .checkedReplace("iosArm64()", "iosArm64{binaries.framework {isStatic = true}}") + .checkedReplace("iosX64()", "iosX64{binaries.framework {isStatic = true}}") + .addBeforeSubstring("//", "binaries.framework(listOf(DEBUG))") + } + build("fat") { + assertSuccessful() + } + } + } + companion object { @BeforeClass @JvmStatic 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 4accb564ab7..2f8c228be45 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 @@ -134,9 +134,9 @@ open class FatFrameworkTask : DefaultTask() { * Adds the specified frameworks in this fat framework. */ fun from(frameworks: Iterable) { - frameworks.forEach { - val arch = it.konanTarget.architecture - val family = it.konanTarget.family + frameworks.forEach { framework -> + val arch = framework.konanTarget.architecture + val family = framework.konanTarget.family val fatFrameworkFamily = getFatFrameworkFamily() require(fatFrameworkFamily == null || family == fatFrameworkFamily) { "Cannot add a binary with platform family '${family.visibleName}' to the fat framework:\n" + @@ -149,8 +149,20 @@ open class FatFrameworkTask : DefaultTask() { "This fat framework already has a binary for architecture `${arch.name.toLowerCase()}` " + "(${alreadyAdded.name} for target `${alreadyAdded.target.name}`)" } - archToFramework[arch] = it - dependsOn(it.linkTask) + + require(archToFramework.all { it.value.isStatic == framework.isStatic }) { + fun Framework.staticOrDynamic(): String = if (isStatic) "static" else "dynamic" + + buildString { + append("Cannot create a fat framework from:\n") + archToFramework.forEach { append("${it.value.baseName} - ${it.key.name.toLowerCase()} - ${it.value.staticOrDynamic()}\n") } + append("${framework.baseName} - ${arch.name.toLowerCase()} - ${framework.staticOrDynamic()}\n") + append("All input frameworks must be either static or dynamic") + } + } + + archToFramework[arch] = framework + dependsOn(framework.linkTask) } } @@ -218,9 +230,26 @@ open class FatFrameworkTask : DefaultTask() { ) } - private fun mergeBinaries(outputFile: File) = + private fun runInstallNameTool(file: File, frameworkName: String) { + project.exec { exec -> + exec.executable = "install_name_tool" + exec.args = listOf( + "-id", + "@rpath/${frameworkName}.framework/${frameworkName}", + file.absolutePath + ) + } + } + + private fun mergeBinaries(outputFile: File) { + runLipo(archToFramework.values.map { it.files.binary }, outputFile) + if (archToFramework.values.any{ it.isStatic.not() && it.baseName != fatFrameworkName }) { + runInstallNameTool(outputFile, fatFrameworkName) + } + } + private fun mergeHeaders(outputFile: File) = outputFile.writer().use { writer -> val headerContents = archToFramework.mapValues { (_, framework) ->