Implement language settings API for Kotlin source sets
This commit is contained in:
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.plugin
|
||||
|
||||
interface LanguageSettingsBuilder {
|
||||
var languageVersion: String?
|
||||
|
||||
var apiVersion: String?
|
||||
|
||||
var progressiveMode: Boolean
|
||||
|
||||
fun enableLanguageFeature(name: String)
|
||||
|
||||
val enabledLanguageFeatures: Set<String>
|
||||
}
|
||||
+8
-5
@@ -9,19 +9,22 @@ import groovy.lang.Closure
|
||||
import org.gradle.api.Named
|
||||
import org.gradle.api.file.SourceDirectorySet
|
||||
import org.jetbrains.kotlin.gradle.plugin.HasKotlinDependencies
|
||||
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
|
||||
|
||||
interface KotlinSourceSet : Named, HasKotlinDependencies {
|
||||
val kotlin: SourceDirectorySet
|
||||
fun kotlin(configureClosure: Closure<Any?>): SourceDirectorySet
|
||||
|
||||
val resources: SourceDirectorySet
|
||||
|
||||
fun kotlin(configureClosure: Closure<Any?>): SourceDirectorySet
|
||||
val languageSettings: LanguageSettingsBuilder
|
||||
fun languageSettings(configureClosure: Closure<Any?>): LanguageSettingsBuilder
|
||||
|
||||
fun dependsOn(other: KotlinSourceSet)
|
||||
val dependsOn: Set<KotlinSourceSet>
|
||||
|
||||
companion object {
|
||||
const val COMMON_MAIN_SOURCE_SET_NAME = "commonMain"
|
||||
const val COMMON_TEST_SOURCE_SET_NAME = "commonTest"
|
||||
}
|
||||
|
||||
fun dependsOn(other: KotlinSourceSet)
|
||||
|
||||
val dependsOn: Set<KotlinSourceSet>
|
||||
}
|
||||
+66
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.gradle
|
||||
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.SourceSetConsistencyChecks
|
||||
import org.jetbrains.kotlin.gradle.util.checkBytecodeContains
|
||||
import org.jetbrains.kotlin.gradle.util.modify
|
||||
import org.junit.Assert
|
||||
@@ -226,4 +227,69 @@ class NewMultiplatformIT : BaseGradleIT() {
|
||||
expectedKotlinOutputFiles.forEach { assertFileExists(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLanguageSettingsApplied() = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
|
||||
setupWorkingDir()
|
||||
|
||||
gradleBuildScript().appendText(
|
||||
"\n" + """
|
||||
kotlin.sourceSets.jvm6Main.languageSettings {
|
||||
languageVersion = '1.3'
|
||||
apiVersion = '1.3'
|
||||
enableLanguageFeature('InlineClasses')
|
||||
progressiveMode = true
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
build("compileKotlinJvm6") {
|
||||
assertSuccessful()
|
||||
assertContains("-language-version 1.3", "-api-version 1.3", "-XXLanguage:+InlineClasses", " -Xprogressive")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLanguageSettingsConsistency() = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
|
||||
setupWorkingDir()
|
||||
|
||||
gradleBuildScript().appendText(
|
||||
"\n" + """
|
||||
kotlin.sourceSets {
|
||||
foo { }
|
||||
bar { dependsOn foo }
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
fun testMonotonousCheck(sourceSetConfigurationChange: String, expectedErrorHint: String) {
|
||||
gradleBuildScript().appendText("\nkotlin.sourceSets.foo.${sourceSetConfigurationChange}")
|
||||
build("tasks") {
|
||||
assertFailed()
|
||||
assertContains(expectedErrorHint)
|
||||
}
|
||||
gradleBuildScript().appendText("\nkotlin.sourceSets.bar.${sourceSetConfigurationChange}")
|
||||
build("tasks") {
|
||||
assertSuccessful()
|
||||
}
|
||||
}
|
||||
|
||||
testMonotonousCheck("languageSettings.languageVersion = '1.4'", SourceSetConsistencyChecks.languageVersionCheckHint)
|
||||
testMonotonousCheck("languageSettings.enableLanguageFeature('InlineClasses')", SourceSetConsistencyChecks.unstableFeaturesHint)
|
||||
|
||||
// check that enabling a bugfix feature and progressive mode or advancing API level
|
||||
// don't require doing the same for dependent source sets:
|
||||
gradleBuildScript().appendText(
|
||||
"\n" + """
|
||||
kotlin.sourceSets.foo.languageSettings {
|
||||
apiVersion = '1.3'
|
||||
enableLanguageFeature('SoundSmartcastForEnumEntries')
|
||||
progressiveMode = true
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
build("tasks") {
|
||||
assertSuccessful()
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -164,7 +164,7 @@ internal class Kotlin2JvmSourceSetProcessor(
|
||||
super.defaultKotlinDestinationDir
|
||||
|
||||
override fun doCreateTask(project: Project, taskName: String): KotlinCompile =
|
||||
tasksProvider.createKotlinJVMTask(project, taskName, kotlinCompilation.compilationName)
|
||||
tasksProvider.createKotlinJVMTask(project, taskName, kotlinCompilation)
|
||||
|
||||
override fun doTargetSpecificProcessing() {
|
||||
Kapt3KotlinGradleSubplugin.createAptConfigurationIfNeeded(project, kotlinCompilation.compilationName)
|
||||
@@ -254,7 +254,7 @@ internal class Kotlin2JsSourceSetProcessor(
|
||||
kotlinCompilation = kotlinCompilation
|
||||
) {
|
||||
override fun doCreateTask(project: Project, taskName: String): Kotlin2JsCompile =
|
||||
tasksProvider.createKotlinJSTask(project, taskName, kotlinCompilation.compilationName)
|
||||
tasksProvider.createKotlinJSTask(project, taskName, kotlinCompilation)
|
||||
|
||||
override fun doTargetSpecificProcessing() {
|
||||
project.tasks.findByName(kotlinCompilation.compileAllTaskName)!!.dependsOn(kotlinTask)
|
||||
@@ -332,7 +332,7 @@ internal class KotlinCommonSourceSetProcessor(
|
||||
}
|
||||
|
||||
override fun doCreateTask(project: Project, taskName: String): KotlinCompileCommon =
|
||||
tasksProvider.createKotlinCommonTask(project, taskName, kotlinCompilation.compilationName)
|
||||
tasksProvider.createKotlinCommonTask(project, taskName, kotlinCompilation)
|
||||
}
|
||||
|
||||
internal abstract class AbstractKotlinPlugin(
|
||||
@@ -712,7 +712,7 @@ abstract class AbstractAndroidProjectHandler<V>(private val kotlinConfigurationT
|
||||
|
||||
val kotlinTaskName = compilation.compileKotlinTaskName
|
||||
// todo: Investigate possibility of creating and configuring kotlinTask before evaluation
|
||||
val kotlinTask = tasksProvider.createKotlinJVMTask(project, kotlinTaskName, variantDataName)
|
||||
val kotlinTask = tasksProvider.createKotlinJVMTask(project, kotlinTaskName, compilation)
|
||||
kotlinTask.parentKotlinOptionsImpl = rootKotlinOptions
|
||||
|
||||
// store kotlin classes in separate directory. They will serve as class-path to java compiler
|
||||
|
||||
+12
@@ -22,8 +22,10 @@ import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.tasks.SourceSet
|
||||
import org.gradle.api.tasks.SourceSetOutput
|
||||
import org.gradle.util.ConfigureUtil
|
||||
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.defaultSourceSetLanguageSettingsChecker
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy
|
||||
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
|
||||
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
|
||||
@@ -82,6 +84,16 @@ abstract class AbstractKotlinCompilation(
|
||||
if (this is KotlinCompilationToRunnableFiles) {
|
||||
addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName, forced = false)
|
||||
}
|
||||
|
||||
if (sourceSet.name != defaultSourceSetName) {
|
||||
// Temporary solution for checking consistency across source sets participating in a compilation that may
|
||||
// not be interconnected with the dependsOn relation: check the settings as if the default source set of
|
||||
// the compilation depends on the one added to the compilation:
|
||||
defaultSourceSetLanguageSettingsChecker.runAllChecks(
|
||||
kotlinExtension.sourceSets.getByName(defaultSourceSetName),
|
||||
sourceSet
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -13,6 +13,7 @@ import org.gradle.api.internal.file.DefaultSourceDirectorySet
|
||||
import org.gradle.api.internal.file.FileResolver
|
||||
import org.gradle.util.ConfigureUtil
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
|
||||
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler
|
||||
import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
|
||||
@@ -41,10 +42,16 @@ class DefaultKotlinSourceSet(
|
||||
filter.include("**/*.java", "**/*.kt", "**/*.kts")
|
||||
}
|
||||
|
||||
override val languageSettings: LanguageSettingsBuilder = DefaultLanguageSettingsBuilder()
|
||||
|
||||
override val resources: SourceDirectorySet = createDefaultSourceDirectorySet(displayName + " resources", fileResolver)
|
||||
|
||||
override fun kotlin(configureClosure: Closure<Any?>): SourceDirectorySet = kotlin.apply { ConfigureUtil.configure(configureClosure, this) }
|
||||
|
||||
override fun languageSettings(configureClosure: Closure<Any?>): LanguageSettingsBuilder = languageSettings.apply {
|
||||
ConfigureUtil.configure(configureClosure, this)
|
||||
}
|
||||
|
||||
override fun getName(): String = displayName
|
||||
|
||||
override fun dependencies(configure: KotlinDependencyHandler.() -> Unit): Unit =
|
||||
@@ -58,6 +65,8 @@ class DefaultKotlinSourceSet(
|
||||
|
||||
// Fail-fast approach: check on each new added edge and report a circular dependency at once when the edge is added.
|
||||
checkForCircularDependencies()
|
||||
|
||||
project.afterEvaluate { defaultSourceSetLanguageSettingsChecker.runAllChecks(this, other) }
|
||||
}
|
||||
|
||||
private val dependsOnSourceSetsImpl = mutableSetOf<KotlinSourceSet>()
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.plugin.sources
|
||||
|
||||
import org.gradle.api.InvalidUserDataException
|
||||
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
|
||||
|
||||
internal class DefaultLanguageSettingsBuilder : LanguageSettingsBuilder {
|
||||
private var languageVersionImpl: LanguageVersion? = null
|
||||
|
||||
override var languageVersion: String?
|
||||
get() = languageVersionImpl?.versionString
|
||||
set(value) {
|
||||
languageVersionImpl = value?.let { versionString ->
|
||||
LanguageVersion.fromVersionString(versionString) ?: throw InvalidUserDataException(
|
||||
"Incorrect language version. Expected one of: ${LanguageVersion.values().joinToString { "'${it.versionString}'" }}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var apiVersionImpl: ApiVersion? = null
|
||||
|
||||
override var apiVersion: String?
|
||||
get() = apiVersionImpl?.versionString
|
||||
set(value) {
|
||||
apiVersionImpl = value ?.let { versionString ->
|
||||
parseApiVersionSettings(versionString) ?: throw InvalidUserDataException(
|
||||
"Incorrect API version. Expected one of: ${apiVersionValues.joinToString { "'${it.versionString}'" }}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override var progressiveMode: Boolean = false
|
||||
|
||||
private val enabledLanguageFeaturesImpl = mutableSetOf<LanguageFeature>()
|
||||
|
||||
override val enabledLanguageFeatures: Set<String>
|
||||
get() = enabledLanguageFeaturesImpl.map { it.name }.toSet()
|
||||
|
||||
override fun enableLanguageFeature(name: String) {
|
||||
val languageFeature = parseLanguageFeature(name) ?: throw InvalidUserDataException(
|
||||
"Unknown language feature '${name}'"
|
||||
)
|
||||
enabledLanguageFeaturesImpl += languageFeature
|
||||
}
|
||||
|
||||
companion object {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun applyLanguageSettingsToKotlinTask(
|
||||
languageSettingsBuilder: LanguageSettingsBuilder,
|
||||
kotlinTask: org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>
|
||||
) = with(kotlinTask.kotlinOptions) {
|
||||
languageVersion = languageVersion ?: languageSettingsBuilder.languageVersion
|
||||
apiVersion = apiVersion ?: languageSettingsBuilder.apiVersion
|
||||
|
||||
if (languageSettingsBuilder.progressiveMode) {
|
||||
freeCompilerArgs += "-Xprogressive"
|
||||
}
|
||||
|
||||
languageSettingsBuilder.enabledLanguageFeatures.forEach { featureName ->
|
||||
freeCompilerArgs += "-XXLanguage:+$featureName"
|
||||
}
|
||||
}
|
||||
|
||||
private val apiVersionValues = ApiVersion.run { listOf(KOTLIN_1_0, KOTLIN_1_1, KOTLIN_1_2, KOTLIN_1_3) }
|
||||
|
||||
internal fun parseLanguageVersionSetting(versionString: String) = LanguageVersion.fromVersionString(versionString)
|
||||
internal fun parseApiVersionSettings(versionString: String) = apiVersionValues.find { it.versionString == versionString }
|
||||
internal fun parseLanguageFeature(featureName: String) = LanguageFeature.fromString(featureName)
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.plugin.sources
|
||||
|
||||
import org.gradle.api.InvalidUserDataException
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.config.LanguageVersion
|
||||
import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet
|
||||
|
||||
internal class ConsistencyCheck<T, S>(
|
||||
val name: String,
|
||||
val getValue: (T) -> S,
|
||||
val leftExtendsRightConsistently: (S, S) -> Boolean,
|
||||
val consistencyConditionHint: String
|
||||
)
|
||||
|
||||
object SourceSetConsistencyChecks {
|
||||
private val defaultLanguageVersion = LanguageVersion.LATEST_STABLE
|
||||
|
||||
const val languageVersionCheckHint =
|
||||
"The language version of the dependent source set must be greater than or equal to that of its dependency."
|
||||
|
||||
internal val languageVersionCheck = ConsistencyCheck<KotlinSourceSet, LanguageVersion>(
|
||||
name = "language version",
|
||||
getValue = { sourceSet ->
|
||||
sourceSet.languageSettings.languageVersion?.let { parseLanguageVersionSetting(it) } ?: defaultLanguageVersion
|
||||
},
|
||||
leftExtendsRightConsistently = { left, right -> left >= right },
|
||||
consistencyConditionHint = languageVersionCheckHint
|
||||
)
|
||||
|
||||
const val unstableFeaturesHint = "The dependent source set must enable all unstable language features that its dependency has."
|
||||
|
||||
internal val unstableFeaturesCheck = ConsistencyCheck<KotlinSourceSet, Set<LanguageFeature>>(
|
||||
name = "unstable language feature set",
|
||||
getValue = { sourceSet ->
|
||||
sourceSet.languageSettings.enabledLanguageFeatures
|
||||
.map { parseLanguageFeature(it)!! }
|
||||
.filterTo(mutableSetOf()) { it.kind == LanguageFeature.Kind.UNSTABLE_FEATURE }
|
||||
},
|
||||
leftExtendsRightConsistently = { left, right -> left.containsAll(right) },
|
||||
consistencyConditionHint = unstableFeaturesHint
|
||||
)
|
||||
}
|
||||
|
||||
internal class SourceSetConsistencyChecker(
|
||||
val checks: List<ConsistencyCheck<KotlinSourceSet, *>>
|
||||
) {
|
||||
fun <S> runSingleCheck(
|
||||
dependent: KotlinSourceSet,
|
||||
dependency: KotlinSourceSet,
|
||||
check: ConsistencyCheck<KotlinSourceSet, S>
|
||||
) {
|
||||
val leftValue = check.getValue(dependent)
|
||||
val rightValue = check.getValue(dependency)
|
||||
|
||||
if (!check.leftExtendsRightConsistently(leftValue, rightValue)) {
|
||||
throw InvalidUserDataException(
|
||||
"Inconsistent settings for Kotlin source sets: '${dependent.name}' depends on '${dependency.name}'\n" +
|
||||
"'${dependent.name}': ${check.name} is ${leftValue}\n" +
|
||||
"'${dependency.name}': ${check.name} is ${rightValue}\n" +
|
||||
check.consistencyConditionHint
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun runAllChecks(dependent: KotlinSourceSet, dependency: KotlinSourceSet) {
|
||||
for (check in checks) {
|
||||
runSingleCheck(dependent, dependency, check)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val defaultSourceSetLanguageSettingsChecker = with(SourceSetConsistencyChecks) {
|
||||
// We don't check the progressive mode, since the features it enables are bugfixes
|
||||
SourceSetConsistencyChecker(listOf(languageVersionCheck, unstableFeaturesCheck))
|
||||
}
|
||||
+24
-13
@@ -17,34 +17,45 @@
|
||||
package org.jetbrains.kotlin.gradle.tasks
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.jetbrains.kotlin.gradle.plugin.RegexTaskToFriendTaskMapper
|
||||
import org.jetbrains.kotlin.gradle.plugin.TaskToFriendTaskMapper
|
||||
import org.jetbrains.kotlin.gradle.plugin.mapKotlinTaskProperties
|
||||
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.defaultSourceSetName
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.applyLanguageSettingsToKotlinTask
|
||||
|
||||
internal open class KotlinTasksProvider(val targetName: String) {
|
||||
open fun createKotlinJVMTask(project: Project, name: String, sourceSetName: String): KotlinCompile =
|
||||
open fun createKotlinJVMTask(
|
||||
project: Project,
|
||||
name: String,
|
||||
compilation: KotlinCompilation
|
||||
): KotlinCompile =
|
||||
project.tasks.create(name, KotlinCompile::class.java).apply {
|
||||
configure(this, project, sourceSetName)
|
||||
configure(this, project, compilation)
|
||||
}
|
||||
|
||||
fun createKotlinJSTask(project: Project, name: String, sourceSetName: String): Kotlin2JsCompile =
|
||||
fun createKotlinJSTask(project: Project, name: String, compilation: KotlinCompilation): Kotlin2JsCompile =
|
||||
project.tasks.create(name, Kotlin2JsCompile::class.java).apply {
|
||||
configure(this, project, sourceSetName)
|
||||
configure(this, project, compilation)
|
||||
}
|
||||
|
||||
fun createKotlinCommonTask(project: Project, name: String, sourceSetName: String): KotlinCompileCommon =
|
||||
fun createKotlinCommonTask(project: Project, name: String, compilation: KotlinCompilation): KotlinCompileCommon =
|
||||
project.tasks.create(name, KotlinCompileCommon::class.java).apply {
|
||||
configure(this, project, sourceSetName)
|
||||
configure(this, project, compilation)
|
||||
}
|
||||
|
||||
open fun configure(
|
||||
kotlinTask: AbstractKotlinCompile<*>,
|
||||
project: Project,
|
||||
sourceSetName: String
|
||||
compilation: KotlinCompilation
|
||||
) {
|
||||
kotlinTask.sourceSetName = sourceSetName
|
||||
kotlinTask.sourceSetName = compilation.name
|
||||
kotlinTask.friendTaskName = taskToFriendTaskMapper[kotlinTask]
|
||||
mapKotlinTaskProperties(project, kotlinTask)
|
||||
|
||||
project.whenEvaluated {
|
||||
val languageSettings = project.kotlinExtension.sourceSets.getByName(compilation.defaultSourceSetName).languageSettings
|
||||
kotlinTask as org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>
|
||||
applyLanguageSettingsToKotlinTask(languageSettings, kotlinTask)
|
||||
}
|
||||
}
|
||||
|
||||
protected open val taskToFriendTaskMapper: TaskToFriendTaskMapper =
|
||||
@@ -55,8 +66,8 @@ internal class AndroidTasksProvider(targetName: String) : KotlinTasksProvider(ta
|
||||
override val taskToFriendTaskMapper: TaskToFriendTaskMapper =
|
||||
RegexTaskToFriendTaskMapper.Android(targetName)
|
||||
|
||||
override fun configure(kotlinTask: AbstractKotlinCompile<*>, project: Project, sourceSetName: String) {
|
||||
super.configure(kotlinTask, project, sourceSetName)
|
||||
override fun configure(kotlinTask: AbstractKotlinCompile<*>, project: Project, compilation: KotlinCompilation) {
|
||||
super.configure(kotlinTask, project, compilation)
|
||||
kotlinTask.useModuleDetection = true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user