[Gradle] Implement BuildToolsApiVersionInconsistency diagnostic

This diagnostic checks if the Build Tools API implementation version is consistent with KGP in the case compilation goes using the old way
#KT-61449 Fixed
This commit is contained in:
Alexander.Likhachev
2023-08-24 20:49:21 +02:00
committed by Space Team
parent 271d767138
commit 501c111e36
5 changed files with 144 additions and 9 deletions
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle
import org.gradle.testkit.runner.BuildResult
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.internals.asFinishLogMessage
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy
import org.jetbrains.kotlin.gradle.tasks.USING_JVM_INCREMENTAL_COMPILATION_MESSAGE
@@ -19,6 +20,42 @@ import org.junit.jupiter.api.DisplayName
class BuildToolsApiJvmCompilationIT : KGPBaseTest() {
override val defaultBuildOptions = super.defaultBuildOptions.copy(runViaBuildToolsApi = true)
@GradleTest
@DisplayName("Build Tools version consistency checker works if the old way of compilation is used together with the build tools API transform")
fun versionConsistencyDiagnosticWorks(gradleVersion: GradleVersion) {
project(
"simpleProject", gradleVersion, buildOptions = defaultBuildOptions.copy(
runViaBuildToolsApi = false,
incremental = false,
)
) {
build("assemble") {
assertNoDiagnostic(KotlinToolingDiagnostics.BuildToolsApiVersionInconsistency)
}
enableOtherVersionBuildToolsImpl()
buildAndFail("assemble") {
assertHasDiagnostic(KotlinToolingDiagnostics.BuildToolsApiVersionInconsistency)
assertOutputContains("Expected version: ${defaultBuildOptions.kotlinVersion}")
assertOutputContains("Actual resolved version: $OTHER_KOTLIN_VERSION")
}
}
}
@GradleTest
@DisplayName("Build Tools version consistency checker disabled if compilation goes via the build tools api")
fun versionConsistencyDiagnosticDisabled(gradleVersion: GradleVersion) {
project(
"simpleProject", gradleVersion, buildOptions = defaultBuildOptions.copy(
incremental = false,
)
) {
enableOtherVersionBuildToolsImpl()
build("assemble") {
assertNoDiagnostic(KotlinToolingDiagnostics.BuildToolsApiVersionInconsistency)
}
}
}
@GradleTest
@DisplayName("Simple project non-incremental in-process compilation")
fun compileJvmInProcessNonIncremental(gradleVersion: GradleVersion) = testSimpleProject(
@@ -79,4 +116,19 @@ class BuildToolsApiJvmCompilationIT : KGPBaseTest() {
}
}
}
}
private const val OTHER_KOTLIN_VERSION = "1.9.30-dev-460"
private fun TestProject.enableOtherVersionBuildToolsImpl() {
buildGradle.append(
"""
repositories {
maven { setUrl("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap") }
}
kotlin {
useCompilerVersion("$OTHER_KOTLIN_VERSION")
}
""".trimIndent()
)
}
@@ -12,6 +12,7 @@ import org.gradle.api.file.FileSystemLocation
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.jetbrains.kotlin.buildtools.api.CompilationService
import org.jetbrains.kotlin.buildtools.api.jvm.ClassSnapshotGranularity
@@ -19,13 +20,16 @@ import org.jetbrains.kotlin.buildtools.api.jvm.ClassSnapshotGranularity.CLASS_LE
import org.jetbrains.kotlin.buildtools.api.jvm.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL
import org.jetbrains.kotlin.compilerRunner.btapi.SharedApiClassesClassLoaderProvider
import org.jetbrains.kotlin.gradle.internal.ClassLoadersCachingBuildService
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.diagnostics.TransformActionUsingKotlinToolingDiagnostics
import java.io.File
/** Transform to create a snapshot of a classpath entry (directory or jar). */
@CacheableTransform
abstract class BuildToolsApiClasspathEntrySnapshotTransform : TransformAction<BuildToolsApiClasspathEntrySnapshotTransform.Parameters> {
abstract class BuildToolsApiClasspathEntrySnapshotTransform : TransformAction<BuildToolsApiClasspathEntrySnapshotTransform.Parameters>,
TransformActionUsingKotlinToolingDiagnostics<BuildToolsApiClasspathEntrySnapshotTransform.Parameters> {
abstract class Parameters : TransformParameters {
abstract class Parameters : TransformParameters, TransformActionUsingKotlinToolingDiagnostics.Parameters {
@get:Internal
abstract val gradleUserHomeDir: DirectoryProperty
@@ -34,13 +38,38 @@ abstract class BuildToolsApiClasspathEntrySnapshotTransform : TransformAction<Bu
@get:Classpath
internal abstract val classpath: ConfigurableFileCollection
@get:Input
internal abstract val compilationViaBuildToolsApi: Property<Boolean>
@get:Internal
internal abstract val buildToolsImplVersion: Property<String>
@get:Internal
internal abstract val kgpVersion: Property<String>
@get:Internal
internal abstract val suppressVersionInconsistencyChecks: Property<Boolean>
}
@get:Classpath
@get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
private fun checkVersionConsistency() {
if (parameters.suppressVersionInconsistencyChecks.get()) return
val kgpVersion = parameters.kgpVersion.get()
val buildToolsImplVersion = parameters.buildToolsImplVersion.orNull
.takeIf { it != "null" } // workaround for incorrect nullability of `map`
if (kgpVersion != buildToolsImplVersion) {
reportDiagnostic(KotlinToolingDiagnostics.BuildToolsApiVersionInconsistency(kgpVersion, buildToolsImplVersion))
}
}
override fun transform(outputs: TransformOutputs) {
if (!parameters.compilationViaBuildToolsApi.get()) {
checkVersionConsistency()
}
val classpathEntryInputDirOrJar = inputArtifact.get().asFile
val snapshotOutputFile = outputs.file(classpathEntryInputDirOrJar.name.replace('.', '_') + "-snapshot.bin")
@@ -549,6 +549,13 @@ internal class PropertiesProvider private constructor(private val project: Proje
val konanDataDir: String?
get() = property(PropertyNames.KONAN_DATA_DIR)
/**
* Allows suppressing the diagnostic [KotlinToolingDiagnostics.BuildToolsApiVersionInconsistency].
* Required only for Kotlin repo bootstrapping.
*/
val suppressBuildToolsApiVersionConsistencyChecks: Boolean
get() = booleanProperty(PropertyNames.KOTLIN_SUPPRESS_BUILD_TOOLS_API_VERSION_CONSISTENCY_CHECKS) ?: false
/**
* Retrieves a comma-separated list of browsers to use when running karma tests for [target]
* @see KOTLIN_JS_KARMA_BROWSERS
@@ -648,6 +655,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
val KOTLIN_NATIVE_IGNORE_DISABLED_TARGETS = property("kotlin.native.ignoreDisabledTargets")
val KOTLIN_NATIVE_SUPPRESS_EXPERIMENTAL_ARTIFACTS_DSL_WARNING = property("kotlin.native.suppressExperimentalArtifactsDslWarning")
val KONAN_DATA_DIR = property("konan.data.dir")
val KOTLIN_SUPPRESS_BUILD_TOOLS_API_VERSION_CONSISTENCY_CHECKS = property("kotlin.internal.suppress.buildToolsApiVersionConsistencyChecks")
/**
* Internal properties: builds get big non-suppressible warning when such properties are used
@@ -10,6 +10,8 @@ import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.PRESETS_DEPRECATION_MESSAGE_SUFFIX
import org.jetbrains.kotlin.gradle.dsl.KotlinSourceSetConvention.isRegisteredByKotlinSourceSetConventionAt
import org.jetbrains.kotlin.gradle.dsl.NativeTargetShortcutTrace
import org.jetbrains.kotlin.gradle.internal.KOTLIN_BUILD_TOOLS_API_IMPL
import org.jetbrains.kotlin.gradle.internal.KOTLIN_MODULE_GROUP
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
@@ -639,6 +641,17 @@ object KotlinToolingDiagnostics {
""".trimMargin()
)
}
object BuildToolsApiVersionInconsistency : ToolingDiagnosticFactory(FATAL) {
operator fun invoke(expectedVersion: String, actualVersion: String?) = build(
"""
Artifact $KOTLIN_MODULE_GROUP:$KOTLIN_BUILD_TOOLS_API_IMPL must have version aligned with the version of KGP when compilation via the Build Tools API is disabled.
Expected version: $expectedVersion
Actual resolved version: ${actualVersion ?: "not found"}
""".trimIndent(),
)
}
}
private fun String.indentLines(nSpaces: Int = 4, skipFirstLine: Boolean = true): String {
@@ -6,17 +6,24 @@
package org.jetbrains.kotlin.gradle.tasks.configuration
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.artifacts.transform.TransformSpec
import org.gradle.api.attributes.Attribute
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
import org.jetbrains.kotlin.gradle.internal.ClassLoadersCachingBuildService
import org.jetbrains.kotlin.gradle.internal.KOTLIN_BUILD_TOOLS_API_IMPL
import org.jetbrains.kotlin.gradle.internal.KOTLIN_MODULE_GROUP
import org.jetbrains.kotlin.gradle.internal.transforms.BuildToolsApiClasspathEntrySnapshotTransform
import org.jetbrains.kotlin.gradle.plugin.BUILD_TOOLS_API_CLASSPATH_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationInfo
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.diagnostics.setupTransformActionToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaCompilation
@@ -35,7 +42,8 @@ internal open class BaseKotlinCompileConfig<TASK : KotlinCompile> : AbstractKotl
val useClasspathSnapshot = propertiesProvider.useClasspathSnapshot
val classpathConfiguration = if (useClasspathSnapshot) {
val jvmToolchain = taskProvider.flatMap { it.defaultKotlinJavaToolchain }
registerTransformsOnce(project, jvmToolchain)
val runKotlinCompilerViaBuildToolsApi = propertiesProvider.runKotlinCompilerViaBuildToolsApi
registerTransformsOnce(project, jvmToolchain, runKotlinCompilerViaBuildToolsApi)
// Note: Creating configurations should be done during build configuration, not task configuration, to avoid issues with
// composite builds (e.g., https://issuetracker.google.com/183952598).
project.configurations.detachedConfiguration(
@@ -117,19 +125,23 @@ internal open class BaseKotlinCompileConfig<TASK : KotlinCompile> : AbstractKotl
private fun registerTransformsOnce(
project: Project,
jvmToolchain: Provider<DefaultKotlinJavaToolchain>,
runKotlinCompilerViaBuildToolsApi: Boolean,
) {
if (project.extensions.extraProperties.has(TRANSFORMS_REGISTERED)) {
return
}
project.extensions.extraProperties[TRANSFORMS_REGISTERED] = true
registerBuildToolsApiTransformations(project, jvmToolchain)
registerBuildToolsApiTransformations(project, jvmToolchain, runKotlinCompilerViaBuildToolsApi)
}
private fun TransformSpec<BuildToolsApiClasspathEntrySnapshotTransform.Parameters>.configureCommonParameters(
kgpVersion: String,
classLoadersCachingService: Provider<ClassLoadersCachingBuildService>,
classpath: Provider<out FileCollection>,
classpath: Provider<out Configuration>,
jvmToolchain: Provider<DefaultKotlinJavaToolchain>,
runKotlinCompilerViaBuildToolsApi: Boolean,
suppressVersionInconsistencyChecks: Boolean,
) {
parameters.gradleUserHomeDir.set(project.gradle.gradleUserHomeDir)
parameters.classLoadersCachingService.set(classLoadersCachingService)
@@ -142,20 +154,41 @@ internal open class BaseKotlinCompileConfig<TASK : KotlinCompile> : AbstractKotl
emptySet()
}
})
parameters.compilationViaBuildToolsApi.set(runKotlinCompilerViaBuildToolsApi)
if (!suppressVersionInconsistencyChecks) {
parameters.buildToolsImplVersion.set(classpath.map { configuration -> configuration.findBuildToolsApiImplVersion() })
}
parameters.kgpVersion.set(kgpVersion)
parameters.suppressVersionInconsistencyChecks.set(suppressVersionInconsistencyChecks)
}
private fun registerBuildToolsApiTransformations(project: Project, jvmToolchain: Provider<DefaultKotlinJavaToolchain>) {
private fun Configuration.findBuildToolsApiImplVersion() = incoming.resolutionResult.allDependencies
.filterIsInstance<ResolvedDependencyResult>()
.map { it.selected.id }
.filterIsInstance<ModuleComponentIdentifier>()
.find { it.group == KOTLIN_MODULE_GROUP && it.module == KOTLIN_BUILD_TOOLS_API_IMPL }
?.version ?: "null" // workaround for incorrect nullability of `map`
private fun registerBuildToolsApiTransformations(
project: Project,
jvmToolchain: Provider<DefaultKotlinJavaToolchain>,
runKotlinCompilerViaBuildToolsApi: Boolean
) {
val classLoadersCachingService = ClassLoadersCachingBuildService.registerIfAbsent(project)
val classpath = project.configurations.named(BUILD_TOOLS_API_CLASSPATH_CONFIGURATION_NAME)
val kgpVersion = project.getKotlinPluginVersion()
val suppressVersionInconsistencyChecks = project.kotlinPropertiesProvider.suppressBuildToolsApiVersionConsistencyChecks
project.dependencies.registerTransform(BuildToolsApiClasspathEntrySnapshotTransform::class.java) {
it.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, JAR_ARTIFACT_TYPE)
it.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE)
it.configureCommonParameters(classLoadersCachingService, classpath, jvmToolchain)
it.configureCommonParameters(kgpVersion, classLoadersCachingService, classpath, jvmToolchain, runKotlinCompilerViaBuildToolsApi, suppressVersionInconsistencyChecks)
it.setupTransformActionToolingDiagnostics(project)
}
project.dependencies.registerTransform(BuildToolsApiClasspathEntrySnapshotTransform::class.java) {
it.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, DIRECTORY_ARTIFACT_TYPE)
it.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE)
it.configureCommonParameters(classLoadersCachingService, classpath, jvmToolchain)
it.configureCommonParameters(kgpVersion, classLoadersCachingService, classpath, jvmToolchain, runKotlinCompilerViaBuildToolsApi, suppressVersionInconsistencyChecks)
it.setupTransformActionToolingDiagnostics(project)
}
}