From 36eea1f4c97e6f3f788f1f5dbf7c633e1af6f83f Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Mon, 16 Feb 2026 14:54:20 +0000 Subject: [PATCH] tests rules_jvm: ensure '.kotlin_module' content is always in-sync with the output - ensure output state always corresponds to last known good state of dependency graph: if compilation attempt finishes with errors, all output generated during this compile attempt is deleted. This ensures there will be no stale class files in the output later on. Merge-request: IJ-MR-191763 Merged-by: Eugene Zhuravlev GitOrigin-RevId: e2adffd268c8e71b8795aec7d070b470551f57e3 --- .../jvm-inc-builder-tests/BUILD.bazel | 1 + .../tools/build/bazel/KotlinTests.java | 18 -- .../build/bazel/impl/BazelIncBuildTest.java | 157 ++++++++++++++++++ .../java/classModifiers/addStatic/build.log | 2 +- .../convertToCheckedException/build.log | 2 +- .../removeExtendsAffectsFieldAccess/build.log | 2 +- .../build.log | 2 +- .../removeImplements/build.log | 2 +- .../hidePublicWithPackagePrivate/build.log | 2 +- .../generics/argumentContainment3/build.log | 2 +- .../addSAMInterfaceAbstractMethod/build.log | 2 +- .../membersChange/addVarargMethod/build.log | 2 +- .../kotlin/pureKotlin/moveClass/build.log | 8 +- .../build.log | 5 +- .../pureKotlin/packageRecreated/a/dummy.kt | 2 + .../pureKotlin/packageRecreated/build.log | 4 +- .../packageRecreatedAfterRenaming/a/dummy.kt | 2 + .../pureKotlin/removeUnusedFile/build.log | 4 +- .../bazel/jvmIncBuilder/BazelIncBuilder.java | 87 ++++++++-- .../impl/BatchBuildProcessLogger.java | 126 ++++++++++++++ .../jvmIncBuilder/impl/BuildContextImpl.java | 2 +- .../impl/BuildProcessLoggerImpl.java | 4 +- .../impl/KotlinCompilerRunner.java | 103 ++++++++---- .../impl/KotlinIncrementalCacheImpl.java | 5 +- .../jvmIncBuilder/runner/CompilerRunner.java | 6 - 25 files changed, 450 insertions(+), 102 deletions(-) create mode 100644 build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageRecreated/a/dummy.kt create mode 100644 build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageRecreatedAfterRenaming/a/dummy.kt create mode 100644 build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BatchBuildProcessLogger.java diff --git a/build/jvm-rules/jvm-inc-builder-tests/BUILD.bazel b/build/jvm-rules/jvm-inc-builder-tests/BUILD.bazel index 73ceb2514119..201ad13e6d59 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/BUILD.bazel +++ b/build/jvm-rules/jvm-inc-builder-tests/BUILD.bazel @@ -75,6 +75,7 @@ kt_jvm_library( "@bazel_tools//tools/java/runfiles", "//:annotations_import", "//:asm-all_import", + "//:kotlin-metadata-jvm_import", "//:junit4", "//dependency-graph", "//jps-builders-6:build-javac-rt", diff --git a/build/jvm-rules/jvm-inc-builder-tests/src/com/intellij/tools/build/bazel/KotlinTests.java b/build/jvm-rules/jvm-inc-builder-tests/src/com/intellij/tools/build/bazel/KotlinTests.java index 6c28319bbefe..b996389e0f04 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/src/com/intellij/tools/build/bazel/KotlinTests.java +++ b/build/jvm-rules/jvm-inc-builder-tests/src/com/intellij/tools/build/bazel/KotlinTests.java @@ -373,13 +373,10 @@ public class KotlinTests extends BazelIncBuildTest { performTest("kotlin/pureKotlin/localClassChanged").assertSuccessful(); } - /* - //todo: handle scenario: remove outdated class references from kotlin_module explicitly, if kotlinc cannot be started due to empty source set @Test public void testMoveClass() throws Exception { performTest(2, "kotlin/pureKotlin/moveClass").assertSuccessful(); } - */ @Test public void testMoveFileWithChangingPackage() throws Exception { @@ -426,18 +423,10 @@ public class KotlinTests extends BazelIncBuildTest { performTest("kotlin/pureKotlin/packageFileChangedPackage").assertSuccessful(); } - // todo: investigate scenario: - // 1. the only change on disk is the removal of some "a.kt" file with top-level declarations - // 2. => corresponding AKt.class is removed from the output - // 3. Since there are no sources to compile, compiler will not be run - // 4. => the kotlin_module file in the output is not updated and might contain references to AKt - // 5. All successive compilations know nothing about deleted AKt => the corresponding entry in the kotlin_module may stuck forever. - /* @Test public void testPackageFileChangedThenOtherRemoved() throws Exception { performTest(2, "kotlin/pureKotlin/packageFileChangedThenOtherRemoved").assertSuccessful(); } - */ @Test public void testPackageFileRemoved() throws Exception { @@ -464,14 +453,10 @@ public class KotlinTests extends BazelIncBuildTest { performTest("kotlin/pureKotlin/packagePrivateOnlyChanged").assertSuccessful(); } - /* - // todo: handle scenario: remove outdated class references from kotlin_module explicitly, if kotlinc cannot be started due to empty source set - @Test public void testPackageRecreated() throws Exception { performTest(2, "kotlin/pureKotlin/packageRecreated").assertSuccessful(); } - */ @Test public void testPackageRecreatedAfterRenaming() throws Exception { @@ -618,13 +603,10 @@ public class KotlinTests extends BazelIncBuildTest { performTest("kotlin/pureKotlin/removeTopLevelTypeAlias").assertSuccessful(); } - /* - // todo: handle scenario: remove outdated class references from kotlin_module explicitly, if kotlinc cannot be started due to empty source set @Test public void testRemoveUnusedFile() throws Exception { performTest("kotlin/pureKotlin/removeUnusedFile").assertSuccessful(); } - */ @Test public void testRenameClass() throws Exception { diff --git a/build/jvm-rules/jvm-inc-builder-tests/src/com/intellij/tools/build/bazel/impl/BazelIncBuildTest.java b/build/jvm-rules/jvm-inc-builder-tests/src/com/intellij/tools/build/bazel/impl/BazelIncBuildTest.java index 6423f00b2e7b..1b71987b5c6b 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/src/com/intellij/tools/build/bazel/impl/BazelIncBuildTest.java +++ b/build/jvm-rules/jvm-inc-builder-tests/src/com/intellij/tools/build/bazel/impl/BazelIncBuildTest.java @@ -1,9 +1,22 @@ package com.intellij.tools.build.bazel.impl; import com.intellij.tools.build.bazel.jvmIncBuilder.DataPaths; +import com.intellij.tools.build.bazel.jvmIncBuilder.NodeSourceSnapshot; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.ConfigurationState; import com.intellij.tools.build.bazel.jvmIncBuilder.impl.Utils; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.graph.PersistentMVStoreMapletFactory; +import kotlin.metadata.jvm.KmModule; +import kotlin.metadata.jvm.KmPackageParts; +import kotlin.metadata.jvm.KotlinModuleMetadata; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.dependency.DependencyGraph; +import org.jetbrains.jps.dependency.Node; +import org.jetbrains.jps.dependency.NodeSource; +import org.jetbrains.jps.dependency.impl.DependencyGraphImpl; +import org.jetbrains.jps.dependency.impl.PathSourceMapper; +import org.jetbrains.jps.dependency.java.JVMClassNode; +import org.jetbrains.jps.dependency.java.KotlinMeta; import org.jetbrains.jps.util.Iterators; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -28,13 +41,22 @@ import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.time.Duration; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import static org.jetbrains.jps.util.Iterators.collect; +import static org.jetbrains.jps.util.Iterators.filter; +import static org.jetbrains.jps.util.Iterators.map; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -177,6 +199,7 @@ public abstract class BazelIncBuildTest { runBazelBuild(bazelTarget).assertSuccessful(); // the initial build assertTrue("Tests output root directory " + testOutputDir + " should exist. Probably test expectations differ from Bazel's current output dir naming policy", Files.exists(testOutputDir)); + validateBuildOutput(testDataRelativePath, testOutputDir); ExecutionResult result = null; StringBuilder buildLog = new StringBuilder(); @@ -192,6 +215,8 @@ public abstract class BazelIncBuildTest { buildLog.append("\n").append("------------------------------------------"); buildLog.append("\n").append("Exit code: ").append(result.isSuccessful()? "OK" : "ERROR"); } + + validateBuildOutput(testDataRelativePath, testOutputDir); } String expectedBuildLog = Files.readString(expectedBuildLogFile, StandardCharsets.UTF_8).replaceAll("\r\n?", "\n").trim(); @@ -280,6 +305,138 @@ public abstract class BazelIncBuildTest { }); } + protected void validateBuildOutput(String testDataRelativePath, Path testOutputDir) throws IOException { + // Base validation: + // 1. For a successful build: for every class node in graph the corresponding .class file must exist in the output and vice versa + // 2. For a failed build: for every class file in the output, there must exist a class node in the graph. + // If a class in the graph corresponds to a non-up-to-date source (deleted or modified), there must be no corresponding class file in the output + // see StorageManager.cleanBuildState() + // 3. ConfigurationState with sources with empty digest (=> classes in the graph corresponding to these sources) form dirty scope + // 4. if sources contain 1 or more kotlin files (= for Kotlin tests), kotlin_module must exist in the output + // 5. kotlin_module must contain all facade classes from the output without facade classes from dirty scope + for (BuildOutput output : BuildOutput.scanOutputs(testOutputDir)) { + try (DependencyGraph graph = output.graph) { + NodeSourceSnapshot srcSnapshot = output.configState.getSources(); + Set dirtySources = collect(filter(srcSnapshot.getElements(), src -> srcSnapshot.getDigest(src).isBlank()), new HashSet<>()); + // output .class paths for bytecode + Set dirtyClasses = new HashSet<>(); + Set allGraphClasses = new HashSet<>(); + Set allOutputClasses = new HashSet<>(); + + boolean hasKotlinBytecode = false; + String kotlinModuleEntryPath = null; + // class names + Set kotlinModuleFacadeClassNames = new HashSet<>(); + Set graphFacadeClassNames = new HashSet<>(); + Set graphDirtyFacadeClassNames = new HashSet<>(); + + for (NodeSource src : graph.getSources()) { + boolean isSourceDirty = dirtySources.contains(src); + Consumer acc = isSourceDirty? p -> {dirtyClasses.add(p); allGraphClasses.add(p);} : allGraphClasses::add; + for (Node node : graph.getNodes(src)) { + if (node instanceof JVMClassNode clsNode) { + acc.accept(clsNode.getOutFilePath()); + Iterator metadata = clsNode.getMetadata(KotlinMeta.class).iterator(); + if (metadata.hasNext()) { + hasKotlinBytecode = true; + if (metadata.next().isTopLevelDeclarationContainer()) { + graphFacadeClassNames.add(clsNode.getName()); + if (isSourceDirty) { + graphDirtyFacadeClassNames.add(clsNode.getName()); + } + } + } + } + } + } + + try (var zis = new ZipInputStream(new BufferedInputStream(Files.newInputStream(output.outputJar)))) { + for (ZipEntry entry = zis.getNextEntry(); entry != null; entry = zis.getNextEntry()) { + String path = entry.getName(); + if (path.endsWith(".class")) { + allOutputClasses.add(path); + } + if (path.startsWith("META-INF/") && path.endsWith(DataPaths.KOTLIN_MODULE_EXTENSION)) { + kotlinModuleEntryPath = path; + KmModule module = KotlinModuleMetadata.read(zis.readAllBytes()).getKmModule(); + for (KmPackageParts parts : module.getPackageParts().values()) { + kotlinModuleFacadeClassNames.addAll(parts.getFileFacades()); + kotlinModuleFacadeClassNames.addAll(parts.getMultiFileClassParts().values()); + } + } + } + } + + if (hasKotlinBytecode) { + assertNotNull("Test " + testDataRelativePath + " must have '.kotlin_module' file in its output", kotlinModuleEntryPath); + } + + if (dirtySources.isEmpty()) { + // => successful build + assertTrue(dirtyClasses.isEmpty()); + assertTrue(graphDirtyFacadeClassNames.isEmpty()); + assertEquals(allGraphClasses, allOutputClasses); + if (hasKotlinBytecode) { + assertEquals("Set of registered Kotlin facade classes in the dependency graph must be the same as in '.kotlin_module' output file", graphFacadeClassNames, kotlinModuleFacadeClassNames); + } + } + else { + // => build completed with errors + assertTrue("All classes in the output should be registered in the dependency graph", allGraphClasses.containsAll(allOutputClasses)); + for (String graphClass : allGraphClasses) { + if (dirtyClasses.contains(graphClass)) { + assertFalse("Classes from modified or deleted sources must not be in the output: " + graphClass, allOutputClasses.contains(graphClass)); + } + else { + assertTrue("Classes from up-to-date sources must be in the output: " + graphClass, allOutputClasses.contains(graphClass)); + } + } + + if (hasKotlinBytecode) { + assertTrue("All Kotlin facade classes from the '.kotlin_module' file must be registered in the dependency graph", graphFacadeClassNames.containsAll(kotlinModuleFacadeClassNames)); + for (String graphClassName : graphFacadeClassNames) { + if (graphDirtyFacadeClassNames.contains(graphClassName)) { + assertFalse("Facade classes from modified or deleted sources must not be in '.kotlin_module' file: " + graphClassName, kotlinModuleFacadeClassNames.contains(graphClassName)); + } + else { + assertTrue("Facade classes from up-to-date sources must be in '.kotlin_module' file: " + graphClassName, kotlinModuleFacadeClassNames.contains(graphClassName)); + } + } + } + } + + } + } + } + + private record BuildOutput(DependencyGraph graph, ConfigurationState configState, Path outputJar) { + + static Iterable scanOutputs(Path testOutputDir) throws IOException { + List targetOutputs = Files.list(testOutputDir).filter(path -> matches(path, ".jar") && !matches(path, DataPaths.ABI_JAR_SUFFIX)).toList(); + + return map(targetOutputs, output -> { + try { + String dataDirName = DataPaths.truncateExtension(getFileName(output)) + DataPaths.DATA_DIR_NAME_SUFFIX; + Path graphPath = output.resolveSibling(dataDirName).resolve(DataPaths.DEP_GRAPH_FILE_NAME); + Path configStatePath = graphPath.resolveSibling(DataPaths.CONFIG_STATE_FILE_NAME); + + assertTrue("Dependency graph storage is missing for the output " + output, Files.exists(graphPath)); + assertTrue("Configuration state storage is missing for the output " + output, Files.exists(configStatePath)); + + return new BuildOutput( + new DependencyGraphImpl(new PersistentMVStoreMapletFactory(graphPath.toString(), 1)), + new ConfigurationState(new PathSourceMapper(), configStatePath), + output + ); + } + catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + } + @NotNull protected ExecutionResult runBazelBuild(String... options) throws Exception { return runBazelCommand( diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/java/classModifiers/addStatic/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/java/classModifiers/addStatic/build.log index 033a40579e00..4e5d7b1f5153 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/java/classModifiers/addStatic/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/java/classModifiers/addStatic/build.log @@ -15,4 +15,4 @@ Compiling files: java/classModifiers/addStatic/a/C.java End of files ------------------------------------------ -Exit code: ERROR +Exit code: ERROR \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/convertToCheckedException/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/convertToCheckedException/build.log index 8c342a16d016..b479216a179f 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/convertToCheckedException/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/convertToCheckedException/build.log @@ -14,4 +14,4 @@ Compiling files: java/classProperties/convertToCheckedException/a/ppp/Task.java End of files ------------------------------------------ -Exit code: ERROR +Exit code: ERROR \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/removeExtendsAffectsFieldAccess/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/removeExtendsAffectsFieldAccess/build.log index cb5c4a4197ca..e32e506058a8 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/removeExtendsAffectsFieldAccess/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/removeExtendsAffectsFieldAccess/build.log @@ -14,4 +14,4 @@ Compiling files: java/classProperties/removeExtendsAffectsFieldAccess/a/SwitchProvider.java End of files ------------------------------------------ -Exit code: ERROR +Exit code: ERROR \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/removeExtendsAffectsMethodAccess/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/removeExtendsAffectsMethodAccess/build.log index 695b2a2f86b7..2a1da89165ab 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/removeExtendsAffectsMethodAccess/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/removeExtendsAffectsMethodAccess/build.log @@ -14,4 +14,4 @@ Compiling files: java/classProperties/removeExtendsAffectsMethodAccess/a/SwitchProvider.java End of files ------------------------------------------ -Exit code: ERROR +Exit code: ERROR \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/removeImplements/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/removeImplements/build.log index 49e203ea6e26..f79de314949b 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/removeImplements/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/java/classProperties/removeImplements/build.log @@ -14,4 +14,4 @@ Compiling files: java/classProperties/removeImplements/a/Client2.java End of files ------------------------------------------ -Exit code: ERROR +Exit code: ERROR \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/java/fieldModifiers/hidePublicWithPackagePrivate/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/java/fieldModifiers/hidePublicWithPackagePrivate/build.log index 49cddcee5b53..51e93ff29334 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/java/fieldModifiers/hidePublicWithPackagePrivate/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/java/fieldModifiers/hidePublicWithPackagePrivate/build.log @@ -16,4 +16,4 @@ Compiling files: java/fieldModifiers/hidePublicWithPackagePrivate/a/qqq/DerivedOutside.java End of files ------------------------------------------ -Exit code: ERROR +Exit code: ERROR \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/java/generics/argumentContainment3/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/java/generics/argumentContainment3/build.log index 6152fd77f3a5..6591fd2c23fc 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/java/generics/argumentContainment3/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/java/generics/argumentContainment3/build.log @@ -14,4 +14,4 @@ Compiling files: java/generics/argumentContainment3/a/GenericBound.java End of files ------------------------------------------ -Exit code: ERROR +Exit code: ERROR \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/java/membersChange/addSAMInterfaceAbstractMethod/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/java/membersChange/addSAMInterfaceAbstractMethod/build.log index c9a19451283a..41b07a3a115c 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/java/membersChange/addSAMInterfaceAbstractMethod/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/java/membersChange/addSAMInterfaceAbstractMethod/build.log @@ -16,4 +16,4 @@ Compiling files: java/membersChange/addSAMInterfaceAbstractMethod/a/ServiceClient.java End of files ------------------------------------------ -Exit code: ERROR +Exit code: ERROR \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/java/membersChange/addVarargMethod/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/java/membersChange/addVarargMethod/build.log index a345c66f48bd..78534a5fc8fb 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/java/membersChange/addVarargMethod/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/java/membersChange/addVarargMethod/build.log @@ -14,4 +14,4 @@ Compiling files: java/membersChange/addVarargMethod/a/D.java End of files ------------------------------------------ -Exit code: ERROR +Exit code: ERROR \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/moveClass/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/moveClass/build.log index bd94561412f0..9b546c44eb82 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/moveClass/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/moveClass/build.log @@ -2,6 +2,9 @@ Cleaning output files: Foo.class End of files +Cleaning output files: + META-INF/kotlin_pureKotlin_moveClass-a.kotlin_module +End of files Compiling files: kotlin/pureKotlin/moveClass/a/b.kt End of files @@ -10,9 +13,6 @@ Exit code: OK ================ Step #2 ================= Cleaning output files: Foo.class - META-INF/kotlin_pureKotlin_moveClass-a.kotlin_module -End of files -Compiling files: End of files ------------------------------------------ -Exit code: OK +Exit code: OK \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageFileChangedThenOtherRemoved/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageFileChangedThenOtherRemoved/build.log index b05efcbc2aa6..6ef42b345ab4 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageFileChangedThenOtherRemoved/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageFileChangedThenOtherRemoved/build.log @@ -10,10 +10,7 @@ End of files Exit code: OK ================ Step #2 ================= Cleaning output files: - META-INF/kotlin_pureKotlin_packageFileChangedThenOtherRemoved-a.kotlin_module test/BKt.class End of files -Compiling files: -End of files ------------------------------------------ -Exit code: OK +Exit code: OK \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageRecreated/a/dummy.kt b/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageRecreated/a/dummy.kt new file mode 100644 index 000000000000..6bd48850b662 --- /dev/null +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageRecreated/a/dummy.kt @@ -0,0 +1,2 @@ +// Dummy file so that for this target is always less than 100% of sources are modified. +// => the builder will work in incremental mode and will not rebuild whole target from scratch \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageRecreated/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageRecreated/build.log index f97310c2fdfc..22debbf965b8 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageRecreated/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageRecreated/build.log @@ -2,8 +2,6 @@ Cleaning output files: test/AKt.class End of files -Compiling files: -End of files ------------------------------------------ Exit code: OK ================ Step #2 ================= @@ -14,4 +12,4 @@ Compiling files: kotlin/pureKotlin/packageRecreated/a/b.kt End of files ------------------------------------------ -Exit code: OK +Exit code: OK \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageRecreatedAfterRenaming/a/dummy.kt b/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageRecreatedAfterRenaming/a/dummy.kt new file mode 100644 index 000000000000..6bd48850b662 --- /dev/null +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/packageRecreatedAfterRenaming/a/dummy.kt @@ -0,0 +1,2 @@ +// Dummy file so that for this target is always less than 100% of sources are modified. +// => the builder will work in incremental mode and will not rebuild whole target from scratch \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/removeUnusedFile/build.log b/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/removeUnusedFile/build.log index f42b12d29cba..4b9ba2b21311 100644 --- a/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/removeUnusedFile/build.log +++ b/build/jvm-rules/jvm-inc-builder-tests/testData/kotlin/pureKotlin/removeUnusedFile/build.log @@ -3,7 +3,5 @@ Cleaning output files: test/Bar.class test/UnusedKt.class End of files -Compiling files: -End of files ------------------------------------------ -Exit code: OK +Exit code: OK \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/BazelIncBuilder.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/BazelIncBuilder.java index 13a98fe0266d..02b15fe55b0a 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/BazelIncBuilder.java +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/BazelIncBuilder.java @@ -1,14 +1,33 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.tools.build.bazel.jvmIncBuilder; -import com.intellij.tools.build.bazel.jvmIncBuilder.impl.*; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.BatchBuildProcessLogger; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.BuildDiagnosticCollector; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.ConfigurationState; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.ElementSnapshotDeltaImpl; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.FormsCompiler; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.OutputSinkImpl; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.PostponedDiagnosticSink; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.ResourcesSnapshotDelta; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.RunnerRegistry; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.SnapshotDeltaImpl; +import com.intellij.tools.build.bazel.jvmIncBuilder.impl.Utils; import com.intellij.tools.build.bazel.jvmIncBuilder.impl.forms.FormBinding; import com.intellij.tools.build.bazel.jvmIncBuilder.impl.graph.AsyncLibraryGraphLoader; import com.intellij.tools.build.bazel.jvmIncBuilder.impl.graph.DeltaView; import com.intellij.tools.build.bazel.jvmIncBuilder.runner.CompilerRunner; +import com.intellij.tools.build.bazel.jvmIncBuilder.runner.OutputFile; +import com.intellij.tools.build.bazel.jvmIncBuilder.runner.OutputOrigin; +import com.intellij.tools.build.bazel.jvmIncBuilder.runner.OutputSink; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jps.dependency.*; +import org.jetbrains.jps.dependency.CompositeGraph; +import org.jetbrains.jps.dependency.Delta; +import org.jetbrains.jps.dependency.DependencyGraph; +import org.jetbrains.jps.dependency.Graph; +import org.jetbrains.jps.dependency.Node; +import org.jetbrains.jps.dependency.NodeSource; +import org.jetbrains.jps.dependency.NodeSourcePathMapper; import org.jetbrains.jps.dependency.java.JVMClassNode; import org.jetbrains.jps.util.Pair; import org.jetbrains.jps.util.SystemInfo; @@ -18,12 +37,24 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; -import static org.jetbrains.jps.util.Iterators.*; +import static org.jetbrains.jps.util.Iterators.collect; +import static org.jetbrains.jps.util.Iterators.contains; +import static org.jetbrains.jps.util.Iterators.count; +import static org.jetbrains.jps.util.Iterators.filter; +import static org.jetbrains.jps.util.Iterators.flat; +import static org.jetbrains.jps.util.Iterators.isEmpty; +import static org.jetbrains.jps.util.Iterators.map; /** @noinspection SSBasedInspection*/ public class BazelIncBuilder { @@ -238,6 +269,9 @@ public class BazelIncBuilder { for (CompilerRunner runner : roundCompilers) { Iterable toCompile = collect(filter(srcSnapshotDelta.getModified(), runner::canCompile), new ArrayList<>()); + if (context.getBuildLogger() instanceof BatchBuildProcessLogger batchLogger) { + batchLogger.startBatch(); + } if (!srcSnapshotDelta.isRecompileAll() && !isEmpty(toCompile)) { // delete outputs corresponding to recompiled sources before running the compiler @@ -245,16 +279,20 @@ public class BazelIncBuilder { Collection cleaned = deleteCompilerOutputs( storageManager.getGraph(), toCompile, outBuilder, new ArrayList<>() ); - for (String toDelete : runner.getOutputPathsToDelete()) { - if (outBuilder.deleteEntry(toDelete)) { - cleaned.add(toDelete); - } - } logDeletedPaths(context, cleaned); } ExitCode code = runner.compile(toCompile, filter(srcSnapshotDelta.getDeleted(), runner::canCompile), diagnostic, outSink); + + if (context.getBuildLogger() instanceof BatchBuildProcessLogger batchLogger) { + batchLogger.stopBatch(); + } + if (code == ExitCode.CANCEL) { + if (!srcSnapshotDelta.isRecompileAll()) { + // in case of errors, clean partially compiled output to maintain consistent state + deleteGeneratedOutputs(outSink, storageManager.getCompositeOutputBuilder()); + } return code; } if (code == ExitCode.ERROR && !diagnostic.hasErrors()) { @@ -274,19 +312,26 @@ public class BazelIncBuilder { srcSnapshotDelta = nextSnapshotDelta; } else { - if (srcSnapshotDelta.isRecompileAll() || !nextSnapshotDelta.hasChanges()) { + if (srcSnapshotDelta.isRecompileAll()) { + // no need to clean partial outputs, the next build will start from the clean state anyway return ExitCode.ERROR; } - // keep previous snapshot delta, just augment it with the newly found sources for recompilation - if (nextSnapshotDelta.isRecompileAll()) { - srcSnapshotDelta.markRecompileAll(); - } - else { - for (NodeSource source : nextSnapshotDelta.getModified()) { - srcSnapshotDelta.markRecompile(source); + + if (nextSnapshotDelta.hasChanges()) { + // keep previous snapshot delta, just augment it with the newly found sources for recompilation + if (nextSnapshotDelta.isRecompileAll()) { + srcSnapshotDelta.markRecompileAll(); + } + else { + for (NodeSource source : nextSnapshotDelta.getModified()) { + srcSnapshotDelta.markRecompile(source); + } } } - if (!isInitialRound) { + + if (!isInitialRound || !nextSnapshotDelta.hasChanges()) { + // in case of errors, clean partially compiled output to maintain consistent state + deleteGeneratedOutputs(outSink, storageManager.getCompositeOutputBuilder()); return ExitCode.ERROR; } // for initial round, partial compilation and when analysis has expanded the scope, attempt automatic error recovery by repeating the compilation with the expanded scope @@ -494,6 +539,12 @@ public class BazelIncBuilder { return deletedPathsAcc; } + private static void deleteGeneratedOutputs(OutputSink sink, ZipOutputBuilder outBuilder) { + for (String outputPath : flat(map(EnumSet.allOf(OutputOrigin.Kind.class), origin -> sink.getGeneratedOutputPaths(origin, OutputFile.Kind.bytecode)))) { + outBuilder.deleteEntry(outputPath); + } + } + private static void logDeletedPaths(BuildContext context, Iterable deletedPaths) { if (!context.isRebuild()) { BuildProcessLogger logger = context.getBuildLogger(); diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BatchBuildProcessLogger.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BatchBuildProcessLogger.java new file mode 100644 index 000000000000..a7db4d18bd0b --- /dev/null +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BatchBuildProcessLogger.java @@ -0,0 +1,126 @@ +package com.intellij.tools.build.bazel.jvmIncBuilder.impl; + +import com.intellij.tools.build.bazel.jvmIncBuilder.BuildProcessLogger; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import static org.jetbrains.jps.util.Iterators.flat; + +public final class BatchBuildProcessLogger implements BuildProcessLogger { + private final BuildProcessLogger myDelegate; + private boolean myBatchActive; + private final List myEvents = new ArrayList<>(); + + public BatchBuildProcessLogger(BuildProcessLogger delegate) { + myDelegate = delegate; + } + + public void startBatch() { + myBatchActive = true; + } + + public void stopBatch() { + myBatchActive = false; + try { + for (PostponedEvent event : myEvents) { + event.process(myDelegate); + } + } + finally { + myEvents.clear(); + } + } + + @Override + public boolean isEnabled() { + return myDelegate.isEnabled(); + } + + @Override + public void logDeletedPaths(Iterable paths) { + if (myBatchActive && isEnabled()) { + if (myEvents.isEmpty() || !myEvents.getLast().mergeDeleted(paths)) { + myEvents.add(PostponedEvent.pathsDeleted(paths)); + } + } + else { + myDelegate.logDeletedPaths(paths); + } + } + + @Override + public void logCompiledPaths(Iterable files, String builderId, String description) { + if (myBatchActive && isEnabled()) { + if (myEvents.isEmpty() || !myEvents.getLast().mergeCompiled(files, builderId, description)) { + myEvents.add(PostponedEvent.pathsCompiled(files, builderId, description)); + } + } + else { + myDelegate.logCompiledPaths(files, builderId, description); + } + } + + @Override + public String getCollectedData() { + stopBatch(); // ensure all postponed data is logged + return myDelegate.getCollectedData(); + } + + private interface PostponedEvent { + void process(BuildProcessLogger target); + boolean mergeDeleted(Iterable paths); + boolean mergeCompiled(Iterable files, String builderId, String description); + + static PostponedEvent pathsDeleted(Iterable paths) { + return new PostponedEvent() { + Iterable myData = paths; + @Override + public void process(BuildProcessLogger target) { + target.logDeletedPaths(myData); + } + + @Override + public boolean mergeDeleted(Iterable paths) { + myData = flat(myData, paths); + return true; + } + + @Override + public boolean mergeCompiled(Iterable files, String builderId, String description) { + return false; + } + }; + } + + static PostponedEvent pathsCompiled(Iterable files, String builderId, String description) { + return new PostponedEvent() { + private Iterable myData = files; + private String myDescription = description; + @Override + public void process(BuildProcessLogger target) { + target.logCompiledPaths(myData, builderId, myDescription); + } + + @Override + public boolean mergeDeleted(Iterable paths) { + return false; + } + + @Override + public boolean mergeCompiled(Iterable otherFiles, String otherBuilderId, String otherDescription) { + if (!builderId.equals(otherBuilderId)) { + return false; + } + myData = flat(myData, otherFiles); + if (!myDescription.endsWith(otherDescription)) { + myDescription = myDescription + "\n" + otherDescription; + } + return true; + } + }; + } + } + +} diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BuildContextImpl.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BuildContextImpl.java index 585c6c7c3d18..d771acfc8e80 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BuildContextImpl.java +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BuildContextImpl.java @@ -172,7 +172,7 @@ public class BuildContextImpl implements BuildContext { } myBuilderOptions = BuilderOptions.create(buildJavaOptions(flags), buildKotlinOptions(flags, map(myLibraries.getElements(), myPathMapper::toPath))); - myBuildProcessLogger = VMFlags.isBuildProcessLoggerEnabled()? new BuildProcessLoggerImpl(baseDir) : BuildProcessLogger.EMPTY; + myBuildProcessLogger = VMFlags.isBuildProcessLoggerEnabled()? new BatchBuildProcessLogger(new BuildProcessLoggerImpl(baseDir)) : BuildProcessLogger.EMPTY; } private static @NotNull List buildKotlinOptions(Map> flags, @NotNull Iterable<@NotNull Path> classpath) { diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BuildProcessLoggerImpl.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BuildProcessLoggerImpl.java index 6b8f5b19fc8d..8bce1ca77804 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BuildProcessLoggerImpl.java +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BuildProcessLoggerImpl.java @@ -9,7 +9,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import static org.jetbrains.jps.util.Iterators.*; +import static org.jetbrains.jps.util.Iterators.collect; +import static org.jetbrains.jps.util.Iterators.isEmpty; +import static org.jetbrains.jps.util.Iterators.map; public class BuildProcessLoggerImpl implements BuildProcessLogger { private final StringBuilder myBuf = new StringBuilder(); diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinCompilerRunner.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinCompilerRunner.java index 3ef52c8257e7..744111b36cef 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinCompilerRunner.java +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinCompilerRunner.java @@ -2,6 +2,7 @@ package com.intellij.tools.build.bazel.jvmIncBuilder.impl; import com.intellij.tools.build.bazel.jvmIncBuilder.BuildContext; +import com.intellij.tools.build.bazel.jvmIncBuilder.BuildProcessLogger; import com.intellij.tools.build.bazel.jvmIncBuilder.CLFlags; import com.intellij.tools.build.bazel.jvmIncBuilder.DataPaths; import com.intellij.tools.build.bazel.jvmIncBuilder.DiagnosticSink; @@ -16,6 +17,8 @@ import com.intellij.tools.build.bazel.jvmIncBuilder.runner.OutputOrigin; import com.intellij.tools.build.bazel.jvmIncBuilder.runner.OutputSink; import kotlin.Unit; import kotlin.jvm.functions.Function1; +import kotlin.metadata.jvm.KmPackageParts; +import kotlin.metadata.jvm.KotlinModuleMetadata; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.dependency.NodeSource; @@ -59,10 +62,12 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; import static com.intellij.tools.build.bazel.jvmIncBuilder.impl.KotlinPluginsKt.configurePlugins; @@ -91,9 +96,6 @@ public class KotlinCompilerRunner implements CompilerRunner { private final @NotNull Map<@NotNull String, List> myInternalPluginIdToOptions = new HashMap<>(); private final List myJavaSources; - private final @Nullable String myModuleEntryPath; - private byte @Nullable [] myLastGoodModuleEntryContent; - public KotlinCompilerRunner(BuildContext context, StorageManager storageManager) { myContext = context; myStorageManager = storageManager; @@ -123,19 +125,6 @@ public class KotlinCompilerRunner implements CompilerRunner { myJavaSources = collect( map(filter(context.getSources().getElements(), KotlinCompilerRunner::isJavaSource), ns -> myPathMapper.toPath(ns).toString()), new ArrayList<>() ); - - String moduleEntryPath = null; - try { - ZipOutputBuilderImpl outBuilder = storageManager.getOutputBuilder(); - moduleEntryPath = find(outBuilder.listEntries("META-INF/"), n -> n.endsWith(DataPaths.KOTLIN_MODULE_EXTENSION)); - if (moduleEntryPath != null) { - myLastGoodModuleEntryContent = outBuilder.getContent(moduleEntryPath); - } - } - catch (IOException e) { - context.report(Message.create(this, e)); - } - myModuleEntryPath = moduleEntryPath; } @Override @@ -156,19 +145,38 @@ public class KotlinCompilerRunner implements CompilerRunner { return src.toString().endsWith(".java"); } - @Override - public Iterable getOutputPathsToDelete() { - return myModuleEntryPath != null? List.of(myModuleEntryPath) : List.of(); - } - @Override public ExitCode compile(Iterable sources, Iterable deletedSources, DiagnosticSink diagnostic, OutputSink out) throws Exception { try { + + ZipOutputBuilder outputBuilder = myStorageManager.getOutputBuilder(); + ZipOutputBuilder abiOutputBuilder = myStorageManager.getAbiOutputBuilder(); + + String moduleEntryPath = find(outputBuilder.listEntries("META-INF/"), n -> n.endsWith(DataPaths.KOTLIN_MODULE_EXTENSION)); + byte[] moduleEntryContent = moduleEntryPath != null? outputBuilder.getContent(moduleEntryPath) : null; + byte[] abiModuleEntryContent = moduleEntryPath != null && abiOutputBuilder != null? abiOutputBuilder.getContent(moduleEntryPath) : null; + + IncrementalCache incCache = new KotlinIncrementalCacheImpl(myStorageManager, flat(deletedSources, sources), moduleEntryPath, moduleEntryContent); + if (isEmpty(sources)) { + if (moduleEntryPath != null && !isEmpty(deletedSources)) { + // ObsoletePackageParts here are only classes corresponding to deleted sources + outputBuilder.putEntry(moduleEntryPath, cleanObsoletePackageParts(incCache, moduleEntryContent)); + if (abiOutputBuilder != null){ + abiOutputBuilder.putEntry(moduleEntryPath, cleanObsoletePackageParts(incCache, abiModuleEntryContent)); + } + } return ExitCode.OK; } + + if (moduleEntryPath != null && myStorageManager.getCompositeOutputBuilder().deleteEntry(moduleEntryPath)) { // ensure previous state is removed + BuildProcessLogger logger = myContext.getBuildLogger(); + if (logger.isEnabled()) { + logger.logDeletedPaths(List.of(moduleEntryPath)); + } + } + K2JVMCompilerArguments kotlinArgs = buildKotlinCompilerArguments(myContext, sources); - KotlinIncrementalCacheImpl incCache = new KotlinIncrementalCacheImpl(myStorageManager, flat(deletedSources, sources), myModuleEntryPath, myLastGoodModuleEntryContent); OutputVirtualFile outputFileSystemRoot = new OutputFileSystem(new KotlinVirtualFileProvider(out)).root; Services services = buildServices(kotlinArgs.getModuleName(), incCache, outputFileSystemRoot); MessageCollector messageCollector = new KotlinMessageCollector(diagnostic, this); @@ -205,15 +213,14 @@ public class KotlinCompilerRunner implements CompilerRunner { } finally { processTrackers(out, generatedClasses); - if (myModuleEntryPath != null) { - byte[] updated = myStorageManager.getOutputBuilder().getContent(myModuleEntryPath); - if (updated != null) { - // save the updated state for the next round - myLastGoodModuleEntryContent = updated; + if (moduleEntryPath != null) { + if (outputBuilder.getContent(moduleEntryPath) == null) { + // restore adjusted module entry + outputBuilder.putEntry(moduleEntryPath, cleanObsoletePackageParts(incCache, moduleEntryContent)); } - else { - // make sure the output contains the module entry corresponding to last known good state - myStorageManager.getOutputBuilder().putEntry(myModuleEntryPath, myLastGoodModuleEntryContent); + if (abiOutputBuilder != null && abiOutputBuilder.getContent(moduleEntryPath) == null) { + // restore adjusted module entry + abiOutputBuilder.putEntry(moduleEntryPath, cleanObsoletePackageParts(incCache, abiModuleEntryContent)); } } } @@ -227,6 +234,37 @@ public class KotlinCompilerRunner implements CompilerRunner { } } + private static byte[] cleanObsoletePackageParts(IncrementalCache incCache, byte[] moduleEntryContent) { + Collection _parts = incCache.getObsoletePackageParts(); + if (_parts.isEmpty()) { + return moduleEntryContent; + } + Set obsoleteFacades = _parts instanceof Set? (Set) _parts : collect(_parts, new HashSet<>()); + KotlinModuleMetadata moduleMeta = KotlinModuleMetadata.read(moduleEntryContent); + Map packageParts = moduleMeta.getKmModule().getPackageParts(); // mutable + boolean changed = false; + for (Iterator> packagePartsIterator = packageParts.entrySet().iterator(); packagePartsIterator.hasNext(); ) { + KmPackageParts parts = packagePartsIterator.next().getValue(); + List facades = parts.getFileFacades(); // mutable + Map multiFileParts = parts.getMultiFileClassParts(); // mutable + + changed |= facades.removeAll(obsoleteFacades); + for (Iterator> it = multiFileParts.entrySet().iterator(); it.hasNext(); ) { + Map.Entry entry = it.next(); + if (obsoleteFacades.contains(entry.getValue())) { + it.remove(); + changed = true; + } + } + + if (facades.isEmpty() && multiFileParts.isEmpty()) { + packagePartsIterator.remove(); + changed = true; + } + } + return changed? moduleMeta.write() : moduleEntryContent; + } + private record GeneratedClass(String jvmClassName, File source) {} private void processTrackers(OutputSink out, List generated) { @@ -241,10 +279,7 @@ public class KotlinCompilerRunner implements CompilerRunner { processInferredTypeTracker(inferredTypeTracker, out); } - - private static void processInlineConstTracker(InlineConstTrackerImpl inlineConstTracker, - GeneratedClass output, - OutputSink callback) { + private static void processInlineConstTracker(InlineConstTrackerImpl inlineConstTracker, GeneratedClass output, OutputSink callback) { Map> constMap = inlineConstTracker.getInlineConstMap(); Collection constantRefs = constMap.get(output.source.getPath()); if (constantRefs == null) return; diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinIncrementalCacheImpl.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinIncrementalCacheImpl.java index 4ea13839b304..5d9667e48084 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinIncrementalCacheImpl.java +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinIncrementalCacheImpl.java @@ -17,7 +17,10 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; -import static org.jetbrains.jps.util.Iterators.*; +import static org.jetbrains.jps.util.Iterators.filter; +import static org.jetbrains.jps.util.Iterators.find; +import static org.jetbrains.jps.util.Iterators.flat; +import static org.jetbrains.jps.util.Iterators.map; public final class KotlinIncrementalCacheImpl implements IncrementalCache { diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/runner/CompilerRunner.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/runner/CompilerRunner.java index 88f5720feb6a..7700bfe68527 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/runner/CompilerRunner.java +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/runner/CompilerRunner.java @@ -9,8 +9,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.dependency.NodeSource; import org.jetbrains.jps.dependency.NodeSourcePathMapper; -import java.util.List; - import static org.jetbrains.jps.util.Iterators.isEmpty; import static org.jetbrains.jps.util.Iterators.map; @@ -20,10 +18,6 @@ public interface CompilerRunner extends Runner{ ExitCode compile(Iterable sources, Iterable deletedSources, DiagnosticSink diagnostic, OutputSink out) throws Exception; - default Iterable getOutputPathsToDelete() { - return List.of(); - } - default void logCompiledFiles(BuildContext context, Iterable<@NotNull NodeSource> toCompile) { if (!context.isRebuild()) { BuildProcessLogger logger = context.getBuildLogger();