flush persistent maps after each module chunk
[idea/community.git] / jps / jps-builders / src / org / jetbrains / jps / incremental / CompileContext.java
1 package org.jetbrains.jps.incremental;
2
3 import com.intellij.openapi.util.Pair;
4 import com.intellij.openapi.util.UserDataHolderBase;
5 import org.jetbrains.annotations.NotNull;
6 import org.jetbrains.annotations.Nullable;
7 import org.jetbrains.ether.dependencyView.Mappings;
8 import org.jetbrains.jps.*;
9 import org.jetbrains.jps.api.CanceledStatus;
10 import org.jetbrains.jps.incremental.messages.BuildMessage;
11 import org.jetbrains.jps.incremental.messages.ProgressMessage;
12 import org.jetbrains.jps.incremental.messages.UptoDateFilesSavedEvent;
13 import org.jetbrains.jps.incremental.storage.BuildDataManager;
14 import org.jetbrains.jps.incremental.storage.SourceToOutputMapping;
15 import org.jetbrains.jps.incremental.storage.TimestampStorage;
16
17 import java.io.File;
18 import java.util.*;
19
20 /**
21  * @author Eugene Zhuravlev
22  *         Date: 9/17/11
23  */
24 public class CompileContext extends UserDataHolderBase implements MessageHandler{
25   private final CompileScope myScope;
26   private final boolean myIsMake;
27   private final boolean myIsProjectRebuild;
28   private final ProjectChunks myProductionChunks;
29   private final ProjectChunks myTestChunks;
30   private final FSState myFsState;
31   private final MessageHandler myDelegateMessageHandler;
32   private volatile boolean myCompilingTests = false;
33   private final BuildDataManager myDataManager;
34   private final ModuleRootsIndex myRootsIndex;
35
36   private final Set<Pair<Module, DirtyMarkScope>> myNonIncrementalModules = new HashSet<Pair<Module, DirtyMarkScope>>();
37
38   private final ProjectPaths myProjectPaths;
39   private volatile boolean myErrorsFound = false;
40   private final long myCompilationStartStamp;
41   private final TimestampStorage myTsStorage;
42   private final CanceledStatus myCancelStatus;
43   private float myDone = -1.0f;
44
45   public CompileContext(CompileScope scope,
46                         boolean isMake,
47                         boolean isProjectRebuild,
48                         ProjectChunks productionChunks,
49                         ProjectChunks testChunks,
50                         FSState fsState, final BuildDataManager dataManager, TimestampStorage tsStorage, MessageHandler delegateMessageHandler, final ModuleRootsIndex rootsIndex, CanceledStatus cancelStatus) throws ProjectBuildException {
51     myTsStorage = tsStorage;
52     myCancelStatus = cancelStatus;
53     myCompilationStartStamp = System.currentTimeMillis();
54     myScope = scope;
55     myIsProjectRebuild = isProjectRebuild;
56     myIsMake = isProjectRebuild? false : isMake;
57     myProductionChunks = productionChunks;
58     myTestChunks = testChunks;
59     myFsState = fsState;
60     myDelegateMessageHandler = delegateMessageHandler;
61     myDataManager = dataManager;
62     final Project project = scope.getProject();
63     myProjectPaths = new ProjectPaths(project);
64     myRootsIndex = rootsIndex;
65   }
66
67   public Project getProject() {
68     return myScope.getProject();
69   }
70
71   public ProjectPaths getProjectPaths() {
72     return myProjectPaths;
73   }
74
75   public boolean isMake() {
76     return myIsMake;
77   }
78
79   public boolean isProjectRebuild() {
80     return myIsProjectRebuild;
81   }
82
83   public void markDirty(final File file) throws Exception {
84     final RootDescriptor descriptor = getModuleAndRoot(file);
85     if (descriptor != null) {
86       myFsState.markDirty(file, descriptor, myTsStorage);
87     }
88   }
89
90   public void markDirty(final ModuleChunk chunk) throws Exception {
91     final Set<Module> modules = chunk.getModules();
92     for (Module module : modules) {
93       markDirtyFiles(module, myTsStorage, true, isCompilingTests()? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION, null);
94     }
95   }
96
97   public void markDirtyRecursively(ModuleChunk chunk) throws Exception {
98     final Set<Module> modules = chunk.getModules();
99     final Set<Module> dirtyModules = new HashSet<Module>(modules);
100
101     // now mark all modules that depend on dirty modules
102     final ClasspathKind classpathKind = ClasspathKind.compile(isCompilingTests());
103     final ProjectChunks chunks = isCompilingTests()? myTestChunks : myProductionChunks;
104     boolean found = false;
105     for (ModuleChunk moduleChunk : chunks.getChunkList()) {
106       if (!found) {
107         if (moduleChunk.equals(chunk)) {
108           found = true;
109         }
110       }
111       else {
112         MODULES_LOOP: for (final Module module : moduleChunk.getModules()) {
113           for (ClasspathItem dependency : module.getClasspath(classpathKind)) {
114             if (dependency instanceof Module && dirtyModules.contains((Module)dependency)) {
115               dirtyModules.addAll(moduleChunk.getModules());
116               break MODULES_LOOP;
117             }
118           }
119         }
120       }
121     }
122
123     for (Module module : dirtyModules) {
124       markDirtyFiles(module, myTsStorage, true, isCompilingTests()? DirtyMarkScope.TESTS : DirtyMarkScope.BOTH, null);
125       if (isMake()) {
126         if (!isCompilingTests()) {
127           myNonIncrementalModules.add(new Pair<Module, DirtyMarkScope>(module, DirtyMarkScope.PRODUCTION));
128         }
129         myNonIncrementalModules.add(new Pair<Module, DirtyMarkScope>(module, DirtyMarkScope.TESTS));
130       }
131     }
132   }
133
134   boolean shouldDifferentiate(ModuleChunk chunk, boolean forTests) {
135     if (!isMake()) {
136       // the check makes sense only in make mode
137       return true;
138     }
139     final DirtyMarkScope dirtyScope = forTests ? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION;
140     for (Module module : chunk.getModules()) {
141       if (myNonIncrementalModules.contains(new Pair<Module, DirtyMarkScope>(module, dirtyScope))) {
142         return false;
143       }
144     }
145     return true;
146   }
147
148   public Mappings createDelta() {
149     return myDataManager.getMappings().createDelta();
150   }
151
152   public boolean isCompilingTests() {
153     return myCompilingTests;
154   }
155
156   public CanceledStatus getCancelStatus() {
157     return myCancelStatus;
158   }
159
160   void setCompilingTests(boolean compilingTests) {
161     myCompilingTests = compilingTests;
162   }
163
164   public void onChunkBuildStart(ModuleChunk chunk) {
165     myFsState.setContextChunk(chunk);
166   }
167
168   void beforeNextCompileRound(@NotNull ModuleChunk chunk) {
169     myFsState.beforeNextRoundStart();
170   }
171
172   void onChunkBuildComplete(@NotNull ModuleChunk chunk) throws Exception {
173     final Mappings mappings = myDataManager.getMappings();
174     mappings.clearMemoryCaches();
175     myDataManager.flush();
176
177     try {
178       if (!myErrorsFound && !myCancelStatus.isCanceled()) {
179         final boolean compilingTests = isCompilingTests();
180         final DirtyMarkScope dirtyScope = compilingTests ? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION;
181         boolean marked = false;
182         for (Module module : chunk.getModules()) {
183           if (isMake()) {
184             // ensure non-incremental flag cleared
185             myNonIncrementalModules.remove(new Pair<Module, DirtyMarkScope>(module, dirtyScope));
186           }
187           if (isProjectRebuild()) {
188             myFsState.markInitialScanPerformed(module, compilingTests);
189           }
190           final List<RootDescriptor> roots = myRootsIndex.getModuleRoots(module);
191           for (RootDescriptor descriptor : roots) {
192             if (compilingTests? descriptor.isTestRoot : !descriptor.isTestRoot) {
193               marked |= myFsState.markAllUpToDate(getScope(), descriptor, myTsStorage, myCompilationStartStamp);
194             }
195           }
196         }
197         if (marked) {
198           processMessage(UptoDateFilesSavedEvent.INSTANCE);
199         }
200       }
201     }
202     finally {
203       myFsState.clearContextRoundData();
204     }
205   }
206
207   public CompileScope getScope() {
208     return myScope;
209   }
210
211   public BuildDataManager getDataManager() {
212     return myDataManager;
213   }
214
215   public void processMessage(BuildMessage msg) {
216     if (msg.getKind() == BuildMessage.Kind.ERROR) {
217       myErrorsFound = true;
218     }
219     if (msg instanceof ProgressMessage) {
220       ((ProgressMessage)msg).setDone(myDone);
221     }
222     myDelegateMessageHandler.processMessage(msg);
223   }
224
225   public void processFilesToRecompile(ModuleChunk chunk, FileProcessor processor) throws Exception {
226     for (Module module : chunk.getModules()) {
227       myFsState.processFilesToRecompile(this, module, processor);
228     }
229   }
230
231   final void ensureFSStateInitialized(ModuleChunk chunk) throws Exception {
232     for (Module module : chunk.getModules()) {
233       if (isProjectRebuild()) {
234         markDirtyFiles(module, myTsStorage, true, isCompilingTests() ? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION, null);
235       }
236       else {
237         if (isMake()) {
238           if (myFsState.markInitialScanPerformed(module, isCompilingTests())) {
239             initModuleFSState(module);
240           }
241         }
242         else {
243           // forced compilation mode
244           if (getScope().isRecompilationForced(module)) {
245             markDirtyFiles(module, myTsStorage, true, isCompilingTests() ? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION, null);
246           }
247         }
248       }
249     }
250   }
251
252   private void initModuleFSState(Module module) throws Exception {
253     final HashSet<File> currentFiles = new HashSet<File>();
254     markDirtyFiles(module, myTsStorage, false, isCompilingTests() ? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION, currentFiles);
255
256     final String moduleName = module.getName().toLowerCase(Locale.US);
257     final SourceToOutputMapping sourceToOutputMap = getDataManager().getSourceToOutputMap(moduleName, isCompilingTests());
258     for (final Iterator<String> it = sourceToOutputMap.getKeysIterator(); it.hasNext();) {
259       final String path = it.next();
260       // can check if the file exists
261       final File file = new File(path);
262       if (!currentFiles.contains(file)) {
263         myFsState.registerDeleted(module, file, isCompilingTests(), myTsStorage);
264       }
265     }
266   }
267
268   public boolean hasRemovedSources() {
269     final Set<String> removed = Paths.CHUNK_REMOVED_SOURCES_KEY.get(this);
270     return removed != null && !removed.isEmpty();
271   }
272
273   @Nullable
274   public RootDescriptor getModuleAndRoot(File file) {
275     return myRootsIndex.getModuleAndRoot(file);
276   }
277
278   @NotNull
279   public List<RootDescriptor> getModuleRoots(Module module) {
280     return myRootsIndex.getModuleRoots(module);
281   }
282
283   public void setDone(float done) {
284     myDone = done;
285     //processMessage(new ProgressMessage("", done));
286   }
287
288   public static enum DirtyMarkScope{
289     PRODUCTION, TESTS, BOTH
290   }
291
292   private void markDirtyFiles(Module module, final TimestampStorage tsStorage, final boolean forceMarkDirty, @NotNull final DirtyMarkScope scope, @Nullable final Set<File> currentFiles) throws Exception {
293     final Set<File> excludes = new HashSet<File>();
294     for (String excludePath : module.getExcludes()) {
295       excludes.add(new File(excludePath));
296     }
297     for (RootDescriptor rd : getModuleRoots(module)) {
298       if (scope == DirtyMarkScope.TESTS) {
299         if (!rd.isTestRoot) {
300           continue;
301         }
302       }
303       else if (scope == DirtyMarkScope.PRODUCTION) {
304         if (rd.isTestRoot) {
305           continue;
306         }
307       }
308       if (!rd.root.exists()) {
309         continue;
310       }
311       if (forceMarkDirty) {
312         myFsState.clearRecompile(rd);
313         myFsState.clearDeletedPaths(module, isCompilingTests());
314       }
315       traverseRecursively(rd, rd.root, excludes, tsStorage, forceMarkDirty, currentFiles);
316     }
317   }
318
319   private void traverseRecursively(final RootDescriptor rd, final File file, Set<File> excludes, @NotNull final TimestampStorage tsStorage, final boolean forceDirty, @Nullable Set<File> currentFiles) throws Exception {
320     if (file.isDirectory()) {
321       if (!PathUtil.isUnder(excludes, file)) {
322         final File[] children = file.listFiles();
323         if (children != null) {
324           for (File child : children) {
325             traverseRecursively(rd, child, excludes, tsStorage, forceDirty, currentFiles);
326           }
327         }
328       }
329     }
330     else {
331       boolean markDirty = forceDirty;
332       if (!markDirty) {
333         markDirty = tsStorage.getStamp(file) != file.lastModified();
334       }
335       if (markDirty) {
336         // if it is full project rebuild, all storages are already completely cleared;
337         // so passing null because there is no need to access the storage to clear non-existing data
338         final TimestampStorage _tsStorage = isProjectRebuild() ? null : tsStorage;
339         myFsState.markDirty(file, rd, _tsStorage);
340       }
341       if (currentFiles != null) {
342         currentFiles.add(file);
343       }
344     }
345   }
346 }