diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt index 56a2a0c7f9a..8bbd22e19f7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt @@ -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 { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/InternalKotlinCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/InternalKotlinCompilation.kt index abc2a7e708e..dec7cdc60d6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/InternalKotlinCompilation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/InternalKotlinCompilation.kt @@ -27,6 +27,11 @@ internal interface InternalKotlinCompilation : Kotl val configurations: KotlinCompilationConfigurationsContainer val friendPaths: Iterable val processResourcesTaskName: String? + + /** + * @see [org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationArchiveTasks] + */ + val archiveTaskName: String? } internal val KotlinCompilation.internal: InternalKotlinCompilation diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationImpl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationImpl.kt index 89ee56d4e7f..b640c8ad817 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationImpl.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationImpl.kt @@ -164,6 +164,8 @@ internal class KotlinCompilationImpl constructor( //endregion + override val archiveTaskName: String? + get() = project.kotlinCompilationArchiveTasksOrNull?.getArchiveTaskOrNull(this)?.name //region CompilerOptions & KotlinOptions diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCreateCompilationArchiveTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCreateCompilationArchiveTask.kt new file mode 100644 index 00000000000..f3617560f62 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCreateCompilationArchiveTask.kt @@ -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? +} + +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> = 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) { + require(kotlinCompilation.project in currentBuild) { "Compilation $kotlinCompilation is not from current project" } + tasksMap[Key(kotlinCompilation)] = task + } + + override fun getArchiveTaskOrNull(kotlinCompilation: KotlinCompilation<*>): TaskProvider? { + 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) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt index e94f7ea1a7e..746bcc06bd6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt @@ -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 { diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinCompilationArchiveTasksTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinCompilationArchiveTasksTest.kt new file mode 100644 index 00000000000..229414c5452 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinCompilationArchiveTasksTest.kt @@ -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") + } + } +} \ No newline at end of file