From b3eef05e6ef0d0688930556346b6cae58d0863c9 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Wed, 27 Feb 2019 20:44:19 +0300 Subject: [PATCH] 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 --- .../AbstractKotlinAndroidGradleTests.kt | 64 +++++++++++++++++++ .../new-mpp-android/lib/build.gradle | 12 +++- .../kotlin/gradle/plugin/KotlinPlugin.kt | 3 + .../plugin/mpp/KotlinMultiplatformPlugin.kt | 51 +++++++++------ .../gradle/plugin/mpp/kotlinCompilations.kt | 1 - .../gradle/plugin/mpp/kotlinVariants.kt | 13 ++-- .../jvm/KotlinJvmAndroidCompilation.kt | 27 +++++--- 7 files changed, 135 insertions(+), 36 deletions(-) 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 a38ad7b2e0f..1f9312dcf0f 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 @@ -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 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 64d315c00a4..ca9a42518bd 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 @@ -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') } 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 0f494fd5f09..66e066d2682 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 @@ -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(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) 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 7827881e8bd..caab8f79f57 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 @@ -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 copyAttribute(key: Attribute, from: AttributeContainer, to: AttributeContainer) { + to.attribute(key, from.getAttribute(key)!!) + } - fun copyAttribute(key: Attribute, 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().flatMap { kotlinVariant -> + kotlinVariant.usages.filterIsInstance().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) } } } } 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 6bd66f251ea..4d6ab2b9e8f 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 @@ -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 } 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 48524b163be..b699ba3f486 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 @@ -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 } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilation.kt index c0e9a1a73e1..c7c56eb1663 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilation.kt @@ -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(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(target, name) { - override val relatedConfigurationNames: List - get() = super.relatedConfigurationNames + listOf("${name}ApiElements", "${name}RuntimeElements") -} \ No newline at end of file + 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 + get() = super.relatedConfigurationNames + listOf( + "${androidVariant.name}ApiElements", + "${androidVariant.name}RuntimeElements", + androidVariant.compileConfiguration.name, + androidVariant.runtimeConfiguration.name + ) + } \ No newline at end of file