[Gradle] Create archive tasks for custom compilations

This change is needed for kotlin-test in order to support
correct project dependencies in kotlin.git repository.

^KT-62943 Verification Pending
This commit is contained in:
Anton Lakotka
2023-11-01 18:12:28 +01:00
committed by Space Team
parent e747793e62
commit f2ddae533e
6 changed files with 268 additions and 0 deletions
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType.Companion.jsCompi
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_ABI_SNAPSHOT
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_COMPILER_KEEP_INCREMENTAL_COMPILATION_CACHES_IN_MEMORY
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_COMPILER_USE_PRECISE_COMPILATION_RESULTS_BACKUP
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_CREATE_ARCHIVE_TASKS_FOR_CUSTOM_COMPILATIONS
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_CREATE_DEFAULT_MULTIPLATFORM_PUBLICATIONS
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_EXPERIMENTAL_TRY_K2
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_INTERNAL_DIAGNOSTICS_SHOW_STACKTRACE
@@ -477,6 +478,9 @@ internal class PropertiesProvider private constructor(private val project: Proje
val createDefaultMultiplatformPublications: Boolean
get() = booleanProperty(KOTLIN_CREATE_DEFAULT_MULTIPLATFORM_PUBLICATIONS) ?: true
val createArchiveTasksForCustomCompilations: Boolean
get() = booleanProperty(KOTLIN_CREATE_ARCHIVE_TASKS_FOR_CUSTOM_COMPILATIONS) ?: false
val runKotlinCompilerViaBuildToolsApi: Boolean
get() = booleanProperty(KOTLIN_RUN_COMPILER_VIA_BUILD_TOOLS_API) ?: false
@@ -636,6 +640,8 @@ internal class PropertiesProvider private constructor(private val project: Proje
val KOTLIN_INTERNAL_DIAGNOSTICS_SHOW_STACKTRACE = property("$KOTLIN_INTERNAL_NAMESPACE.diagnostics.showStacktrace")
val KOTLIN_SUPPRESS_GRADLE_PLUGIN_ERRORS = property("$KOTLIN_INTERNAL_NAMESPACE.suppressGradlePluginErrors")
val MPP_13X_FLAGS_SET_BY_PLUGIN = property("$KOTLIN_INTERNAL_NAMESPACE.mpp.13X.flags.setByPlugin")
val KOTLIN_CREATE_ARCHIVE_TASKS_FOR_CUSTOM_COMPILATIONS =
property("$KOTLIN_INTERNAL_NAMESPACE.mpp.createArchiveTasksForCustomCompilations")
}
companion object {
@@ -27,6 +27,11 @@ internal interface InternalKotlinCompilation<out T : KotlinCommonOptions> : Kotl
val configurations: KotlinCompilationConfigurationsContainer
val friendPaths: Iterable<FileCollection>
val processResourcesTaskName: String?
/**
* @see [org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationArchiveTasks]
*/
val archiveTaskName: String?
}
internal val <T : KotlinCommonOptions> KotlinCompilation<T>.internal: InternalKotlinCompilation<T>
@@ -164,6 +164,8 @@ internal class KotlinCompilationImpl constructor(
//endregion
override val archiveTaskName: String?
get() = project.kotlinCompilationArchiveTasksOrNull?.getArchiveTaskOrNull(this)?.name
//region CompilerOptions & KotlinOptions
@@ -0,0 +1,139 @@
/*
* 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.plugin.mpp.compilationImpl
import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.bundling.Zip
import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinProjectSetupAction
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName
import org.jetbrains.kotlin.gradle.plugin.mpp.isMain
import org.jetbrains.kotlin.gradle.plugin.mpp.isTest
import org.jetbrains.kotlin.gradle.utils.CurrentBuildIdentifier
import org.jetbrains.kotlin.gradle.utils.currentBuild
import org.jetbrains.kotlin.gradle.utils.libsDirectory
/**
* **WARNING!** This is internal Kotlin Gradle Plugin API, it is not supported outside JetBrains.
*
* Extension DSL to access Archive Tasks for custom compilations
* These archive tasks can be exported as artifacts via consumable configuration.
* And IDE will correctly import them.
*
* Example:
* ```kotlin
* // project :lib
* kotlin {
* jvm {
* val customCompilation by compilations.creating
* val archiveTask = kotlinCompilationArchiveTasks
* .getArchiveTaskOrNull(customCompilation)!!
*
* val jvmCustomApiElements by configurations.creating {
* isCanBeConsumed = true
* }
* project.artifacts.add(jvmCustomApiElements.name, archiveTask)
* }
* }
*
* // project :app
* kotlin {
* jvm()
* sourceSets {
* jvmMain {
* dependencies {
* // This dependency will be correctly resolved in IDE
* // as Module dependency to `lib.jvmCustom` source set
* compileOnly(project(":lib", configuration = "jvmCustomApiElements"))
* }
* }
* }
* }
* ```
*/
@InternalKotlinGradlePluginApi
interface KotlinCompilationArchiveTasks {
/**
* Returns archive task associated with [kotlinCompilation] instance.
*
* This method searches via Identity class (Project path, target and compilation names) of Compilations not by reference.
* So it is safe to pass decorated or wrapped instances.
*/
fun getArchiveTaskOrNull(kotlinCompilation: KotlinCompilation<*>): TaskProvider<out AbstractArchiveTask>?
}
internal val KotlinRegisterCompilationArchiveTasksExtension = KotlinProjectSetupAction {
if (!project.kotlinPropertiesProvider.createArchiveTasksForCustomCompilations) return@KotlinProjectSetupAction
project.extensions.add(
KotlinCompilationArchiveTasks::class.java,
"kotlinCompilationsArchiveTasks",
KotlinCompilationArchiveTasksImpl(project.currentBuild)
)
}
private val Project.kotlinCompilationArchiveTasksImplOrNull: KotlinCompilationArchiveTasksImpl?
get() = extensions.findByName("kotlinCompilationsArchiveTasks") as? KotlinCompilationArchiveTasksImpl?
internal val Project.kotlinCompilationArchiveTasksOrNull: KotlinCompilationArchiveTasks? get() = kotlinCompilationArchiveTasksImplOrNull
private class KotlinCompilationArchiveTasksImpl(
private val currentBuild: CurrentBuildIdentifier
) : KotlinCompilationArchiveTasks {
private val tasksMap: MutableMap<Key, TaskProvider<out AbstractArchiveTask>> = mutableMapOf()
private data class Key(
val targetName: String,
val compilationName: String
) {
constructor(kotlinCompilation: KotlinCompilation<*>) : this(
targetName = kotlinCompilation.target.targetName,
compilationName = kotlinCompilation.compilationName
)
}
fun store(kotlinCompilation: KotlinCompilation<*>, task: TaskProvider<out AbstractArchiveTask>) {
require(kotlinCompilation.project in currentBuild) { "Compilation $kotlinCompilation is not from current project" }
tasksMap[Key(kotlinCompilation)] = task
}
override fun getArchiveTaskOrNull(kotlinCompilation: KotlinCompilation<*>): TaskProvider<out AbstractArchiveTask>? {
if (kotlinCompilation.project !in currentBuild) return null
return tasksMap[Key(kotlinCompilation)]
}
}
internal val KotlinCreateCompilationArchivesTask = KotlinCompilationSideEffect { compilation ->
if (compilation.isMain() || compilation.isTest()) return@KotlinCompilationSideEffect
if (compilation.target is KotlinMetadataTarget) return@KotlinCompilationSideEffect
// Pessimistically exclude Android target to avoid any conflicts
if (compilation.target.platformType == KotlinPlatformType.androidJvm) return@KotlinCompilationSideEffect
val project = compilation.project
if (!project.kotlinPropertiesProvider.createArchiveTasksForCustomCompilations) return@KotlinCompilationSideEffect
val archiveTask = if (compilation.target.platformType == KotlinPlatformType.jvm) {
project.tasks.register(compilation.disambiguateName("jar"), Jar::class.java) { task ->
task.from(compilation.output.allOutputs)
task.archiveBaseName.convention(compilation.target.disambiguateName(compilation.name))
}
} else {
project.tasks.register(compilation.disambiguateName("klib"), Zip::class.java) { task ->
task.from(compilation.output.allOutputs)
task.archiveBaseName.convention(compilation.target.disambiguateName(compilation.name))
task.destinationDirectory.convention(project.libsDirectory)
task.archiveExtension.set("klib")
}
}
project.kotlinCompilationArchiveTasksImplOrNull?.store(compilation, archiveTask)
}
@@ -109,6 +109,7 @@ internal fun Project.registerKotlinPluginExtensions() {
register(project, CInteropCommonizedCInteropApiElementsConfigurationsSetupAction)
register(project, AddBuildListenerForXCodeSetupAction)
register(project, CreateFatFrameworksSetupAction)
register(project, KotlinRegisterCompilationArchiveTasksExtension)
}
}
@@ -132,6 +133,7 @@ internal fun Project.registerKotlinPluginExtensions() {
register(project, KotlinCreateNativeCompileTasksSideEffect)
register(project, KotlinCompilationProcessorSideEffect)
register(project, KotlinCreateNativeCInteropTasksSideEffect)
register(project, KotlinCreateCompilationArchivesTask)
}
KotlinTargetArtifact.extensionPoint.apply {
@@ -0,0 +1,114 @@
/*
* 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.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle.unitTests
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_CREATE_ARCHIVE_TASKS_FOR_CUSTOM_COMPILATIONS
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationArchiveTasks
import org.jetbrains.kotlin.gradle.plugin.mpp.internal
import org.jetbrains.kotlin.gradle.util.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.fail
class KotlinCompilationArchiveTasksTest {
private fun Project.enableKotlinCompilationArchiveTasksCreation(enabled: Boolean = true) {
propertiesExtension.set(KOTLIN_CREATE_ARCHIVE_TASKS_FOR_CUSTOM_COMPILATIONS, enabled.toString())
}
private val Project.kotlinCompilationsArchiveTasks: KotlinCompilationArchiveTasks
get() = extensions.getByName("kotlinCompilationsArchiveTasks") as KotlinCompilationArchiveTasks
val testProject: Project = buildProject {
enableKotlinCompilationArchiveTasksCreation()
applyMultiplatformPlugin()
kotlin {
jvm()
linuxX64()
}
}
@Test
fun `custom jvm compilation has archive task`() {
testProject.kotlin {
jvm {
val customCompilation = compilations.create("custom")
val archiveTaskProvider = project.kotlinCompilationsArchiveTasks.getArchiveTaskOrNull(customCompilation)
assertNotNull(archiveTaskProvider)
val archiveFile = archiveTaskProvider.get().archiveFile.get().asFile
if (!archiveFile.endsWith("libs/jvmCustom.jar")) fail("Unexpected archive file for compilation $customCompilation")
assertEquals(archiveTaskProvider.name, customCompilation.internal.archiveTaskName)
}
}
}
@Test
fun `custom native compilation has archive`() {
testProject.kotlin {
linuxX64 {
val customCompilation = compilations.create("custom")
val archiveTaskProvider = project.kotlinCompilationsArchiveTasks.getArchiveTaskOrNull(customCompilation)
assertNotNull(archiveTaskProvider)
val archiveFile = archiveTaskProvider.get().archiveFile.get().asFile
if (!archiveFile.endsWith("libs/linuxX64Custom.klib"))
fail("Unexpected archive file for compilation $customCompilation: $archiveFile")
}
}
}
@Test
fun `main and test compilations should not have archive task`() {
testProject.multiplatformExtension.targets.flatMap { it.compilations }.forEach { compilation ->
if (compilation.internal.archiveTaskName != null)
fail("Archive tasks should not be created for default compilations, but $compilation has it")
}
}
@Test
fun `archive tasks should not be created for custom compilation when feature flag is not set`() {
val project = buildProject {
enableKotlinCompilationArchiveTasksCreation(enabled = false)
applyMultiplatformPlugin()
kotlin {
jvm().compilations.create("custom")
linuxX64().compilations.create("custom")
}
}
project.multiplatformExtension.targets.flatMap { it.compilations }.forEach { compilation ->
if (compilation.internal.archiveTaskName != null)
fail("Archive tasks should not be created")
}
}
@Test
fun `archive tasks should not be created for any android compilation`() {
val project = buildProject {
enableKotlinCompilationArchiveTasksCreation()
applyMultiplatformPlugin()
androidLibrary {
compileSdk = 33
productFlavors {
create("customFlavor")
}
}
kotlin {
linuxX64()
androidTarget()
}
}
project.multiplatformExtension.androidTarget().compilations.forEach { compilation ->
if (compilation.internal.archiveTaskName != null)
fail("Archive tasks should not be created for $compilation")
}
}
}