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:
+22
-1
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+88
@@ -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") {
|
||||
|
||||
+13
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
-5
@@ -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 " +
|
||||
|
||||
+26
-2
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user