diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt
index c7a7d0763f4..094b6aa7057 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt
@@ -23,7 +23,7 @@ class KotlinAndroid32GradleIT : KotlinAndroid3GradleIT(androidGradlePluginVersio
get() = GradleVersionRequired.AtLeast("4.6")
@Test
- fun testAndroidWithNewMppApp() = with(Project("new-mpp-android")) {
+ fun testAndroidWithNewMppApp() = with(Project("new-mpp-android", GradleVersionRequired.AtLeast("4.7"))) {
build("assemble", "compileDebugUnitTestJavaWithJavac", "printCompilerPluginOptions") {
assertSuccessful()
@@ -70,6 +70,107 @@ class KotlinAndroid32GradleIT : KotlinAndroid3GradleIT(androidGradlePluginVersio
)
}
}
+
+ // By default, no Android variant should be published in 1.3.20:
+ val groupDir = "lib/build/repo/com/example/"
+ build("publish") {
+ assertSuccessful()
+ assertFileExists(groupDir + "lib-jvmlib")
+ assertFileExists(groupDir + "lib-jslib")
+ assertNoSuchFile(groupDir + "lib-androidlib")
+ assertNoSuchFile(groupDir + "lib-androidlib-debug")
+ projectDir.resolve(groupDir).deleteRecursively()
+ }
+
+ // Choose a single variant to publish, check that it's there:
+ gradleBuildScript("lib").appendText("\nkotlin.android('androidLib').publishLibraryVariants = ['release']")
+ build("publish") {
+ assertSuccessful()
+ assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0.aar")
+ assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0-sources.jar")
+ assertNoSuchFile(groupDir + "lib-androidlib-debug")
+ projectDir.resolve(groupDir).deleteRecursively()
+ }
+
+ // Enable publishing for all Android variants:
+ gradleBuildScript("lib").appendText("\nkotlin.android('androidLib') { publishAllLibraryVariants() }")
+ build("publish") {
+ assertSuccessful()
+ assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0.aar")
+ assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0-sources.jar")
+ assertFileExists(groupDir + "lib-androidlib-debug/1.0/lib-androidlib-debug-1.0.aar")
+ assertFileExists(groupDir + "lib-androidlib-debug/1.0/lib-androidlib-debug-1.0-sources.jar")
+ projectDir.resolve(groupDir).deleteRecursively()
+ }
+
+ // Then group the variants by flavor and check that only one publication is created:
+ gradleBuildScript("lib").appendText("\nkotlin.android('androidLib').publishLibraryVariantsGroupedByFlavor = true")
+ build("publish") {
+ assertSuccessful()
+ assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0.aar")
+ assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0-sources.jar")
+ assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0-debug.aar")
+ assertFileExists(groupDir + "lib-androidlib/1.0/lib-androidlib-1.0-debug-sources.jar")
+ projectDir.resolve(groupDir).deleteRecursively()
+ }
+
+ // Add one flavor dimension with two flavors, check that the flavors produce grouped publications:
+ gradleBuildScript("lib").appendText(
+ "\nandroid { flavorDimensions('foo'); productFlavors { foo1 { dimension 'foo' }; foo2 { dimension 'foo' } } }"
+ )
+ build("publish") {
+ assertSuccessful()
+ listOf("foo1", "foo2").forEach { flavor ->
+ assertFileExists(groupDir + "lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0.aar")
+ assertFileExists(groupDir + "lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-sources.jar")
+ assertFileExists(groupDir + "lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-debug.aar")
+ assertFileExists(groupDir + "lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-debug-sources.jar")
+ }
+ projectDir.resolve(groupDir).deleteRecursively()
+ }
+
+ // Disable the grouping and check that all the variants are published under separate artifactIds:
+ gradleBuildScript("lib").appendText(
+ "\nkotlin.android('androidLib') { publishLibraryVariantsGroupedByFlavor = false }"
+ )
+ build("publish") {
+ assertSuccessful()
+ listOf("foo1", "foo2").forEach { flavor ->
+ listOf("-debug", "").forEach { buildType ->
+ assertFileExists(groupDir + "lib-androidlib-$flavor$buildType/1.0/lib-androidlib-$flavor$buildType-1.0.aar")
+ assertFileExists(groupDir + "lib-androidlib-$flavor$buildType/1.0/lib-androidlib-$flavor$buildType-1.0-sources.jar")
+ }
+ }
+ projectDir.resolve(groupDir).deleteRecursively()
+ }
+
+ // Convert the 'app' project to a library, publish the two without metadata,
+ // check that the dependencies in the POMs are correctly rewritten:
+ gradleSettingsScript().modify { it.replace("enableFeaturePreview", "//") }
+ gradleBuildScript("app").modify {
+ it.replace("com.android.application", "com.android.library")
+ .replace("applicationId", "//") + "\n" + """
+ apply plugin: 'maven-publish'
+ publishing { repositories { maven { url = uri("${'$'}buildDir/repo") } } }
+ kotlin.android('androidApp') { publishAllLibraryVariants() }
+ android { flavorDimensions('foo'); productFlavors { foo1 { dimension 'foo' }; foo2 { dimension 'foo' } } }
+ """.trimIndent()
+ }
+ build("publish") {
+ assertSuccessful()
+ val appGroupDir = "app/build/repo/com/example/"
+ listOf("foo1", "foo2").forEach { flavor ->
+ listOf("-debug", "").forEach { buildType ->
+ assertFileExists(appGroupDir + "app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.aar")
+ assertFileExists(appGroupDir + "app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0-sources.jar")
+ val pomText = projectDir.resolve(
+ appGroupDir + "app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.pom"
+ ).readText()
+ assertTrue { "lib-androidlib-$flavor$buildType" in pomText }
+ }
+ }
+ projectDir.resolve(groupDir).deleteRecursively()
+ }
}
@Test
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android/app/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android/app/build.gradle
index 3f93e8853c0..a53f35cae83 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android/app/build.gradle
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android/app/build.gradle
@@ -2,6 +2,9 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-multiplatform'
apply plugin: 'kotlin-android-extensions'
+group 'com.example'
+version '1.0'
+
android {
compileSdkVersion 27
defaultConfig {
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android/lib/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android/lib/build.gradle
index 8ee1a222fcb..70703d9f6f8 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android/lib/build.gradle
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android/lib/build.gradle
@@ -1,5 +1,9 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-multiplatform'
+apply plugin: 'maven-publish'
+
+group 'com.example'
+version '1.0'
android {
compileSdkVersion 27
@@ -51,4 +55,12 @@ kotlin {
fromPreset(presets.jvm, 'jvmLib')
fromPreset(presets.js, 'jsLib')
}
+}
+
+publishing {
+ repositories {
+ maven {
+ url = uri("$buildDir/repo")
+ }
+ }
}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/agp25/kotlin/org/jetbrains/kotlin/gradle/plugin/android/Android25ProjectHandler.kt b/libraries/tools/kotlin-gradle-plugin/src/agp25/kotlin/org/jetbrains/kotlin/gradle/plugin/android/Android25ProjectHandler.kt
index 5ac1bfe660a..ac330fde923 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/agp25/kotlin/org/jetbrains/kotlin/gradle/plugin/android/Android25ProjectHandler.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/agp25/kotlin/org/jetbrains/kotlin/gradle/plugin/android/Android25ProjectHandler.kt
@@ -6,6 +6,7 @@ import com.android.build.gradle.tasks.MergeResources
import com.android.builder.model.SourceProvider
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
import org.jetbrains.kotlin.gradle.internal.KaptTask
@@ -83,6 +84,10 @@ class Android25ProjectHandler(kotlinConfigurationTools: KotlinConfigurationTools
override fun getVariantName(variant: BaseVariant): String = variant.name
+ override fun getFlavorNames(variant: BaseVariant): List = variant.productFlavors.map { it.name }
+
+ override fun getBuildTypeName(variant: BaseVariant): String = variant.buildType.name
+
override fun getTestedVariantData(variantData: BaseVariant): BaseVariant? = when (variantData) {
is TestVariant -> variantData.testedVariant
is UnitTestVariant -> variantData.testedVariant as? BaseVariant
@@ -114,6 +119,9 @@ class Android25ProjectHandler(kotlinConfigurationTools: KotlinConfigurationTools
return project.files(Callable { variantData.mergeResources?.computeResourceSetList0() ?: emptyList() })
}
+ override fun getLibraryOutputTask(variant: BaseVariant): AbstractArchiveTask? =
+ (variant as? LibraryVariant)?.packageLibrary
+
override fun setUpDependencyResolution(variant: BaseVariant, compilation: KotlinJvmAndroidCompilation) {
val project = compilation.target.project
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt
index a0a9814bfb2..cabe474ffb2 100755
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt
@@ -17,6 +17,7 @@ import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.CompileClasspathNormalizer
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.SourceSet
+import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.jvm.tasks.Jar
@@ -658,10 +659,13 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationT
abstract fun forEachVariant(project: Project, action: (V) -> Unit): Unit
abstract fun getTestedVariantData(variantData: V): V?
abstract fun getResDirectories(variantData: V): FileCollection
+ abstract fun getVariantName(variant: V): String
+ abstract fun getFlavorNames(variant: V): List
+ abstract fun getBuildTypeName(variant: V): String
+ abstract fun getLibraryOutputTask(variant: V): AbstractArchiveTask?
protected abstract fun getSourceProviders(variantData: V): Iterable
protected abstract fun getAllJavaSources(variantData: V): Iterable
- protected abstract fun getVariantName(variant: V): String
protected abstract fun getJavaTask(variantData: V): AbstractCompile?
protected abstract fun addJavaSourceDirectoryToVariantModel(variantData: V, javaSourceDirectory: File): Unit
@@ -705,11 +709,11 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationT
ext.addExtension(KOTLIN_OPTIONS_DSL_NAME, kotlinOptions)
project.afterEvaluate { project ->
- forEachVariant(project) { variant ->
- val variantName = getVariantName(variant)
- val compilation = kotlinAndroidTarget.compilations.create(variantName)
- setUpDependencyResolution(variant, compilation)
- }
+ forEachVariant(project) { variant ->
+ val variantName = getVariantName(variant)
+ val compilation = kotlinAndroidTarget.compilations.create(variantName)
+ setUpDependencyResolution(variant, compilation)
+ }
val androidPluginIds = listOf(
"android", "com.android.application", "android-library", "com.android.library",
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/LegacyAndroidAndroidProjectHandler.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/LegacyAndroidAndroidProjectHandler.kt
index 09337b34324..fa776ae5d8a 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/LegacyAndroidAndroidProjectHandler.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/LegacyAndroidAndroidProjectHandler.kt
@@ -15,6 +15,7 @@ import com.android.builder.model.SourceProvider
import org.gradle.api.Project
import org.gradle.api.ProjectConfigurationException
import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.kotlin.gradle.internal.KaptTask
import org.jetbrains.kotlin.gradle.internal.KaptVariantData
@@ -44,6 +45,9 @@ internal class LegacyAndroidAndroidProjectHandler(kotlinConfigurationTools: Kotl
variantManager.variantDataList.forEach(action)
}
+ override fun getLibraryOutputTask(variant: BaseVariantData): AbstractArchiveTask? =
+ null // We don't support publishing AARs of old Android in MPP
+
override fun wireKotlinTasks(
project: Project,
compilation: KotlinJvmAndroidCompilation,
@@ -92,7 +96,16 @@ internal class LegacyAndroidAndroidProjectHandler(kotlinConfigurationTools: Kotl
override fun getVariantName(variant: BaseVariantData): String = variant.name
- override fun checkVariantIsValid(variant: BaseVariantData): Unit {
+ private fun operationNotSupportedInOldAndroidPlugin(): Nothing =
+ throw UnsupportedOperationException("Publishing is not supported for Android Gradle plugin 2.3.x and below. Please upgrade to 3.0+")
+
+ override fun getFlavorNames(variant: BaseVariantData): List =
+ operationNotSupportedInOldAndroidPlugin()
+
+ override fun getBuildTypeName(variant: BaseVariantData): String =
+ operationNotSupportedInOldAndroidPlugin()
+
+ override fun checkVariantIsValid(variant: BaseVariantData) {
if (AndroidGradleWrapper.isJackEnabled(variant)) {
throw ProjectConfigurationException(
"Kotlin Gradle plugin does not support the deprecated Jack toolchain.\n" +
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt
index 4847a09f501..32092b35be8 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt
@@ -176,7 +176,13 @@ class KotlinMultiplatformPlugin(
(targets.getByName(METADATA_TARGET_NAME) as AbstractKotlinTarget).createMavenPublications(publishing.publications)
targets
.withType(AbstractKotlinTarget::class.java).matching { it.publishable && it.name != METADATA_TARGET_NAME }
- .all { it.createMavenPublications(publishing.publications) }
+ .all {
+ if (it is KotlinAndroidTarget)
+ // Android targets have their variants created in afterEvaluate; TODO handle this better?
+ project.whenEvaluated { it.createMavenPublications(publishing.publications) }
+ else
+ it.createMavenPublications(publishing.publications)
+ }
}
project.components.add(kotlinSoftwareComponent)
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt
index 3f031ab27b7..95df74a4048 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt
@@ -50,7 +50,7 @@ class DefaultKotlinUsageContext(
private val overrideConfigurationArtifacts: Set? = null
) : KotlinUsageContext {
- val kotlinTarget: KotlinTarget get() = compilation.target
+ private val kotlinTarget: KotlinTarget get() = compilation.target
private val project: Project get() = kotlinTarget.project
override fun getUsage(): Usage = usage
@@ -87,21 +87,28 @@ class DefaultKotlinUsageContext(
}
private fun rewriteMppDependenciesToTargetModuleDependencies(
- context: DefaultKotlinUsageContext,
- configuration: Configuration
-): Set = with(context.kotlinTarget.project) {
- val target = context.kotlinTarget
- val moduleDependencies = configuration.incoming.dependencies.withType(ModuleDependency::class.java).ifEmpty { return emptySet() }
-
- val targetMainCompilation = target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)
- ?: return moduleDependencies // Android is not yet supported
+ usageContext: KotlinUsageContext,
+ fromConfiguration: Configuration
+): Set = with(usageContext.compilation.target.project) {
+ val compilation = usageContext.compilation
+ val moduleDependencies = fromConfiguration.incoming.dependencies.withType(ModuleDependency::class.java).ifEmpty { return emptySet() }
val targetCompileDependenciesConfiguration = project.configurations.getByName(
- when (context.dependencyConfigurationName) {
- target.apiElementsConfigurationName -> targetMainCompilation.compileDependencyConfigurationName
- target.runtimeElementsConfigurationName ->
- (targetMainCompilation as KotlinCompilationToRunnableFiles).runtimeDependencyConfigurationName
- else -> error("unexpected configuration")
+ when (compilation) {
+ is KotlinJvmAndroidCompilation -> {
+ // TODO handle Android configuration names in a general way once we drop AGP < 3.0.0
+ val variantName = compilation.name
+ when (usageContext.usage.name) {
+ Usage.JAVA_API -> variantName + "CompileClasspath"
+ Usage.JAVA_RUNTIME_JARS -> variantName + "RuntimeClasspath"
+ else -> error("Unexpected Usage for usage context: ${usageContext.usage}")
+ }
+ }
+ else -> when (usageContext.usage.name) {
+ Usage.JAVA_API -> compilation.compileDependencyConfigurationName
+ Usage.JAVA_RUNTIME_JARS -> (compilation as KotlinCompilationToRunnableFiles).runtimeDependencyConfigurationName
+ else -> error("Unexpected Usage for usage context: ${usageContext.usage}")
+ }
}
)
@@ -128,16 +135,18 @@ private fun rewriteMppDependenciesToTargetModuleDependencies(
val resolvedToConfiguration = resolved.configuration
- val dependencyTarget = dependencyProjectKotlinExtension.targets.singleOrNull {
- resolvedToConfiguration in setOf(
- it.apiElementsConfigurationName,
- it.runtimeElementsConfigurationName,
- it.defaultConfigurationName
- )
- } ?: return@map dependency
+ val dependencyTargetComponent: KotlinTargetComponent = run {
+ dependencyProjectKotlinExtension.targets.forEach { target ->
+ target.components.forEach { component ->
+ if (component.findUsageContext(resolvedToConfiguration) != null)
+ return@run component
+ }
+ }
+ // Failed to find a matching component:
+ return@map dependency
+ }
- val dependencyTargetComponent = dependencyTarget.components.single() // FIXME handle multiple components later
- val publicationDelegate = (dependencyTargetComponent as KotlinVariant).publicationDelegate
+ val publicationDelegate = (dependencyTargetComponent as? KotlinTargetComponentWithPublication)?.publicationDelegate
dependencies.module(
listOf(
@@ -150,3 +159,19 @@ private fun rewriteMppDependenciesToTargetModuleDependencies(
}
}.toSet()
}
+
+internal fun KotlinTargetComponent.findUsageContext(configurationName: String): UsageContext? {
+ val usageContexts = when (this) {
+ is KotlinVariantWithMetadataDependency -> originalUsages
+ is SoftwareComponentInternal -> usages
+ else -> emptySet()
+ }
+ return usageContexts.find { usageContext ->
+ if (usageContext !is KotlinUsageContext) return@find false
+ val compilation = usageContext.compilation
+ configurationName in compilation.relatedConfigurationNames ||
+ configurationName == compilation.target.apiElementsConfigurationName ||
+ configurationName == compilation.target.runtimeElementsConfigurationName ||
+ configurationName == compilation.target.defaultConfigurationName
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt
index 5701624c9bb..8e5dafdcc44 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt
@@ -264,6 +264,9 @@ class KotlinJvmAndroidCompilation(
) : AbstractKotlinCompilationToRunnableFiles(target, name) {
override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile
get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+ override val relatedConfigurationNames: List
+ get() = super.relatedConfigurationNames + listOf("${name}ApiElements", "${name}RuntimeElements")
}
class KotlinJsCompilation(
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt
index 1581b1a0a1a..a22d179c3a9 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt
@@ -5,10 +5,7 @@
package org.jetbrains.kotlin.gradle.plugin.mpp
import groovy.lang.Closure
-import org.gradle.api.Action
-import org.gradle.api.DomainObjectSet
-import org.gradle.api.NamedDomainObjectContainer
-import org.gradle.api.Project
+import org.gradle.api.*
import org.gradle.api.artifacts.ConfigurablePublishArtifact
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.PublishArtifact
@@ -178,8 +175,162 @@ open class KotlinAndroidTarget(
override val compilations: NamedDomainObjectContainer =
project.container(compilationFactory.itemClass, compilationFactory)
- override val components: Set
- get() = emptySet()
+ /** Names of the Android library variants that should be published from the target's project within the default publications which are
+ * set up if the `maven-publish` Gradle plugin is applied.
+ *
+ * Item examples:
+ * * 'release' (in case no product flavors were defined)
+ * * 'fooRelease' (for the release build type of a flavor 'foo')
+ * * 'fooBarRelease' (for the release build type multi-dimensional flavors 'foo' and 'bar').
+ *
+ * If set to null, which can also be done with [publishAllLibraryVariants],
+ * all library variants will be published, but not test or application variants. */
+ var publishLibraryVariants: List? = listOf()
+
+ /** Add Android library variant names to [publishLibraryVariants]. */
+ fun publishLibraryVariants(vararg names: String) {
+ publishLibraryVariants = publishLibraryVariants.orEmpty() + names
+ }
+
+ /** Set up all of the Android library variants to be published from this target's project within the default publications, which are
+ * set up if the `maven-publish` Gradle plugin is applied. This overrides the variants chosen with [publishLibraryVariants] */
+ fun publishAllLibraryVariants() {
+ publishLibraryVariants = null
+ }
+
+ /** If true, a publication will be created per merged product flavor, with the build types used as classifiers for the artifacts
+ * published within each publication. If set to false, each Android variant will have a separate publication. */
+ var publishLibraryVariantsGroupedByFlavor = false
+
+ private fun checkPublishLibraryVariantsExist() {
+ // Capture type parameter T
+ fun AbstractAndroidProjectHandler.getVariantNames() =
+ mutableSetOf().apply { forEachVariant(project) { add(getVariantName(it)) } }
+
+ val variantNames =
+ KotlinAndroidPlugin.androidTargetHandler(project.getKotlinPluginVersion()!!, this)
+ .getVariantNames()
+
+ val missingVariants =
+ publishLibraryVariants?.minus(variantNames).orEmpty()
+
+ if (missingVariants.isNotEmpty())
+ throw InvalidUserDataException(
+ "Kotlin target '$targetName' tried to set up publishing for Android library variants that are not present " +
+ "in the project:\n" + missingVariants.joinToString("\n") { "* $it" } +
+ "\nCheck the 'publishLibraryVariants' property, it should point to existing Android build variants."
+ )
+ }
+
+ override val components by lazy {
+ checkPublishLibraryVariantsExist()
+
+ KotlinAndroidPlugin.androidTargetHandler(project.getKotlinPluginVersion()!!, this).doCreateComponents()
+ .also { project.components.addAll(it) }
+ }
+
+ // Capture the type parameter T for `AbstractAndroidProjectHandler`
+ private fun AbstractAndroidProjectHandler.doCreateComponents(): Set {
+ if (!isGradleVersionAtLeast(4, 7))
+ return emptySet()
+
+ val publishableVariants = mutableListOf()
+ .apply { forEachVariant(project) { add(it) } }
+ .toList() // Defensive copy against unlikely modification by the lambda that captures the list above in forEachVariant { }
+ .filter { getLibraryOutputTask(it) != null && publishLibraryVariants?.contains(getVariantName(it)) ?: true }
+
+ val publishableVariantGroups = publishableVariants.groupBy { variant ->
+ val flavorNames = getFlavorNames(variant)
+ if (publishLibraryVariantsGroupedByFlavor) {
+ // For each flavor, we group its variants (which differ only in the build type) in a single component in order to publish
+ // all of the build types of the flavor as a single module with the build type as the classifier of the artifacts
+ flavorNames
+ } else {
+ flavorNames + getBuildTypeName(variant)
+ }
+ }
+
+ return publishableVariantGroups.map { (flavorGroupNameParts, androidVariants) ->
+ val nestedVariants = androidVariants.mapTo(mutableSetOf()) { androidVariant ->
+ val androidVariantName = getVariantName(androidVariant)
+ val compilation = compilations.getByName(androidVariantName)
+ val buildTypeName = getBuildTypeName(androidVariant)
+ val usageContexts = createAndroidUsageContexts(androidVariant, compilation, getFlavorNames(androidVariant), buildTypeName)
+ createKotlinVariant(
+ lowerCamelCaseName(compilation.target.name, *flavorGroupNameParts.toTypedArray()),
+ compilation,
+ usageContexts
+ ).apply {
+ if (!publishLibraryVariantsGroupedByFlavor) {
+ defaultArtifactIdSuffix =
+ lowerSpinalCaseName(getFlavorNames(androidVariant) + getBuildTypeName(androidVariant).takeIf { it != "release" })
+ .takeIf { it.isNotEmpty() }
+ }
+ }
+ }
+
+ if (publishLibraryVariantsGroupedByFlavor) {
+ JointAndroidKotlinTargetComponent(
+ this@KotlinAndroidTarget,
+ nestedVariants,
+ flavorGroupNameParts
+ )
+ } else {
+ nestedVariants.single()
+ } as KotlinTargetComponent
+ }.toSet()
+ }
+
+ private fun AbstractAndroidProjectHandler.createAndroidUsageContexts(
+ variant: T,
+ compilation: KotlinCompilation<*>,
+ flavorNames: List,
+ buildTypeName: String
+ ): Set {
+ val artifactClassifier = buildTypeName.takeIf { it != "release" && publishLibraryVariantsGroupedByFlavor }
+
+ val sourcesJarArtifact = sourcesJarArtifact(
+ compilation, compilation.disambiguateName(""),
+ lowerSpinalCaseName(compilation.target.name, *flavorNames.toTypedArray(), buildTypeName.takeIf { it != "release" }),
+ classifierPrefix = artifactClassifier
+ )
+
+ val publishWithKotlinMetadata = (project.kotlinExtension as? KotlinMultiplatformExtension)?.isGradleMetadataAvailable
+ ?: true // In non-MPP project, these usage contexts do not get published anyway
+
+ val variantName = getVariantName(variant)
+ val outputTask = getLibraryOutputTask(variant) ?: return emptySet()
+ val artifact = run {
+ val archivesConfigurationName = lowerCamelCaseName(targetName, variantName, "archives")
+ project.configurations.maybeCreate(archivesConfigurationName).apply {
+ isCanBeConsumed = false
+ isCanBeResolved = false
+ }
+ project.artifacts.add(archivesConfigurationName, outputTask) { artifact ->
+ artifact.classifier = artifactClassifier
+ }
+ }
+
+ val apiElementsConfigurationName = lowerCamelCaseName(variantName, "apiElements")
+ val runtimeElementsConfigurationName = lowerCamelCaseName(variantName, "runtimeElements")
+
+ // Here, `JAVA_API` and `JAVA_RUNTIME_JARS` are used intentionally as Gradle needs this for
+ // ordering of the usage contexts (prioritizing the dependencies) when merging them into the POM;
+ // These Java usages should not be replaced with the custom Kotlin usages.
+ return listOf(
+ apiElementsConfigurationName to JAVA_API,
+ runtimeElementsConfigurationName to JAVA_RUNTIME_JARS
+ ).mapTo(mutableSetOf()) { (dependencyConfigurationName, usageName) ->
+ DefaultKotlinUsageContext(
+ compilation,
+ project.usageByName(usageName),
+ dependencyConfigurationName,
+ publishWithKotlinMetadata,
+ sourcesJarArtifact,
+ overrideConfigurationArtifacts = setOf(artifact)
+ )
+ }
+ }
}
open class KotlinWithJavaTarget(
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinVariants.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinVariants.kt
index bd3b4814097..8cd91f9e830 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinVariants.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinVariants.kt
@@ -116,3 +116,22 @@ class KotlinVariantWithMetadataDependency(
override fun getGlobalExcludes(): Set = emptySet()
}
}
+
+class JointAndroidKotlinTargetComponent(
+ override val target: KotlinAndroidTarget,
+ private val nestedVariants: Set,
+ val flavorNames: List
+) : KotlinTargetComponentWithCoordinatesAndPublication, SoftwareComponentInternal {
+
+ override fun getUsages(): Set = nestedVariants.flatMap { it.usages }.toSet()
+
+ override fun getName(): String = lowerCamelCaseName(target.targetName, *flavorNames.toTypedArray())
+
+ override val publishable: Boolean
+ get() = target.publishable
+
+ override val defaultArtifactId: String =
+ lowerSpinalCaseName(target.project.name, target.targetName, *flavorNames.toTypedArray())
+
+ override var publicationDelegate: MavenPublication? = null
+}