Add artifacts collection inside KotlinArtifacts extension.

And make KotlinArtifact instances expandable via groovy machinery.
This commit is contained in:
konstantin.tskhovrebov
2022-03-29 10:17:03 +02:00
committed by Space
parent aafab7800b
commit 9430f0bafb
8 changed files with 157 additions and 137 deletions
@@ -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.native.GeneralNativeIT.Companion.withNativeCommandLineArguments
import org.jetbrains.kotlin.gradle.transformProjectWithPluginsDsl
import org.jetbrains.kotlin.konan.target.HostManager
import org.junit.Assume
import org.junit.jupiter.api.Assertions.assertFalse
@@ -19,14 +20,14 @@ class NativeLibraryDslIT : BaseGradleIT() {
@Test
fun `check registered gradle tasks`() {
with(Project("new-kn-library-dsl")) {
with(transformProjectWithPluginsDsl("new-kn-library-dsl")) {
build(":shared:tasks") {
assertSuccessful()
assertTasksRegistered(
":shared:assembleMyfatframeFatFramework",
":shared:assembleMyframeFramework",
":shared:assembleMylibSharedLibrary",
":shared:assembleMyslibSharedLibrary",
":shared:assembleMyframeFrameworkIosArm64",
":shared:assembleMylibSharedLibraryLinuxX64",
":shared:assembleMyslibSharedLibraryLinuxX64",
":shared:assembleSharedXCFramework"
)
assertTasksNotRegistered(
@@ -37,8 +38,8 @@ class NativeLibraryDslIT : BaseGradleIT() {
assertSuccessful()
assertTasksRegistered(
":lib:assembleGroofatframeFatFramework",
":lib:assembleGrooframeFramework",
":lib:assembleGroolibSharedLibrary",
":lib:assembleGrooframeFrameworkIosArm64",
":lib:assembleGroolibSharedLibraryIosX64",
":lib:assembleLibXCFramework"
)
}
@@ -47,7 +48,7 @@ class NativeLibraryDslIT : BaseGradleIT() {
@Test
fun `link shared library from two gradle modules`() {
with(Project("new-kn-library-dsl")) {
with(transformProjectWithPluginsDsl("new-kn-library-dsl")) {
build(":shared:assembleMyslibDebugSharedLibraryLinuxX64") {
assertSuccessful()
assertTasksExecuted(
@@ -63,7 +64,7 @@ class NativeLibraryDslIT : BaseGradleIT() {
@Test
fun `link shared library from single gradle module`() {
with(Project("new-kn-library-dsl")) {
with(transformProjectWithPluginsDsl("new-kn-library-dsl")) {
build(":shared:assembleMylibDebugSharedLibraryLinuxX64") {
assertSuccessful()
assertTasksExecuted(
@@ -86,8 +87,7 @@ class NativeLibraryDslIT : BaseGradleIT() {
@Test
fun `link shared library from single gradle module with additional link args`() {
with(Project("new-kn-library-dsl")) {
setupWorkingDir()
with(transformProjectWithPluginsDsl("new-kn-library-dsl")) {
gradleProperties().appendText("\nkotlin.native.linkArgs=-Xfoo=bar -Xbaz=qux")
build(":shared:assembleMylibDebugSharedLibraryLinuxX64") {
assertSuccessful()
@@ -112,7 +112,7 @@ class NativeLibraryDslIT : BaseGradleIT() {
@Test
fun `link release XCFramework from two gradle modules`() {
Assume.assumeTrue(HostManager.hostIsMac)
with(Project("new-kn-library-dsl")) {
with(transformProjectWithPluginsDsl("new-kn-library-dsl")) {
build(":shared:assembleSharedReleaseXCFramework") {
assertSuccessful()
assertTasksExecuted(
@@ -1,3 +1,7 @@
plugins {
kotlin("multiplatform").apply(false)
}
allprojects {
repositories {
mavenLocal()
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.gradle.targets.native.tasks.artifact
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.attributes.Usage
@@ -18,7 +17,12 @@ import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.presetName
abstract class KotlinArtifact {
abstract class KotlinArtifact(
val project: Project,
val artifactName: String
) {
internal abstract val taskName: String
internal val modules = mutableSetOf<Any>()
fun setModules(vararg project: Any) {
modules.clear()
@@ -29,11 +33,11 @@ abstract class KotlinArtifact {
modules.add(project)
}
internal abstract fun validate(project: Project, name: String): Boolean
internal abstract fun registerAssembleTask(project: Project, name: String)
internal abstract fun validate()
internal abstract fun registerAssembleTask()
}
abstract class KotlinNativeArtifact : KotlinArtifact() {
abstract class KotlinNativeArtifact(project: Project, artifactName: String) : KotlinArtifact(project, artifactName) {
var modes: Set<NativeBuildType> = NativeBuildType.DEFAULT_BUILD_TYPES
fun modes(vararg modes: NativeBuildType) {
this.modes = modes.toSet()
@@ -52,22 +56,16 @@ abstract class KotlinNativeArtifact : KotlinArtifact() {
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
override fun validate() {
check(modules.isNotEmpty()) {
"Native artifact '$artifactName' wasn't configured because it requires at least one module for linking"
}
if (modes.isEmpty()) {
logger.error("Native library '${name}' wasn't configured because it requires at least one build type in modes")
return false
check(modes.isNotEmpty()) {
"Native artifact '$artifactName' wasn't configured because it requires at least one build type in modes"
}
return true
}
fun Project.registerLibsDependencies(target: KonanTarget, artifactName: String, deps: Set<Any>): String {
protected fun Project.registerLibsDependencies(target: KonanTarget, artifactName: String, deps: Set<Any>): String {
val librariesConfigurationName = lowerCamelCaseName(target.presetName, artifactName, "linkLibrary")
configurations.maybeCreate(librariesConfigurationName).apply {
isVisible = false
@@ -82,7 +80,7 @@ abstract class KotlinNativeArtifact : KotlinArtifact() {
return librariesConfigurationName
}
fun Project.registerExportDependencies(target: KonanTarget, artifactName: String, deps: Set<Any>): String {
protected fun Project.registerExportDependencies(target: KonanTarget, artifactName: String, deps: Set<Any>): String {
val exportConfigurationName = lowerCamelCaseName(target.presetName, artifactName, "linkExport")
configurations.maybeCreate(exportConfigurationName).apply {
isVisible = false
@@ -8,25 +8,16 @@ package org.jetbrains.kotlin.gradle.targets.native.tasks.artifact
import org.gradle.api.Action
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.utils.castIsolatedKotlinPluginClassLoaderAware
import org.jetbrains.kotlin.konan.target.KonanTarget
import javax.inject.Inject
private val SAFE_NAME_PATTERN = """\W""".toRegex()
private inline fun <reified T : KotlinArtifact> Project.kotlinArtifact(configure: Action<T>) {
kotlinArtifact(name.replace(SAFE_NAME_PATTERN, "_"), configure)
}
private inline fun <reified T : KotlinArtifact> Project.kotlinArtifact(name: String, configure: Action<T>) {
val artifact = objects.newInstance(T::class.java)
artifact.addModule(this)
configure.execute(artifact)
if (artifact.validate(this, name)) {
artifact.registerAssembleTask(this, name)
}
}
//Groovy script DSL
abstract class NativeArtifacts @Inject constructor(private val project: Project) {
companion object {
private val UNSAFE_NAME_SYMBOLS = """\W""".toRegex()
}
@RequiresOptIn(
message = "This API is experimental. It may be changed in the future.",
level = RequiresOptIn.Level.WARNING
@@ -37,51 +28,74 @@ abstract class NativeArtifacts @Inject constructor(private val project: Project)
@ExperimentalArtifactDsl
fun Library(name: String, configure: Action<KotlinNativeLibrary>) {
project.kotlinArtifact(name, configure)
addKotlinArtifact(name, configure)
}
@ExperimentalArtifactDsl
fun Library(configure: Action<KotlinNativeLibrary>) {
project.kotlinArtifact(configure)
addKotlinArtifact(configure)
}
@ExperimentalArtifactDsl
fun Framework(name: String, configure: Action<KotlinNativeFramework>) {
project.kotlinArtifact(name, configure)
addKotlinArtifact(name, configure)
}
@ExperimentalArtifactDsl
fun Framework(configure: Action<KotlinNativeFramework>) {
project.kotlinArtifact(configure)
addKotlinArtifact(configure)
}
@ExperimentalArtifactDsl
fun FatFramework(name: String, configure: Action<KotlinNativeFatFramework>) {
project.kotlinArtifact(name, configure)
addKotlinArtifact(name, configure)
}
@ExperimentalArtifactDsl
fun FatFramework(configure: Action<KotlinNativeFatFramework>) {
project.kotlinArtifact(configure)
addKotlinArtifact(configure)
}
@ExperimentalArtifactDsl
fun XCFramework(name: String, configure: Action<KotlinNativeXCFramework>) {
project.kotlinArtifact(name, configure)
addKotlinArtifact(name, configure)
}
@ExperimentalArtifactDsl
fun XCFramework(configure: Action<KotlinNativeXCFramework>) {
project.kotlinArtifact(configure)
addKotlinArtifact(configure)
}
private inline fun <reified T : KotlinArtifact> addKotlinArtifact(configure: Action<T>) {
addKotlinArtifact(project.name.replace(UNSAFE_NAME_SYMBOLS, "_"), configure)
}
private inline fun <reified T : KotlinArtifact> addKotlinArtifact(name: String, configure: Action<T>) {
//create via newInstance for extensibility
val artifact = project.objects.newInstance(T::class.java, project, name)
//we should add artifact to collection BEFORE configuration
//because other plugins can add extensions for artifacts which will be used in configuration block
project.kotlinArtifactsExtension.artifacts.add(artifact)
artifact.addModule(project)
configure.execute(artifact)
artifact.registerAssembleTask()
}
}
//Groovy script DSL
private const val KOTLIN_ARTIFACTS_EXTENSION_NAME = "kotlinArtifacts"
internal fun Project.registerKotlinArtifactsExtension() {
extensions.create("kotlinArtifacts", KotlinArtifactsExtension::class.java, this)
extensions.create(KOTLIN_ARTIFACTS_EXTENSION_NAME, KotlinArtifactsExtension::class.java, this)
}
internal val Project.kotlinArtifactsExtension: KotlinArtifactsExtension
get() = extensions.getByName(KOTLIN_ARTIFACTS_EXTENSION_NAME).castIsolatedKotlinPluginClassLoaderAware()
abstract class KotlinArtifactsExtension @Inject constructor(project: Project) {
val artifacts = project.objects.domainObjectSet(KotlinArtifact::class.java)
val DEBUG = NativeBuildType.DEBUG
val RELEASE = NativeBuildType.RELEASE
@@ -17,8 +17,14 @@ 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.util.visibleName
import javax.inject.Inject
open class KotlinNativeFatFramework @Inject constructor(
project: Project,
artifactName: String
) : KotlinNativeArtifact(project, artifactName) {
override val taskName get() = lowerCamelCaseName("assemble", artifactName, "FatFramework")
open class KotlinNativeFatFramework : KotlinNativeArtifact() {
var targets: Set<KonanTarget> = emptySet()
fun targets(vararg targets: KonanTarget) {
this.targets = targets.toSet()
@@ -28,50 +34,47 @@ open class KotlinNativeFatFramework : KotlinNativeArtifact() {
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
override fun validate() {
super.validate()
check(targets.isNotEmpty()) {
"Native artifact '$artifactName' wasn't configured because it requires at least one target"
}
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
val wrongTarget = targets.firstOrNull { !kind.availableFor(it) }
check(wrongTarget == null) {
"Native artifact '$artifactName' wasn't configured because ${kind.description} is not available for ${wrongTarget!!.visibleName}"
}
return true
}
override fun registerAssembleTask(project: Project, name: String) {
val parentTask = project.registerTask<Task>(lowerCamelCaseName("assemble", name, "FatFramework")) {
override fun registerAssembleTask() {
validate()
val parentTask = project.registerTask<Task>(taskName) {
it.group = "build"
it.description = "Assemble all types of registered '$name' FatFramework"
it.description = "Assemble all types of registered '$artifactName' FatFramework"
}
project.tasks.named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).dependsOn(parentTask)
modes.forEach { buildType ->
val fatTask = project.registerTask<FatFrameworkTask>(
lowerCamelCaseName("assemble", name, buildType.visibleName, "FatFramework")
lowerCamelCaseName("assemble", artifactName, buildType.visibleName, "FatFramework")
) {
it.baseName = name
it.baseName = artifactName
it.destinationDir = project.buildDir.resolve("out/fatframework/${buildType.getName()}")
}
parentTask.dependsOn(fatTask)
val nameSuffix = "ForFat"
val frameworkDescriptors: List<FrameworkDescriptor> = targets.map { target ->
val librariesConfigurationName = project.registerLibsDependencies(target, name + nameSuffix, modules)
val exportConfigurationName = project.registerExportDependencies(target, name + nameSuffix, modules)
val librariesConfigurationName = project.registerLibsDependencies(target, artifactName + nameSuffix, modules)
val exportConfigurationName = project.registerExportDependencies(target, artifactName + nameSuffix, modules)
val targetTask = registerLinkFrameworkTask(
project,
name,
artifactName,
target,
buildType,
librariesConfigurationName,
exportConfigurationName,
embedBitcode,
"${name}FatFrameworkTemp",
"${artifactName}FatFrameworkTemp",
nameSuffix
)
fatTask.dependsOn(targetTask)
@@ -20,41 +20,40 @@ 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
import javax.inject.Inject
open class KotlinNativeFramework : KotlinNativeArtifact() {
open class KotlinNativeFramework @Inject constructor(project: Project, artifactName: String) : KotlinNativeArtifact(project, artifactName) {
lateinit var target: KonanTarget
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 (!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
}
override val taskName get() = lowerCamelCaseName("assemble", artifactName, kind.taskNameClassifier, target.presetName)
return true
override fun validate() {
super.validate()
check(this::target.isInitialized) {
"Native artifact '$artifactName' wasn't configured because it requires target"
}
check(kind.availableFor(target)) {
"Native artifact '$artifactName' wasn't configured because ${kind.description} is not available for ${target.visibleName}"
}
}
override fun registerAssembleTask(project: Project, name: String) {
val resultTask = project.registerTask<Task>(lowerCamelCaseName("assemble", name, kind.taskNameClassifier)) { task ->
override fun registerAssembleTask() {
validate()
val resultTask = project.registerTask<Task>(taskName) { task ->
task.group = BasePlugin.BUILD_GROUP
task.description = "Assemble ${kind.description} '$name'."
task.description = "Assemble ${kind.description} '$artifactName' for ${target.visibleName}."
task.enabled = target.enabledOnCurrentHost
}
val librariesConfigurationName = project.registerLibsDependencies(target, name, modules)
val exportConfigurationName = project.registerExportDependencies(target, name, modules)
val librariesConfigurationName = project.registerLibsDependencies(target, artifactName, modules)
val exportConfigurationName = project.registerExportDependencies(target, artifactName, modules)
modes.forEach { buildType ->
val targetTask = registerLinkFrameworkTask(
project,
name,
artifactName,
target,
buildType,
librariesConfigurationName,
@@ -17,49 +17,48 @@ 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
import javax.inject.Inject
open class KotlinNativeLibrary : KotlinNativeArtifact() {
open class KotlinNativeLibrary @Inject constructor(
project: Project,
artifactName: String
) : KotlinNativeArtifact(project, artifactName) {
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
}
override val taskName get() = lowerCamelCaseName("assemble", artifactName, kind.taskNameClassifier, "Library", target.presetName)
return true
override fun validate() {
super.validate()
check(this::target.isInitialized) {
"Native artifact '$artifactName' wasn't configured because it requires target"
}
check(kind.availableFor(target)) {
"Native artifact '$artifactName' wasn't configured because ${kind.description} is not available for ${target.visibleName}"
}
}
override fun registerAssembleTask(project: Project, name: String) {
val resultTask = project.registerTask<Task>(
lowerCamelCaseName("assemble", name, kind.taskNameClassifier, "Library")
) { task ->
override fun registerAssembleTask() {
validate()
val resultTask = project.registerTask<Task>(taskName) { task ->
task.group = BasePlugin.BUILD_GROUP
task.description = "Assemble all types of registered '$name' ${kind.description}."
task.description = "Assemble all types of registered '$artifactName' ${kind.description} for ${target.visibleName}."
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)
val librariesConfigurationName = project.registerLibsDependencies(target, artifactName, modules)
val exportConfigurationName = project.registerExportDependencies(target, artifactName, modules)
modes.forEach { buildType ->
val targetTask = project.registerTask<KotlinNativeLinkArtifactTask>(
lowerCamelCaseName("assemble", name, buildType.visibleName, kind.taskNameClassifier, "Library", target.presetName),
lowerCamelCaseName("assemble", artifactName, buildType.visibleName, kind.taskNameClassifier, "Library", target.presetName),
listOf(target, kind.compilerOutputKind)
) { task ->
task.description = "Assemble ${kind.description} '$name' for a target '${target.name}'."
task.description = "Assemble ${kind.description} '$artifactName' for a target '${target.name}'."
task.enabled = target.enabledOnCurrentHost
task.baseName = name
task.baseName = artifactName
task.optimized = buildType.optimized
task.debuggable = buildType.debuggable
task.linkerOptions = linkerOptions
@@ -17,8 +17,14 @@ 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 javax.inject.Inject
open class KotlinNativeXCFramework @Inject constructor(
project: Project,
artifactName: String
) : KotlinNativeArtifact(project, artifactName) {
override val taskName get() = lowerCamelCaseName("assemble", artifactName, "XCFramework")
open class KotlinNativeXCFramework : KotlinNativeArtifact() {
var targets: Set<KonanTarget> = emptySet()
fun targets(vararg targets: KonanTarget) {
this.targets = targets.toSet()
@@ -28,46 +34,43 @@ open class KotlinNativeXCFramework : KotlinNativeArtifact() {
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
override fun validate() {
super.validate()
check(targets.isNotEmpty()) {
"Native artifact '$artifactName' wasn't configured because it requires at least one target"
}
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
val wrongTarget = targets.firstOrNull { !kind.availableFor(it) }
check(wrongTarget == null) {
"Native artifact '$artifactName' wasn't configured because ${kind.description} is not available for ${wrongTarget!!.visibleName}"
}
return true
}
override fun registerAssembleTask(project: Project, name: String) {
val parentTask = project.registerTask<Task>(lowerCamelCaseName("assemble", name, "XCFramework")) {
override fun registerAssembleTask() {
validate()
val parentTask = project.registerTask<Task>(taskName) {
it.group = "build"
it.description = "Assemble all types of registered '$name' XCFramework"
it.description = "Assemble all types of registered '$artifactName' XCFramework"
}
project.tasks.named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).dependsOn(parentTask)
modes.forEach { buildType ->
val holder = XCFrameworkTaskHolder.create(project, name, buildType).also {
val holder = XCFrameworkTaskHolder.create(project, artifactName, 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 librariesConfigurationName = project.registerLibsDependencies(target, artifactName + nameSuffix, modules)
val exportConfigurationName = project.registerExportDependencies(target, artifactName + nameSuffix, modules)
val targetTask = registerLinkFrameworkTask(
project,
name,
artifactName,
target,
buildType,
librariesConfigurationName,
exportConfigurationName,
embedBitcode,
"${name}XCFrameworkTemp",
"${artifactName}XCFrameworkTemp",
nameSuffix
)
holder.task.dependsOn(targetTask)
@@ -76,7 +79,7 @@ open class KotlinNativeXCFramework : KotlinNativeArtifact() {
val group = AppleTarget.values().firstOrNull { it.targets.contains(target) }
holder.fatTasks[group]?.configure { fatTask ->
fatTask.baseName = name
fatTask.baseName = artifactName
fatTask.fromFrameworkDescriptors(listOf(descriptor))
fatTask.dependsOn(targetTask)
}