From cfdd5673ee9b0ee094b89fc1fa2bc7a708ecb1f6 Mon Sep 17 00:00:00 2001 From: "konstantin.tskhovrebov" Date: Fri, 26 Nov 2021 16:06:43 +0100 Subject: [PATCH] Add new DSL for configuring K/N library artifact. --- .../native/tasks/artifact/KotlinArtifact.kt | 123 ++++++++++++++++++ .../tasks/artifact/KotlinNativeLibrary.kt | 74 +++++++++++ 2 files changed, 197 insertions(+) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinArtifact.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeLibrary.kt diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinArtifact.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinArtifact.kt new file mode 100644 index 00000000000..b68a6be6595 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinArtifact.kt @@ -0,0 +1,123 @@ +/* + * 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.attributes.Usage +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonToolOptions +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages +import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.konan.target.presetName + +abstract class KotlinArtifact { + internal val modules = mutableSetOf() + fun setModules(vararg project: Any) { + modules.clear() + modules.addAll(project) + } + + fun addModule(project: Any) { + modules.add(project) + } + + internal abstract fun validate(project: Project, name: String): Boolean + internal abstract fun registerAssembleTask(project: Project, name: String) +} + +abstract class KotlinNativeArtifact : KotlinArtifact() { + var modes: Set = NativeBuildType.DEFAULT_BUILD_TYPES + var isStatic: Boolean = false + var linkerOptions: List = emptyList() + + internal var kotlinOptionsFn: KotlinCommonToolOptions.() -> Unit = {} + fun kotlinOptions(fn: KotlinCommonToolOptions.() -> Unit) { + kotlinOptionsFn = fn + } + + internal val binaryOptions: MutableMap = mutableMapOf() + fun binaryOption(name: String, value: String) { + binaryOptions[name] = value + } + + override fun validate(project: Project, name: String): Boolean { + val logger = project.logger + if (modules.isEmpty()) { + logger.error("Native library '${name}' wasn't configured because it requires at least one module for linking") + return false + } + + if (modes.isEmpty()) { + logger.error("Native library '${name}' wasn't configured because it requires at least one build type in modes") + return false + } + + return true + } + + fun Project.registerLibsDependencies(target: KonanTarget, artifactName: String, deps: Set): String { + val librariesConfigurationName = lowerCamelCaseName(target.presetName, artifactName, "linkLibrary") + configurations.maybeCreate(librariesConfigurationName).apply { + isVisible = false + isCanBeConsumed = false + isCanBeResolved = true + isTransitive = true + attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.native) + attributes.attribute(KotlinNativeTarget.konanTargetAttribute, target.name) + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, KotlinUsages.KOTLIN_API)) + } + deps.forEach { dependencies.add(librariesConfigurationName, it) } + return librariesConfigurationName + } + + fun Project.registerExportDependencies(target: KonanTarget, artifactName: String, deps: Set): String { + val exportConfigurationName = lowerCamelCaseName(target.presetName, artifactName, "linkExport") + configurations.maybeCreate(exportConfigurationName).apply { + isVisible = false + isCanBeConsumed = false + isCanBeResolved = true + isTransitive = false + attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.native) + attributes.attribute(KotlinNativeTarget.konanTargetAttribute, target.name) + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, KotlinUsages.KOTLIN_API)) + } + deps.forEach { dependencies.add(exportConfigurationName, it) } + return exportConfigurationName + } +} + +open class ArtifactType(internal val create: () -> T) +object Library : ArtifactType({ KotlinNativeLibrary() }) +object Framework : ArtifactType({ KotlinNativeFramework() }) +object FatFramework : ArtifactType({ KotlinNativeFatFramework() }) +object XCFramework : ArtifactType({ KotlinNativeXCFramework() }) + +@RequiresOptIn( + message = "This API is experimental. It may be changed in the future.", + level = RequiresOptIn.Level.WARNING +) +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.FUNCTION) +annotation class ExperimentalArtifactDsl + +private val SAFE_NAME_PATTERN = """\W""".toRegex() + +@ExperimentalArtifactDsl +fun Project.kotlinArtifact(type: ArtifactType, configure: T.() -> Unit) = + kotlinArtifact(name.replace(SAFE_NAME_PATTERN, "_"), type, configure) + +@ExperimentalArtifactDsl +fun Project.kotlinArtifact(name: String, type: ArtifactType, configure: T.() -> Unit) { + val artifact = type.create() + artifact.addModule(this) + configure(artifact) + if (artifact.validate(this, name)) { + artifact.registerAssembleTask(this, name) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeLibrary.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeLibrary.kt new file mode 100644 index 00000000000..1009f654f6a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/artifact/KotlinNativeLibrary.kt @@ -0,0 +1,74 @@ +/* + * 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.api.plugins.BasePlugin +import org.gradle.language.base.plugins.LifecycleBasePlugin +import org.jetbrains.kotlin.gradle.plugin.mpp.NativeOutputKind +import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost +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 +import org.jetbrains.kotlin.konan.target.presetName +import org.jetbrains.kotlin.konan.util.visibleName + +class KotlinNativeLibrary : KotlinNativeArtifact() { + lateinit var target: KonanTarget + + private val kind: NativeOutputKind + get() = if (isStatic) NativeOutputKind.STATIC else NativeOutputKind.DYNAMIC + + override fun validate(project: Project, name: String): Boolean { + val logger = project.logger + if (!super.validate(project, name)) return false + if (!this::target.isInitialized) { + logger.error("Native library '${name}' wasn't configured because it requires target") + return false + } + if (!kind.availableFor(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 resultTask = project.registerTask( + lowerCamelCaseName("assemble", name, kind.taskNameClassifier, "Library") + ) { task -> + task.group = BasePlugin.BUILD_GROUP + task.description = "Assemble all types of registered '$name' ${kind.description}." + task.enabled = target.enabledOnCurrentHost + } + project.tasks.named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).dependsOn(resultTask) + + val librariesConfigurationName = project.registerLibsDependencies(target, name, modules) + val exportConfigurationName = project.registerExportDependencies(target, name, modules) + modes.forEach { buildType -> + val targetTask = project.registerTask( + lowerCamelCaseName("assemble", name, buildType.visibleName, kind.taskNameClassifier, "Library", target.presetName), + listOf(target, kind.compilerOutputKind) + ) { task -> + task.description = "Assemble ${kind.description} '$name' for a target '${target.name}'." + task.enabled = target.enabledOnCurrentHost + task.baseName = name + task.optimized = buildType.optimized + task.debuggable = buildType.debuggable + task.linkerOptions = linkerOptions + task.binaryOptions = binaryOptions + task.librariesConfiguration = librariesConfigurationName + task.exportLibrariesConfiguration = exportConfigurationName + task.kotlinOptions(kotlinOptionsFn) + } + resultTask.dependsOn(targetTask) + } + } +} \ No newline at end of file