Publish Android variants from multiplatform projects
Also update the code for rewriting the dependencies, as Android variants have their dependencies configurations formed a bit differently, and also introduce the logic for choosing the right component by the configuration name the dependency resolves to. Issue #KT-27535 Fixed
This commit is contained in:
+102
-1
@@ -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 { "<artifactId>lib-androidlib-$flavor$buildType</artifactId>" in pomText }
|
||||
}
|
||||
}
|
||||
projectDir.resolve(groupDir).deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
@@ -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 {
|
||||
|
||||
+12
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -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<String> = 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
|
||||
|
||||
|
||||
+10
-6
@@ -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<V>(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<String>
|
||||
abstract fun getBuildTypeName(variant: V): String
|
||||
abstract fun getLibraryOutputTask(variant: V): AbstractArchiveTask?
|
||||
|
||||
protected abstract fun getSourceProviders(variantData: V): Iterable<SourceProvider>
|
||||
protected abstract fun getAllJavaSources(variantData: V): Iterable<File>
|
||||
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<V>(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",
|
||||
|
||||
+14
-1
@@ -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<out BaseVariantOutputData>): 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<out BaseVariantOutputData>): String = variant.name
|
||||
|
||||
override fun checkVariantIsValid(variant: BaseVariantData<out BaseVariantOutputData>): 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<out BaseVariantOutputData>): List<String> =
|
||||
operationNotSupportedInOldAndroidPlugin()
|
||||
|
||||
override fun getBuildTypeName(variant: BaseVariantData<out BaseVariantOutputData>): String =
|
||||
operationNotSupportedInOldAndroidPlugin()
|
||||
|
||||
override fun checkVariantIsValid(variant: BaseVariantData<out BaseVariantOutputData>) {
|
||||
if (AndroidGradleWrapper.isJackEnabled(variant)) {
|
||||
throw ProjectConfigurationException(
|
||||
"Kotlin Gradle plugin does not support the deprecated Jack toolchain.\n" +
|
||||
|
||||
+7
-1
@@ -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)
|
||||
|
||||
+48
-23
@@ -50,7 +50,7 @@ class DefaultKotlinUsageContext(
|
||||
private val overrideConfigurationArtifacts: Set<PublishArtifact>? = 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<ModuleDependency> = 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<ModuleDependency> = 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
|
||||
}
|
||||
}
|
||||
+3
@@ -264,6 +264,9 @@ class KotlinJvmAndroidCompilation(
|
||||
) : AbstractKotlinCompilationToRunnableFiles<KotlinJvmOptions>(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<String>
|
||||
get() = super.relatedConfigurationNames + listOf("${name}ApiElements", "${name}RuntimeElements")
|
||||
}
|
||||
|
||||
class KotlinJsCompilation(
|
||||
|
||||
+157
-6
@@ -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<out KotlinJvmAndroidCompilation> =
|
||||
project.container(compilationFactory.itemClass, compilationFactory)
|
||||
|
||||
override val components: Set<KotlinTargetComponent>
|
||||
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<String>? = 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 <T> AbstractAndroidProjectHandler<T>.getVariantNames() =
|
||||
mutableSetOf<String>().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 <T> AbstractAndroidProjectHandler<T>.doCreateComponents(): Set<KotlinTargetComponent> {
|
||||
if (!isGradleVersionAtLeast(4, 7))
|
||||
return emptySet()
|
||||
|
||||
val publishableVariants = mutableListOf<T>()
|
||||
.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 <T> AbstractAndroidProjectHandler<T>.createAndroidUsageContexts(
|
||||
variant: T,
|
||||
compilation: KotlinCompilation<*>,
|
||||
flavorNames: List<String>,
|
||||
buildTypeName: String
|
||||
): Set<DefaultKotlinUsageContext> {
|
||||
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<KotlinOptionsType : KotlinCommonOptions>(
|
||||
|
||||
+19
@@ -116,3 +116,22 @@ class KotlinVariantWithMetadataDependency(
|
||||
override fun getGlobalExcludes(): Set<ExcludeRule> = emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
class JointAndroidKotlinTargetComponent(
|
||||
override val target: KotlinAndroidTarget,
|
||||
private val nestedVariants: Set<KotlinVariant>,
|
||||
val flavorNames: List<String>
|
||||
) : KotlinTargetComponentWithCoordinatesAndPublication, SoftwareComponentInternal {
|
||||
|
||||
override fun getUsages(): Set<UsageContext> = 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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user