[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.testkit.runner.BuildResult
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.internals.asFinishLogMessage 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.testbase.*
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy
import org.jetbrains.kotlin.gradle.tasks.USING_JVM_INCREMENTAL_COMPILATION_MESSAGE import org.jetbrains.kotlin.gradle.tasks.USING_JVM_INCREMENTAL_COMPILATION_MESSAGE
@@ -19,6 +20,42 @@ import org.junit.jupiter.api.DisplayName
class BuildToolsApiJvmCompilationIT : KGPBaseTest() { class BuildToolsApiJvmCompilationIT : KGPBaseTest() {
override val defaultBuildOptions = super.defaultBuildOptions.copy(runViaBuildToolsApi = true) 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 @GradleTest
@DisplayName("Simple project non-incremental in-process compilation") @DisplayName("Simple project non-incremental in-process compilation")
fun compileJvmInProcessNonIncremental(gradleVersion: GradleVersion) = testSimpleProject( fun compileJvmInProcessNonIncremental(gradleVersion: GradleVersion) = testSimpleProject(
@@ -80,3 +117,18 @@ 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.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Internal
import org.jetbrains.kotlin.buildtools.api.CompilationService import org.jetbrains.kotlin.buildtools.api.CompilationService
import org.jetbrains.kotlin.buildtools.api.jvm.ClassSnapshotGranularity 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.buildtools.api.jvm.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL
import org.jetbrains.kotlin.compilerRunner.btapi.SharedApiClassesClassLoaderProvider import org.jetbrains.kotlin.compilerRunner.btapi.SharedApiClassesClassLoaderProvider
import org.jetbrains.kotlin.gradle.internal.ClassLoadersCachingBuildService 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 import java.io.File
/** Transform to create a snapshot of a classpath entry (directory or jar). */ /** Transform to create a snapshot of a classpath entry (directory or jar). */
@CacheableTransform @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 @get:Internal
abstract val gradleUserHomeDir: DirectoryProperty abstract val gradleUserHomeDir: DirectoryProperty
@@ -34,13 +38,38 @@ abstract class BuildToolsApiClasspathEntrySnapshotTransform : TransformAction<Bu
@get:Classpath @get:Classpath
internal abstract val classpath: ConfigurableFileCollection 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:Classpath
@get:InputArtifact @get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation> 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) { override fun transform(outputs: TransformOutputs) {
if (!parameters.compilationViaBuildToolsApi.get()) {
checkVersionConsistency()
}
val classpathEntryInputDirOrJar = inputArtifact.get().asFile val classpathEntryInputDirOrJar = inputArtifact.get().asFile
val snapshotOutputFile = outputs.file(classpathEntryInputDirOrJar.name.replace('.', '_') + "-snapshot.bin") 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? val konanDataDir: String?
get() = property(PropertyNames.KONAN_DATA_DIR) 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] * Retrieves a comma-separated list of browsers to use when running karma tests for [target]
* @see KOTLIN_JS_KARMA_BROWSERS * @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_IGNORE_DISABLED_TARGETS = property("kotlin.native.ignoreDisabledTargets")
val KOTLIN_NATIVE_SUPPRESS_EXPERIMENTAL_ARTIFACTS_DSL_WARNING = property("kotlin.native.suppressExperimentalArtifactsDslWarning") val KOTLIN_NATIVE_SUPPRESS_EXPERIMENTAL_ARTIFACTS_DSL_WARNING = property("kotlin.native.suppressExperimentalArtifactsDslWarning")
val KONAN_DATA_DIR = property("konan.data.dir") 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 * 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.PRESETS_DEPRECATION_MESSAGE_SUFFIX
import org.jetbrains.kotlin.gradle.dsl.KotlinSourceSetConvention.isRegisteredByKotlinSourceSetConventionAt import org.jetbrains.kotlin.gradle.dsl.KotlinSourceSetConvention.isRegisteredByKotlinSourceSetConventionAt
import org.jetbrains.kotlin.gradle.dsl.NativeTargetShortcutTrace 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.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
@@ -639,6 +641,17 @@ object KotlinToolingDiagnostics {
""".trimMargin() """.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 { private fun String.indentLines(nSpaces: Int = 4, skipFirstLine: Boolean = true): String {
@@ -6,17 +6,24 @@
package org.jetbrains.kotlin.gradle.tasks.configuration package org.jetbrains.kotlin.gradle.tasks.configuration
import org.gradle.api.Project 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.artifacts.transform.TransformSpec
import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.Attribute
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
import org.jetbrains.kotlin.gradle.internal.ClassLoadersCachingBuildService 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.internal.transforms.BuildToolsApiClasspathEntrySnapshotTransform
import org.jetbrains.kotlin.gradle.plugin.BUILD_TOOLS_API_CLASSPATH_CONFIGURATION_NAME 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.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.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaCompilation
@@ -35,7 +42,8 @@ internal open class BaseKotlinCompileConfig<TASK : KotlinCompile> : AbstractKotl
val useClasspathSnapshot = propertiesProvider.useClasspathSnapshot val useClasspathSnapshot = propertiesProvider.useClasspathSnapshot
val classpathConfiguration = if (useClasspathSnapshot) { val classpathConfiguration = if (useClasspathSnapshot) {
val jvmToolchain = taskProvider.flatMap { it.defaultKotlinJavaToolchain } 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 // 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). // composite builds (e.g., https://issuetracker.google.com/183952598).
project.configurations.detachedConfiguration( project.configurations.detachedConfiguration(
@@ -117,19 +125,23 @@ internal open class BaseKotlinCompileConfig<TASK : KotlinCompile> : AbstractKotl
private fun registerTransformsOnce( private fun registerTransformsOnce(
project: Project, project: Project,
jvmToolchain: Provider<DefaultKotlinJavaToolchain>, jvmToolchain: Provider<DefaultKotlinJavaToolchain>,
runKotlinCompilerViaBuildToolsApi: Boolean,
) { ) {
if (project.extensions.extraProperties.has(TRANSFORMS_REGISTERED)) { if (project.extensions.extraProperties.has(TRANSFORMS_REGISTERED)) {
return return
} }
project.extensions.extraProperties[TRANSFORMS_REGISTERED] = true project.extensions.extraProperties[TRANSFORMS_REGISTERED] = true
registerBuildToolsApiTransformations(project, jvmToolchain) registerBuildToolsApiTransformations(project, jvmToolchain, runKotlinCompilerViaBuildToolsApi)
} }
private fun TransformSpec<BuildToolsApiClasspathEntrySnapshotTransform.Parameters>.configureCommonParameters( private fun TransformSpec<BuildToolsApiClasspathEntrySnapshotTransform.Parameters>.configureCommonParameters(
kgpVersion: String,
classLoadersCachingService: Provider<ClassLoadersCachingBuildService>, classLoadersCachingService: Provider<ClassLoadersCachingBuildService>,
classpath: Provider<out FileCollection>, classpath: Provider<out Configuration>,
jvmToolchain: Provider<DefaultKotlinJavaToolchain>, jvmToolchain: Provider<DefaultKotlinJavaToolchain>,
runKotlinCompilerViaBuildToolsApi: Boolean,
suppressVersionInconsistencyChecks: Boolean,
) { ) {
parameters.gradleUserHomeDir.set(project.gradle.gradleUserHomeDir) parameters.gradleUserHomeDir.set(project.gradle.gradleUserHomeDir)
parameters.classLoadersCachingService.set(classLoadersCachingService) parameters.classLoadersCachingService.set(classLoadersCachingService)
@@ -142,20 +154,41 @@ internal open class BaseKotlinCompileConfig<TASK : KotlinCompile> : AbstractKotl
emptySet() 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 classLoadersCachingService = ClassLoadersCachingBuildService.registerIfAbsent(project)
val classpath = project.configurations.named(BUILD_TOOLS_API_CLASSPATH_CONFIGURATION_NAME) 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) { project.dependencies.registerTransform(BuildToolsApiClasspathEntrySnapshotTransform::class.java) {
it.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, JAR_ARTIFACT_TYPE) it.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, JAR_ARTIFACT_TYPE)
it.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, CLASSPATH_ENTRY_SNAPSHOT_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) { project.dependencies.registerTransform(BuildToolsApiClasspathEntrySnapshotTransform::class.java) {
it.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, DIRECTORY_ARTIFACT_TYPE) it.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, DIRECTORY_ARTIFACT_TYPE)
it.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, CLASSPATH_ENTRY_SNAPSHOT_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)
} }
} }