Add language consistency checks for fragments

Migrate exiting consistency checker for source sets to be generic
and reuse it for fragments
This commit is contained in:
Anton Lakotka
2021-03-25 13:24:24 +01:00
committed by Sergey Igushkin
parent de4221afd8
commit c3264a2979
5 changed files with 121 additions and 93 deletions
@@ -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
@@ -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<KotlinGradleFragment>
get() = (this as KotlinModuleFragment).refinesClosure.map { it as KotlinGradleFragment }.toSet()
internal val kotlinGradleFragmentConsistencyChecker =
FragmentConsistencyChecker<KotlinGradleFragment>(
unitsName = "fragments",
name = { name },
checks = FragmentConsistencyChecks<KotlinGradleFragment>(
unitName = "fragment",
languageSettings = { languageSettings }
).allChecks
)
@@ -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<T, S>(
val name: String,
val getValue: (T) -> S,
val leftExtendsRightConsistently: (S, S) -> Boolean,
val consistencyConditionHint: String
)
internal class FragmentConsistencyChecks<T>(
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<T, LanguageVersion>(
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<T, Set<LanguageFeature>>(
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<T, Set<String>>(
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<T>(
private val unitsName: String,
private val name: T.() -> String,
val checks: List<ConsistencyCheck<T, *>>
) {
fun <S> runSingleCheck(
dependent: T,
dependency: T,
check: ConsistencyCheck<T, S>
) {
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)
}
}
}
@@ -202,6 +202,17 @@ class DefaultKotlinSourceSet(
//endregion
}
internal val defaultSourceSetLanguageSettingsChecker =
FragmentConsistencyChecker<KotlinSourceSet>(
unitsName = "source sets",
name = { name },
checks = FragmentConsistencyChecks<KotlinSourceSet>(
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
@@ -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<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
)
const val experimentalAnnotationsInUseHint = "The dependent source set must use all experimental annotations that its dependency uses."
internal val experimentalAnnotationsCheck = ConsistencyCheck<KotlinSourceSet, Set<String>>(
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<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, experimentalAnnotationsCheck, unstableFeaturesCheck))
}