diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt index 168fa4b2357..284e5e3fbd2 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt @@ -21,7 +21,6 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmWithJavaTargetPreset import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin import org.jetbrains.kotlin.gradle.plugin.mpp.UnusedSourceSetsChecker import org.jetbrains.kotlin.gradle.plugin.sources.METADATA_CONFIGURATION_NAME_SUFFIX -import org.jetbrains.kotlin.gradle.plugin.sources.SourceSetConsistencyChecks import org.jetbrains.kotlin.gradle.plugin.sources.UnsatisfiedSourceSetVisibilityException import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget import org.jetbrains.kotlin.gradle.util.* @@ -810,17 +809,17 @@ class NewMultiplatformIT : BaseGradleIT() { testMonotonousCheck( "languageSettings.languageVersion = '1.3'", "languageSettings.languageVersion = '1.4'", - SourceSetConsistencyChecks.languageVersionCheckHint + "The language version of the dependent source set must be greater than or equal to that of its dependency." ) testMonotonousCheck( "languageSettings.enableLanguageFeature('InlineClasses')", - SourceSetConsistencyChecks.unstableFeaturesHint + "The dependent source set must enable all unstable language features that its dependency has." ) testMonotonousCheck( "languageSettings.useExperimentalAnnotation('kotlin.ExperimentalUnsignedTypes')", - SourceSetConsistencyChecks.experimentalAnnotationsInUseHint + "The dependent source set must use all experimental annotations that its dependency uses." ) // check that enabling a bugfix feature and progressive mode or advancing API level diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt index d56596b8c31..2eb4b4a2a0a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/KotlinGradleFragment.kt @@ -14,6 +14,8 @@ import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler import org.jetbrains.kotlin.gradle.plugin.mpp.toModuleDependency import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder +import org.jetbrains.kotlin.gradle.plugin.sources.FragmentConsistencyChecker +import org.jetbrains.kotlin.gradle.plugin.sources.FragmentConsistencyChecks import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import org.jetbrains.kotlin.project.model.KotlinModuleDependency @@ -47,6 +49,10 @@ open class KotlinGradleFragmentInternal @Inject constructor( ).forEach { getConfiguration -> project.addExtendsFromRelation(getConfiguration(this), getConfiguration(other.get())) // todo eager instantiation; fix? } + + project.whenEvaluated { + kotlinGradleFragmentConsistencyChecker.runAllChecks(this@KotlinGradleFragmentInternal, other.get()) + } } private fun checkCanRefine(other: KotlinGradleFragment) { @@ -99,3 +105,13 @@ internal fun KotlinModuleFragment.disambiguateName(simpleName: String) = val KotlinGradleFragment.refinesClosure: Set get() = (this as KotlinModuleFragment).refinesClosure.map { it as KotlinGradleFragment }.toSet() + +internal val kotlinGradleFragmentConsistencyChecker = + FragmentConsistencyChecker( + unitsName = "fragments", + name = { name }, + checks = FragmentConsistencyChecks( + unitName = "fragment", + languageSettings = { languageSettings } + ).allChecks + ) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/ConsistencyChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/ConsistencyChecker.kt new file mode 100644 index 00000000000..5809eb50487 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/ConsistencyChecker.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.project.model.LanguageSettings + +internal class ConsistencyCheck( + val name: String, + val getValue: (T) -> S, + val leftExtendsRightConsistently: (S, S) -> Boolean, + val consistencyConditionHint: String +) + +internal class FragmentConsistencyChecks( + unitName: String, // "fragment" or "source set" + private val languageSettings: T.() -> LanguageSettings +) { + private val defaultLanguageVersion = LanguageVersion.LATEST_STABLE + + private val languageVersionCheckHint = + "The language version of the dependent $unitName must be greater than or equal to that of its dependency." + + val languageVersionCheck = ConsistencyCheck( + name = "language version", + getValue = { unit -> + unit.languageSettings().languageVersion?.let { parseLanguageVersionSetting(it) } ?: defaultLanguageVersion + }, + leftExtendsRightConsistently = { left, right -> left >= right }, + consistencyConditionHint = languageVersionCheckHint + ) + + private val unstableFeaturesHint = "The dependent $unitName must enable all unstable language features that its dependency has." + + val unstableFeaturesCheck = ConsistencyCheck>( + name = "unstable language feature set", + getValue = { unit -> + unit.languageSettings().enabledLanguageFeatures + .map { parseLanguageFeature(it)!! } + .filterTo(mutableSetOf()) { it.kind == LanguageFeature.Kind.UNSTABLE_FEATURE } + }, + leftExtendsRightConsistently = { left, right -> left.containsAll(right) }, + consistencyConditionHint = unstableFeaturesHint + ) + + private val experimentalAnnotationsInUseHint = "The dependent $unitName must use all experimental annotations that its dependency uses." + + val experimentalAnnotationsCheck = ConsistencyCheck>( + name = "set of experimental annotations in use", + getValue = { unit -> unit.languageSettings().experimentalAnnotationsInUse }, + leftExtendsRightConsistently = { left, right -> left.containsAll(right) }, + consistencyConditionHint = experimentalAnnotationsInUseHint + ) + + val allChecks = listOf(languageVersionCheck, unstableFeaturesCheck, experimentalAnnotationsCheck) +} + +internal class FragmentConsistencyChecker( + private val unitsName: String, + private val name: T.() -> String, + val checks: List> +) { + fun runSingleCheck( + dependent: T, + dependency: T, + check: ConsistencyCheck + ) { + val leftValue = check.getValue(dependent) + val rightValue = check.getValue(dependency) + + if (!check.leftExtendsRightConsistently(leftValue, rightValue)) { + throw InvalidUserDataException( + "Inconsistent settings for Kotlin $unitsName: '${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: T, dependency: T) { + for (check in checks) { + runSingleCheck(dependent, dependency, check) + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt index 859aa481fbf..26a3d3eeaed 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt @@ -202,6 +202,17 @@ class DefaultKotlinSourceSet( //endregion } + +internal val defaultSourceSetLanguageSettingsChecker = + FragmentConsistencyChecker( + unitsName = "source sets", + name = { name }, + checks = FragmentConsistencyChecks( + unitName = "source set", + languageSettings = { languageSettings } + ).allChecks + ) + private fun KotlinSourceSet.checkForCircularDependencies() { // If adding an edge creates a cycle, than the source node of the edge belongs to the cycle, so run DFS from that node // to check whether it became reachable from itself diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/SourceSetConsistencyChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/SourceSetConsistencyChecker.kt deleted file mode 100644 index f8beafdcc51..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/SourceSetConsistencyChecker.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. - * 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.KotlinSourceSet - -internal class ConsistencyCheck( - 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( - 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>( - 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 - ) - - const val experimentalAnnotationsInUseHint = "The dependent source set must use all experimental annotations that its dependency uses." - - internal val experimentalAnnotationsCheck = ConsistencyCheck>( - name = "set of experimental annotations in use", - getValue = { sourceSet -> sourceSet.languageSettings.experimentalAnnotationsInUse }, - leftExtendsRightConsistently = { left, right -> left.containsAll(right) }, - consistencyConditionHint = experimentalAnnotationsInUseHint - ) -} - -internal class SourceSetConsistencyChecker( - val checks: List> -) { - fun runSingleCheck( - dependent: KotlinSourceSet, - dependency: KotlinSourceSet, - check: ConsistencyCheck - ) { - 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, experimentalAnnotationsCheck, unstableFeaturesCheck)) -} \ No newline at end of file