mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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 <eugene.zhuravlev@jetbrains.com> GitOrigin-RevId: e2adffd268c8e71b8795aec7d070b470551f57e3
This commit is contained in:
committed by
intellij-monorepo-bot
parent
019bd2a161
commit
36eea1f4c9
@@ -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",
|
||||
|
||||
-18
@@ -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 {
|
||||
|
||||
+157
@@ -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<NodeSource> dirtySources = collect(filter(srcSnapshot.getElements(), src -> srcSnapshot.getDigest(src).isBlank()), new HashSet<>());
|
||||
// output .class paths for bytecode
|
||||
Set<String> dirtyClasses = new HashSet<>();
|
||||
Set<String> allGraphClasses = new HashSet<>();
|
||||
Set<String> allOutputClasses = new HashSet<>();
|
||||
|
||||
boolean hasKotlinBytecode = false;
|
||||
String kotlinModuleEntryPath = null;
|
||||
// class names
|
||||
Set<String> kotlinModuleFacadeClassNames = new HashSet<>();
|
||||
Set<String> graphFacadeClassNames = new HashSet<>();
|
||||
Set<String> graphDirtyFacadeClassNames = new HashSet<>();
|
||||
|
||||
for (NodeSource src : graph.getSources()) {
|
||||
boolean isSourceDirty = dirtySources.contains(src);
|
||||
Consumer<String> 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<KotlinMeta> 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<BuildOutput> scanOutputs(Path testOutputDir) throws IOException {
|
||||
List<Path> 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(
|
||||
|
||||
+1
-1
@@ -15,4 +15,4 @@ Compiling files:
|
||||
java/classModifiers/addStatic/a/C.java
|
||||
End of files
|
||||
------------------------------------------
|
||||
Exit code: ERROR
|
||||
Exit code: ERROR
|
||||
+1
-1
@@ -14,4 +14,4 @@ Compiling files:
|
||||
java/classProperties/convertToCheckedException/a/ppp/Task.java
|
||||
End of files
|
||||
------------------------------------------
|
||||
Exit code: ERROR
|
||||
Exit code: ERROR
|
||||
+1
-1
@@ -14,4 +14,4 @@ Compiling files:
|
||||
java/classProperties/removeExtendsAffectsFieldAccess/a/SwitchProvider.java
|
||||
End of files
|
||||
------------------------------------------
|
||||
Exit code: ERROR
|
||||
Exit code: ERROR
|
||||
+1
-1
@@ -14,4 +14,4 @@ Compiling files:
|
||||
java/classProperties/removeExtendsAffectsMethodAccess/a/SwitchProvider.java
|
||||
End of files
|
||||
------------------------------------------
|
||||
Exit code: ERROR
|
||||
Exit code: ERROR
|
||||
+1
-1
@@ -14,4 +14,4 @@ Compiling files:
|
||||
java/classProperties/removeImplements/a/Client2.java
|
||||
End of files
|
||||
------------------------------------------
|
||||
Exit code: ERROR
|
||||
Exit code: ERROR
|
||||
+1
-1
@@ -16,4 +16,4 @@ Compiling files:
|
||||
java/fieldModifiers/hidePublicWithPackagePrivate/a/qqq/DerivedOutside.java
|
||||
End of files
|
||||
------------------------------------------
|
||||
Exit code: ERROR
|
||||
Exit code: ERROR
|
||||
+1
-1
@@ -14,4 +14,4 @@ Compiling files:
|
||||
java/generics/argumentContainment3/a/GenericBound.java
|
||||
End of files
|
||||
------------------------------------------
|
||||
Exit code: ERROR
|
||||
Exit code: ERROR
|
||||
+1
-1
@@ -16,4 +16,4 @@ Compiling files:
|
||||
java/membersChange/addSAMInterfaceAbstractMethod/a/ServiceClient.java
|
||||
End of files
|
||||
------------------------------------------
|
||||
Exit code: ERROR
|
||||
Exit code: ERROR
|
||||
+1
-1
@@ -14,4 +14,4 @@ Compiling files:
|
||||
java/membersChange/addVarargMethod/a/D.java
|
||||
End of files
|
||||
------------------------------------------
|
||||
Exit code: ERROR
|
||||
Exit code: ERROR
|
||||
+4
-4
@@ -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
|
||||
+1
-4
@@ -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
|
||||
+2
@@ -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
|
||||
+1
-3
@@ -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
|
||||
+2
@@ -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
|
||||
+1
-3
@@ -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
|
||||
+69
-18
@@ -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<NodeSource> 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<String> 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<String> deletedPaths) {
|
||||
if (!context.isRebuild()) {
|
||||
BuildProcessLogger logger = context.getBuildLogger();
|
||||
|
||||
+126
@@ -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<PostponedEvent> 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<String> 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<Path> 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<String> paths);
|
||||
boolean mergeCompiled(Iterable<Path> files, String builderId, String description);
|
||||
|
||||
static PostponedEvent pathsDeleted(Iterable<String> paths) {
|
||||
return new PostponedEvent() {
|
||||
Iterable<String> myData = paths;
|
||||
@Override
|
||||
public void process(BuildProcessLogger target) {
|
||||
target.logDeletedPaths(myData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mergeDeleted(Iterable<String> paths) {
|
||||
myData = flat(myData, paths);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mergeCompiled(Iterable<Path> files, String builderId, String description) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static PostponedEvent pathsCompiled(Iterable<Path> files, String builderId, String description) {
|
||||
return new PostponedEvent() {
|
||||
private Iterable<Path> myData = files;
|
||||
private String myDescription = description;
|
||||
@Override
|
||||
public void process(BuildProcessLogger target) {
|
||||
target.logCompiledPaths(myData, builderId, myDescription);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mergeDeleted(Iterable<String> paths) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mergeCompiled(Iterable<Path> 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -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<String> buildKotlinOptions(Map<CLFlags, List<String>> flags, @NotNull Iterable<@NotNull Path> classpath) {
|
||||
|
||||
+3
-1
@@ -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();
|
||||
|
||||
+69
-34
@@ -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<CliOptionValue>> myInternalPluginIdToOptions = new HashMap<>();
|
||||
private final List<String> 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<String> getOutputPathsToDelete() {
|
||||
return myModuleEntryPath != null? List.of(myModuleEntryPath) : List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitCode compile(Iterable<NodeSource> sources, Iterable<NodeSource> 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<String> _parts = incCache.getObsoletePackageParts();
|
||||
if (_parts.isEmpty()) {
|
||||
return moduleEntryContent;
|
||||
}
|
||||
Set<String> obsoleteFacades = _parts instanceof Set? (Set<String>) _parts : collect(_parts, new HashSet<>());
|
||||
KotlinModuleMetadata moduleMeta = KotlinModuleMetadata.read(moduleEntryContent);
|
||||
Map<String, KmPackageParts> packageParts = moduleMeta.getKmModule().getPackageParts(); // mutable
|
||||
boolean changed = false;
|
||||
for (Iterator<Map.Entry<String, KmPackageParts>> packagePartsIterator = packageParts.entrySet().iterator(); packagePartsIterator.hasNext(); ) {
|
||||
KmPackageParts parts = packagePartsIterator.next().getValue();
|
||||
List<String> facades = parts.getFileFacades(); // mutable
|
||||
Map<String, String> multiFileParts = parts.getMultiFileClassParts(); // mutable
|
||||
|
||||
changed |= facades.removeAll(obsoleteFacades);
|
||||
for (Iterator<Map.Entry<String, String>> it = multiFileParts.entrySet().iterator(); it.hasNext(); ) {
|
||||
Map.Entry<String, String> 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<GeneratedClass> 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<String, Collection<ConstantRef>> constMap = inlineConstTracker.getInlineConstMap();
|
||||
Collection<ConstantRef> constantRefs = constMap.get(output.source.getPath());
|
||||
if (constantRefs == null) return;
|
||||
|
||||
+4
-1
@@ -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 {
|
||||
|
||||
|
||||
-6
@@ -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<NodeSource> sources, Iterable<NodeSource> deletedSources, DiagnosticSink diagnostic, OutputSink out)
|
||||
throws Exception;
|
||||
|
||||
default Iterable<String> getOutputPathsToDelete() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
default void logCompiledFiles(BuildContext context, Iterable<@NotNull NodeSource> toCompile) {
|
||||
if (!context.isRebuild()) {
|
||||
BuildProcessLogger logger = context.getBuildLogger();
|
||||
|
||||
Reference in New Issue
Block a user