From e0029b14ee65667180313571753f52c265692ce3 Mon Sep 17 00:00:00 2001 From: "Aleksei.Cherepanov" Date: Mon, 18 Apr 2022 11:06:15 +0000 Subject: [PATCH] Rebuild module on plugin classpaths change Add another one reason for rebuild: if pluginClasspaths was changed or even its jar content, module will be marked for rebuild Merge-request: KT-MR-5839 Merged-by: Aleksei Cherepanov --- .../jetbrains/kotlin/build/BuildMetaInfo.kt | 8 ++- .../kotlin/build/CommonBuildMetaInfo.kt | 9 ++- .../jetbrains/kotlin/build/JsBuildMetaInfo.kt | 9 ++- .../kotlin/build/JvmBuildMetaInfo.kt | 9 ++- .../kotlin/build/BuildMetaInfoTest.kt | 3 +- .../kotlin/config/PluginClasspathsUtils.kt | 66 +++++++++++++++++++ .../kotlin/jps/build/KotlinJpsBuildTest.kt | 34 ++++++++++ .../jps/targets/KotlinModuleBuildTarget.kt | 12 ++-- 8 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 compiler/util/src/org/jetbrains/kotlin/config/PluginClasspathsUtils.kt diff --git a/build-common/src/org/jetbrains/kotlin/build/BuildMetaInfo.kt b/build-common/src/org/jetbrains/kotlin/build/BuildMetaInfo.kt index b852deb2d87..d9c73106b42 100644 --- a/build-common/src/org/jetbrains/kotlin/build/BuildMetaInfo.kt +++ b/build-common/src/org/jetbrains/kotlin/build/BuildMetaInfo.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.build import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.config.LanguageVersion +import org.jetbrains.kotlin.config.PluginClasspaths import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion import kotlin.reflect.KClass @@ -23,6 +24,7 @@ interface BuildMetaInfo { val ownVersion: Int val coroutinesVersion: Int val multiplatformVersion: Int + val pluginClasspaths: String } abstract class BuildMetaInfoFactory(private val metaInfoClass: KClass) { @@ -35,7 +37,8 @@ abstract class BuildMetaInfoFactory(private val metaInfoClass ownVersion: Int, coroutinesVersion: Int, multiplatformVersion: Int, - metadataVersionArray: IntArray? + metadataVersionArray: IntArray?, + pluginClasspaths: String ): T fun create(args: CommonCompilerArguments): T { @@ -50,7 +53,8 @@ abstract class BuildMetaInfoFactory(private val metaInfoClass ownVersion = OWN_VERSION, coroutinesVersion = COROUTINES_VERSION, multiplatformVersion = MULTIPLATFORM_VERSION, - metadataVersionArray = args.metadataVersion?.let { BinaryVersion.parseVersionArray(it) } + metadataVersionArray = args.metadataVersion?.let { BinaryVersion.parseVersionArray(it) }, + pluginClasspaths = PluginClasspaths(args.pluginClasspaths).serialize() ) } diff --git a/build-common/src/org/jetbrains/kotlin/build/CommonBuildMetaInfo.kt b/build-common/src/org/jetbrains/kotlin/build/CommonBuildMetaInfo.kt index 09e6931fbeb..b2aa9ccd27a 100644 --- a/build-common/src/org/jetbrains/kotlin/build/CommonBuildMetaInfo.kt +++ b/build-common/src/org/jetbrains/kotlin/build/CommonBuildMetaInfo.kt @@ -21,7 +21,8 @@ data class CommonBuildMetaInfo( override val metadataVersionPatch: Int, override val ownVersion: Int, override val coroutinesVersion: Int, - override val multiplatformVersion: Int + override val multiplatformVersion: Int, + override val pluginClasspaths: String ) : BuildMetaInfo { companion object : BuildMetaInfoFactory(CommonBuildMetaInfo::class) { override fun create( @@ -33,7 +34,8 @@ data class CommonBuildMetaInfo( ownVersion: Int, coroutinesVersion: Int, multiplatformVersion: Int, - metadataVersionArray: IntArray? + metadataVersionArray: IntArray?, + pluginClasspaths: String ): CommonBuildMetaInfo { val metadataVersion = metadataVersionArray?.let(::JvmMetadataVersion) ?: JvmMetadataVersion.INSTANCE return CommonBuildMetaInfo( @@ -47,7 +49,8 @@ data class CommonBuildMetaInfo( metadataVersionPatch = metadataVersion.patch, ownVersion = ownVersion, coroutinesVersion = coroutinesVersion, - multiplatformVersion = multiplatformVersion + multiplatformVersion = multiplatformVersion, + pluginClasspaths = pluginClasspaths ) } } diff --git a/build-common/src/org/jetbrains/kotlin/build/JsBuildMetaInfo.kt b/build-common/src/org/jetbrains/kotlin/build/JsBuildMetaInfo.kt index 6d97112df9c..095d20725f6 100644 --- a/build-common/src/org/jetbrains/kotlin/build/JsBuildMetaInfo.kt +++ b/build-common/src/org/jetbrains/kotlin/build/JsBuildMetaInfo.kt @@ -21,7 +21,8 @@ data class JsBuildMetaInfo( override val metadataVersionPatch: Int, override val ownVersion: Int, override val coroutinesVersion: Int, - override val multiplatformVersion: Int + override val multiplatformVersion: Int, + override val pluginClasspaths: String ) : BuildMetaInfo { companion object : BuildMetaInfoFactory(JsBuildMetaInfo::class) { override fun create( @@ -33,7 +34,8 @@ data class JsBuildMetaInfo( ownVersion: Int, coroutinesVersion: Int, multiplatformVersion: Int, - metadataVersionArray: IntArray? + metadataVersionArray: IntArray?, + pluginClasspaths: String ): JsBuildMetaInfo { val metadataVersion = metadataVersionArray?.let(::JsMetadataVersion) ?: JsMetadataVersion.INSTANCE return JsBuildMetaInfo( @@ -47,7 +49,8 @@ data class JsBuildMetaInfo( metadataVersionPatch = metadataVersion.patch, ownVersion = ownVersion, coroutinesVersion = coroutinesVersion, - multiplatformVersion = multiplatformVersion + multiplatformVersion = multiplatformVersion, + pluginClasspaths = pluginClasspaths ) } } diff --git a/build-common/src/org/jetbrains/kotlin/build/JvmBuildMetaInfo.kt b/build-common/src/org/jetbrains/kotlin/build/JvmBuildMetaInfo.kt index 00a64474419..24f735a08ae 100644 --- a/build-common/src/org/jetbrains/kotlin/build/JvmBuildMetaInfo.kt +++ b/build-common/src/org/jetbrains/kotlin/build/JvmBuildMetaInfo.kt @@ -36,7 +36,8 @@ data class JvmBuildMetaInfo( val bytecodeVersionPatch: Int, override val ownVersion: Int, override val coroutinesVersion: Int, - override val multiplatformVersion: Int + override val multiplatformVersion: Int, + override val pluginClasspaths: String ) : BuildMetaInfo { companion object : BuildMetaInfoFactory(JvmBuildMetaInfo::class) { override fun create( @@ -48,7 +49,8 @@ data class JvmBuildMetaInfo( ownVersion: Int, coroutinesVersion: Int, multiplatformVersion: Int, - metadataVersionArray: IntArray? + metadataVersionArray: IntArray?, + pluginClasspaths: String ): JvmBuildMetaInfo { val metadataVersion = metadataVersionArray?.let(::JvmMetadataVersion) ?: JvmMetadataVersion.INSTANCE return JvmBuildMetaInfo( @@ -65,7 +67,8 @@ data class JvmBuildMetaInfo( bytecodeVersionPatch = JvmBytecodeBinaryVersion.INSTANCE.patch, ownVersion = ownVersion, coroutinesVersion = coroutinesVersion, - multiplatformVersion = multiplatformVersion + multiplatformVersion = multiplatformVersion, + pluginClasspaths = pluginClasspaths ) } } diff --git a/build-common/test/org/jetbrains/kotlin/build/BuildMetaInfoTest.kt b/build-common/test/org/jetbrains/kotlin/build/BuildMetaInfoTest.kt index cc7e504553b..a3c7282831b 100644 --- a/build-common/test/org/jetbrains/kotlin/build/BuildMetaInfoTest.kt +++ b/build-common/test/org/jetbrains/kotlin/build/BuildMetaInfoTest.kt @@ -41,7 +41,8 @@ class BuildMetaInfoTest : TestCase() { "metadataVersionPatch", "multiplatformEnable", "multiplatformVersion", - "ownVersion" + "ownVersion", + "pluginClasspaths" ) assertEquals(expectedKeys, actual.split("\r\n", "\n").map { line -> line.split("=").first() }) } diff --git a/compiler/util/src/org/jetbrains/kotlin/config/PluginClasspathsUtils.kt b/compiler/util/src/org/jetbrains/kotlin/config/PluginClasspathsUtils.kt new file mode 100644 index 00000000000..ae1e303326f --- /dev/null +++ b/compiler/util/src/org/jetbrains/kotlin/config/PluginClasspathsUtils.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2010-2022 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.config + +import java.io.File +import java.security.DigestInputStream +import java.security.MessageDigest + +class PluginClasspaths(val classpaths: Array?) { + companion object { + fun deserializeWithHashes(str: String): List> = + str.split(":") + .filter(String::isNotBlank) + .map { Pair(File(it.substringBeforeLast("-")).name, it.substringAfterLast("-")) } + } + + fun serialize() = classpaths?.mapNotNull { it -> + val jar = File(it).takeIf { it.exists() } ?: return@mapNotNull null + val jarPath = jar.absolutePath + val jarHash = jar.sha256() + "$jarPath-$jarHash" + }?.joinToString(":") ?: "" + + private fun File.sha256(): String { + val digest = MessageDigest.getInstance("SHA-256") + DigestInputStream(this.inputStream(), digest).use { dis -> + val buffer = ByteArray(8192) + var bytesRead = 0 + while (bytesRead != -1) { + bytesRead = dis.read(buffer, 0, buffer.size) + } + } + // Convert to hex: + return digest.digest().joinToString("") { + Integer.toHexString((it.toInt() and 0xff) + 0x100).substring(1) + } + } +} + +class PluginClasspathsComparator(old: String, new: String) { + private val oldPluginClasspath: List> by lazy { PluginClasspaths.deserializeWithHashes(old) } + private val newPluginClasspath: List> by lazy { PluginClasspaths.deserializeWithHashes(new) } + + fun equals() = oldPluginClasspath == newPluginClasspath + + fun describeDifferencesOrNull(): String? { + val changed = oldPluginClasspath != newPluginClasspath + if (!changed) return null + val added = newPluginClasspath.subtract(oldPluginClasspath).takeIf { it.isNotEmpty() }?.toList() + val deleted = oldPluginClasspath.subtract(newPluginClasspath).takeIf { it.isNotEmpty() }?.toList() + return StringBuilder().apply { + if (added == null && deleted == null) { + this.append("Plugin classpaths was reordered.") + return@apply + } + this.append("Plugin classpaths was changed.") + if (added != null) this.append(" Added: ${added.joinToString(", ")}.") + if (deleted != null) this.append(" Deleted: ${deleted.joinToString(", ")}.") + this.append(" Rebuild initiated.") + }.toString() + } +} + diff --git a/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt index ba654ff3b67..580bb0db902 100644 --- a/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt +++ b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt @@ -45,9 +45,11 @@ import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.JvmCodegenUtil import org.jetbrains.kotlin.config.IncrementalCompilation +import org.jetbrains.kotlin.config.KotlinFacetSettings import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.jps.build.KotlinJpsBuildTestBase.LibraryDependency.* import org.jetbrains.kotlin.jps.incremental.CacheAttributesDiff +import org.jetbrains.kotlin.jps.model.JpsKotlinFacetModuleExtension import org.jetbrains.kotlin.jps.model.kotlinCommonCompilerArguments import org.jetbrains.kotlin.jps.model.kotlinCompilerArguments import org.jetbrains.kotlin.jps.targets.KotlinModuleBuildTarget @@ -952,6 +954,38 @@ open class KotlinJpsBuildTest : KotlinJpsBuildTestBase() { buildAllModules().assertSuccessful() } + @WorkingDir("KotlinProject") + fun testModuleRebuildOnPluginClasspathsChange() { + initProject(JVM_MOCK_RUNTIME) + myProject.modules.forEach { + val facet = KotlinFacetSettings() + facet.useProjectSettings = false + facet.compilerArguments = K2JVMCompilerArguments() + facet.compilerArguments?.pluginClasspaths = arrayOf(PathUtil.kotlinPathsForDistDirectory.lombokPluginJarPath.path) + + it.container.setChild( + JpsKotlinFacetModuleExtension.KIND, + JpsKotlinFacetModuleExtension(facet) + ) + } + buildAllModules().assertSuccessful() + myProject.modules.forEach { + val facet = KotlinFacetSettings() + facet.useProjectSettings = false + facet.compilerArguments = K2JVMCompilerArguments() + facet.compilerArguments?.pluginClasspaths = arrayOf( + PathUtil.kotlinPathsForDistDirectory.lombokPluginJarPath.path, + PathUtil.kotlinPathsForDistDirectory.allOpenPluginJarPath.path + ) + it.container.setChild( + JpsKotlinFacetModuleExtension.KIND, + JpsKotlinFacetModuleExtension(facet) + ) + } + + checkWhen(emptyArray(), null, packageClasses("kotlinProject", "src/test1.kt", "Test1Kt")) + } + private fun BuildResult.checkErrors() { val actualErrors = getMessages(BuildMessage.Kind.ERROR) .map { it as CompilerMessage } diff --git a/jps/jps-plugin/src/org/jetbrains/kotlin/jps/targets/KotlinModuleBuildTarget.kt b/jps/jps-plugin/src/org/jetbrains/kotlin/jps/targets/KotlinModuleBuildTarget.kt index 475667eb718..6ae8e2a6b8a 100644 --- a/jps/jps-plugin/src/org/jetbrains/kotlin/jps/targets/KotlinModuleBuildTarget.kt +++ b/jps/jps-plugin/src/org/jetbrains/kotlin/jps/targets/KotlinModuleBuildTarget.kt @@ -21,9 +21,7 @@ import org.jetbrains.kotlin.build.GeneratedFile import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.compilerRunner.JpsCompilerEnvironment -import org.jetbrains.kotlin.config.ApiVersion -import org.jetbrains.kotlin.config.LanguageVersion -import org.jetbrains.kotlin.config.Services +import org.jetbrains.kotlin.config.* import org.jetbrains.kotlin.incremental.ChangesCollector import org.jetbrains.kotlin.incremental.ExpectActualTrackerImpl import org.jetbrains.kotlin.incremental.components.ExpectActualTracker @@ -339,7 +337,8 @@ abstract class KotlinModuleBuildTarget intern val prevBuildMetaInfo = try { - buildMetaInfoFactory.deserializeFromString(Files.newInputStream(file).bufferedReader().use { it.readText() }) ?: return false + buildMetaInfoFactory.deserializeFromString(Files.newInputStream(file).bufferedReader().use { it.readText() }) + ?: return false } catch (e: Exception) { KotlinBuilder.LOG.error("Could not deserialize build meta info", e) return false @@ -355,7 +354,10 @@ abstract class KotlinModuleBuildTarget intern // If EAP->Non-EAP build with IC, then rebuild all kotlin "Last build was compiled with EAP-plugin" } - else -> null + else -> PluginClasspathsComparator( + prevBuildMetaInfo.pluginClasspaths, + buildMetaInfo.pluginClasspaths + ).describeDifferencesOrNull() } if (reasonToRebuild != null) {