Add new DSL for configuring K/N xcframework artifact.

This commit is contained in:
konstantin.tskhovrebov
2021-11-26 16:07:39 +01:00
committed by TeamCityServer
parent 43fe1e52c9
commit 2e86fbd2b5
3 changed files with 125 additions and 46 deletions
@@ -43,9 +43,9 @@ class XCFrameworkIT : BaseGradleIT() {
assertTasksExecuted(":shared:assembleSdkDebugWatchosFatFrameworkForXCFramework")
assertTasksExecuted(":shared:assembleSdkDebugXCFramework")
assertFileExists("/shared/build/XCFrameworks/debug/sdk.xcframework")
assertFileExists("/shared/build/XCFrameworks/debug/sdk.xcframework/ios-arm64/dSYMs/sdk.framework.dSYM")
assertFileExists("/shared/build/fat-framework/debug/sdk/watchos/sdk.framework")
assertFileExists("/shared/build/fat-framework/debug/sdk/watchos/sdk.framework.dSYM")
assertFileExists("/shared/build/XCFrameworks/debug/sdk.xcframework/ios-arm64/dSYMs/shared.framework.dSYM")
assertFileExists("/shared/build/sdkXCFrameworkTemp/fatframework/debug/watchos/sdk.framework")
assertFileExists("/shared/build/sdkXCFrameworkTemp/fatframework/debug/watchos/sdk.framework.dSYM")
}
build("assembleSdkDebugXCFramework") {
@@ -68,12 +68,12 @@ class XCFrameworkIT : BaseGradleIT() {
assertSuccessful()
assertTasksExecuted(":shared:linkReleaseFrameworkIosArm64")
assertTasksExecuted(":shared:linkReleaseFrameworkIosX64")
assertTasksExecuted(":shared:assembleReleaseXCFramework")
assertTasksExecuted(":shared:assembleSharedReleaseXCFramework")
assertFileExists("/shared/build/XCFrameworks/release/shared.xcframework")
assertFileExists("/shared/build/XCFrameworks/release/shared.xcframework/ios-arm64/dSYMs/shared.framework.dSYM")
assertTasksExecuted(":shared:linkDebugFrameworkIosArm64")
assertTasksExecuted(":shared:linkDebugFrameworkIosX64")
assertTasksExecuted(":shared:assembleDebugXCFramework")
assertTasksExecuted(":shared:assembleSharedDebugXCFramework")
assertFileExists("/shared/build/XCFrameworks/debug/shared.xcframework")
assertFileExists("/shared/build/XCFrameworks/debug/shared.xcframework/ios-arm64/dSYMs/shared.framework.dSYM")
}
@@ -86,8 +86,8 @@ class XCFrameworkIT : BaseGradleIT() {
build("tasks") {
assertSuccessful()
assertTasksNotRegistered(
":shared:assembleDebugXCFramework",
":shared:assembleReleaseXCFramework",
":shared:assembleSharedDebugXCFramework",
":shared:assembleSharedReleaseXCFramework",
":shared:assembleXCFramework"
)
}
@@ -105,7 +105,7 @@ class XCFrameworkIT : BaseGradleIT() {
build("tasks") {
assertFailed()
assertContains("Cannot add task 'assembleReleaseXCFramework' as a task with that name already exists.")
assertContains("Cannot add task 'assembleSharedReleaseXCFramework' as a task with that name already exists.")
}
}
}
@@ -8,16 +8,12 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.apple
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.FileTree
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.tasks.dependsOn
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
@@ -43,10 +39,7 @@ internal class XCFrameworkTaskHolder(
companion object {
fun create(project: Project, xcFrameworkName: String, buildType: NativeBuildType): XCFrameworkTaskHolder {
require(xcFrameworkName.isNotBlank())
val parentTask = project.parentAssembleXCFrameworkTask(xcFrameworkName)
val task = project.registerAssembleXCFrameworkTask(xcFrameworkName, buildType)
parentTask.dependsOn(task)
val fatTasks = AppleTarget.values().associate { fatTarget ->
val fatTask = project.registerAssembleFatForXCFrameworkTask(xcFrameworkName, buildType, fatTarget)
@@ -62,13 +55,17 @@ internal class XCFrameworkTaskHolder(
class XCFrameworkConfig {
private val taskHolders: List<XCFrameworkTaskHolder>
constructor(project: Project, xcFrameworkName: String) {
taskHolders = NativeBuildType.values().map { buildType ->
XCFrameworkTaskHolder.create(project, xcFrameworkName, buildType)
constructor(project: Project, xcFrameworkName: String, buildTypes: Set<NativeBuildType>) {
val parentTask = project.parentAssembleXCFrameworkTask(xcFrameworkName)
taskHolders = buildTypes.map { buildType ->
XCFrameworkTaskHolder.create(project, xcFrameworkName, buildType).also {
parentTask.dependsOn(it.task)
}
}
}
constructor(project: Project) : this(project, project.name)
constructor(project: Project, xcFrameworkName: String) : this(project, xcFrameworkName, NativeBuildType.values().toSet())
/**
* Adds the specified frameworks in this XCFramework.
@@ -106,7 +103,7 @@ private fun Project.registerAssembleXCFrameworkTask(
): TaskProvider<XCFrameworkTask> {
val taskName = lowerCamelCaseName(
"assemble",
eraseIfDefault(xcFrameworkName),
xcFrameworkName,
buildType.getName(),
"XCFramework"
)
@@ -156,21 +153,14 @@ abstract class XCFrameworkTask : DefaultTask() {
@Input
var buildType: NativeBuildType = NativeBuildType.RELEASE
/**
* A collection of frameworks used ot build the XCFramework.
*/
private val allFrameworks: MutableSet<Framework> = mutableSetOf()
@get:Internal // We take it into account as an input in the inputFrameworkFiles property.
val frameworks: List<Framework>
get() = allFrameworks.filter { it.buildType == buildType }
private val groupedFrameworkFiles: MutableMap<AppleTarget, MutableList<FrameworkDescriptor>> = mutableMapOf()
@get:IgnoreEmptyDirectories
@get:InputFiles
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:SkipWhenEmpty
protected val inputFrameworkFiles: Iterable<FileTree>
get() = frameworks.map { project.fileTree(it.outputFile) }
val inputFrameworkFiles: Collection<File>
get() = groupedFrameworkFiles.values.flatten().map { it.file }
/**
* A parent directory for the XCFramework.
@@ -197,34 +187,37 @@ abstract class XCFrameworkTask : DefaultTask() {
require(framework.konanTarget.family.isAppleFamily) {
"XCFramework supports Apple frameworks only"
}
allFrameworks.add(framework)
dependsOn(framework.linkTask)
}
fromFrameworkDescriptors(frameworks.map { FrameworkDescriptor(it) })
}
fun fromFrameworkDescriptors(vararg frameworks: FrameworkDescriptor) = fromFrameworkDescriptors(frameworks.toList())
fun fromFrameworkDescriptors(frameworks: Iterable<FrameworkDescriptor>) {
frameworks.forEach { framework ->
val group = AppleTarget.values().first { it.targets.contains(framework.target) }
groupedFrameworkFiles.getOrPut(group, { mutableListOf() }).add(framework)
}
}
@TaskAction
fun assemble() {
val frameworksForXCFramework = AppleTarget.values().mapNotNull { appleTarget ->
val group = frameworks.filter { it.konanTarget in appleTarget.targets }
val frameworksForXCFramework = groupedFrameworkFiles.entries.mapNotNull { (group, files) ->
when {
group.size == 1 -> {
XCFrameworkFile(group.first().outputFile, group.first().isStatic)
}
group.size > 1 -> {
XCFrameworkFile(
fatFrameworksDir.resolve(appleTarget.targetName).resolve("${xcFrameworkName.get()}.framework"),
group.all { it.isStatic }
)
}
files.size == 1 -> files.first()
files.size > 1 -> FrameworkDescriptor(
fatFrameworksDir.resolve(group.targetName).resolve("${xcFrameworkName.get()}.framework"),
files.all { it.isStatic },
group.targets.first() //will be not used
)
else -> null
}
}
createXCFramework(frameworksForXCFramework, outputXCFrameworkFile, buildType)
}
private data class XCFrameworkFile (val file: File, val isStatic: Boolean)
private fun createXCFramework(frameworkFiles: List<XCFrameworkFile>, output: File, buildType: NativeBuildType) {
private fun createXCFramework(frameworkFiles: List<FrameworkDescriptor>, output: File, buildType: NativeBuildType) {
if (output.exists()) output.deleteRecursively()
val cmdArgs = mutableListOf("xcodebuild", "-create-xcframework")
@@ -251,9 +244,9 @@ abstract class XCFrameworkTask : DefaultTask() {
buildType: NativeBuildType,
appleTarget: AppleTarget? = null
) = project.buildDir
.resolve("fat-framework")
.resolve(xcFrameworkName + "XCFrameworkTemp")
.resolve("fatframework")
.resolve(buildType.getName())
.resolve(xcFrameworkName)
.resolveIfNotNull(appleTarget?.targetName)
private fun File.resolveIfNotNull(relative: String?): File = if (relative == null) this else this.resolve(relative)
@@ -0,0 +1,86 @@
/*
* Copyright 2010-2021 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.tasks.artifact
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeOutputKind
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AppleTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkTaskHolder
import org.jetbrains.kotlin.gradle.tasks.FrameworkDescriptor
import org.jetbrains.kotlin.gradle.tasks.dependsOn
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.KonanTarget
class KotlinNativeXCFramework : KotlinNativeArtifact() {
var targets: Set<KonanTarget> = emptySet()
var embedBitcode: BitcodeEmbeddingMode? = null
private val kind = NativeOutputKind.FRAMEWORK
override fun validate(project: Project, name: String): Boolean {
val logger = project.logger
if (!super.validate(project, name)) return false
if (targets.isEmpty()) {
logger.error("Native library '${name}' wasn't configured because it requires at least one target")
return false
}
targets.firstOrNull { !kind.availableFor(it) }?.let { target ->
logger.error("Native library '${name}' wasn't configured because ${kind.description} is not available for ${target.visibleName}")
return false
}
return true
}
override fun registerAssembleTask(project: Project, name: String) {
val parentTask = project.registerTask<Task>(lowerCamelCaseName("assemble", name, "XCFramework")) {
it.group = "build"
it.description = "Assemble all types of registered '$name' XCFramework"
}
project.tasks.named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).dependsOn(parentTask)
modes.forEach { buildType ->
val holder = XCFrameworkTaskHolder.create(project, name, buildType).also {
parentTask.dependsOn(it.task)
}
val nameSuffix = "ForXCF"
val frameworkDescriptors: List<FrameworkDescriptor> = targets.map { target ->
val librariesConfigurationName = project.registerLibsDependencies(target, name + nameSuffix, modules)
val exportConfigurationName = project.registerExportDependencies(target, name + nameSuffix, modules)
val targetTask = registerLinkFrameworkTask(
project,
name,
target,
buildType,
librariesConfigurationName,
exportConfigurationName,
embedBitcode,
"${name}XCFrameworkTemp",
nameSuffix
)
holder.task.dependsOn(targetTask)
val frameworkFileProvider = targetTask.map { it.outputFile }
val descriptor = FrameworkDescriptor(frameworkFileProvider.get(), isStatic, target)
val group = AppleTarget.values().firstOrNull { it.targets.contains(target) }
holder.fatTasks[group]?.configure { fatTask ->
fatTask.fromFrameworkDescriptors(listOf(descriptor))
fatTask.dependsOn(targetTask)
}
descriptor
}
holder.task.configure {
it.fromFrameworkDescriptors(frameworkDescriptors)
it.outputDir = project.buildDir.resolve("out/xcframework")
}
}
}
}