Propagate the subplugin options from the tasks to the source sets

If a source set is used in only one compilation, take the options from
its compile task.

If a source set is used by multiple compilations of a single target,
either choose the 'main' compilation or choose any (this will happen
for Android, and it looks OK for the first time). If there are multiple
compilations of different targets, use the metadata compilation.

Issue #KT-27499 In Progress
This commit is contained in:
Sergey Igushkin
2018-10-15 21:47:18 +03:00
parent e573911e16
commit 9f2e5cdc4d
5 changed files with 204 additions and 8 deletions
@@ -8,6 +8,8 @@ import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.junit.Test
import java.io.File
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class KotlinAndroidGradleIT : AbstractKotlinAndroidGradleTests(androidGradlePluginVersion = "2.3.0") {
@@ -22,7 +24,7 @@ class KotlinAndroid32GradleIT : KotlinAndroid3GradleIT(androidGradlePluginVersio
@Test
fun testAndroidWithNewMppApp() = with(Project("new-mpp-android")) {
build("assemble", "compileDebugUnitTestJavaWithJavac") {
build("assemble", "compileDebugUnitTestJavaWithJavac", "printCompilerPluginOptions") {
assertSuccessful()
assertTasksExecuted(
@@ -48,6 +50,25 @@ class KotlinAndroid32GradleIT : KotlinAndroid3GradleIT(androidGradlePluginVersio
assertFileExists("app/build/tmp/kotlin-classes/$variant/com/example/app/AKt.class")
assertFileExists("app/build/tmp/kotlin-classes/$variant/com/example/app/KtUsageKt.class")
}
// Check that Android extensions arguments are available only in the Android source sets:
val compilerPluginArgsRegex = "(\\w+)${Regex.escape("=args=>")}(.*)".toRegex()
val compilerPluginOptionsBySourceSet =
compilerPluginArgsRegex.findAll(output).associate { it.groupValues[1] to it.groupValues[2] }
compilerPluginOptionsBySourceSet.entries.forEach { (sourceSetName, argsString) ->
val shouldHaveAndroidExtensionArgs = sourceSetName.startsWith("androidApp")
if (shouldHaveAndroidExtensionArgs)
assertTrue("$sourceSetName is an Android source set and should have Android Extensions in the args") {
"plugin:org.jetbrains.kotlin.android" in argsString
}
else
assertEquals(
"[]",
argsString,
"$sourceSetName is not an Android source set and should not have Android Extensions in the args"
)
}
}
}
@@ -928,6 +928,94 @@ class NewMultiplatformIT : BaseGradleIT() {
}
}
@Test
fun testMppBuildWithCompilerPlugins() = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
setupWorkingDir()
val printOptionsTaskName = "printCompilerPluginOptions"
val argsMarker = "=args=>"
val classpathMarker = "=cp=>"
val compilerPluginArgsRegex = "(\\w+)${Regex.escape(argsMarker)}(.*)".toRegex()
val compilerPluginClasspathRegex = "(\\w+)${Regex.escape(classpathMarker)}(.*)".toRegex()
gradleBuildScript().appendText(
"\n" + """
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-allopen:${'$'}kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-noarg:${'$'}kotlin_version"
}
}
apply plugin: 'kotlin-allopen'
apply plugin: 'kotlin-noarg'
allOpen { annotation 'com.example.Annotation' }
noArg { annotation 'com.example.Annotation' }
task $printOptionsTaskName {
doFirst {
kotlin.sourceSets.each { sourceSet ->
def args = sourceSet.languageSettings.compilerPluginArguments
def cp = sourceSet.languageSettings.compilerPluginClasspath.files
println sourceSet.name + '$argsMarker' + args
println sourceSet.name + '$classpathMarker' + cp
}
}
}
""".trimIndent()
)
projectDir.resolve("src/commonMain/kotlin/Annotation.kt").writeText(
"""
package com.example
annotation class Annotation
""".trimIndent()
)
projectDir.resolve("src/commonMain/kotlin/Annotated.kt").writeText(
"""
package com.example
@Annotation
open class Annotated(var y: Int) { var x = 2 }
""".trimIndent()
)
// TODO once Kotlin/Native properly supports compiler plugins, move this class to the common sources
listOf("jvm6", "nodeJs").forEach {
projectDir.resolve("src/${it}Main/kotlin/Override.kt").writeText(
"""
package com.example
@Annotation
class Override : Annotated(0) {
override var x = 3
}
""".trimIndent()
)
}
build("assemble", printOptionsTaskName) {
assertSuccessful()
assertTasksExecuted(*listOf("Jvm6", "NodeJs", nativeHostTargetName.capitalize()).map { ":compileKotlin$it" }.toTypedArray())
assertFileExists("build/classes/kotlin/jvm6/main/com/example/Annotated.class")
assertFileExists("build/classes/kotlin/jvm6/main/com/example/Override.class")
assertFileContains("build/classes/kotlin/nodeJs/main/sample-lib.js", "Override")
val (compilerPluginArgsBySourceSet, compilerPluginClasspathBySourceSet) =
listOf(compilerPluginArgsRegex, compilerPluginClasspathRegex)
.map { marker ->
marker.findAll(output).associate { it.groupValues[1] to it.groupValues[2] }
}
// TODO once Kotlin/Native properly supports compiler plugins, expand this to all source sets:
listOf("commonMain", "commonTest", "jvm6Main", "jvm6Test", "nodeJsMain", "nodeJsTest").forEach {
val expectedArgs = "[plugin:org.jetbrains.kotlin.allopen:annotation=com.example.Annotation, " +
"plugin:org.jetbrains.kotlin.noarg:annotation=com.example.Annotation]"
assertEquals(expectedArgs, compilerPluginArgsBySourceSet[it], "Expected $expectedArgs as plugin args for $it")
assertTrue { compilerPluginClasspathBySourceSet[it]!!.contains("kotlin-allopen") }
assertTrue { compilerPluginClasspathBySourceSet[it]!!.contains("kotlin-noarg") }
}
}
}
@Test
fun testJsDceInMpp() = with(Project("new-mpp-js-dce", gradleVersion)) {
build("runRhino") {
@@ -1,5 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-multiplatform'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
@@ -55,4 +56,16 @@ kotlin {
fromPreset(presets.jvm, 'jvmApp')
fromPreset(presets.js, 'jsApp')
}
}
// test diagnostic task, not needed by the build
task printCompilerPluginOptions {
doFirst {
kotlin.sourceSets.each { sourceSet ->
def args = sourceSet.languageSettings.compilerPluginArguments
def cp = sourceSet.languageSettings.compilerPluginClasspath.files
println sourceSet.name + '=args=>' + args
println sourceSet.name + '=cp=>' + cp
}
}
}
@@ -19,18 +19,21 @@ import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal
import org.gradle.api.publish.maven.tasks.AbstractPublishToMaven
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.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.utils.SingleWarningPerBuild
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.presetName
internal val Project.multiplatformExtension get(): KotlinMultiplatformExtension? =
project.extensions.findByName("kotlin") as? KotlinMultiplatformExtension
internal val Project.multiplatformExtension
get(): KotlinMultiplatformExtension? =
project.extensions.findByName("kotlin") as? KotlinMultiplatformExtension
class KotlinMultiplatformPlugin(
private val fileResolver: FileResolver,
@@ -88,6 +91,53 @@ class KotlinMultiplatformPlugin(
KotlinMetadataTargetPreset(project, instantiator, fileResolver, kotlinPluginVersion),
METADATA_TARGET_NAME
)
// propagate compiler plugin options to the source set language settings
setupCompilerPluginOptions(project)
}
private fun setupCompilerPluginOptions(project: Project) {
// common source sets use the compiler options from the metadata compilation:
val metadataCompilation =
project.multiplatformExtension!!.targets
.getByName(METADATA_TARGET_NAME)
.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME)
val primaryCompilationsBySourceSet by lazy { // don't evaluate eagerly: Android targets are not created at this point
val allCompilationsForSourceSets =
project.multiplatformExtension!!.targets
.flatMap { target ->
target.compilations.flatMap { compilation ->
compilation.allKotlinSourceSets.map { it to compilation }
}
}
.groupBy(keySelector = { (sourceSet, _) -> sourceSet }, valueTransform = { (_, compilation) -> compilation })
allCompilationsForSourceSets.mapValues { (_, compilations) -> // choose one primary compilation
when (compilations.size) {
0 -> metadataCompilation
1 -> compilations.single()
else -> {
val sourceSetTargets = compilations.map { it.target }.distinct()
when (sourceSetTargets.size) {
1 -> sourceSetTargets.single().compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)
?: // use any of the compilations for now, looks OK for Android TODO maybe reconsider
compilations.first()
else -> metadataCompilation
}
}
}
}
}
project.kotlinExtension.sourceSets.all { sourceSet ->
(sourceSet.languageSettings as? DefaultLanguageSettingsBuilder)?.run {
compilerPluginOptionsTask = lazy {
val associatedCompilation = primaryCompilationsBySourceSet[sourceSet] ?: metadataCompilation
project.tasks.getByName(associatedCompilation.compileKotlinTaskName) as AbstractCompile
}
}
}
}
fun setupDefaultPresets(project: Project) {
@@ -170,7 +220,7 @@ class KotlinMultiplatformPlugin(
private fun configureSourceJars(project: Project) = with(project.kotlinExtension as KotlinMultiplatformExtension) {
targets.all { target ->
val mainCompilation = target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)
// If a target has no `main` compilation (e.g. Android), don't create the source JAR
// If a target has no `main` compilation (e.g. Android), don't create the source JAR
?: return@all
val sourcesJar = project.tasks.create(target.sourcesJarTaskName, Jar::class.java) { sourcesJar ->
@@ -189,7 +239,7 @@ class KotlinMultiplatformPlugin(
}
}
private fun configureSourceSets(project: Project) = with (project.kotlinExtension as KotlinMultiplatformExtension) {
private fun configureSourceSets(project: Project) = with(project.kotlinExtension as KotlinMultiplatformExtension) {
val production = sourceSets.create(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME)
val test = sourceSets.create(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME)
@@ -247,7 +297,7 @@ class KotlinMultiplatformPlugin(
const val METADATA_TARGET_NAME = "metadata"
const val GRADLE_METADATA_WARNING =
// TODO point the user to some MPP docs explaining this in more detail
// TODO point the user to some MPP docs explaining this in more detail
"This build is set up to publish Kotlin multiplatform libraries with experimental Gradle metadata. " +
"Future Gradle versions may fail to resolve dependencies on these publications. " +
"You can disable Gradle metadata usage during publishing and dependencies resolution by removing " +
@@ -6,10 +6,14 @@
package org.jetbrains.kotlin.gradle.plugin.sources
import org.gradle.api.InvalidUserDataException
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
internal class DefaultLanguageSettingsBuilder : LanguageSettingsBuilder {
private var languageVersionImpl: LanguageVersion? = null
@@ -58,8 +62,28 @@ internal class DefaultLanguageSettingsBuilder : LanguageSettingsBuilder {
experimentalAnnotationsInUseImpl += name
}
companion object {
}
/* A Kotlin task that is responsible for code analysis of the owner of this language settings builder. */
lateinit var compilerPluginOptionsTask: Lazy<AbstractCompile>
val compilerPluginArguments: List<String>
get() {
val pluginOptionsTask = compilerPluginOptionsTask.value
return when (pluginOptionsTask) {
is AbstractKotlinCompile<*> -> pluginOptionsTask.pluginOptions
is KotlinNativeCompile -> pluginOptionsTask.compilerPluginOptions
else -> error("Unexpected task: $compilerPluginOptionsTask")
}.arguments
}
val compilerPluginClasspath: FileCollection
get() {
val pluginClasspathTask = compilerPluginOptionsTask.value
return when (pluginClasspathTask) {
is AbstractKotlinCompile<*> -> pluginClasspathTask.pluginClasspath
is KotlinNativeCompile -> pluginClasspathTask.compilerPluginClasspath ?: pluginClasspathTask.project.files()
else -> error("Unexpected task: $compilerPluginOptionsTask")
}
}
}
internal fun applyLanguageSettingsToKotlinTask(