From 2e86fbd2b5ffb496f8e78642d8dc48478e8dd658 Mon Sep 17 00:00:00 2001 From: "konstantin.tskhovrebov" Date: Fri, 26 Nov 2021 16:07:39 +0100 Subject: [PATCH] Add new DSL for configuring K/N xcframework artifact. --- .../kotlin/gradle/native/XCFrameworkIT.kt | 16 ++-- .../plugin/mpp/apple/XCFrameworkTask.kt | 69 +++++++-------- .../tasks/artifact/KotlinNativeXCFramework.kt | 86 +++++++++++++++++++ 3 files changed, 125 insertions(+), 46 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeXCFramework.kt diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/XCFrameworkIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/XCFrameworkIT.kt index dbb53a16ad6..b59511cb1bb 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/XCFrameworkIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/XCFrameworkIT.kt @@ -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.") } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XCFrameworkTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XCFrameworkTask.kt index 845dcb88832..02e3d615ed0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XCFrameworkTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/XCFrameworkTask.kt @@ -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 - constructor(project: Project, xcFrameworkName: String) { - taskHolders = NativeBuildType.values().map { buildType -> - XCFrameworkTaskHolder.create(project, xcFrameworkName, buildType) + constructor(project: Project, xcFrameworkName: String, buildTypes: Set) { + 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 { 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 = mutableSetOf() - - @get:Internal // We take it into account as an input in the inputFrameworkFiles property. - val frameworks: List - get() = allFrameworks.filter { it.buildType == buildType } + private val groupedFrameworkFiles: MutableMap> = mutableMapOf() @get:IgnoreEmptyDirectories @get:InputFiles @get:PathSensitive(PathSensitivity.ABSOLUTE) @get:SkipWhenEmpty - protected val inputFrameworkFiles: Iterable - get() = frameworks.map { project.fileTree(it.outputFile) } + val inputFrameworkFiles: Collection + 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) { + 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, output: File, buildType: NativeBuildType) { + private fun createXCFramework(frameworkFiles: List, 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) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeXCFramework.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeXCFramework.kt new file mode 100644 index 00000000000..9687dadbcb4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeXCFramework.kt @@ -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 = 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(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 = 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") + } + } + } +} \ No newline at end of file