reduce the number of force() calls on storages
[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     myDataManager.flush(true);
174
175     try {
176       if (!myErrorsFound && !myCancelStatus.isCanceled()) {
177         final boolean compilingTests = isCompilingTests();
178         final DirtyMarkScope dirtyScope = compilingTests ? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION;
179         boolean marked = false;
180         for (Module module : chunk.getModules()) {
181           if (isMake()) {
182             // ensure non-incremental flag cleared
183             myNonIncrementalModules.remove(new Pair<Module, DirtyMarkScope>(module, dirtyScope));
184           }
185           if (isProjectRebuild()) {
186             myFsState.markInitialScanPerformed(module, compilingTests);
187           }
188           final List<RootDescriptor> roots = myRootsIndex.getModuleRoots(module);
189           for (RootDescriptor descriptor : roots) {
190             if (compilingTests? descriptor.isTestRoot : !descriptor.isTestRoot) {
191               marked |= myFsState.markAllUpToDate(getScope(), descriptor, myTsStorage, myCompilationStartStamp);
192             }
193           }
194         }
195         if (marked) {
196           processMessage(UptoDateFilesSavedEvent.INSTANCE);
197         }
198       }
199     }
200     finally {
201       myFsState.clearContextRoundData();
202     }
203   }
204
205   public CompileScope getScope() {
206     return myScope;
207   }
208
209   public BuildDataManager getDataManager() {
210     return myDataManager;
211   }
212
213   public TimestampStorage getTimestampStorage() {
214     return myTsStorage;
215   }
216
217   public void processMessage(BuildMessage msg) {
218     if (msg.getKind() == BuildMessage.Kind.ERROR) {
219       myErrorsFound = true;
220     }
221     if (msg instanceof ProgressMessage) {
222       ((ProgressMessage)msg).setDone(myDone);
223     }
224     myDelegateMessageHandler.processMessage(msg);
225   }
226
227   public void processFilesToRecompile(ModuleChunk chunk, FileProcessor processor) throws Exception {
228     for (Module module : chunk.getModules()) {
229       myFsState.processFilesToRecompile(this, module, processor);
230     }
231   }
232
233   final void ensureFSStateInitialized(ModuleChunk chunk) throws Exception {
234     for (Module module : chunk.getModules()) {
235       if (isProjectRebuild()) {
236         markDirtyFiles(module, myTsStorage, true, isCompilingTests() ? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION, null);
237       }
238       else {
239         if (isMake()) {
240           if (myFsState.markInitialScanPerformed(module, isCompilingTests())) {
241             initModuleFSState(module);
242           }
243         }
244         else {
245           // forced compilation mode
246           if (getScope().isRecompilationForced(module)) {
247             markDirtyFiles(module, myTsStorage, true, isCompilingTests() ? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION, null);
248           }
249         }
250       }
251     }
252   }
253
254   private void initModuleFSState(Module module) throws Exception {
255     final HashSet<File> currentFiles = new HashSet<File>();
256     markDirtyFiles(module, myTsStorage, false, isCompilingTests() ? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION, currentFiles);
257
258     final String moduleName = module.getName().toLowerCase(Locale.US);
259     final SourceToOutputMapping sourceToOutputMap = getDataManager().getSourceToOutputMap(moduleName, isCompilingTests());
260     for (final Iterator<String> it = sourceToOutputMap.getKeysIterator(); it.hasNext();) {
261       final String path = it.next();
262       // can check if the file exists
263       final File file = new File(path);
264       if (!currentFiles.contains(file)) {
265         myFsState.registerDeleted(module, file, isCompilingTests(), myTsStorage);
266       }
267     }
268   }
269
270   public boolean hasRemovedSources() {
271     final Set<String> removed = Paths.CHUNK_REMOVED_SOURCES_KEY.get(this);
272     return removed != null && !removed.isEmpty();
273   }
274
275   @Nullable
276   public RootDescriptor getModuleAndRoot(File file) {
277     return myRootsIndex.getModuleAndRoot(file);
278   }
279
280   @NotNull
281   public List<RootDescriptor> getModuleRoots(Module module) {
282     return myRootsIndex.getModuleRoots(module);
283   }
284
285   public void setDone(float done) {
286     myDone = done;
287     //processMessage(new ProgressMessage("", done));
288   }
289
290   public static enum DirtyMarkScope{
291     PRODUCTION, TESTS, BOTH
292   }
293
294   private void markDirtyFiles(Module module, final TimestampStorage tsStorage, final boolean forceMarkDirty, @NotNull final DirtyMarkScope scope, @Nullable final Set<File> currentFiles) throws Exception {
295     final Set<File> excludes = new HashSet<File>();
296     for (String excludePath : module.getExcludes()) {
297       excludes.add(new File(excludePath));
298     }
299     for (RootDescriptor rd : getModuleRoots(module)) {
300       if (scope == DirtyMarkScope.TESTS) {
301         if (!rd.isTestRoot) {
302           continue;
303         }
304       }
305       else if (scope == DirtyMarkScope.PRODUCTION) {
306         if (rd.isTestRoot) {
307           continue;
308         }
309       }
310       if (!rd.root.exists()) {
311         continue;
312       }
313       if (forceMarkDirty) {
314         myFsState.clearRecompile(rd);
315         myFsState.clearDeletedPaths(module, isCompilingTests());
316       }
317       traverseRecursively(rd, rd.root, excludes, tsStorage, forceMarkDirty, currentFiles);
318     }
319   }
320
321   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 {
322     if (file.isDirectory()) {
323       if (!PathUtil.isUnder(excludes, file)) {
324         final File[] children = file.listFiles();
325         if (children != null) {
326           for (File child : children) {
327             traverseRecursively(rd, child, excludes, tsStorage, forceDirty, currentFiles);
328           }
329         }
330       }
331     }
332     else {
333       boolean markDirty = forceDirty;
334       if (!markDirty) {
335         markDirty = tsStorage.getStamp(file) != file.lastModified();
336       }
337       if (markDirty) {
338         // if it is full project rebuild, all storages are already completely cleared;
339         // so passing null because there is no need to access the storage to clear non-existing data
340         final TimestampStorage _tsStorage = isProjectRebuild() ? null : tsStorage;
341         myFsState.markDirty(file, rd, _tsStorage);
342       }
343       if (currentFiles != null) {
344         currentFiles.add(file);
345       }
346     }
347   }
348 }