diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildToolsApiJvmCompilationIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildToolsApiJvmCompilationIT.kt index 31a4a9f8f1b..60476ed6e07 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildToolsApiJvmCompilationIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildToolsApiJvmCompilationIT.kt @@ -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() + ) } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/transforms/BuildToolsApiClasspathEntrySnapshotTransform.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/transforms/BuildToolsApiClasspathEntrySnapshotTransform.kt index 949457c7e0d..f7f95e7653f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/transforms/BuildToolsApiClasspathEntrySnapshotTransform.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/transforms/BuildToolsApiClasspathEntrySnapshotTransform.kt @@ -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 { +abstract class BuildToolsApiClasspathEntrySnapshotTransform : TransformAction, + TransformActionUsingKotlinToolingDiagnostics { - abstract class Parameters : TransformParameters { + abstract class Parameters : TransformParameters, TransformActionUsingKotlinToolingDiagnostics.Parameters { @get:Internal abstract val gradleUserHomeDir: DirectoryProperty @@ -34,13 +38,38 @@ abstract class BuildToolsApiClasspathEntrySnapshotTransform : TransformAction + + @get:Internal + internal abstract val buildToolsImplVersion: Property + + @get:Internal + internal abstract val kgpVersion: Property + + @get:Internal + internal abstract val suppressVersionInconsistencyChecks: Property } @get:Classpath @get:InputArtifact abstract val inputArtifact: Provider + 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") diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt index 88dab5165ef..1a6ae2137ce 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt index 806f9e2d045..0dff64a7ce5 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt @@ -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 { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/configuration/KotlinCompileConfig.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/configuration/KotlinCompileConfig.kt index 2567af8786a..cbdc09ac840 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/configuration/KotlinCompileConfig.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/configuration/KotlinCompileConfig.kt @@ -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 : 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 : AbstractKotl private fun registerTransformsOnce( project: Project, jvmToolchain: Provider, + 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.configureCommonParameters( + kgpVersion: String, classLoadersCachingService: Provider, - classpath: Provider, + classpath: Provider, jvmToolchain: Provider, + runKotlinCompilerViaBuildToolsApi: Boolean, + suppressVersionInconsistencyChecks: Boolean, ) { parameters.gradleUserHomeDir.set(project.gradle.gradleUserHomeDir) parameters.classLoadersCachingService.set(classLoadersCachingService) @@ -142,20 +154,41 @@ internal open class BaseKotlinCompileConfig : 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) { + private fun Configuration.findBuildToolsApiImplVersion() = incoming.resolutionResult.allDependencies + .filterIsInstance() + .map { it.selected.id } + .filterIsInstance() + .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, + 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) } }