Apply user-defined attributes in single-platform plugins

The attributes applied by the user to the target and its compilations
were ignored in single-platform plugins while the target was not a part
of the API.

Since the user will be able to access and configure the target, we need
to apply the attributes to the relevant configurations in the same way
as in MPP.
This commit is contained in:
Sergey Igushkin
2019-02-28 18:17:51 +03:00
parent 8fa1630695
commit 5b5316214f
3 changed files with 98 additions and 50 deletions
@@ -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'")
}
}
}
@@ -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) }
@@ -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 <T> copyAttribute(key: Attribute<T>, 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<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 -> 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 <T> copyAttribute(key: Attribute<T>, 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<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 -> copyAttributes(compilationAttributes, configuration.attributes) }
}
}
}
internal fun sourcesJarTask(compilation: KotlinCompilation<*>, componentName: String?, artifactNameAppendix: String): Jar {
val project = compilation.target.project
val taskName = lowerCamelCaseName(componentName, "sourcesJar")