diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt index 05cc6121b6c..2ec9ae71c61 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt @@ -921,4 +921,46 @@ class KotlinGradleIT : BaseGradleIT() { assertFileExists(moduleDir + "new-model-1.0-sources.jar") } } + + @Test + fun testUserDefinedAttributesInSinglePlatformProject() = + with(Project("multiprojectWithDependency", GradleVersionRequired.AtLeast("4.7"))) { + setupWorkingDir() + gradleBuildScript("projA").appendText( + "\n" + """ + def targetAttribute = Attribute.of("com.example.target", String) + def compilationAttribute = Attribute.of("com.example.compilation", String) + kotlin.target.attributes.attribute(targetAttribute, "foo") + kotlin.target.compilations["main"].attributes.attribute(compilationAttribute, "foo") + """.trimIndent() + ) + gradleBuildScript("projB").appendText( + "\n" + """ + def targetAttribute = Attribute.of("com.example.target", String) + def compilationAttribute = Attribute.of("com.example.compilation", String) + kotlin.target.attributes.attribute(targetAttribute, "foo") + kotlin.target.compilations["main"].attributes.attribute(compilationAttribute, "foo") + """.trimIndent() + ) + build(":projB:compileKotlin") { + assertSuccessful() + } + // Break dependency resolution by providing incompatible custom attributes in the target: + gradleBuildScript("projB").appendText("\nkotlin.target.attributes.attribute(targetAttribute, \"bar\")") + build(":projB:compileKotlin") { + assertFailed() + assertContains("Required com.example.target 'bar'") + } + // And using the compilation attributes (fix the target attributes first): + gradleBuildScript("projB").appendText( + "\n" + """ + kotlin.target.attributes.attribute(targetAttribute, "foo") + kotlin.target.compilations["main"].attributes.attribute(compilationAttribute, "bar") + """.trimIndent() + ) + build(":projB:compileKotlin") { + assertFailed() + assertContains("Required com.example.compilation 'bar'") + } + } } 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 af227521cd4..c660b0cbd6c 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 @@ -354,6 +354,8 @@ internal abstract class AbstractKotlinPlugin( { compilation -> buildSourceSetProcessor(project, compilation, kotlinPluginVersion) } ) + applyUserDefinedAttributes(target) + rewriteMppDependenciesInPom(target) configureProjectGlobalSettings(project, kotlinPluginVersion) @@ -644,6 +646,9 @@ internal open class KotlinAndroidPlugin( (project.kotlinExtension as KotlinAndroidProjectExtension).target = androidTarget applyToTarget(kotlinPluginVersion, androidTarget) + + applyUserDefinedAttributes(androidTarget) + registry.register(KotlinModelBuilder(kotlinPluginVersion, androidTarget)) project.whenEvaluated { project.components.addAll(androidTarget.components) } 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 caab8f79f57..b4b35220923 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 @@ -23,7 +23,10 @@ import org.gradle.api.tasks.compile.AbstractCompile import org.gradle.internal.reflect.Instantiator import org.gradle.jvm.tasks.Jar import org.gradle.util.ConfigureUtil -import org.jetbrains.kotlin.gradle.dsl.* +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.configureOrCreate +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder import org.jetbrains.kotlin.gradle.utils.SingleWarningPerBuild @@ -85,7 +88,7 @@ class KotlinMultiplatformPlugin( ) configurePublishingWithMavenPublish(project) - setUpConfigurationAttributes(project) + targetsContainer.all { applyUserDefinedAttributes(it) } // propagate compiler plugin options to the source set language settings setupCompilerPluginOptions(project) @@ -226,54 +229,6 @@ 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 -> - fun copyAttributes(from: AttributeContainer, to: AttributeContainer) { - fun copyAttribute(key: Attribute, from: AttributeContainer, to: AttributeContainer) { - to.attribute(key, from.getAttribute(key)!!) - } - - from.keySet().forEach { key -> copyAttribute(key, from, to) } - } - - // 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 -> copyAttributes(compilationAttributes, configuration.attributes) } - } - } - } - } - companion object { const val METADATA_TARGET_NAME = "metadata" @@ -286,6 +241,52 @@ 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 + */ +internal fun applyUserDefinedAttributes(target: KotlinTarget) { + val project = target.project + + project.whenEvaluated { + fun copyAttributes(from: AttributeContainer, to: AttributeContainer) { + fun copyAttribute(key: Attribute, from: AttributeContainer, to: AttributeContainer) { + to.attribute(key, from.getAttribute(key)!!) + } + + from.keySet().forEach { key -> copyAttribute(key, from, to) } + } + + // 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 -> copyAttributes(compilationAttributes, configuration.attributes) } + } + } +} + internal fun sourcesJarTask(compilation: KotlinCompilation<*>, componentName: String?, artifactNameAppendix: String): Jar { val project = compilation.target.project val taskName = lowerCamelCaseName(componentName, "sourcesJar")