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