revert parallelizer changes, need further testing
[idea/community.git] / jps / jps-builders / src / org / jetbrains / jps / incremental / IncProjectBuilder.java
1 // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package org.jetbrains.jps.incremental;
3
4 import com.intellij.openapi.diagnostic.Logger;
5 import com.intellij.openapi.util.*;
6 import com.intellij.openapi.util.io.FileUtil;
7 import com.intellij.openapi.util.text.StringUtil;
8 import com.intellij.util.SmartList;
9 import com.intellij.util.concurrency.AppExecutorUtil;
10 import com.intellij.util.containers.ContainerUtil;
11 import com.intellij.util.containers.FileCollectionFactory;
12 import com.intellij.util.containers.MultiMap;
13 import gnu.trove.THashSet;
14 import org.jetbrains.annotations.NotNull;
15 import org.jetbrains.annotations.Nullable;
16 import org.jetbrains.jps.ModuleChunk;
17 import org.jetbrains.jps.TimingLog;
18 import org.jetbrains.jps.api.BuildParametersKeys;
19 import org.jetbrains.jps.api.CanceledStatus;
20 import org.jetbrains.jps.api.GlobalOptions;
21 import org.jetbrains.jps.builders.*;
22 import org.jetbrains.jps.builders.impl.BuildOutputConsumerImpl;
23 import org.jetbrains.jps.builders.impl.BuildTargetChunk;
24 import org.jetbrains.jps.builders.impl.DirtyFilesHolderBase;
25 import org.jetbrains.jps.builders.java.JavaBuilderUtil;
26 import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType;
27 import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor;
28 import org.jetbrains.jps.builders.logging.ProjectBuilderLogger;
29 import org.jetbrains.jps.builders.storage.BuildDataCorruptedException;
30 import org.jetbrains.jps.builders.storage.SourceToOutputMapping;
31 import org.jetbrains.jps.cmdline.BuildRunner;
32 import org.jetbrains.jps.cmdline.ProjectDescriptor;
33 import org.jetbrains.jps.incremental.fs.BuildFSState;
34 import org.jetbrains.jps.incremental.fs.CompilationRound;
35 import org.jetbrains.jps.incremental.fs.FilesDelta;
36 import org.jetbrains.jps.incremental.messages.*;
37 import org.jetbrains.jps.incremental.storage.*;
38 import org.jetbrains.jps.indices.ModuleExcludeIndex;
39 import org.jetbrains.jps.javac.ExternalJavacManager;
40 import org.jetbrains.jps.javac.JavacMain;
41 import org.jetbrains.jps.model.java.JpsJavaExtensionService;
42 import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerConfiguration;
43 import org.jetbrains.jps.model.module.JpsModule;
44 import org.jetbrains.jps.service.SharedThreadPool;
45 import org.jetbrains.jps.util.JpsPathUtil;
46
47 import java.io.File;
48 import java.io.IOException;
49 import java.lang.invoke.MethodHandle;
50 import java.lang.invoke.MethodHandles;
51 import java.lang.reflect.InvocationHandler;
52 import java.lang.reflect.Method;
53 import java.lang.reflect.Proxy;
54 import java.nio.file.FileSystem;
55 import java.nio.file.FileSystems;
56 import java.nio.file.Files;
57 import java.nio.file.Path;
58 import java.util.*;
59 import java.util.concurrent.*;
60 import java.util.concurrent.atomic.AtomicInteger;
61 import java.util.concurrent.atomic.AtomicLong;
62 import java.util.concurrent.atomic.AtomicReference;
63 import java.util.function.Predicate;
64 import java.util.stream.Collectors;
65
66 /**
67  * @author Eugene Zhuravlev
68  */
69 public final class IncProjectBuilder {
70   private static final Logger LOG = Logger.getInstance(IncProjectBuilder.class);
71
72   private static final String CLASSPATH_INDEX_FILE_NAME = "classpath.index";
73   // CLASSPATH_INDEX_FILE_NAME cannot be used because IDEA on run creates CLASSPATH_INDEX_FILE_NAME only if some module class is loaded,
74   // so, not possible to distinguish case
75   // "classpath.index doesn't exist because deleted on module file change" vs "classpath.index doesn't exist because was not created"
76   private static final String UNMODIFIED_MARK_FILE_NAME = ".unmodified";
77
78   //private static final boolean GENERATE_CLASSPATH_INDEX = Boolean.parseBoolean(System.getProperty(GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION, "false"));
79   private static final boolean SYNC_DELETE = Boolean.parseBoolean(System.getProperty("jps.sync.delete", "false"));
80   private static final GlobalContextKey<Set<BuildTarget<?>>> TARGET_WITH_CLEARED_OUTPUT = GlobalContextKey.create("_targets_with_cleared_output_");
81   public static final int MAX_BUILDER_THREADS;
82   static {
83     int maxThreads = Math.min(10, (75 * Runtime.getRuntime().availableProcessors()) / 100); // 75% of available logical cores, but not more than 10 threads
84     try {
85       maxThreads = Math.max(1, Integer.parseInt(System.getProperty(GlobalOptions.COMPILE_PARALLEL_MAX_THREADS_OPTION, Integer.toString(maxThreads))));
86     }
87     catch (NumberFormatException ignored) {
88       maxThreads = Math.max(1, maxThreads);
89     }
90     MAX_BUILDER_THREADS = maxThreads;
91   }
92
93   private final ProjectDescriptor myProjectDescriptor;
94   private final BuilderRegistry myBuilderRegistry;
95   private final Map<String, String> myBuilderParams;
96   private final CanceledStatus myCancelStatus;
97   private final List<MessageHandler> myMessageHandlers = new ArrayList<>();
98   private final MessageHandler myMessageDispatcher = new MessageHandler() {
99     @Override
100     public void processMessage(BuildMessage msg) {
101       for (MessageHandler h : myMessageHandlers) {
102         h.processMessage(msg);
103       }
104     }
105   };
106   private final boolean myIsTestMode;
107
108   private final int myTotalModuleLevelBuilderCount;
109   private final List<Future> myAsyncTasks = Collections.synchronizedList(new ArrayList<>());
110   private final ConcurrentMap<Builder, AtomicLong> myElapsedTimeNanosByBuilder = new ConcurrentHashMap<>();
111   private final ConcurrentMap<Builder, AtomicInteger> myNumberOfSourcesProcessedByBuilder = new ConcurrentHashMap<>();
112
113   public IncProjectBuilder(ProjectDescriptor pd, BuilderRegistry builderRegistry, Map<String, String> builderParams, CanceledStatus cs, final boolean isTestMode) {
114     myProjectDescriptor = pd;
115     myBuilderRegistry = builderRegistry;
116     myBuilderParams = builderParams;
117     myCancelStatus = cs;
118     myTotalModuleLevelBuilderCount = builderRegistry.getModuleLevelBuilderCount();
119     myIsTestMode = isTestMode;
120   }
121
122   public void addMessageHandler(MessageHandler handler) {
123     myMessageHandlers.add(handler);
124   }
125
126   public void checkUpToDate(CompileScope scope) {
127     CompileContextImpl context = null;
128     try {
129       context = createContext(scope);
130       final BuildFSState fsState = myProjectDescriptor.fsState;
131       for (BuildTarget<?> target : myProjectDescriptor.getBuildTargetIndex().getAllTargets()) {
132         if (scope.isAffected(target)) {
133           BuildOperations.ensureFSStateInitialized(context, target, true);
134           final FilesDelta delta = fsState.getEffectiveFilesDelta(context, target);
135           delta.lockData();
136           try {
137             for (Set<File> files : delta.getSourcesToRecompile().values()) {
138               for (File file : files) {
139                 if (scope.isAffected(target, file)) {
140                   // this will serve as a marker that compiler has work to do
141                   myMessageDispatcher.processMessage(DoneSomethingNotification.INSTANCE);
142                   return;
143                 }
144               }
145             }
146           }
147           finally {
148             delta.unlockData();
149           }
150         }
151       }
152     }
153     catch (Exception e) {
154       LOG.info(e);
155       // this will serve as a marker that compiler has work to do
156       myMessageDispatcher.processMessage(DoneSomethingNotification.INSTANCE);
157     }
158     finally {
159       if (context != null) {
160         flushContext(context);
161       }
162     }
163   }
164
165
166   public void build(CompileScope scope, boolean forceCleanCaches) throws RebuildRequestedException {
167     checkRebuildRequired(scope);
168
169     final LowMemoryWatcher memWatcher = LowMemoryWatcher.register(() -> {
170       JavacMain.clearCompilerZipFileCache();
171       myProjectDescriptor.dataManager.flush(false);
172       myProjectDescriptor.getProjectStamps().getStampStorage().force();
173     });
174
175     final CleanupTempDirectoryExtension cleaner = CleanupTempDirectoryExtension.getInstance();
176     final Future<Void> cleanupTask = cleaner != null && cleaner.getCleanupTask() != null? cleaner.getCleanupTask() : startTempDirectoryCleanupTask(myProjectDescriptor);
177     if (cleanupTask != null) {
178       myAsyncTasks.add(cleanupTask);
179     }
180
181     CompileContextImpl context = null;
182     BuildTargetSourcesState sourcesState = null;
183     try {
184       context = createContext(scope);
185       sourcesState = new BuildTargetSourcesState(context);
186       // Clear source state report if force clean or rebuild
187       if (forceCleanCaches || context.isProjectRebuild()) {
188         sourcesState.clearSourcesState();
189       }
190       runBuild(context, forceCleanCaches);
191       myProjectDescriptor.dataManager.saveVersion();
192       myProjectDescriptor.dataManager.reportUnhandledRelativizerPaths();
193       sourcesState.reportSourcesState();
194       reportRebuiltModules(context);
195       reportUnprocessedChanges(context);
196     }
197     catch (StopBuildException e) {
198       reportRebuiltModules(context);
199       reportUnprocessedChanges(context);
200       // If build was canceled for some reasons e.g compilation error we should report built modules
201       sourcesState.reportSourcesState();
202       // some builder decided to stop the build
203       // report optional progress message if any
204       final String msg = e.getMessage();
205       if (!StringUtil.isEmptyOrSpaces(msg)) {
206         myMessageDispatcher.processMessage(new ProgressMessage(msg));
207       }
208     }
209     catch (BuildDataCorruptedException e) {
210       LOG.info(e);
211       requestRebuild(e, e);
212     }
213     catch (ProjectBuildException e) {
214       LOG.info(e);
215       final Throwable cause = e.getCause();
216       if (cause instanceof IOException ||
217           cause instanceof BuildDataCorruptedException ||
218           (cause instanceof RuntimeException && cause.getCause() instanceof IOException)) {
219         requestRebuild(e, cause);
220       }
221       else {
222         // should stop the build with error
223         final String errMessage = e.getMessage();
224         final CompilerMessage msg;
225         if (StringUtil.isEmptyOrSpaces(errMessage)) {
226           msg = new CompilerMessage("", cause != null ? cause : e);
227         }
228         else {
229           final String causeMessage = cause != null ? cause.getMessage() : "";
230           msg = new CompilerMessage("", BuildMessage.Kind.ERROR, StringUtil.isEmptyOrSpaces(causeMessage) || errMessage.trim().endsWith(causeMessage)
231                                                                  ? errMessage
232                                                                  : errMessage + ": " + causeMessage);
233         }
234         myMessageDispatcher.processMessage(msg);
235       }
236     }
237     finally {
238       memWatcher.stop();
239       flushContext(context);
240       // wait for async tasks
241       final CanceledStatus status = context == null ? CanceledStatus.NULL : context.getCancelStatus();
242       synchronized (myAsyncTasks) {
243         for (Future task : myAsyncTasks) {
244           if (status.isCanceled()) {
245             break;
246           }
247           waitForTask(status, task);
248         }
249       }
250     }
251   }
252
253   private void checkRebuildRequired(final CompileScope scope) throws RebuildRequestedException {
254     if (myIsTestMode) {
255       // do not use the heuristic in tests in order to properly test all cases
256       return;
257     }
258     final BuildTargetsState targetsState = myProjectDescriptor.getTargetsState();
259     final long timeThreshold = targetsState.getLastSuccessfulRebuildDuration() * 95 / 100; // 95% of last registered clean rebuild time
260     if (timeThreshold <= 0) {
261       return; // no stats available
262     }
263     // check that this is a whole-project incremental build
264     // checking only JavaModuleBuildTargetType because these target types directly correspond to project modules
265     for (BuildTargetType<?> type : JavaModuleBuildTargetType.ALL_TYPES) {
266       if (!scope.isBuildIncrementally(type) || !scope.isAllTargetsOfTypeAffected(type)) {
267         return;
268       }
269     }
270     // compute estimated times for dirty targets
271     long estimatedWorkTime = 0L;
272
273     final Predicate<BuildTarget<?>> isAffected = new Predicate<BuildTarget<?>>() {
274       private final Set<BuildTargetType<?>> allTargetsAffected = new HashSet<>(JavaModuleBuildTargetType.ALL_TYPES);
275       @Override
276       public boolean test(BuildTarget<?> target) {
277         // optimization, since we know here that all targets of types JavaModuleBuildTargetType are affected
278         return allTargetsAffected.contains(target.getTargetType()) || scope.isAffected(target);
279       }
280     };
281     final BuildTargetIndex targetIndex = myProjectDescriptor.getBuildTargetIndex();
282     for (BuildTarget<?> target : targetIndex.getAllTargets()) {
283       if (!targetIndex.isDummy(target)) {
284         final long avgTimeToBuild = targetsState.getAverageBuildTime(target.getTargetType());
285         if (avgTimeToBuild > 0) {
286           // 1. in general case this time should include dependency analysis and cache update times
287           // 2. need to check isAffected() since some targets (like artifacts) may be unaffected even for rebuild
288           if (targetsState.getTargetConfiguration(target).isTargetDirty(myProjectDescriptor) && isAffected.test(target)) {
289             estimatedWorkTime += avgTimeToBuild;
290           }
291         }
292       }
293     }
294
295     if (LOG.isDebugEnabled()) {
296       LOG.debug("Rebuild heuristic: estimated build time / timeThreshold : " + estimatedWorkTime + " / " + timeThreshold);
297     }
298
299     if (estimatedWorkTime >= timeThreshold) {
300       final String message = JpsBuildBundle.message("build.message.too.many.modules.require.recompilation.forcing.full.project.rebuild");
301       LOG.info(message);
302       LOG.info("Estimated build duration (linear): " + StringUtil.formatDuration(estimatedWorkTime));
303       LOG.info("Last successful rebuild duration (linear): " + StringUtil.formatDuration(targetsState.getLastSuccessfulRebuildDuration()));
304       LOG.info("Rebuild heuristic time threshold: " + StringUtil.formatDuration(timeThreshold));
305       myMessageDispatcher.processMessage(new CompilerMessage("", BuildMessage.Kind.INFO, message));
306       throw new RebuildRequestedException(null);
307     }
308   }
309
310   private void requestRebuild(Exception e, Throwable cause) throws RebuildRequestedException {
311     myMessageDispatcher.processMessage(new CompilerMessage(
312       "", BuildMessage.Kind.INFO, JpsBuildBundle.message("build.message.internal.caches.are.corrupted", e.getMessage()))
313     );
314     throw new RebuildRequestedException(cause);
315   }
316
317   private static void waitForTask(@NotNull CanceledStatus status, Future task) {
318     try {
319       while (true) {
320         try {
321           task.get(500L, TimeUnit.MILLISECONDS);
322           break;
323         }
324         catch (TimeoutException ignored) {
325           if (status.isCanceled()) {
326             break;
327           }
328         }
329       }
330     }
331     catch (Throwable th) {
332       LOG.info(th);
333     }
334   }
335
336   private static void reportRebuiltModules(CompileContextImpl context) {
337     final Set<JpsModule> modules = BuildTargetConfiguration.MODULES_WITH_TARGET_CONFIG_CHANGED_KEY.get(context);
338     if (modules == null || modules.isEmpty()) {
339       return;
340     }
341     int shown = modules.size() == 6 ? 6 : Math.min(5, modules.size());
342     String modulesText = modules.stream().limit(shown).map(m -> "'" + m.getName() + "'").collect(Collectors.joining(", "));
343     String text = JpsBuildBundle.message("build.messages.modules.were.fully.rebuilt", modulesText, modules.size(),
344                                          modules.size() - shown, ModuleBuildTarget.REBUILD_ON_DEPENDENCY_CHANGE ? 1 : 0);
345     context.processMessage(new CompilerMessage("", BuildMessage.Kind.INFO, text));
346   }
347
348   private static void reportUnprocessedChanges(CompileContextImpl context) {
349     final ProjectDescriptor pd = context.getProjectDescriptor();
350     final BuildFSState fsState = pd.fsState;
351     for (BuildTarget<?> target : pd.getBuildTargetIndex().getAllTargets()) {
352       if (fsState.hasUnprocessedChanges(context, target)) {
353         context.processMessage(new UnprocessedFSChangesNotification());
354         break;
355       }
356     }
357   }
358
359   private static void flushContext(CompileContext context) {
360     if (context != null) {
361       final ProjectDescriptor pd = context.getProjectDescriptor();
362       pd.getProjectStamps().getStampStorage().force();
363       pd.dataManager.flush(false);
364     }
365     final ExternalJavacManager server = ExternalJavacManager.KEY.get(context);
366     if (server != null) {
367       server.stop();
368       ExternalJavacManager.KEY.set(context, null);
369     }
370   }
371
372   private static boolean isParallelBuild(CompileContext context) {
373     return Boolean.parseBoolean(context.getBuilderParameter(BuildParametersKeys.IS_AUTOMAKE)) ?
374       BuildRunner.PARALLEL_BUILD_AUTOMAKE_ENABLED : BuildRunner.PARALLEL_BUILD_ENABLED;
375   }
376
377   private void runBuild(final CompileContextImpl context, boolean forceCleanCaches) throws ProjectBuildException {
378     context.setDone(0.0f);
379
380     LOG.info("Building project; isRebuild:" +
381              context.isProjectRebuild() +
382              "; isMake:" +
383              context.isMake() +
384              " parallel compilation:" +
385              isParallelBuild(context));
386
387     context.addBuildListener(new ChainedTargetsBuildListener(context));
388
389     // deletes class loader classpath index files for changed output roots
390     context.addBuildListener(new BuildListener() {
391       @Override
392       public void filesGenerated(@NotNull FileGeneratedEvent event) {
393         Collection<Pair<String, String>> paths = event.getPaths();
394         FileSystem fs = FileSystems.getDefault();
395         if (paths.size() == 1) {
396           deleteFiles(paths.iterator().next().first, fs);
397           return;
398         }
399
400         Set<String> outputs = new HashSet<>();
401         for (Pair<String, String> pair : paths) {
402           String root = pair.getFirst();
403           if (outputs.add(root)) {
404             deleteFiles(root, fs);
405           }
406         }
407       }
408
409       private void deleteFiles(String rootPath, FileSystem fs) {
410         Path root = fs.getPath(rootPath);
411         try {
412           Files.deleteIfExists(root.resolve(CLASSPATH_INDEX_FILE_NAME));
413           Files.deleteIfExists(root.resolve(UNMODIFIED_MARK_FILE_NAME));
414         }
415         catch (IOException ignore) {
416         }
417       }
418     });
419
420     for (TargetBuilder builder : myBuilderRegistry.getTargetBuilders()) {
421       builder.buildStarted(context);
422     }
423     for (ModuleLevelBuilder builder : myBuilderRegistry.getModuleLevelBuilders()) {
424       builder.buildStarted(context);
425     }
426
427     BuildProgress buildProgress = null;
428     try {
429       buildProgress = new BuildProgress(myProjectDescriptor.dataManager, myProjectDescriptor.getBuildTargetIndex(),
430                                         myProjectDescriptor.getBuildTargetIndex().getSortedTargetChunks(context),
431                                         chunk -> isAffected(context.getScope(), chunk));
432
433       // clean roots for targets for which rebuild is forced
434       cleanOutputRoots(context, context.isProjectRebuild() || forceCleanCaches);
435
436       context.processMessage(new ProgressMessage(JpsBuildBundle.message("progress.message.running.before.tasks")));
437       runTasks(context, myBuilderRegistry.getBeforeTasks());
438       TimingLog.LOG.debug("'before' tasks finished");
439
440       context.processMessage(new ProgressMessage(JpsBuildBundle.message("progress.message.checking.sources")));
441       buildChunks(context, buildProgress);
442       TimingLog.LOG.debug("Building targets finished");
443
444       context.processMessage(new ProgressMessage(JpsBuildBundle.message("progress.message.running.after.tasks")));
445       runTasks(context, myBuilderRegistry.getAfterTasks());
446       TimingLog.LOG.debug("'after' tasks finished");
447       sendElapsedTimeMessages(context);
448     }
449     finally {
450       if (buildProgress != null) {
451         buildProgress.updateExpectedAverageTime();
452         if (context.isProjectRebuild() && !Utils.errorsDetected(context) && !context.getCancelStatus().isCanceled()) {
453           myProjectDescriptor.getTargetsState().setLastSuccessfulRebuildDuration(buildProgress.getAbsoluteBuildTime());
454         }
455       }
456       for (TargetBuilder builder : myBuilderRegistry.getTargetBuilders()) {
457         builder.buildFinished(context);
458       }
459       for (ModuleLevelBuilder builder : myBuilderRegistry.getModuleLevelBuilders()) {
460         builder.buildFinished(context);
461       }
462       context.processMessage(new ProgressMessage(JpsBuildBundle.message("progress.message.finished.saving.caches")));
463     }
464
465   }
466
467   private void sendElapsedTimeMessages(CompileContext context) {
468     myElapsedTimeNanosByBuilder.entrySet()
469       .stream()
470       .map(entry -> {
471         AtomicInteger processedSourcesRef = myNumberOfSourcesProcessedByBuilder.get(entry.getKey());
472         int processedSources = processedSourcesRef != null ? processedSourcesRef.get() : 0;
473         return new BuilderStatisticsMessage(entry.getKey().getPresentableName(), processedSources, entry.getValue().get()/1_000_000);
474       })
475       .sorted(Comparator.comparing(BuilderStatisticsMessage::getBuilderName))
476       .forEach(context::processMessage);
477   }
478
479   static @Nullable Future<Void> startTempDirectoryCleanupTask(final ProjectDescriptor pd) {
480     final String tempPath = System.getProperty("java.io.tmpdir", null);
481     if (StringUtil.isEmptyOrSpaces(tempPath)) {
482       return null;
483     }
484     final File tempDir = new File(tempPath);
485     final File dataRoot = pd.dataManager.getDataPaths().getDataStorageRoot();
486     if (!FileUtil.isAncestor(dataRoot, tempDir, true)) {
487       // cleanup only 'local' temp
488       return null;
489     }
490     final File[] files = tempDir.listFiles();
491     if (files == null) {
492       tempDir.mkdirs(); // ensure the directory exists
493     }
494     else if (files.length > 0) {
495       final RunnableFuture<Void> task = new FutureTask<>(() -> {
496         for (File tempFile : files) {
497           FileUtil.delete(tempFile);
498         }
499       }, null);
500       final Thread thread = new Thread(task, "Temp directory cleanup");
501       thread.setPriority(Thread.MIN_PRIORITY);
502       thread.setDaemon(true);
503       thread.start();
504       return task;
505     }
506     return null;
507   }
508
509   private CompileContextImpl createContext(CompileScope scope) {
510     return new CompileContextImpl(scope, myProjectDescriptor, myMessageDispatcher, myBuilderParams, myCancelStatus);
511   }
512
513   private void cleanOutputRoots(CompileContext context, boolean cleanCaches) throws ProjectBuildException {
514     final ProjectDescriptor projectDescriptor = context.getProjectDescriptor();
515     ProjectBuildException ex = null;
516     try {
517       final JpsJavaCompilerConfiguration configuration = JpsJavaExtensionService.getInstance().getCompilerConfiguration(projectDescriptor.getProject());
518       final boolean shouldClear = configuration.isClearOutputDirectoryOnRebuild();
519       if (shouldClear) {
520         clearOutputs(context);
521       }
522       else {
523         for (BuildTarget<?> target : projectDescriptor.getBuildTargetIndex().getAllTargets()) {
524           context.checkCanceled();
525           if (context.getScope().isBuildForced(target)) {
526             clearOutputFilesUninterruptibly(context, target);
527           }
528         }
529       }
530       for (BuildTargetType<?> type : TargetTypeRegistry.getInstance().getTargetTypes()) {
531         if (context.getScope().isAllTargetsOfTypeAffected(type)) {
532           cleanOutputOfStaleTargets(type, context);
533         }
534       }
535     }
536     catch (ProjectBuildException e) {
537       ex = e;
538     }
539     finally {
540       if (cleanCaches) {
541         try {
542           projectDescriptor.getProjectStamps().getStampStorage().clean();
543         }
544         catch (IOException e) {
545           if (ex == null) {
546             ex = new ProjectBuildException(JpsBuildBundle.message("build.message.error.cleaning.timestamps.storage"), e);
547           }
548           else {
549             LOG.info("Error cleaning timestamps storage", e);
550           }
551         }
552         finally {
553           try {
554             projectDescriptor.dataManager.clean();
555           }
556           catch (IOException e) {
557             if (ex == null) {
558               ex = new ProjectBuildException(JpsBuildBundle.message("build.message.error.cleaning.compiler.storages"), e);
559             }
560             else {
561               LOG.info("Error cleaning compiler storages", e);
562             }
563           }
564           finally {
565             projectDescriptor.fsState.clearAll();
566             if (ex != null) {
567               throw ex;
568             }
569           }
570         }
571       }
572       else {
573         final BuildTargetsState targetsState = projectDescriptor.getTargetsState();
574         for (BuildTarget<?> target : getTargetsWithClearedOutput(context)) {
575           // This will ensure the target will be fully rebuilt either in this or in the future build session.
576           // if this build fails or is cancelled, all such targets will still be marked as needing recompilation
577           targetsState.getTargetConfiguration(target).invalidate();
578         }
579       }
580     }
581   }
582
583   private void cleanOutputOfStaleTargets(BuildTargetType<?> type, CompileContext context) {
584     List<Pair<String, Integer>> targetIds = myProjectDescriptor.dataManager.getTargetsState().getStaleTargetIds(type);
585     if (targetIds.isEmpty()) return;
586
587     context.processMessage(new ProgressMessage(JpsBuildBundle.message("progress.message.cleaning.old.output.directories")));
588     for (Pair<String, Integer> ids : targetIds) {
589       String stringId = ids.first;
590       try {
591         SourceToOutputMappingImpl mapping = null;
592         try {
593           mapping = myProjectDescriptor.dataManager.createSourceToOutputMapForStaleTarget(type, stringId);
594           clearOutputFiles(context, mapping, type, ids.second);
595         }
596         finally {
597           if (mapping != null) {
598             mapping.close();
599           }
600         }
601         FileUtil.delete(myProjectDescriptor.dataManager.getDataPaths().getTargetDataRoot(type, stringId));
602         myProjectDescriptor.dataManager.getTargetsState().cleanStaleTarget(type, stringId);
603       }
604       catch (IOException e) {
605         LOG.warn(e);
606         myMessageDispatcher.processMessage(new CompilerMessage("", BuildMessage.Kind.WARNING,
607                                                                JpsBuildBundle.message("build.message.failed.to.delete.output.files.from.obsolete.0.target.1",
608                                                                                       stringId, e.toString())));
609       }
610     }
611   }
612
613   public static void clearOutputFiles(CompileContext context, BuildTarget<?> target) throws IOException {
614     final SourceToOutputMapping map = context.getProjectDescriptor().dataManager.getSourceToOutputMap(target);
615     BuildTargetType<?> targetType = target.getTargetType();
616     clearOutputFiles(context, map, targetType, context.getProjectDescriptor().dataManager.getTargetsState().getBuildTargetId(target));
617     registerTargetsWithClearedOutput(context, Collections.singletonList(target));
618   }
619
620   private static void clearOutputFiles(CompileContext context,
621                                        SourceToOutputMapping mapping,
622                                        BuildTargetType<?> targetType,
623                                        int targetId) throws IOException {
624     Set<File> dirsToDelete = targetType instanceof ModuleBasedBuildTargetType<?> ? FileCollectionFactory.createCanonicalFileSet() : null;
625     OutputToTargetRegistry outputToTargetRegistry = context.getProjectDescriptor().dataManager.getOutputToTargetRegistry();
626     for (String srcPath : mapping.getSources()) {
627       final Collection<String> outs = mapping.getOutputs(srcPath);
628       if (outs != null && !outs.isEmpty()) {
629         List<String> deletedPaths = new ArrayList<>();
630         for (String out : outs) {
631           BuildOperations.deleteRecursively(out, deletedPaths, dirsToDelete);
632         }
633         outputToTargetRegistry.removeMapping(outs, targetId);
634         if (!deletedPaths.isEmpty()) {
635           context.processMessage(new FileDeletedEvent(deletedPaths));
636         }
637       }
638     }
639     if (dirsToDelete != null) {
640       FSOperations.pruneEmptyDirs(context, dirsToDelete);
641     }
642   }
643
644   private static void registerTargetsWithClearedOutput(CompileContext context, Collection<? extends BuildTarget<?>> targets) {
645     synchronized (TARGET_WITH_CLEARED_OUTPUT) {
646       Set<BuildTarget<?>> data = context.getUserData(TARGET_WITH_CLEARED_OUTPUT);
647       if (data == null) {
648         data = new HashSet<>();
649         context.putUserData(TARGET_WITH_CLEARED_OUTPUT, data);
650       }
651       data.addAll(targets);
652     }
653   }
654
655   private static boolean isTargetOutputCleared(CompileContext context, BuildTarget<?> target) {
656     synchronized (TARGET_WITH_CLEARED_OUTPUT) {
657       Set<BuildTarget<?>> data = context.getUserData(TARGET_WITH_CLEARED_OUTPUT);
658       return data != null && data.contains(target);
659     }
660   }
661
662   private static Set<BuildTarget<?>> getTargetsWithClearedOutput(CompileContext context) {
663     synchronized (TARGET_WITH_CLEARED_OUTPUT) {
664       Set<BuildTarget<?>> data = context.getUserData(TARGET_WITH_CLEARED_OUTPUT);
665       return data != null? Collections.unmodifiableSet(new HashSet<>(data)) : Collections.emptySet();
666     }
667   }
668
669   private enum Applicability {
670     NONE, PARTIAL, ALL;
671
672     static <T> Applicability calculate(Predicate<? super T> p, Collection<? extends T> collection) {
673       int count = 0;
674       int item = 0;
675       for (T elem : collection) {
676         item++;
677         if (p.test(elem)) {
678           count++;
679           if (item > count) {
680             return PARTIAL;
681           }
682         }
683         else {
684           if (count > 0) {
685             return PARTIAL;
686           }
687         }
688       }
689       return count == 0? NONE : ALL;
690     }
691   }
692
693   private void clearOutputs(CompileContext context) throws ProjectBuildException {
694     final long cleanStart = System.nanoTime();
695     final MultiMap<File, BuildTarget<?>> rootsToDelete = MultiMap.createSet();
696     final Set<File> allSourceRoots = FileCollectionFactory.createCanonicalFileSet();
697
698     final ProjectDescriptor projectDescriptor = context.getProjectDescriptor();
699     final List<? extends BuildTarget<?>> allTargets = projectDescriptor.getBuildTargetIndex().getAllTargets();
700     for (BuildTarget<?> target : allTargets) {
701       if (target instanceof ModuleBasedTarget) {
702         for (File file : target.getOutputRoots(context)) {
703           rootsToDelete.putValue(file, target);
704         }
705       }
706       else {
707         if (context.getScope().isBuildForced(target)) {
708           clearOutputFilesUninterruptibly(context, target);
709         }
710       }
711     }
712
713     final ModuleExcludeIndex moduleIndex = projectDescriptor.getModuleExcludeIndex();
714     for (BuildTarget<?> target : allTargets) {
715       for (BuildRootDescriptor descriptor : projectDescriptor.getBuildRootIndex().getTargetRoots(target, context)) {
716         // excluding from checks roots with generated sources; because it is safe to delete generated stuff
717         if (!descriptor.isGenerated()) {
718           File rootFile = descriptor.getRootFile();
719           //some roots aren't marked by as generated but in fact they are produced by some builder and it's safe to remove them.
720           //However if a root isn't excluded it means that its content will be shown in 'Project View' and an user can create new files under it so it would be dangerous to clean such roots
721           if (moduleIndex.isInContent(rootFile)) {
722             allSourceRoots.add(rootFile);
723           }
724         }
725       }
726     }
727
728     // check that output and source roots are not overlapping
729     final CompileScope compileScope = context.getScope();
730     final List<File> filesToDelete = new ArrayList<>();
731     final Predicate<BuildTarget<?>> forcedBuild = input -> compileScope.isBuildForced(input);
732     for (Map.Entry<File, Collection<BuildTarget<?>>> entry : rootsToDelete.entrySet()) {
733       context.checkCanceled();
734       final File outputRoot = entry.getKey();
735       final Collection<BuildTarget<?>> rootTargets = entry.getValue();
736       final Applicability applicability = Applicability.calculate(forcedBuild, rootTargets);
737       if (applicability == Applicability.NONE) {
738         continue;
739       }
740       // It makes no sense to delete already empty root, but instead it makes sense to cleanup the target, because there may exist
741       // a directory that has been previously the output root for the target
742       boolean okToDelete = applicability == Applicability.ALL && !isEmpty(outputRoot);
743       if (okToDelete && !moduleIndex.isExcluded(outputRoot)) {
744         // if output root itself is directly or indirectly excluded,
745         // there cannot be any manageable sources under it, even if the output root is located under some source root
746         // so in this case it is safe to delete such root
747         if (JpsPathUtil.isUnder(allSourceRoots, outputRoot)) {
748           okToDelete = false;
749         }
750         else {
751           final Set<File> _outRoot = FileCollectionFactory.createCanonicalFileSet(Collections.singletonList(outputRoot));
752           for (File srcRoot : allSourceRoots) {
753             if (JpsPathUtil.isUnder(_outRoot, srcRoot)) {
754               okToDelete = false;
755               break;
756             }
757           }
758         }
759         if (!okToDelete) {
760           context.processMessage(new CompilerMessage(
761             "", BuildMessage.Kind.WARNING,
762             JpsBuildBundle.message("build.message.output.path.0.intersects.with.a.source.root", outputRoot.getPath()))
763           );
764         }
765       }
766
767       if (okToDelete) {
768         // do not delete output root itself to avoid lots of unnecessary "roots_changed" events in IDEA
769         final File[] children = outputRoot.listFiles();
770         if (children != null) {
771           for (File child : children) {
772             if (!child.delete()) {
773               filesToDelete.add(child);
774             }
775           }
776         }
777         else { // the output root must be file
778           if (!outputRoot.delete()) {
779             filesToDelete.add(outputRoot);
780           }
781         }
782         registerTargetsWithClearedOutput(context, rootTargets);
783       }
784       else {
785         context.processMessage(new ProgressMessage(JpsBuildBundle.message("progress.message.cleaning.output.directories")));
786         // clean only those files we are aware of
787         for (BuildTarget<?> target : rootTargets) {
788           if (compileScope.isBuildForced(target)) {
789             clearOutputFilesUninterruptibly(context, target);
790           }
791         }
792       }
793     }
794
795     if (!filesToDelete.isEmpty()) {
796       context.processMessage(new ProgressMessage(JpsBuildBundle.message("progress.message.cleaning.output.directories")));
797       if (SYNC_DELETE) {
798         for (File file : filesToDelete) {
799           context.checkCanceled();
800           FileUtil.delete(file);
801         }
802       }
803       else {
804         myAsyncTasks.add(FileUtil.asyncDelete(filesToDelete));
805       }
806     }
807     LOG.info("Cleaned output directories in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - cleanStart) + " ms");
808   }
809
810   private static boolean isEmpty(File outputRoot) {
811     final String[] files = outputRoot.list();
812     return files == null || files.length == 0;
813   }
814
815   private static void clearOutputFilesUninterruptibly(CompileContext context, BuildTarget<?> target) {
816     try {
817       clearOutputFiles(context, target);
818     }
819     catch (Throwable e) {
820       LOG.info(e);
821       String reason = e.getMessage();
822       if (reason == null) {
823         reason = e.getClass().getName();
824       }
825       context.processMessage(new CompilerMessage("", BuildMessage.Kind.WARNING, JpsBuildBundle
826         .message("build.message.problems.clearing.output.files.for.target.0.1", target.getPresentableName(), reason)));
827     }
828   }
829
830   private static void runTasks(CompileContext context, final List<? extends BuildTask> tasks) throws ProjectBuildException {
831     for (BuildTask task : tasks) {
832       task.build(context);
833     }
834   }
835
836   private void buildChunks(final CompileContextImpl context, BuildProgress buildProgress) throws ProjectBuildException {
837     try {
838
839       boolean compileInParallel = isParallelBuild(context);
840       if (compileInParallel && MAX_BUILDER_THREADS <= 1) {
841         LOG.info("Switched off parallel compilation because maximum number of builder threads is less than 2. Set '"
842                  + GlobalOptions.COMPILE_PARALLEL_MAX_THREADS_OPTION + "' system property to a value greater than 1 to really enable parallel compilation.");
843         compileInParallel = false;
844       }
845
846       if (compileInParallel) {
847         new BuildParallelizer(context, buildProgress).buildInParallel();
848       }
849       else {
850         // non-parallel build
851         ProjectDescriptor pd = context.getProjectDescriptor();
852         for (BuildTargetChunk chunk : pd.getBuildTargetIndex().getSortedTargetChunks(context)) {
853           try {
854             buildChunkIfAffected(context, context.getScope(), chunk, buildProgress);
855           }
856           finally {
857             pd.dataManager.closeSourceToOutputStorages(Collections.singleton(chunk));
858             pd.dataManager.flush(true);
859           }
860         }
861       }
862     }
863     catch (IOException e) {
864       throw new ProjectBuildException(e);
865     }
866   }
867
868   private static final class BuildChunkTask {
869     private final BuildTargetChunk myChunk;
870     private final Set<BuildChunkTask> myNotBuiltDependencies = new HashSet<>();
871     private final List<BuildChunkTask> myTasksDependsOnThis = new ArrayList<>();
872     private int mySelfScore = 0;
873     private int myDepsScore = 0;
874
875     private BuildChunkTask(BuildTargetChunk chunk) {
876       myChunk = chunk;
877     }
878
879     private int getScore() {
880       return myDepsScore + mySelfScore;
881     }
882
883     public BuildTargetChunk getChunk() {
884       return myChunk;
885     }
886
887     public boolean isReady() {
888       return myNotBuiltDependencies.isEmpty();
889     }
890
891     public void addDependency(BuildChunkTask dependency) {
892       if (myNotBuiltDependencies.add(dependency)) {
893         dependency.myTasksDependsOnThis.add(this);
894       }
895     }
896
897     public List<BuildChunkTask> markAsFinishedAndGetNextReadyTasks() {
898       List<BuildChunkTask> nextTasks = new SmartList<>();
899       for (BuildChunkTask task : myTasksDependsOnThis) {
900         final boolean removed = task.myNotBuiltDependencies.remove(this);
901         LOG.assertTrue(removed, task.getChunk().toString() + " didn't have " + getChunk().toString());
902
903         if (task.isReady()) {
904           nextTasks.add(task);
905         }
906       }
907       return nextTasks;
908     }
909   }
910
911   private final class BuildParallelizer {
912     private final ExecutorService myParallelBuildExecutor = AppExecutorUtil.createCustomPriorityQueueBoundedApplicationPoolExecutor(
913       "IncProjectBuilder Executor Pool", SharedThreadPool.getInstance(), MAX_BUILDER_THREADS, (o1, o2) -> {
914       int p1 = o1 instanceof RunnableWithPriority ? ((RunnableWithPriority)o1).priority : 1;
915       int p2 = o1 instanceof RunnableWithPriority ? ((RunnableWithPriority)o2).priority : 1;
916       return Integer.compare(p2, p1);
917     });
918     private final CompileContext myContext;
919     private final BuildProgress myBuildProgress;
920     private final AtomicReference<Throwable> myException = new AtomicReference<>();
921     private final Object myQueueLock = new Object();
922     private final CountDownLatch myTasksCountDown;
923     private final List<BuildChunkTask> myTasks;
924
925     private BuildParallelizer(CompileContext context, BuildProgress buildProgress) {
926       myContext = context;
927       myBuildProgress = buildProgress;
928       final ProjectDescriptor pd = myContext.getProjectDescriptor();
929       final BuildTargetIndex targetIndex = pd.getBuildTargetIndex();
930
931       List<BuildTargetChunk> chunks = targetIndex.getSortedTargetChunks(myContext);
932       myTasks = new ArrayList<>(chunks.size());
933       Map<BuildTarget<?>, BuildChunkTask> targetToTask = new HashMap<>(chunks.size());
934       for (BuildTargetChunk chunk : chunks) {
935         BuildChunkTask task = new BuildChunkTask(chunk);
936         myTasks.add(task);
937         for (BuildTarget<?> target : chunk.getTargets()) {
938           targetToTask.put(target, task);
939           task.mySelfScore += 1;
940         }
941       }
942
943       Map<BuildTarget<?>, Collection<BuildTarget<?>>> transitiveDependencyCache = new HashMap<>(myTasks.size());
944
945       for (BuildChunkTask task : myTasks) {
946         for (BuildTarget<?> target : task.getChunk().getTargets()) {
947           for (BuildTarget<?> dependency : targetIndex.getDependencies(target, myContext)) {
948             BuildChunkTask depTask = targetToTask.get(dependency);
949             if (depTask != null && depTask != task) {
950               task.addDependency(depTask);
951             }
952           }
953           for (BuildTarget<?> dependency : getTransitiveDeps(targetIndex, target, myContext, transitiveDependencyCache)) {
954             BuildChunkTask depTask = targetToTask.get(dependency);
955             if (depTask != null && depTask != task) {
956               depTask.myDepsScore += task.mySelfScore;
957             }
958           }
959         }
960       }
961
962       myTasksCountDown = new CountDownLatch(myTasks.size());
963     }
964
965     public void buildInParallel() throws IOException, ProjectBuildException {
966       List<BuildChunkTask> initialTasks = new ArrayList<>();
967       for (BuildChunkTask task : myTasks) {
968         if (task.isReady()) {
969           initialTasks.add(task);
970         }
971       }
972       queueTasks(initialTasks);
973
974       try {
975         myTasksCountDown.await();
976       }
977       catch (InterruptedException e) {
978         LOG.info(e);
979       }
980
981       final Throwable throwable = myException.get();
982       if (throwable instanceof ProjectBuildException) {
983         throw (ProjectBuildException)throwable;
984       }
985       else if (throwable != null) {
986         throw new ProjectBuildException(throwable);
987       }
988     }
989
990     private void queueTasks(List<? extends BuildChunkTask> tasks) {
991       if (tasks.isEmpty()) return;
992       ArrayList<? extends BuildChunkTask> sorted = new ArrayList<>(tasks);
993       sorted.sort(Comparator.comparingLong(BuildChunkTask::getScore).reversed());
994
995       if (LOG.isDebugEnabled()) {
996         final List<BuildTargetChunk> chunksToLog = new ArrayList<>();
997         for (BuildChunkTask task : sorted) {
998           chunksToLog.add(task.getChunk());
999         }
1000         final StringBuilder logBuilder = new StringBuilder("Queuing " + chunksToLog.size() + " chunks in parallel: ");
1001         chunksToLog.sort(Comparator.comparing(BuildTargetChunk::toString));
1002         for (BuildTargetChunk chunk : chunksToLog) {
1003           logBuilder.append(chunk.toString()).append("; ");
1004         }
1005         LOG.debug(logBuilder.toString());
1006       }
1007       for (BuildChunkTask task : sorted) {
1008         queueTask(task);
1009       }
1010     }
1011
1012     private abstract class RunnableWithPriority implements Runnable {
1013       public final int priority;
1014
1015       RunnableWithPriority(int priority) {
1016         this.priority = priority;
1017       }
1018     }
1019
1020     private void queueTask(final BuildChunkTask task) {
1021       final CompileContext chunkLocalContext = createContextWrapper(myContext);
1022       myParallelBuildExecutor.execute(new RunnableWithPriority(task.getScore()) {
1023         @Override
1024         public void run() {
1025           try {
1026             try {
1027               if (myException.get() == null) {
1028                 buildChunkIfAffected(chunkLocalContext, myContext.getScope(), task.getChunk(), myBuildProgress);
1029               }
1030             }
1031             finally {
1032               myProjectDescriptor.dataManager.closeSourceToOutputStorages(Collections.singletonList(task.getChunk()));
1033               myProjectDescriptor.dataManager.flush(true);
1034             }
1035           }
1036           catch (Throwable e) {
1037             myException.compareAndSet(null, e);
1038             LOG.info(e);
1039           }
1040           finally {
1041             LOG.debug("Finished compilation of " + task.getChunk().toString());
1042             myTasksCountDown.countDown();
1043             List<BuildChunkTask> nextTasks;
1044             synchronized (myQueueLock) {
1045               nextTasks = task.markAsFinishedAndGetNextReadyTasks();
1046             }
1047             if (!nextTasks.isEmpty()) {
1048               queueTasks(nextTasks);
1049             }
1050           }
1051         }
1052       });
1053     }
1054   }
1055
1056   private static Iterable<? extends BuildTarget<?>> getTransitiveDeps(BuildTargetIndex index,
1057                                                                       BuildTarget<?> target,
1058                                                                       CompileContext context,
1059                                                                       Map<BuildTarget<?>, Collection<BuildTarget<?>>> cache) {
1060     if (cache.containsKey(target)) {
1061       return cache.get(target);
1062     }
1063     Set<BuildTarget<?>> result = new HashSet<>();
1064     LinkedList<BuildTarget<?>> queue = new LinkedList<>();
1065     queue.add(target);
1066     result.add(target);
1067     while (!queue.isEmpty()) {
1068       BuildTarget next = queue.pop();
1069       Collection<BuildTarget<?>> transitive = cache.get(next);
1070       if (transitive != null) {
1071         result.addAll(transitive);
1072       }
1073       else {
1074         Collection<BuildTarget<?>> dependencies = index.getDependencies(next, context);
1075         for (BuildTarget<?> dependency : dependencies) {
1076           if (dependency != target && result.add(dependency)) queue.add(dependency);
1077         }
1078       }
1079     }
1080     result.remove(target);
1081     cache.put(target, result);
1082     return result;
1083   }
1084
1085   private void buildChunkIfAffected(CompileContext context, CompileScope scope, BuildTargetChunk chunk,
1086                                     BuildProgress buildProgress) throws ProjectBuildException {
1087     if (isAffected(scope, chunk)) {
1088       buildTargetsChunk(context, chunk, buildProgress);
1089     }
1090   }
1091
1092   private static boolean isAffected(CompileScope scope, BuildTargetChunk chunk) {
1093     for (BuildTarget<?> target : chunk.getTargets()) {
1094       if (scope.isAffected(target)) {
1095         return true;
1096       }
1097     }
1098     return false;
1099   }
1100
1101   private boolean runBuildersForChunk(final CompileContext context, final BuildTargetChunk chunk, BuildProgress buildProgress) throws ProjectBuildException, IOException {
1102     Set<? extends BuildTarget<?>> targets = chunk.getTargets();
1103     if (targets.size() > 1) {
1104       Set<ModuleBuildTarget> moduleTargets = new LinkedHashSet<>();
1105       for (BuildTarget<?> target : targets) {
1106         if (target instanceof ModuleBuildTarget) {
1107           moduleTargets.add((ModuleBuildTarget)target);
1108         }
1109         else {
1110           final String targetsString = StringUtil.join(targets, target1 -> StringUtil.decapitalize(target1.getPresentableName()), ", ");
1111           final String message = JpsBuildBundle.message("build.message.cannot.build.0.because.it.is.included.into.a.circular.dependency.1", StringUtil.decapitalize(target.getPresentableName()), targetsString);
1112           context.processMessage(new CompilerMessage("", BuildMessage.Kind.ERROR, message));
1113           return false;
1114         }
1115       }
1116
1117       return runModuleLevelBuilders(wrapWithModuleInfoAppender(context, moduleTargets), new ModuleChunk(moduleTargets), buildProgress);
1118     }
1119
1120     final BuildTarget<?> target = targets.iterator().next();
1121     if (target instanceof ModuleBuildTarget) {
1122       final Set<ModuleBuildTarget> mbt = Collections.singleton((ModuleBuildTarget)target);
1123       return runModuleLevelBuilders(wrapWithModuleInfoAppender(context, mbt), new ModuleChunk(mbt), buildProgress);
1124     }
1125
1126     completeRecompiledSourcesSet(context, (Collection<? extends BuildTarget<BuildRootDescriptor>>)targets);
1127
1128     // In general the set of files corresponding to changed source file may be different
1129     // Need this for example, to keep up with case changes in file names  for case-insensitive OSes:
1130     // deleting the output before copying is the only way to ensure the case of the output file's name is exactly the same as source file's case
1131     cleanOldOutputs(context, target);
1132
1133     final List<TargetBuilder<?, ?>> builders = BuilderRegistry.getInstance().getTargetBuilders();
1134     int builderCount = 0;
1135     for (TargetBuilder<?, ?> builder : builders) {
1136       buildTarget(target, context, builder);
1137       builderCount++;
1138       buildProgress.updateProgress(target, ((double)builderCount)/builders.size(), context);
1139     }
1140     return true;
1141   }
1142
1143   private static CompileContext wrapWithModuleInfoAppender(CompileContext context, Collection<ModuleBuildTarget> moduleTargets) {
1144     final Class<MessageHandler> messageHandlerInterface = MessageHandler.class;
1145     return (CompileContext)Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[] {CompileContext.class}, new InvocationHandler() {
1146       @Override
1147       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
1148         if (args != null && args.length > 0 && messageHandlerInterface.equals(method.getDeclaringClass())) {
1149           for (Object arg : args) {
1150             if (arg instanceof CompilerMessage) {
1151               final CompilerMessage compilerMessage = (CompilerMessage)arg;
1152               for (ModuleBuildTarget target : moduleTargets) {
1153                 compilerMessage.addModuleName(target.getModule().getName());
1154               }
1155               break;
1156             }
1157           }
1158         }
1159         final MethodHandle mh = MethodHandles.lookup().unreflect(method);
1160         return args == null? mh.invoke(context) : mh.bindTo(context).asSpreader(Object[].class, args.length).invoke(args);
1161       }
1162     });
1163   }
1164
1165   /**
1166      if an output file is generated from multiple sources, make sure all of them are added for recompilation
1167    */
1168   private static <T extends BuildTarget<R>, R extends BuildRootDescriptor> void completeRecompiledSourcesSet(CompileContext context, Collection<T> targets) throws IOException {
1169     final CompileScope scope = context.getScope();
1170     for (T target : targets) {
1171       if (scope.isBuildForced(target)) {
1172         return; // assuming build is either forced for all targets in a chunk or for none of them
1173       }
1174     }
1175
1176     final ProjectDescriptor pd = context.getProjectDescriptor();
1177     final Set<String> affectedOutputs = new THashSet<>(FileUtil.PATH_HASHING_STRATEGY);
1178     final Set<String> affectedSources = new THashSet<>(FileUtil.PATH_HASHING_STRATEGY);
1179
1180     final List<SourceToOutputMapping> mappings = new ArrayList<>();
1181     for (T target : targets) {
1182       final SourceToOutputMapping srcToOut = pd.dataManager.getSourceToOutputMap(target);
1183       mappings.add(srcToOut);
1184       pd.fsState.processFilesToRecompile(context, target, new FileProcessor<R, T>() {
1185         @Override
1186         public boolean apply(T target, File file, R root) throws IOException {
1187           final String src = FileUtil.toSystemIndependentName(file.getPath());
1188           if (affectedSources.add(src)) {
1189             final Collection<String> outs = srcToOut.getOutputs(src);
1190             if (outs != null) {
1191               // Temporary hack for KTIJ-197
1192               // Change of only one input of *.kotlin_module files didn't trigger recompilation of all inputs in old behaviour.
1193               // Now it does. It isn't yet obvious whether it is right or wrong behaviour. Let's leave old behaviour for a
1194               // while for safety and keeping kotlin incremental JPS tests green
1195               List<String> filteredOuts = ContainerUtil.filter(outs, out -> !"kotlin_module".equals(StringUtil.substringAfterLast(out, ".")));
1196               affectedOutputs.addAll(filteredOuts);
1197             }
1198           }
1199           return true;
1200         }
1201       });
1202     }
1203
1204     if (!affectedOutputs.isEmpty()) {
1205       for (SourceToOutputMapping srcToOut : mappings) {
1206         for (String src : srcToOut.getSources()) {
1207           if (!affectedSources.contains(src)) {
1208             for (Iterator<String> it = srcToOut.getOutputsIterator(src); it.hasNext(); ) {
1209               if (affectedOutputs.contains(it.next())) {
1210                 FSOperations.markDirtyIfNotDeleted(context, CompilationRound.CURRENT, new File(src));
1211                 break;
1212               }
1213             }
1214           }
1215         }
1216       }
1217     }
1218
1219   }
1220
1221   private <R extends BuildRootDescriptor, T extends BuildTarget<R>>
1222   void buildTarget(final T target, final CompileContext context, TargetBuilder<?, ?> builder) throws ProjectBuildException, IOException {
1223
1224     if (builder.getTargetTypes().contains(target.getTargetType())) {
1225       DirtyFilesHolder<R, T> holder = new DirtyFilesHolderBase<R, T>(context) {
1226         @Override
1227         public void processDirtyFiles(@NotNull FileProcessor<R, T> processor) throws IOException {
1228           context.getProjectDescriptor().fsState.processFilesToRecompile(context, target, processor);
1229         }
1230       };
1231       BuildOutputConsumerImpl outputConsumer = new BuildOutputConsumerImpl(target, context);
1232       long start = System.nanoTime();
1233       ((TargetBuilder<R, T>)builder).build(target, holder, outputConsumer, context);
1234       storeBuilderStatistics(builder, System.nanoTime() - start, outputConsumer.getNumberOfProcessedSources());
1235       outputConsumer.fireFileGeneratedEvent();
1236       context.checkCanceled();
1237     }
1238   }
1239
1240   private static <T extends BuildRootDescriptor> void cleanOldOutputs(final CompileContext context, final BuildTarget<T> target) throws ProjectBuildException{
1241     if (!context.getScope().isBuildForced(target)) {
1242       BuildOperations.cleanOutputsCorrespondingToChangedFiles(context, new DirtyFilesHolderBase<T, BuildTarget<T>>(context) {
1243         @Override
1244         public void processDirtyFiles(@NotNull FileProcessor<T, BuildTarget<T>> processor) throws IOException {
1245           context.getProjectDescriptor().fsState.processFilesToRecompile(context, target, processor);
1246         }
1247       });
1248     }
1249   }
1250
1251   private void buildTargetsChunk(CompileContext context, BuildTargetChunk chunk, BuildProgress buildProgress) throws ProjectBuildException {
1252     final BuildFSState fsState = myProjectDescriptor.fsState;
1253     boolean doneSomething;
1254     try {
1255       context.setCompilationStartStamp(chunk.getTargets(), System.currentTimeMillis());
1256
1257       sendBuildingTargetMessages(chunk.getTargets(), BuildingTargetProgressMessage.Event.STARTED);
1258       Utils.ERRORS_DETECTED_KEY.set(context, Boolean.FALSE);
1259
1260       for (BuildTarget<?> target : chunk.getTargets()) {
1261         BuildOperations.ensureFSStateInitialized(context, target, false);
1262       }
1263
1264       doneSomething = processDeletedPaths(context, chunk.getTargets());
1265
1266       fsState.beforeChunkBuildStart(context, chunk);
1267
1268       doneSomething |= runBuildersForChunk(context, chunk, buildProgress);
1269
1270       fsState.clearContextRoundData(context);
1271       fsState.clearContextChunk(context);
1272
1273       if (doneSomething) {
1274         BuildOperations.markTargetsUpToDate(context, chunk);
1275       }
1276
1277       //if (doneSomething && GENERATE_CLASSPATH_INDEX) {
1278       //  myAsyncTasks.add(SharedThreadPool.getInstance().executeOnPooledThread(new Runnable() {
1279       //    @Override
1280       //    public void run() {
1281       //      createClasspathIndex(chunk);
1282       //    }
1283       //  }));
1284       //}
1285     }
1286     catch (BuildDataCorruptedException | ProjectBuildException e) {
1287       throw e;
1288     }
1289     catch (Throwable e) {
1290       @NlsSafe StringBuilder message = new StringBuilder();
1291       message.append(chunk.getPresentableName()).append(": ").append(e.getClass().getName());
1292       final String exceptionMessage = e.getMessage();
1293       if (exceptionMessage != null) {
1294         message.append(": ").append(exceptionMessage);
1295       }
1296       throw new ProjectBuildException(message.toString(), e);
1297     }
1298     finally {
1299       buildProgress.onTargetChunkFinished(chunk, context);
1300       for (BuildRootDescriptor rd : context.getProjectDescriptor().getBuildRootIndex().clearTempRoots(context)) {
1301         context.getProjectDescriptor().fsState.clearRecompile(rd);
1302       }
1303       try {
1304         // restore deleted paths that were not processed by 'integrate'
1305         final Map<BuildTarget<?>, Collection<String>> map = Utils.REMOVED_SOURCES_KEY.get(context);
1306         if (map != null) {
1307           for (Map.Entry<BuildTarget<?>, Collection<String>> entry : map.entrySet()) {
1308             final BuildTarget<?> target = entry.getKey();
1309             final Collection<String> paths = entry.getValue();
1310             if (paths != null) {
1311               for (String path : paths) {
1312                 fsState.registerDeleted(context, target, new File(path), null);
1313               }
1314             }
1315           }
1316         }
1317       }
1318       catch (IOException e) {
1319         //noinspection ThrowFromFinallyBlock
1320         throw new ProjectBuildException(e);
1321       }
1322       finally {
1323         Utils.REMOVED_SOURCES_KEY.set(context, null);
1324         sendBuildingTargetMessages(chunk.getTargets(), BuildingTargetProgressMessage.Event.FINISHED);
1325       }
1326     }
1327   }
1328
1329   private void sendBuildingTargetMessages(@NotNull Set<? extends BuildTarget<?>> targets, @NotNull BuildingTargetProgressMessage.Event event) {
1330     myMessageDispatcher.processMessage(new BuildingTargetProgressMessage(targets, event));
1331   }
1332
1333   private boolean processDeletedPaths(CompileContext context, final Set<? extends BuildTarget<?>> targets) throws ProjectBuildException {
1334     boolean doneSomething = false;
1335     try {
1336       // cleanup outputs
1337       final Map<BuildTarget<?>, Collection<String>> targetToRemovedSources = new HashMap<>();
1338
1339       Set<File> dirsToDelete = FileCollectionFactory.createCanonicalFileSet();
1340       for (BuildTarget<?> target : targets) {
1341         final Collection<String> deletedPaths = myProjectDescriptor.fsState.getAndClearDeletedPaths(target);
1342         if (deletedPaths.isEmpty()) {
1343           continue;
1344         }
1345         targetToRemovedSources.put(target, deletedPaths);
1346         if (isTargetOutputCleared(context, target)) {
1347           continue;
1348         }
1349         final int buildTargetId = context.getProjectDescriptor().getTargetsState().getBuildTargetId(target);
1350         final boolean shouldPruneEmptyDirs = target instanceof ModuleBasedTarget;
1351         final SourceToOutputMapping sourceToOutputStorage = context.getProjectDescriptor().dataManager.getSourceToOutputMap(target);
1352         final ProjectBuilderLogger logger = context.getLoggingManager().getProjectBuilderLogger();
1353         // actually delete outputs associated with removed paths
1354         final Collection<String> pathsForIteration;
1355         if (myIsTestMode) {
1356           // ensure predictable order in test logs
1357           pathsForIteration = new ArrayList<>(deletedPaths);
1358           Collections.sort((List<String>)pathsForIteration);
1359         }
1360         else {
1361           pathsForIteration = deletedPaths;
1362         }
1363         for (String deletedSource : pathsForIteration) {
1364           // deleting outputs corresponding to non-existing source
1365           final Collection<String> outputs = sourceToOutputStorage.getOutputs(deletedSource);
1366           if (outputs != null && !outputs.isEmpty()) {
1367             List<String> deletedOutputPaths = new ArrayList<>();
1368             final OutputToTargetRegistry outputToSourceRegistry = context.getProjectDescriptor().dataManager.getOutputToTargetRegistry();
1369             for (String output : outputToSourceRegistry.getSafeToDeleteOutputs(outputs, buildTargetId)) {
1370               final boolean deleted = BuildOperations.deleteRecursively(output, deletedOutputPaths, shouldPruneEmptyDirs ? dirsToDelete : null);
1371               if (deleted) {
1372                 doneSomething = true;
1373               }
1374             }
1375             for (String outputPath : outputs) {
1376               outputToSourceRegistry.removeMapping(outputPath, buildTargetId);
1377             }
1378             if (!deletedOutputPaths.isEmpty()) {
1379               if (logger.isEnabled()) {
1380                 logger.logDeletedFiles(deletedOutputPaths);
1381               }
1382               context.processMessage(new FileDeletedEvent(deletedOutputPaths));
1383             }
1384           }
1385
1386           if (target instanceof ModuleBuildTarget) {
1387             // check if deleted source was associated with a form
1388             final OneToManyPathsMapping sourceToFormMap = context.getProjectDescriptor().dataManager.getSourceToFormMap();
1389             final Collection<String> boundForms = sourceToFormMap.getState(deletedSource);
1390             if (boundForms != null) {
1391               for (String formPath : boundForms) {
1392                 final File formFile = new File(formPath);
1393                 if (formFile.exists()) {
1394                   FSOperations.markDirty(context, CompilationRound.CURRENT, formFile);
1395                 }
1396               }
1397               sourceToFormMap.remove(deletedSource);
1398             }
1399           }
1400         }
1401       }
1402       if (!targetToRemovedSources.isEmpty()) {
1403         final Map<BuildTarget<?>, Collection<String>> existing = Utils.REMOVED_SOURCES_KEY.get(context);
1404         if (existing != null) {
1405           for (Map.Entry<BuildTarget<?>, Collection<String>> entry : existing.entrySet()) {
1406             final Collection<String> paths = targetToRemovedSources.get(entry.getKey());
1407             if (paths != null) {
1408               paths.addAll(entry.getValue());
1409             }
1410             else {
1411               targetToRemovedSources.put(entry.getKey(), entry.getValue());
1412             }
1413           }
1414         }
1415         Utils.REMOVED_SOURCES_KEY.set(context, targetToRemovedSources);
1416       }
1417
1418       FSOperations.pruneEmptyDirs(context, dirsToDelete);
1419     }
1420     catch (IOException e) {
1421       throw new ProjectBuildException(e);
1422     }
1423     return doneSomething;
1424   }
1425
1426   // return true if changed something, false otherwise
1427   private boolean runModuleLevelBuilders(final CompileContext context, final ModuleChunk chunk, BuildProgress buildProgress) throws ProjectBuildException, IOException {
1428     for (BuilderCategory category : BuilderCategory.values()) {
1429       for (ModuleLevelBuilder builder : myBuilderRegistry.getBuilders(category)) {
1430         builder.chunkBuildStarted(context, chunk);
1431       }
1432     }
1433
1434     completeRecompiledSourcesSet(context, chunk.getTargets());
1435
1436     boolean doneSomething = false;
1437     boolean rebuildFromScratchRequested = false;
1438     boolean nextPassRequired;
1439     ChunkBuildOutputConsumerImpl outputConsumer = new ChunkBuildOutputConsumerImpl(context);
1440     try {
1441       do {
1442         nextPassRequired = false;
1443         myProjectDescriptor.fsState.beforeNextRoundStart(context, chunk);
1444
1445         DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder =
1446           new DirtyFilesHolderBase<JavaSourceRootDescriptor, ModuleBuildTarget>(context) {
1447             @Override
1448             public void processDirtyFiles(@NotNull FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget> processor)
1449               throws IOException {
1450               FSOperations.processFilesToRecompile(context, chunk, processor);
1451             }
1452           };
1453         if (!JavaBuilderUtil.isForcedRecompilationAllJavaModules(context)) {
1454           final Map<ModuleBuildTarget, Set<File>> cleanedSources = BuildOperations.cleanOutputsCorrespondingToChangedFiles(context, dirtyFilesHolder);
1455           for (Map.Entry<ModuleBuildTarget, Set<File>> entry : cleanedSources.entrySet()) {
1456             final ModuleBuildTarget target = entry.getKey();
1457             final Set<File> files = entry.getValue();
1458             if (!files.isEmpty()) {
1459               final SourceToOutputMapping mapping = context.getProjectDescriptor().dataManager.getSourceToOutputMap(target);
1460               for (File srcFile : files) {
1461                 mapping.setOutputs(srcFile.getPath(), Collections.emptyList());
1462               }
1463             }
1464           }
1465         }
1466
1467         try {
1468           int buildersPassed = 0;
1469           BUILDER_CATEGORY_LOOP:
1470           for (BuilderCategory category : BuilderCategory.values()) {
1471             final List<ModuleLevelBuilder> builders = myBuilderRegistry.getBuilders(category);
1472             if (category == BuilderCategory.CLASS_POST_PROCESSOR) {
1473               // ensure changes from instrumenters are visible to class post-processors
1474               saveInstrumentedClasses(outputConsumer);
1475             }
1476             if (builders.isEmpty()) {
1477               continue;
1478             }
1479
1480             try {
1481               for (ModuleLevelBuilder builder : builders) {
1482                 processDeletedPaths(context, chunk.getTargets());
1483                 long start = System.nanoTime();
1484                 int processedSourcesBefore = outputConsumer.getNumberOfProcessedSources();
1485                 final ModuleLevelBuilder.ExitCode buildResult = builder.build(context, chunk, dirtyFilesHolder, outputConsumer);
1486                 storeBuilderStatistics(builder, System.nanoTime() - start,
1487                                        outputConsumer.getNumberOfProcessedSources() - processedSourcesBefore);
1488
1489                 doneSomething |= (buildResult != ModuleLevelBuilder.ExitCode.NOTHING_DONE);
1490
1491                 if (buildResult == ModuleLevelBuilder.ExitCode.ABORT) {
1492                   throw new StopBuildException(
1493                     JpsBuildBundle.message("build.message.builder.0.requested.build.stop", builder.getPresentableName()));
1494                 }
1495                 context.checkCanceled();
1496                 if (buildResult == ModuleLevelBuilder.ExitCode.ADDITIONAL_PASS_REQUIRED) {
1497                   nextPassRequired = true;
1498                 }
1499                 else if (buildResult == ModuleLevelBuilder.ExitCode.CHUNK_REBUILD_REQUIRED) {
1500                   if (!rebuildFromScratchRequested && !JavaBuilderUtil.isForcedRecompilationAllJavaModules(context)) {
1501                     notifyChunkRebuildRequested(context, chunk, builder);
1502                     // allow rebuild from scratch only once per chunk
1503                     rebuildFromScratchRequested = true;
1504                     try {
1505                       // forcibly mark all files in the chunk dirty
1506                       context.getProjectDescriptor().fsState.clearContextRoundData(context);
1507                       FSOperations.markDirty(context, CompilationRound.NEXT, chunk, null);
1508                       // reverting to the beginning
1509                       nextPassRequired = true;
1510                       outputConsumer.clear();
1511                       break BUILDER_CATEGORY_LOOP;
1512                     }
1513                     catch (Exception e) {
1514                       throw new ProjectBuildException(e);
1515                     }
1516                   }
1517                   else {
1518                     LOG.debug("Builder " + builder.getPresentableName() + " requested second chunk rebuild");
1519                   }
1520                 }
1521
1522                 buildersPassed++;
1523                 for (ModuleBuildTarget target : chunk.getTargets()) {
1524                   buildProgress.updateProgress(target, ((double)buildersPassed)/myTotalModuleLevelBuilderCount, context);
1525                 }
1526               }
1527             }
1528             finally {
1529               final boolean moreToCompile = JavaBuilderUtil.updateMappingsOnRoundCompletion(context, dirtyFilesHolder, chunk);
1530               if (moreToCompile) {
1531                 nextPassRequired = true;
1532               }
1533             }
1534           }
1535         }
1536         finally {
1537           JavaBuilderUtil.clearDataOnRoundCompletion(context);
1538         }
1539       }
1540       while (nextPassRequired);
1541     }
1542     finally {
1543       saveInstrumentedClasses(outputConsumer);
1544       outputConsumer.fireFileGeneratedEvents();
1545       outputConsumer.clear();
1546       for (BuilderCategory category : BuilderCategory.values()) {
1547         for (ModuleLevelBuilder builder : myBuilderRegistry.getBuilders(category)) {
1548           builder.chunkBuildFinished(context, chunk);
1549         }
1550       }
1551       if (Utils.errorsDetected(context)) {
1552         context.processMessage(new CompilerMessage("", BuildMessage.Kind.JPS_INFO, JpsBuildBundle.message("build.message.errors.occurred.while.compiling.module.0", chunk.getPresentableShortName())));
1553       }
1554     }
1555
1556     return doneSomething;
1557   }
1558
1559   private static void notifyChunkRebuildRequested(CompileContext context, ModuleChunk chunk, ModuleLevelBuilder builder) {
1560     String infoMessage = JpsBuildBundle.message("builder.0.requested.rebuild.of.module.chunk.1", builder.getPresentableName(), chunk.getName());
1561     LOG.info(infoMessage);
1562     BuildMessage.Kind kind = BuildMessage.Kind.JPS_INFO;
1563     final CompileScope scope = context.getScope();
1564     for (ModuleBuildTarget target : chunk.getTargets()) {
1565       if (!scope.isWholeTargetAffected(target)) {
1566         infoMessage += ".\n";
1567         infoMessage += JpsBuildBundle.message("build.message.consider.building.whole.project.or.rebuilding.the.module");
1568         kind = BuildMessage.Kind.INFO;
1569         break;
1570       }
1571     }
1572     context.processMessage(new CompilerMessage("", kind, infoMessage));
1573   }
1574
1575   private void storeBuilderStatistics(Builder builder, long elapsedTime, int processedFiles) {
1576     myElapsedTimeNanosByBuilder.computeIfAbsent(builder, b -> new AtomicLong()).addAndGet(elapsedTime);
1577     myNumberOfSourcesProcessedByBuilder.computeIfAbsent(builder, b -> new AtomicInteger()).addAndGet(processedFiles);
1578   }
1579
1580   private static void saveInstrumentedClasses(ChunkBuildOutputConsumerImpl outputConsumer) throws IOException {
1581     for (CompiledClass compiledClass : outputConsumer.getCompiledClasses().values()) {
1582       if (compiledClass.isDirty()) {
1583         compiledClass.save();
1584       }
1585     }
1586   }
1587
1588   private static CompileContext createContextWrapper(final CompileContext delegate) {
1589     final UserDataHolderBase localDataHolder = new UserDataHolderBase();
1590     final Set<Object> deletedKeysSet = ContainerUtil.newConcurrentSet();
1591     final Class<UserDataHolder> dataHolderInterface = UserDataHolder.class;
1592     final Class<MessageHandler> messageHandlerInterface = MessageHandler.class;
1593     return (CompileContext)Proxy.newProxyInstance(delegate.getClass().getClassLoader(), new Class[]{CompileContext.class}, new InvocationHandler() {
1594       @Override
1595       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
1596         if (args != null) {
1597           final Class<?> declaringClass = method.getDeclaringClass();
1598           if (dataHolderInterface.equals(declaringClass)) {
1599             final Object firstArgument = args[0];
1600             if (!(firstArgument instanceof GlobalContextKey)) {
1601               final boolean isWriteOperation = args.length == 2 /*&& void.class.equals(method.getReturnType())*/;
1602               if (isWriteOperation) {
1603                 if (args[1] == null) {
1604                   deletedKeysSet.add(firstArgument);
1605                 }
1606                 else {
1607                   deletedKeysSet.remove(firstArgument);
1608                 }
1609               }
1610               else {
1611                 if (deletedKeysSet.contains(firstArgument)) {
1612                   return null;
1613                 }
1614               }
1615               final Object result = method.invoke(localDataHolder, args);
1616               if (isWriteOperation || result != null) {
1617                 return result;
1618               }
1619             }
1620           }
1621           else if (messageHandlerInterface.equals(declaringClass)) {
1622             final BuildMessage msg = (BuildMessage)args[0];
1623             if (msg.getKind() == BuildMessage.Kind.ERROR) {
1624               Utils.ERRORS_DETECTED_KEY.set(localDataHolder, Boolean.TRUE);
1625             }
1626           }
1627           return MethodHandles.lookup().unreflect(method).bindTo(delegate).asSpreader(Object[].class, args.length).invoke(args);
1628         }
1629
1630         return MethodHandles.lookup().unreflect(method).invoke(delegate);
1631       }
1632     });
1633   }
1634 }