Fix user-specified attributes in Android targets (KT-27714)
Ensure that custom target and compilation attributes are copied to relevant configurations of Android targets as well, which did not happen because of Android variants (and missing simple apiElements, runtimeElements) and Android compilations being created late in the project configuration. Issue #KT-27714 Fixed
This commit is contained in:
+64
@@ -196,7 +196,71 @@ class KotlinAndroid32GradleIT : KotlinAndroid3GradleIT(androidGradlePluginVersio
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCustomAttributesInAndroidTargets() = with(Project("new-mpp-android", GradleVersionRequired.AtLeast("4.7"))) {
|
||||
// Test the fix for KT-27714
|
||||
|
||||
setupWorkingDir()
|
||||
|
||||
// Enable publishing for all Android variants:
|
||||
gradleBuildScript("lib").appendText("\nkotlin.android('androidLib') { publishAllLibraryVariants() }")
|
||||
|
||||
val groupDir = "lib/build/repo/com/example/"
|
||||
|
||||
build("publish") {
|
||||
assertSuccessful()
|
||||
|
||||
// Also check that custom user-specified attributes are written in all Android modules metadata:
|
||||
assertFileContains(
|
||||
groupDir + "lib-androidlib/1.0/lib-androidlib-1.0.module",
|
||||
"\"com.example.target\": \"androidLib\"",
|
||||
"\"com.example.compilation\": \"release\""
|
||||
)
|
||||
assertFileContains(
|
||||
groupDir + "lib-androidlib-debug/1.0/lib-androidlib-debug-1.0.module",
|
||||
"\"com.example.target\": \"androidLib\"",
|
||||
"\"com.example.compilation\": \"debug\""
|
||||
)
|
||||
|
||||
projectDir.resolve(groupDir).deleteRecursively()
|
||||
}
|
||||
|
||||
// Check that the consumer side uses custom attributes specified in the target and compilations:
|
||||
run {
|
||||
val appBuildScriptBackup = gradleBuildScript("app").readText()
|
||||
|
||||
gradleBuildScript("app").appendText(
|
||||
"\n" + """
|
||||
kotlin.targets.androidApp.attributes.attribute(
|
||||
Attribute.of("com.example.target", String),
|
||||
"notAndroidLib"
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
build(":app:compileDebugKotlinAndroidApp") {
|
||||
assertFailed() // dependency resolution should fail
|
||||
assertContains("Required com.example.target 'notAndroidLib'")
|
||||
}
|
||||
|
||||
gradleBuildScript("app").writeText(
|
||||
appBuildScriptBackup + "\n" + """
|
||||
kotlin.targets.androidApp.compilations.all {
|
||||
attributes.attribute(
|
||||
Attribute.of("com.example.compilation", String),
|
||||
"notDebug"
|
||||
)
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
build(":app:compileDebugKotlinAndroidApp") {
|
||||
assertFailed()
|
||||
assertContains("Required com.example.compilation 'notDebug'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+11
-1
@@ -66,7 +66,17 @@ kotlin {
|
||||
}
|
||||
|
||||
targets {
|
||||
fromPreset(presets.android, 'androidLib')
|
||||
fromPreset(presets.android, 'androidLib') {
|
||||
attributes {
|
||||
attribute(Attribute.of("com.example.target", String), "androidLib")
|
||||
}
|
||||
compilations.all {
|
||||
attributes {
|
||||
attribute(Attribute.of("com.example.compilation", String), compilationName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fromPreset(presets.jvm, 'jvmLib')
|
||||
fromPreset(presets.js, 'jsLib')
|
||||
}
|
||||
|
||||
+3
@@ -2,6 +2,7 @@ package org.jetbrains.kotlin.gradle.plugin
|
||||
|
||||
import com.android.build.gradle.BaseExtension
|
||||
import com.android.build.gradle.BasePlugin
|
||||
import com.android.build.gradle.api.BaseVariant
|
||||
import com.android.builder.model.SourceProvider
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.*
|
||||
@@ -748,6 +749,8 @@ abstract class AbstractAndroidProjectHandler<V>(private val kotlinConfigurationT
|
||||
// in afterEvaluate, a user's build script might have already attached item handlers to the compilations container, and those
|
||||
// handlers might break when fired on a compilation that is not yet properly configured (e.g. KT-29964):
|
||||
kotlinAndroidTarget.compilationFactory.create(variantName).let { compilation ->
|
||||
compilation.androidVariant = variant as BaseVariant
|
||||
|
||||
setUpDependencyResolution(variant, compilation)
|
||||
|
||||
processVariant(variant, compilation, project, ext, plugin, kotlinOptions, kotlinConfigurationTools.kotlinTasksProvider)
|
||||
|
||||
+30
-21
@@ -78,8 +78,6 @@ class KotlinMultiplatformPlugin(
|
||||
configureDefaultVersionsResolutionStrategy(project, kotlinPluginVersion)
|
||||
configureSourceSets(project)
|
||||
|
||||
setUpConfigurationAttributes(project)
|
||||
|
||||
// set up metadata publishing
|
||||
targetsFromPreset.fromPreset(
|
||||
KotlinMetadataTargetPreset(project, instantiator, fileResolver, kotlinPluginVersion),
|
||||
@@ -87,6 +85,8 @@ class KotlinMultiplatformPlugin(
|
||||
)
|
||||
configurePublishingWithMavenPublish(project)
|
||||
|
||||
setUpConfigurationAttributes(project)
|
||||
|
||||
// propagate compiler plugin options to the source set language settings
|
||||
setupCompilerPluginOptions(project)
|
||||
|
||||
@@ -226,40 +226,49 @@ class KotlinMultiplatformPlugin(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The attributes attached to the targets and compilations need to be propagated to the relevant Gradle configurations:
|
||||
* 1. Output configurations of each target need the corresponding compilation's attributes (and, indirectly, the target's attributes)
|
||||
* 2. Resolvable configurations of each compilation need the compilation's attributes
|
||||
*/
|
||||
private fun setUpConfigurationAttributes(project: Project) {
|
||||
val targets = project.multiplatformExtension.targets
|
||||
|
||||
project.afterEvaluate {
|
||||
targets.all { target ->
|
||||
val mainCompilationAttributes = target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)?.attributes
|
||||
?: return@all
|
||||
fun copyAttributes(from: AttributeContainer, to: AttributeContainer) {
|
||||
fun <T> copyAttribute(key: Attribute<T>, from: AttributeContainer, to: AttributeContainer) {
|
||||
to.attribute(key, from.getAttribute(key)!!)
|
||||
}
|
||||
|
||||
fun <T> copyAttribute(key: Attribute<T>, from: AttributeContainer, to: AttributeContainer) {
|
||||
to.attribute(key, from.getAttribute(key)!!)
|
||||
from.keySet().forEach { key -> copyAttribute(key, from, to) }
|
||||
}
|
||||
|
||||
listOf(
|
||||
target.apiElementsConfigurationName,
|
||||
target.runtimeElementsConfigurationName,
|
||||
target.defaultConfigurationName
|
||||
)
|
||||
.mapNotNull { configurationName -> target.project.configurations.findByName(configurationName) }
|
||||
.forEach { configuration ->
|
||||
mainCompilationAttributes.keySet().forEach { key ->
|
||||
copyAttribute(key, mainCompilationAttributes, configuration.attributes)
|
||||
// To copy the attributes to the output configurations, find those output configurations and their producing compilations
|
||||
// based on the target's components:
|
||||
val outputConfigurationsWithCompilations =
|
||||
target.components.filterIsInstance<KotlinVariant>().flatMap { kotlinVariant ->
|
||||
kotlinVariant.usages.filterIsInstance<KotlinUsageContext>().mapNotNull { usageContext ->
|
||||
project.configurations.findByName(usageContext.dependencyConfigurationName)?.let { configuration ->
|
||||
configuration to usageContext.compilation
|
||||
}
|
||||
}
|
||||
}
|
||||
} + listOfNotNull(
|
||||
target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)?.let { mainCompilation ->
|
||||
project.configurations.findByName(target.defaultConfigurationName)?.to(mainCompilation)
|
||||
}
|
||||
)
|
||||
|
||||
outputConfigurationsWithCompilations.forEach { (configuration, compilation) ->
|
||||
copyAttributes(compilation.attributes, configuration.attributes)
|
||||
}
|
||||
|
||||
target.compilations.all { compilation ->
|
||||
val compilationAttributes = compilation.attributes
|
||||
|
||||
compilation.relatedConfigurationNames
|
||||
.mapNotNull { configurationName -> target.project.configurations.findByName(configurationName) }
|
||||
.forEach { configuration ->
|
||||
compilationAttributes.keySet().forEach { key ->
|
||||
copyAttribute(key, compilationAttributes, configuration.attributes)
|
||||
}
|
||||
}
|
||||
.forEach { configuration -> copyAttributes(compilationAttributes, configuration.attributes) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -20,7 +20,6 @@ import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
|
||||
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
|
||||
import java.util.*
|
||||
import java.util.concurrent.Callable
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
internal fun KotlinCompilation<*>.composeName(prefix: String? = null, suffix: String? = null): String {
|
||||
val compilationNamePart = compilationName.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }
|
||||
|
||||
+9
-4
@@ -106,10 +106,15 @@ class KotlinVariantWithMetadataDependency(
|
||||
}
|
||||
|
||||
private fun metadataDependency(): ModuleDependency {
|
||||
val metadataPublication = (metadataTarget.kotlinComponents.single() as KotlinTargetComponentWithPublication).publicationDelegate!!
|
||||
val metadataGroupId = metadataPublication.groupId
|
||||
val metadataArtifactId = metadataPublication.artifactId
|
||||
val metadataVersion = metadataPublication.version
|
||||
val metadataComponent = metadataTarget.kotlinComponents.single() as KotlinTargetComponentWithPublication
|
||||
val project = metadataTarget.project
|
||||
|
||||
// The metadata component may not be published, e.g. if the whole project is not published:
|
||||
val metadataPublication: MavenPublication? = metadataComponent.publicationDelegate
|
||||
|
||||
val metadataGroupId = metadataPublication?.groupId ?: project.group
|
||||
val metadataArtifactId = metadataPublication?.artifactId ?: metadataComponent.defaultArtifactId
|
||||
val metadataVersion = metadataPublication?.version ?: project.version
|
||||
return target.project.dependencies.module("$metadataGroupId:$metadataArtifactId:$metadataVersion") as ModuleDependency
|
||||
}
|
||||
|
||||
|
||||
+18
-9
@@ -6,15 +6,24 @@
|
||||
@file:Suppress("PackageDirectoryMismatch") // Old package for compatibility
|
||||
package org.jetbrains.kotlin.gradle.plugin.mpp
|
||||
|
||||
import com.android.build.gradle.api.BaseVariant
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
|
||||
|
||||
class KotlinJvmAndroidCompilation(
|
||||
target: KotlinAndroidTarget,
|
||||
name: String
|
||||
) : AbstractKotlinCompilationToRunnableFiles<KotlinJvmOptions>(target, name) {
|
||||
override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
class KotlinJvmAndroidCompilation(
|
||||
target: KotlinAndroidTarget,
|
||||
name: String
|
||||
) : AbstractKotlinCompilationToRunnableFiles<KotlinJvmOptions>(target, name) {
|
||||
|
||||
override val relatedConfigurationNames: List<String>
|
||||
get() = super.relatedConfigurationNames + listOf("${name}ApiElements", "${name}RuntimeElements")
|
||||
}
|
||||
lateinit internal var androidVariant: BaseVariant
|
||||
|
||||
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(
|
||||
"${androidVariant.name}ApiElements",
|
||||
"${androidVariant.name}RuntimeElements",
|
||||
androidVariant.compileConfiguration.name,
|
||||
androidVariant.runtimeConfiguration.name
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user