Fixed misunderstanding between dependency analyzer and builders; tests ok (compiler...
[idea/community.git] / jps / jps-builders / src / org / jetbrains / jps / incremental / ModuleLevelBuilder.java
1 package org.jetbrains.jps.incremental;
2
3 import com.intellij.openapi.diagnostic.Logger;
4 import com.intellij.openapi.util.Key;
5 import org.jetbrains.ether.dependencyView.Mappings;
6 import org.jetbrains.jps.Module;
7 import org.jetbrains.jps.ModuleChunk;
8
9 import java.io.File;
10 import java.io.IOException;
11 import java.util.Collection;
12 import java.util.Collections;
13 import java.util.HashSet;
14 import java.util.Set;
15
16 /**
17  * @author Eugene Zhuravlev
18  *         Date: 9/17/11
19  */
20 public abstract class ModuleLevelBuilder extends Builder {
21   private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.Builder");
22
23   private static final Key<Set<File>> ALL_AFFECTED_FILES_KEY = Key.create("_all_affected_files_");
24   private static final Key<Set<File>> ALL_COMPILED_FILES_KEY = Key.create("_all_compiled_files_");
25
26   public static enum ExitCode {
27     OK, ABORT, ADDITIONAL_PASS_REQUIRED
28   }
29
30   public abstract ExitCode build(CompileContext context, ModuleChunk chunk) throws ProjectBuildException;
31
32   public void cleanupResources(CompileContext context, ModuleChunk chunk) {
33     ALL_AFFECTED_FILES_KEY.set(context, null);
34     ALL_COMPILED_FILES_KEY.set(context, null);
35   }
36
37   /**
38    * @param context
39    * @param delta
40    * @param chunk
41    * @param filesToCompile files compiled in this round
42    * @param successfullyCompiled
43    * @return true if additional compilation pass is required, false otherwise
44    * @throws Exception
45    */
46   public final boolean updateMappings(CompileContext context, final Mappings delta, ModuleChunk chunk, Collection<File> filesToCompile, Collection<File> successfullyCompiled) throws Exception {
47     try {
48       boolean additionalPassRequired = false;
49
50       final Set<String> removedPaths = getRemovedPaths(context);
51
52       final Mappings globalMappings = context.getDataManager().getMappings();
53
54       //noinspection SynchronizationOnLocalVariableOrMethodParameter
55       synchronized (globalMappings) {
56         if (!context.isProjectRebuild() && context.shouldDifferentiate(chunk, context.isCompilingTests())) {
57           final Set<File> allCompiledFiles = getAllCompiledFilesContainer(context);
58           final Set<File> allAffectedFiles = getAllAffectedFilesContainer(context);
59
60           // mark as affected all files that were dirty before compilation
61           allAffectedFiles.addAll(filesToCompile);
62           // accumulate all successfully compiled in this round
63           allCompiledFiles.addAll(successfullyCompiled);
64           // unmark as affected all successfully compiled
65           allAffectedFiles.removeAll(successfullyCompiled);
66
67           final HashSet<File> affectedBeforeDif = new HashSet<File>(allAffectedFiles);
68
69           final boolean incremental = globalMappings.differentiate(
70             delta, removedPaths, filesToCompile, allCompiledFiles, allAffectedFiles
71           );
72
73           if (LOG.isDebugEnabled()) {
74             LOG.debug("Differentiate Results:");
75             LOG.debug("   Compiled Files:");
76             for (final File c : allCompiledFiles) {
77               LOG.debug("      " + c.getAbsolutePath());
78             }
79             LOG.debug("   Affected Files:");
80             for (final File c : allAffectedFiles) {
81               LOG.debug("      " + c.getAbsolutePath());
82             }
83             LOG.debug("End Of Differentiate Results.");
84           }
85
86           if (incremental) {
87             final Set<File> newlyAffectedFiles = new HashSet<File>(allAffectedFiles);
88             newlyAffectedFiles.removeAll(affectedBeforeDif);
89             newlyAffectedFiles.removeAll(allCompiledFiles); // the diff operation may have affected the class already compiled in thic compilation round
90
91             if (!newlyAffectedFiles.isEmpty()) {
92               for (File file : newlyAffectedFiles) {
93                 context.markDirty(file);
94               }
95               additionalPassRequired = context.isMake() && chunkContainsAffectedFiles(context, chunk, newlyAffectedFiles);
96             }
97           }
98           else {
99             additionalPassRequired = context.isMake();
100             context.markDirtyRecursively(chunk);
101           }
102         }
103
104         globalMappings.integrate(delta, successfullyCompiled, removedPaths);
105       }
106
107       return additionalPassRequired;
108     }
109     catch(RuntimeException e) {
110       final Throwable cause = e.getCause();
111       if (cause instanceof IOException) {
112         throw ((IOException)cause);
113       }
114       throw e;
115     }
116   }
117
118   private static boolean chunkContainsAffectedFiles(CompileContext context, ModuleChunk chunk, final Set<File> affected) throws Exception {
119     final Set<Module> chunkModules = new HashSet<Module>(chunk.getModules());
120     if (!chunkModules.isEmpty()) {
121       for (File file : affected) {
122         final RootDescriptor moduleAndRoot = context.getModuleAndRoot(file);
123         if (moduleAndRoot != null && chunkModules.contains(moduleAndRoot.module)) {
124           return true;
125         }
126       }
127     }
128     return false;
129   }
130
131   private static Set<File> getAllAffectedFilesContainer(CompileContext context) {
132     Set<File> allAffectedFiles = ALL_AFFECTED_FILES_KEY.get(context);
133     if (allAffectedFiles == null) {
134       allAffectedFiles = new HashSet<File>();
135       ALL_AFFECTED_FILES_KEY.set(context, allAffectedFiles);
136     }
137     return allAffectedFiles;
138   }
139
140   private static Set<File> getAllCompiledFilesContainer(CompileContext context) {
141     Set<File> allCompiledFiles = ALL_COMPILED_FILES_KEY.get(context);
142     if (allCompiledFiles == null) {
143       allCompiledFiles = new HashSet<File>();
144       ALL_COMPILED_FILES_KEY.set(context, allCompiledFiles);
145     }
146     return allCompiledFiles;
147   }
148
149   private static Set<String> getRemovedPaths(CompileContext context) {
150     final Set<String> removed = Paths.CHUNK_REMOVED_SOURCES_KEY.get(context);
151     return removed != null? removed : Collections.<String>emptySet();
152   }
153
154 }