[Gradle] Don't create fat frameworks from single binary framework
KT-55751 Verification Pending
This commit is contained in:
committed by
Space Team
parent
371e1205c0
commit
427ef8fc47
+8
-104
@@ -10,7 +10,6 @@ import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.NamedDomainObjectCollection
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.api.attributes.Attribute
|
||||
import org.gradle.api.attributes.AttributeContainer
|
||||
@@ -24,6 +23,7 @@ import org.gradle.api.tasks.Exec
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinNativeCompilerOptions
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationInfo.KPM
|
||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.KOTLIN_NATIVE_IGNORE_INCORRECT_DEPENDENCIES
|
||||
@@ -47,12 +47,9 @@ import org.jetbrains.kotlin.gradle.testing.internal.kotlinTestRegistry
|
||||
import org.jetbrains.kotlin.gradle.testing.testTaskName
|
||||
import org.jetbrains.kotlin.gradle.utils.Xcode
|
||||
import org.jetbrains.kotlin.gradle.utils.klibModuleName
|
||||
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
|
||||
import org.jetbrains.kotlin.gradle.utils.newInstance
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
|
||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
|
||||
import java.io.File
|
||||
|
||||
open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotlinTargetConfigurator<T>(
|
||||
@@ -66,7 +63,7 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotl
|
||||
// this afterEvaluate comes from NativeCompilerOptions
|
||||
val compilationCompilerOptions = binary.compilation.compilerOptions
|
||||
val konanPropertiesBuildService = KonanPropertiesBuildService.registerIfAbsent(project)
|
||||
val result = registerTask<KotlinNativeLink>(
|
||||
val linkTask = registerTask<KotlinNativeLink>(
|
||||
binary.linkTaskName, listOf(binary)
|
||||
) {
|
||||
val target = binary.target
|
||||
@@ -81,12 +78,12 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotl
|
||||
|
||||
|
||||
if (binary !is TestExecutable) {
|
||||
tasks.named(binary.compilation.target.artifactsTaskName).dependsOn(result)
|
||||
locateOrRegisterTask<Task>(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).dependsOn(result)
|
||||
tasks.named(binary.compilation.target.artifactsTaskName).dependsOn(linkTask)
|
||||
locateOrRegisterTask<Task>(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).dependsOn(linkTask)
|
||||
}
|
||||
|
||||
if (binary is Framework) {
|
||||
createFrameworkArtifact(binary, result)
|
||||
createFrameworkArtifact(binary, linkTask)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,102 +99,6 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotl
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.createFrameworkArtifact(
|
||||
binary: Framework,
|
||||
linkTask: TaskProvider<KotlinNativeLink>
|
||||
) {
|
||||
fun <T : Task> Configuration.configureConfiguration(taskProvider: TaskProvider<T>) {
|
||||
project.afterEvaluate {
|
||||
val task = taskProvider.get()
|
||||
val artifactFile = when (task) {
|
||||
is FatFrameworkTask -> task.fatFramework
|
||||
else -> binary.outputFile
|
||||
}
|
||||
val linkArtifact = project.artifacts.add(name, artifactFile) { artifact ->
|
||||
artifact.name = name
|
||||
artifact.extension = "framework"
|
||||
artifact.type = "binary"
|
||||
artifact.classifier = "framework"
|
||||
artifact.builtBy(task)
|
||||
}
|
||||
project.extensions.getByType(org.gradle.api.internal.plugins.DefaultArtifactPublicationSet::class.java)
|
||||
.addCandidate(linkArtifact)
|
||||
artifacts.add(linkArtifact)
|
||||
attributes.attribute(KotlinPlatformType.attribute, binary.target.platformType)
|
||||
attributes.attribute(
|
||||
project.artifactTypeAttribute,
|
||||
NativeArtifactFormat.FRAMEWORK
|
||||
)
|
||||
attributes.attribute(
|
||||
KotlinNativeTarget.kotlinNativeBuildTypeAttribute,
|
||||
binary.buildType.name
|
||||
)
|
||||
if (attributes.getAttribute(Framework.frameworkTargets) == null) {
|
||||
attributes.attribute(
|
||||
Framework.frameworkTargets,
|
||||
setOf(binary.target.konanTarget.name)
|
||||
)
|
||||
}
|
||||
// capture type parameter T
|
||||
fun <T> copyAttribute(key: Attribute<T>, from: AttributeContainer, to: AttributeContainer) {
|
||||
to.attribute(key, from.getAttribute(key)!!)
|
||||
}
|
||||
binary.attributes.keySet().filter { it != KotlinNativeTarget.konanTargetAttribute }.forEach {
|
||||
copyAttribute(it, binary.attributes, this.attributes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun configureFatFramework() {
|
||||
val fatFrameworkConfigurationName = lowerCamelCaseName(
|
||||
binary.name,
|
||||
binary.target.konanTarget.family.name.toLowerCaseAsciiOnly(),
|
||||
"fat"
|
||||
)
|
||||
val fatFrameworkTaskName = "link${fatFrameworkConfigurationName.capitalizeAsciiOnly()}"
|
||||
|
||||
val fatFrameworkTask = if (fatFrameworkTaskName in tasks.names) {
|
||||
tasks.named(fatFrameworkTaskName, FatFrameworkTask::class.java)
|
||||
} else {
|
||||
tasks.register(fatFrameworkTaskName, FatFrameworkTask::class.java) {
|
||||
it.baseName = binary.baseName
|
||||
it.destinationDir = it.destinationDir.resolve(binary.buildType.name.toLowerCaseAsciiOnly())
|
||||
}
|
||||
}
|
||||
|
||||
fatFrameworkTask.configure {
|
||||
try {
|
||||
it.from(binary)
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Cannot add binary ${binary.name} dependency to default fat framework", e)
|
||||
}
|
||||
}
|
||||
|
||||
// maybeCreate is not used as it does not provide way to configure once
|
||||
val fatConfiguration =
|
||||
configurations.findByName(fatFrameworkConfigurationName) ?: configurations.create(fatFrameworkConfigurationName) {
|
||||
it.isCanBeConsumed = true
|
||||
it.isCanBeResolved = false
|
||||
it.configureConfiguration(fatFrameworkTask)
|
||||
}
|
||||
|
||||
fatConfiguration.attributes.attribute(
|
||||
Framework.frameworkTargets,
|
||||
(fatConfiguration.attributes.getAttribute(Framework.frameworkTargets) ?: setOf<String>()) + binary.target.konanTarget.name
|
||||
)
|
||||
}
|
||||
|
||||
configurations.create(lowerCamelCaseName(binary.name, binary.target.name)) {
|
||||
it.isCanBeConsumed = true
|
||||
it.isCanBeResolved = false
|
||||
it.configureConfiguration(linkTask)
|
||||
}
|
||||
|
||||
if (FatFrameworkTask.isSupportedTarget(binary.target)) {
|
||||
configureFatFramework()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.createRunTask(binary: Executable) {
|
||||
val taskName = binary.runTaskName ?: return
|
||||
registerTask<Exec>(taskName) { exec ->
|
||||
@@ -337,6 +238,9 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget> : AbstractKotl
|
||||
target.binaries.all {
|
||||
project.createLinkTask(it)
|
||||
}
|
||||
project.runOnceAfterEvaluated("Create fat frameworks") {
|
||||
project.multiplatformExtensionOrNull?.createFatFrameworks()
|
||||
}
|
||||
project.runOnceAfterEvaluated("Sync language settings for NativeLinkTask") {
|
||||
target.binaries.all { binary ->
|
||||
project.syncLanguageSettingsToLinkTask(binary)
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.targets.native
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinNativeTargetConfigurator
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
|
||||
import org.jetbrains.kotlin.gradle.plugin.internal.artifactTypeAttribute
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
|
||||
import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
|
||||
import org.jetbrains.kotlin.gradle.utils.getOrCreate
|
||||
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
|
||||
import org.jetbrains.kotlin.gradle.utils.markConsumable
|
||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
|
||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Contains common data between frameworks that can be bundled to a fat framework.
|
||||
*/
|
||||
private data class FrameworkGroupDescription(
|
||||
val frameworkName: String,
|
||||
val targetFamilyName: String,
|
||||
val baseName: String,
|
||||
val buildType: NativeBuildType
|
||||
)
|
||||
|
||||
private val Framework.frameworkGroupDescription
|
||||
get() = FrameworkGroupDescription(
|
||||
frameworkName = name,
|
||||
targetFamilyName = target.konanTarget.family.name.toLowerCaseAsciiOnly(),
|
||||
baseName = baseName,
|
||||
buildType = buildType
|
||||
)
|
||||
|
||||
internal fun Project.createFrameworkArtifact(binaryFramework: Framework, linkTask: TaskProvider<KotlinNativeLink>) {
|
||||
val frameworkConfiguration = configurations.getOrCreate(binaryFramework.binaryFrameworkConfigurationName, invokeWhenCreated = {
|
||||
it.markConsumable()
|
||||
it.applyBinaryFrameworkAttributes(project, binaryFramework.frameworkGroupDescription, listOf(binaryFramework.target))
|
||||
})
|
||||
|
||||
addFrameworkArtifact(frameworkConfiguration, linkTask.flatMap { it.outputFile })
|
||||
}
|
||||
|
||||
internal fun KotlinMultiplatformExtension.createFatFrameworks() {
|
||||
targets
|
||||
.filterIsInstance<KotlinNativeTarget>()
|
||||
.filter { FatFrameworkTask.isSupportedTarget(it) }
|
||||
.flatMap { it.binaries }
|
||||
.filterIsInstance<Framework>()
|
||||
.groupBy { it.frameworkGroupDescription }
|
||||
.filter { (_, frameworks) -> frameworks.size > 1 }
|
||||
.forEach { (groupDescription, frameworks) -> project.createFatFramework(groupDescription, frameworks) }
|
||||
}
|
||||
|
||||
private val Framework.binaryFrameworkConfigurationName get() = lowerCamelCaseName(name, target.name)
|
||||
private val FrameworkGroupDescription.fatFrameworkConfigurationName get() = lowerCamelCaseName(frameworkName, targetFamilyName, "fat")
|
||||
|
||||
private fun Configuration.applyBinaryFrameworkAttributes(
|
||||
project: Project,
|
||||
frameworkDescription: FrameworkGroupDescription,
|
||||
targets: List<KotlinNativeTarget>
|
||||
) {
|
||||
with(attributes) {
|
||||
attribute(KotlinPlatformType.attribute, KotlinPlatformType.native)
|
||||
attribute(project.artifactTypeAttribute, KotlinNativeTargetConfigurator.NativeArtifactFormat.FRAMEWORK)
|
||||
attribute(KotlinNativeTarget.kotlinNativeBuildTypeAttribute, frameworkDescription.buildType.name)
|
||||
attribute(Framework.frameworkTargets, targets.map { it.konanTarget.name }.toSet())
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.addFrameworkArtifact(configuration: Configuration, artifactFile: Provider<File>) {
|
||||
val frameworkArtifact = artifacts.add(configuration.name, artifactFile) { artifact ->
|
||||
artifact.name = name
|
||||
artifact.extension = "framework"
|
||||
artifact.type = "binary"
|
||||
artifact.classifier = "framework"
|
||||
}
|
||||
project.extensions.getByType(org.gradle.api.internal.plugins.DefaultArtifactPublicationSet::class.java)
|
||||
.addCandidate(frameworkArtifact)
|
||||
}
|
||||
|
||||
private fun Project.createFatFramework(groupDescription: FrameworkGroupDescription, frameworks: List<Framework>) {
|
||||
require(frameworks.size > 1) { "Can't create binary fat framework from a single framework" }
|
||||
val fatFrameworkConfigurationName = groupDescription.fatFrameworkConfigurationName
|
||||
val fatFrameworkTaskName = "link${fatFrameworkConfigurationName.capitalizeAsciiOnly()}"
|
||||
|
||||
val fatFrameworkTask = if (fatFrameworkTaskName in tasks.names) {
|
||||
tasks.named(fatFrameworkTaskName, FatFrameworkTask::class.java)
|
||||
} else {
|
||||
tasks.register(fatFrameworkTaskName, FatFrameworkTask::class.java) {
|
||||
it.baseName = groupDescription.baseName
|
||||
it.destinationDir = it.destinationDir.resolve(groupDescription.buildType.name.toLowerCaseAsciiOnly())
|
||||
}
|
||||
}
|
||||
|
||||
fatFrameworkTask.configure {
|
||||
try {
|
||||
it.from(frameworks)
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Cannot make fat framework from frameworks: ${frameworks.map { it.name }}", e)
|
||||
}
|
||||
}
|
||||
|
||||
val fatFrameworkConfiguration = project.configurations.getOrCreate(fatFrameworkConfigurationName, invokeWhenCreated = {
|
||||
it.markConsumable()
|
||||
it.applyBinaryFrameworkAttributes(project, groupDescription, targets = frameworks.map(Framework::target))
|
||||
})
|
||||
|
||||
addFrameworkArtifact(fatFrameworkConfiguration, fatFrameworkTask.map { it.fatFramework })
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.unitTests
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
|
||||
import org.jetbrains.kotlin.gradle.util.kotlin
|
||||
import kotlin.test.*
|
||||
|
||||
class FatFrameworksTest {
|
||||
@Test
|
||||
fun `two apple frameworks get bundled to a fat framework`() {
|
||||
val project = buildProjectWithMPP {
|
||||
kotlin {
|
||||
iosX64 { binaries.framework("foo", listOf(DEBUG)) }
|
||||
iosArm64 { binaries.framework("foo", listOf(DEBUG)) }
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
project.configurations.names.also(::println)
|
||||
project.assertConfigurationExists("fooDebugFrameworkIosX64")
|
||||
project.assertConfigurationExists("fooDebugFrameworkIosArm64")
|
||||
project.assertConfigurationExists("fooDebugFrameworkIosFat")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single binary framework doesn't produce a fat framework`() {
|
||||
val project = buildProjectWithMPP {
|
||||
kotlin {
|
||||
iosX64 { binaries.framework("foo", listOf(DEBUG)) }
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
project.assertConfigurationExists("fooDebugFrameworkIosX64")
|
||||
project.assertConfigurationDoesntExist("fooDebugFrameworkIosFat")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fat framework grouping -- different families`() = testFatFrameworkGrouping(
|
||||
"fooDebugFrameworkIosFat",
|
||||
"fooDebugFrameworkOsxFat",
|
||||
) {
|
||||
iosX64 { binaries.framework("foo", listOf(DEBUG)) }
|
||||
iosArm64 { binaries.framework("foo", listOf(DEBUG)) }
|
||||
macosX64 { binaries.framework("foo", listOf(DEBUG)) }
|
||||
macosArm64 { binaries.framework("foo", listOf(DEBUG)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fat framework grouping -- different families and different names within one family`() = testFatFrameworkGrouping(
|
||||
"fooDebugFrameworkOsxFat",
|
||||
) {
|
||||
iosX64 { binaries.framework("foo", listOf(DEBUG)) }
|
||||
iosArm64 { binaries.framework("bar", listOf(DEBUG)) }
|
||||
macosX64 { binaries.framework("foo", listOf(DEBUG)) }
|
||||
macosArm64 { binaries.framework("foo", listOf(DEBUG)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fat framework grouping -- build types intersection`() = testFatFrameworkGrouping(
|
||||
"fooReleaseFrameworkIosFat",
|
||||
) {
|
||||
iosX64 { binaries.framework("foo", listOf(RELEASE)) }
|
||||
iosArm64 { binaries.framework("foo", listOf(DEBUG, RELEASE)) }
|
||||
}
|
||||
|
||||
private fun testFatFrameworkGrouping(
|
||||
vararg allExpectedFatFrameworks: String,
|
||||
configureTargets: KotlinMultiplatformExtension.() -> Unit,
|
||||
) {
|
||||
val project = buildProjectWithMPP {
|
||||
kotlin {
|
||||
configureTargets()
|
||||
}
|
||||
}
|
||||
project.evaluate()
|
||||
val allFatFrameworks = project.configurations.names.filter { it.endsWith("Fat") }.toSet()
|
||||
assertEquals(allExpectedFatFrameworks.toSet(), allFatFrameworks)
|
||||
}
|
||||
|
||||
private fun Project.assertConfigurationDoesntExist(name: String) {
|
||||
val configuration = project.configurations.findByName(name)
|
||||
if (configuration != null) fail("'$name' configuration was not expected")
|
||||
}
|
||||
|
||||
private fun Project.assertConfigurationExists(name: String) {
|
||||
project.configurations.findByName(name) ?: fail("'$name' configuration was expected to be created")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user