7a89f170c5a2d1094ebe46cb037f1dc56ff879f1
[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.getMappings().clearMemoryCaches();
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 void processMessage(BuildMessage msg) {
214     if (msg.getKind() == BuildMessage.Kind.ERROR) {
215       myErrorsFound = true;
216     }
217     if (msg instanceof ProgressMessage) {
218       ((ProgressMessage)msg).setDone(myDone);
219     }
220     myDelegateMessageHandler.processMessage(msg);
221   }
222
223   public void processFilesToRecompile(ModuleChunk chunk, FileProcessor processor) throws Exception {
224     for (Module module : chunk.getModules()) {
225       myFsState.processFilesToRecompile(this, module, processor);
226     }
227   }
228
229   final void ensureFSStateInitialized(ModuleChunk chunk) throws Exception {
230     for (Module module : chunk.getModules()) {
231       if (isProjectRebuild()) {
232         markDirtyFiles(module, myTsStorage, true, isCompilingTests() ? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION, null);
233       }
234       else {
235         if (isMake()) {
236           if (myFsState.markInitialScanPerformed(module, isCompilingTests())) {
237             initModuleFSState(module);
238           }
239         }
240         else {
241           // forced compilation mode
242           if (getScope().isRecompilationForced(module)) {
243             markDirtyFiles(module, myTsStorage, true, isCompilingTests() ? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION, null);
244           }
245         }
246       }
247     }
248   }
249
250   private void initModuleFSState(Module module) throws Exception {
251     final HashSet<File> currentFiles = new HashSet<File>();
252     markDirtyFiles(module, myTsStorage, false, isCompilingTests() ? DirtyMarkScope.TESTS : DirtyMarkScope.PRODUCTION, currentFiles);
253
254     final String moduleName = module.getName().toLowerCase(Locale.US);
255     final SourceToOutputMapping sourceToOutputMap = getDataManager().getSourceToOutputMap(moduleName, isCompilingTests());
256     for (final Iterator<String> it = sourceToOutputMap.getKeysIterator(); it.hasNext();) {
257       final String path = it.next();
258       // can check if the file exists
259       final File file = new File(path);
260       if (!currentFiles.contains(file)) {
261         myFsState.registerDeleted(module, file, isCompilingTests(), myTsStorage);
262       }
263     }
264   }
265
266   public boolean hasRemovedSources() {
267     final Set<String> removed = Paths.CHUNK_REMOVED_SOURCES_KEY.get(this);
268     return removed != null && !removed.isEmpty();
269   }
270
271   @Nullable
272   public RootDescriptor getModuleAndRoot(File file) {
273     return myRootsIndex.getModuleAndRoot(file);
274   }
275
276   @NotNull
277   public List<RootDescriptor> getModuleRoots(Module module) {
278     return myRootsIndex.getModuleRoots(module);
279   }
280
281   public void setDone(float done) {
282     myDone = done;
283     //processMessage(new ProgressMessage("", done));
284   }
285
286   public static enum DirtyMarkScope{
287     PRODUCTION, TESTS, BOTH
288   }
289
290   private void markDirtyFiles(Module module, final TimestampStorage tsStorage, final boolean forceMarkDirty, @NotNull final DirtyMarkScope scope, @Nullable final Set<File> currentFiles) throws Exception {
291     final Set<File> excludes = new HashSet<File>();
292     for (String excludePath : module.getExcludes()) {
293       excludes.add(new File(excludePath));
294     }
295     for (RootDescriptor rd : getModuleRoots(module)) {
296       if (scope == DirtyMarkScope.TESTS) {
297         if (!rd.isTestRoot) {
298           continue;
299         }
300       }
301       else if (scope == DirtyMarkScope.PRODUCTION) {
302         if (rd.isTestRoot) {
303           continue;
304         }
305       }
306       if (!rd.root.exists()) {
307         continue;
308       }
309       if (forceMarkDirty) {
310         myFsState.clearRecompile(rd);
311         myFsState.clearDeletedPaths(module, isCompilingTests());
312       }
313       traverseRecursively(rd, rd.root, excludes, tsStorage, forceMarkDirty, currentFiles);
314     }
315   }
316
317   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 {
318     if (file.isDirectory()) {
319       if (!PathUtil.isUnder(excludes, file)) {
320         final File[] children = file.listFiles();
321         if (children != null) {
322           for (File child : children) {
323             traverseRecursively(rd, child, excludes, tsStorage, forceDirty, currentFiles);
324           }
325         }
326       }
327     }
328     else {
329       boolean markDirty = forceDirty;
330       if (!markDirty) {
331         markDirty = tsStorage.getStamp(file) != file.lastModified();
332       }
333       if (markDirty) {
334         // if it is full project rebuild, all storages are already completely cleared;
335         // so passing null because there is no need to access the storage to clear non-existing data
336         final TimestampStorage _tsStorage = isProjectRebuild() ? null : tsStorage;
337         myFsState.markDirty(file, rd, _tsStorage);
338       }
339       if (currentFiles != null) {
340         currentFiles.add(file);
341       }
342     }
343   }
344 }