in sources/java-incremental-compilation/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/BazelIncBuilder.java [33:282]
public ExitCode build(BuildContext context) {
// todo: support cancellation checks
// todo: additional diagnostics, if necessary
DiagnosticSink diagnostic = context;
NodeSourceSnapshotDelta srcSnapshotDelta = null;
ResourcesSnapshotDelta resourcesDelta = null;
Iterable<NodeSource> modifiedLibraries = List.of();
Iterable<NodeSource> deletedLibraries = List.of();
try (StorageManager storageManager = new StorageManager(context)) {
try {
GraphUpdater graphUpdater = new GraphUpdater(context.getTargetName());
LOG.fine(() -> "Building " + context.getTargetName() + " (rebuild requested: " + context.isRebuild() + ")");
if (context.isRebuild() || !storageManager.getOutputBuilder().isInputZipExist()) {
// either rebuild is explicitly requested, or there is no previous data, need to compile the whole target
srcSnapshotDelta = new SnapshotDeltaImpl(context.getSources());
srcSnapshotDelta.markRecompileAll(); // force rebuild
}
else {
ConfigurationState pastState = ConfigurationState.loadSavedState(context);
ConfigurationState presentState = new ConfigurationState(context.getPathMapper(), context.getSources(), context.getResources(), context.getBinaryDependencies(), context.getFlags());
srcSnapshotDelta = new SnapshotDeltaImpl(pastState.getSources(), presentState.getSources());
if (shouldRecompileAll(srcSnapshotDelta) || pastState.getFlagsDigest() != presentState.getFlagsDigest() || pastState.getClasspathStructureDigest() != presentState.getClasspathStructureDigest()) {
int changedPercent = srcSnapshotDelta.getChangedPercent();
LOG.fine(() -> "Marking whole target for recompilation [" + context.getTargetName() + "]. Changed sources: " + changedPercent + "% (threshold " + RECOMPILE_CHANGED_RATIO_PERCENT + "%) ");
srcSnapshotDelta.markRecompileAll();
}
else {
Predicate<NodeSource> isLibTracked = ns -> DataPaths.isLibraryTracked(ns.toString());
ElementSnapshotDeltaImpl<NodeSource> libsSnapshotDelta = new ElementSnapshotDeltaImpl<>(
ElementSnapshot.derive(pastState.getLibraries(), isLibTracked),
ElementSnapshot.derive(context.getBinaryDependencies(), isLibTracked)
);
modifiedLibraries = libsSnapshotDelta.getModified();
deletedLibraries = libsSnapshotDelta.getDeleted();
if (!isEmpty(modifiedLibraries)) {
// differentiate library deps
List<Graph> pastLibGraphs = new ArrayList<>();
List<Graph> presentLibGraphs = new ArrayList<>();
Set<NodeSource> changedLibNodeSources = new HashSet<>();
Set<NodeSource> deletedLibNodeSources = new HashSet<>();
for (NodeSource presentLib : modifiedLibraries) {
Path presentLibPath = context.getPathMapper().toPath(presentLib);
Path pastLibPath = DataPaths.getJarBackupStoreFile(context, presentLibPath);
try {
Pair<NodeSourceSnapshot, Graph> presentGraph = LibraryGraphLoader.getLibraryGraph(presentLib, presentState.getLibraries().getDigest(presentLib), presentLibPath);
Pair<NodeSourceSnapshot, Graph> pastGraph = LibraryGraphLoader.getLibraryGraph(presentLib, pastState.getLibraries().getDigest(presentLib), pastLibPath);
NodeSourceSnapshotDelta delta = new SnapshotDeltaImpl(pastGraph.first, presentGraph.first);
if (!isEmpty(delta.getModified()) || !isEmpty(delta.getDeleted())) {
collect(delta.getModified(), changedLibNodeSources);
collect(delta.getDeleted(), deletedLibNodeSources);
pastLibGraphs.add(pastGraph.second); // all nodes of the past state should be available for analysis
presentLibGraphs.add(presentGraph.second);
}
}
catch (Exception e) { // problems loading library graphs
LOG.log(Level.WARNING, "Problems loading library graphs, recompiling whole target " + context.getTargetName(), e);
srcSnapshotDelta.markRecompileAll();
context.report(Message.create(null, Message.Kind.WARNING, e));
break;
}
}
if (!changedLibNodeSources.isEmpty() || !deletedLibNodeSources.isEmpty()) {
// Add to 'pastLibGraphs' all previously available graph parts, even if they are not changed. Reason: need full nodes info for graph node traversals
for (NodeSource lib : libsSnapshotDelta.getBaseSnapshot().getElements()) {
if (!contains(libsSnapshotDelta.getModified(), lib)) {
pastLibGraphs.add(
LibraryGraphLoader.getLibraryGraph(lib, presentState.getLibraries().getDigest(lib), context.getPathMapper().toPath(lib)).second
);
}
}
try {
Delta libDelta = new DeltaView(changedLibNodeSources, deletedLibNodeSources, CompositeGraph.create(presentLibGraphs));
srcSnapshotDelta = graphUpdater.updateBeforeCompilation(storageManager.getGraph(), srcSnapshotDelta, libDelta, pastLibGraphs);
if (shouldRecompileAll(srcSnapshotDelta)) {
srcSnapshotDelta.markRecompileAll();
}
}
catch (IOException e) {
LOG.log(Level.WARNING, "Problems loading dependency graph, recompiling whole target " + context.getTargetName(), e);
srcSnapshotDelta.markRecompileAll();
context.report(Message.create(null, Message.Kind.WARNING, e));
}
}
}
// expand compile scope
if (!srcSnapshotDelta.isRecompileAll()) {
DependencyGraph graph = storageManager.getGraph();
Delta sourceOnlyDelta = graph.createDelta(srcSnapshotDelta.getModified(), srcSnapshotDelta.getDeleted(), true);
srcSnapshotDelta = graphUpdater.updateBeforeCompilation(graph, srcSnapshotDelta, sourceOnlyDelta, List.of());
if (shouldRecompileAll(srcSnapshotDelta)) {
srcSnapshotDelta.markRecompileAll();
}
}
}
if (!srcSnapshotDelta.isRecompileAll()) {
resourcesDelta = new ResourcesSnapshotDelta(pastState.getResources(), presentState.getResources());
}
}
List<CompilerRunner> roundCompilers = collect(map(RunnerRegistry.getRoundCompilers(), f -> f.create(context, storageManager)), new ArrayList<>());
boolean isInitialRound = true;
do { // build rounds loop
if (srcSnapshotDelta.isRecompileAll()) {
storageManager.cleanBuildState();
modifiedLibraries = ElementSnapshot.derive(context.getBinaryDependencies(), ns -> DataPaths.isLibraryTracked(ns.toString())).getElements();
deletedLibraries = Set.of();
}
else {
if (isInitialRound) {
storageManager.cleanTrashDir();
}
}
diagnostic = isInitialRound? new PostponedDiagnosticSink() : context; // for initial round postpone error reporting
OutputSinkImpl outSink = new OutputSinkImpl(storageManager);
if (isInitialRound) {
// processing resources
ZipOutputBuilder out = storageManager.getOutputBuilder();
if (resourcesDelta == null || srcSnapshotDelta.isRecompileAll()) {
// copy everything
for (ResourceGroup group : context.getResources()) {
copyResources(group, context.getPathMapper(), out);
}
}
else {
// only copy modified and remove deleted
deleteResources(resourcesDelta.getPastResources(), flat(resourcesDelta.getDeleted(), resourcesDelta.getChanged()), out);
copyResources(resourcesDelta.getPresentResources(), resourcesDelta.getModified(), context.getPathMapper(), out);
}
// processing deleted sources makes sense on inintial round only
if (!srcSnapshotDelta.isRecompileAll() && !isEmpty(srcSnapshotDelta.getDeleted())) {
// clean outputs that correspond to deleted sources, no matter of source type
Collection<String> cleaned = deleteCompilerOutputs(
storageManager.getGraph(), srcSnapshotDelta.getDeleted(), storageManager.getCompositeOutputBuilder(), new ArrayList<>()
);
logDeletedPaths(context, cleaned);
}
}
else {
if (srcSnapshotDelta.isRecompileAll()) {
// After several rounds, the IC logic can decide to recompile everything
ZipOutputBuilder out = storageManager.getOutputBuilder();
for (ResourceGroup group : context.getResources()) {
copyResources(group, context.getPathMapper(), out);
}
}
}
for (CompilerRunner runner : roundCompilers) {
Iterable<NodeSource> toCompile = collect(filter(srcSnapshotDelta.getModified(), runner::canCompile), new ArrayList<>());
if (!srcSnapshotDelta.isRecompileAll() && !isEmpty(toCompile)) {
// delete outputs corresponding to recompiled sources before running the compiler
ZipOutputBuilder outBuilder = storageManager.getCompositeOutputBuilder();
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 (code == ExitCode.CANCEL) {
return code;
}
if (code == ExitCode.ERROR && !diagnostic.hasErrors()) {
// ensure we have some error message
diagnostic.report(Message.error(runner, runner.getName() + " completed with errors"));
}
if (diagnostic.hasErrors()) {
break;
}
}
NodeSourceSnapshotDelta nextSnapshotDelta = graphUpdater.updateAfterCompilation(
storageManager.getGraph(), srcSnapshotDelta, createGraphDelta(storageManager.getGraph(), srcSnapshotDelta, outSink), diagnostic.hasErrors()
);
if (!diagnostic.hasErrors()) {
srcSnapshotDelta = nextSnapshotDelta;
}
else {
if (srcSnapshotDelta.isRecompileAll() || !nextSnapshotDelta.hasChanges()) {
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 (!isInitialRound) {
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
}
isInitialRound = false;
}
while (srcSnapshotDelta.hasChanges());
}
catch (Throwable e) {
// catch and report all errors before the storage manager is closed
LOG.log(Level.SEVERE, "Unexpected error compiling " + context.getTargetName(), e);
diagnostic.report(Message.create(null, e));
return ExitCode.ERROR;
}
finally {
if (diagnostic instanceof PostponedDiagnosticSink) {
// report postponed errors, if necessary; ensure all errors are reported before storages are closed
((PostponedDiagnosticSink)diagnostic).drainTo(context);
}
}
return ExitCode.OK;
}
finally {
NodeSourceSnapshot sourcesState = srcSnapshotDelta != null? srcSnapshotDelta.asSnapshot() : null;
saveBuildState(
context, sourcesState, context.getResources(), modifiedLibraries, deletedLibraries
);
}
}