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:
Sergey Igushkin
2019-02-27 20:44:19 +03:00
parent 6fa610156e
commit b3eef05e6e
7 changed files with 135 additions and 36 deletions
@@ -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
@@ -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')
}
@@ -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)
@@ -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) }
}
}
}
@@ -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 }
@@ -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
}
@@ -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
)
}