invoke translating compilers per-module chunk
[fedora-idea.git] / java / compiler / impl / src / com / intellij / compiler / impl / CompileDriver.java
blob38a99a7acbd22a5de3e2295af5de37c10ffb8be1
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 /**
18 * @author: Eugene Zhuravlev
19 * Date: Jan 17, 2003
20 * Time: 1:42:26 PM
22 package com.intellij.compiler.impl;
24 import com.intellij.CommonBundle;
25 import com.intellij.analysis.AnalysisScope;
26 import com.intellij.compiler.*;
27 import com.intellij.compiler.make.CacheCorruptedException;
28 import com.intellij.compiler.make.CacheUtils;
29 import com.intellij.compiler.make.DependencyCache;
30 import com.intellij.compiler.progress.CompilerTask;
31 import com.intellij.diagnostic.IdeErrorsDialog;
32 import com.intellij.diagnostic.PluginException;
33 import com.intellij.openapi.application.ApplicationManager;
34 import com.intellij.openapi.application.ModalityState;
35 import com.intellij.openapi.compiler.*;
36 import com.intellij.openapi.compiler.Compiler;
37 import com.intellij.openapi.compiler.ex.CompileContextEx;
38 import com.intellij.openapi.compiler.ex.CompilerPathsEx;
39 import com.intellij.openapi.diagnostic.Logger;
40 import com.intellij.openapi.extensions.Extensions;
41 import com.intellij.openapi.extensions.PluginId;
42 import com.intellij.openapi.fileEditor.FileDocumentManager;
43 import com.intellij.openapi.fileTypes.FileType;
44 import com.intellij.openapi.fileTypes.FileTypeManager;
45 import com.intellij.openapi.fileTypes.StdFileTypes;
46 import com.intellij.openapi.module.LanguageLevelUtil;
47 import com.intellij.openapi.module.Module;
48 import com.intellij.openapi.module.ModuleManager;
49 import com.intellij.openapi.progress.ProcessCanceledException;
50 import com.intellij.openapi.progress.ProgressIndicator;
51 import com.intellij.openapi.progress.ProgressManager;
52 import com.intellij.openapi.project.DumbService;
53 import com.intellij.openapi.project.Project;
54 import com.intellij.openapi.project.ProjectBundle;
55 import com.intellij.openapi.projectRoots.Sdk;
56 import com.intellij.openapi.roots.ContentEntry;
57 import com.intellij.openapi.roots.ModuleRootManager;
58 import com.intellij.openapi.roots.ProjectRootManager;
59 import com.intellij.openapi.roots.SourceFolder;
60 import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
61 import com.intellij.openapi.roots.ui.configuration.CommonContentEntriesEditor;
62 import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
63 import com.intellij.openapi.ui.MessageType;
64 import com.intellij.openapi.ui.Messages;
65 import com.intellij.openapi.util.*;
66 import com.intellij.openapi.util.io.FileUtil;
67 import com.intellij.openapi.vfs.LocalFileSystem;
68 import com.intellij.openapi.vfs.VfsUtil;
69 import com.intellij.openapi.vfs.VirtualFile;
70 import com.intellij.openapi.vfs.VirtualFileManager;
71 import com.intellij.openapi.vfs.newvfs.RefreshQueue;
72 import com.intellij.openapi.wm.StatusBar;
73 import com.intellij.openapi.wm.ToolWindowId;
74 import com.intellij.openapi.wm.ToolWindowManager;
75 import com.intellij.openapi.wm.WindowManager;
76 import com.intellij.packageDependencies.DependenciesBuilder;
77 import com.intellij.packageDependencies.ForwardDependenciesBuilder;
78 import com.intellij.pom.java.LanguageLevel;
79 import com.intellij.psi.PsiCompiledElement;
80 import com.intellij.psi.PsiDocumentManager;
81 import com.intellij.psi.PsiFile;
82 import com.intellij.psi.PsiManager;
83 import com.intellij.util.Chunk;
84 import com.intellij.util.LocalTimeCounter;
85 import com.intellij.util.StringBuilderSpinAllocator;
86 import com.intellij.util.ThrowableRunnable;
87 import com.intellij.util.containers.ContainerUtil;
88 import com.intellij.util.containers.HashMap;
89 import com.intellij.util.containers.OrderedSet;
90 import gnu.trove.TObjectHashingStrategy;
91 import org.jetbrains.annotations.NonNls;
92 import org.jetbrains.annotations.NotNull;
94 import java.io.*;
95 import java.util.*;
96 import java.util.concurrent.CountDownLatch;
98 public class CompileDriver {
99 private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileDriver");
101 private final Project myProject;
102 private final Map<Pair<IntermediateOutputCompiler, Module>, Pair<VirtualFile, VirtualFile>> myGenerationCompilerModuleToOutputDirMap; // [IntermediateOutputCompiler, Module] -> [ProductionSources, TestSources]
103 private final String myCachesDirectoryPath;
104 private boolean myShouldClearOutputDirectory;
106 private final Map<Module, String> myModuleOutputPaths = new HashMap<Module, String>();
107 private final Map<Module, String> myModuleTestOutputPaths = new HashMap<Module, String>();
109 @NonNls private static final String VERSION_FILE_NAME = "version.dat";
110 @NonNls private static final String LOCK_FILE_NAME = "in_progress.dat";
112 @NonNls private static final boolean GENERATE_CLASSPATH_INDEX = "true".equals(System.getProperty("generate.classpath.index"));
114 private static final FileProcessingCompilerAdapterFactory FILE_PROCESSING_COMPILER_ADAPTER_FACTORY = new FileProcessingCompilerAdapterFactory() {
115 public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) {
116 return new FileProcessingCompilerAdapter(context, compiler);
119 private static final FileProcessingCompilerAdapterFactory FILE_PACKAGING_COMPILER_ADAPTER_FACTORY = new FileProcessingCompilerAdapterFactory() {
120 public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) {
121 return new PackagingCompilerAdapter(context, (PackagingCompiler)compiler);
124 private CompilerFilter myCompilerFilter = CompilerFilter.ALL;
125 private static CompilerFilter SOURCE_PROCESSING_ONLY = new CompilerFilter() {
126 public boolean acceptCompiler(Compiler compiler) {
127 return compiler instanceof SourceProcessingCompiler;
130 private static CompilerFilter ALL_EXCEPT_SOURCE_PROCESSING = new CompilerFilter() {
131 public boolean acceptCompiler(Compiler compiler) {
132 return !SOURCE_PROCESSING_ONLY.acceptCompiler(compiler);
136 private OutputPathFinder myOutputFinder; // need this for updating zip archives (experimental feature)
138 private Set<File> myAllOutputDirectories;
139 private static final long ONE_MINUTE_MS = 60L /*sec*/ * 1000L /*millisec*/;
141 public CompileDriver(Project project) {
142 myProject = project;
143 myCachesDirectoryPath = CompilerPaths.getCacheStoreDirectory(myProject).getPath().replace('/', File.separatorChar);
144 myShouldClearOutputDirectory = CompilerWorkspaceConfiguration.getInstance(myProject).CLEAR_OUTPUT_DIRECTORY;
146 myGenerationCompilerModuleToOutputDirMap = new HashMap<Pair<IntermediateOutputCompiler, Module>, Pair<VirtualFile, VirtualFile>>();
148 final IntermediateOutputCompiler[] generatingCompilers = CompilerManager.getInstance(myProject).getCompilers(IntermediateOutputCompiler.class, myCompilerFilter);
149 if (generatingCompilers.length > 0) {
150 final Module[] allModules = ModuleManager.getInstance(myProject).getModules();
151 for (IntermediateOutputCompiler compiler : generatingCompilers) {
152 for (final Module module : allModules) {
153 final VirtualFile productionOutput = lookupVFile(compiler, module, false);
154 final VirtualFile testOutput = lookupVFile(compiler, module, true);
155 final Pair<IntermediateOutputCompiler, Module> pair = new Pair<IntermediateOutputCompiler, Module>(compiler, module);
156 final Pair<VirtualFile, VirtualFile> outputs = new Pair<VirtualFile, VirtualFile>(productionOutput, testOutput);
157 myGenerationCompilerModuleToOutputDirMap.put(pair, outputs);
163 public void setCompilerFilter(CompilerFilter compilerFilter) {
164 myCompilerFilter = compilerFilter == null? CompilerFilter.ALL : compilerFilter;
167 public void rebuild(CompileStatusNotification callback) {
168 doRebuild(callback, null, true, addAdditionalRoots(new ProjectCompileScope(myProject), ALL_EXCEPT_SOURCE_PROCESSING));
171 public void make(CompileScope scope, CompileStatusNotification callback) {
172 scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING);
173 if (validateCompilerConfiguration(scope, false)) {
174 startup(scope, false, false, callback, null, true, false);
178 public boolean isUpToDate(CompileScope scope) {
179 if (LOG.isDebugEnabled()) {
180 LOG.debug("isUpToDate operation started");
182 scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING);
184 final CompilerTask task = new CompilerTask(myProject, true, "", true);
185 final CompileContextImpl compileContext =
186 new CompileContextImpl(myProject, task, scope, createDependencyCache(), true, false);
188 checkCachesVersion(compileContext);
189 if (compileContext.isRebuildRequested()) {
190 if (LOG.isDebugEnabled()) {
191 LOG.debug("Rebuild requested, up-to-date=false");
193 return false;
196 for (Map.Entry<Pair<IntermediateOutputCompiler, Module>, Pair<VirtualFile, VirtualFile>> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) {
197 final Pair<VirtualFile, VirtualFile> outputs = entry.getValue();
198 Module module = entry.getKey().getSecond();
199 compileContext.assignModule(outputs.getFirst(), module, false);
200 compileContext.assignModule(outputs.getSecond(), module, true);
203 final Ref<ExitStatus> status = new Ref<ExitStatus>();
205 task.start(new Runnable() {
206 public void run() {
207 try {
208 myAllOutputDirectories = getAllOutputDirectories();
209 // need this for updating zip archives experiment, uncomment if the feature is turned on
210 //myOutputFinder = new OutputPathFinder(myAllOutputDirectories);
211 status.set(doCompile(compileContext, false, false, false, true));
213 finally {
214 compileContext.commitZipFiles(); // just to be on the safe side; normally should do nothing if called in isUpToDate()
217 }, null);
219 if (LOG.isDebugEnabled()) {
220 LOG.debug("isUpToDate operation finished");
223 return ExitStatus.UP_TO_DATE.equals(status.get());
226 private DependencyCache createDependencyCache() {
227 return new DependencyCache(myCachesDirectoryPath + File.separator + ".dependency-info");
230 public void compile(CompileScope scope, CompileStatusNotification callback, boolean trackDependencies) {
231 if (trackDependencies) {
232 scope = new TrackDependenciesScope(scope);
234 if (validateCompilerConfiguration(scope, false)) {
235 startup(scope, false, true, callback, null, true, trackDependencies);
239 private static class CompileStatus {
240 final int CACHE_FORMAT_VERSION;
241 final boolean COMPILATION_IN_PROGRESS;
243 private CompileStatus(int cacheVersion, boolean isCompilationInProgress) {
244 CACHE_FORMAT_VERSION = cacheVersion;
245 COMPILATION_IN_PROGRESS = isCompilationInProgress;
249 private CompileStatus readStatus() {
250 final boolean isInProgress = getLockFile().exists();
251 int version = -1;
252 try {
253 final File versionFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME);
254 DataInputStream in = new DataInputStream(new FileInputStream(versionFile));
255 try {
256 version = in.readInt();
258 finally {
259 in.close();
262 catch (FileNotFoundException e) {
263 // ignore
265 catch (IOException e) {
266 LOG.info(e); // may happen in case of IDEA crashed and the file is not written properly
267 return null;
269 return new CompileStatus(version, isInProgress);
272 private void writeStatus(CompileStatus status, CompileContext context) {
273 final File statusFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME);
275 final File lockFile = getLockFile();
276 try {
277 FileUtil.createIfDoesntExist(statusFile);
278 DataOutputStream out = new DataOutputStream(new FileOutputStream(statusFile));
279 try {
280 out.writeInt(status.CACHE_FORMAT_VERSION);
282 finally {
283 out.close();
285 if (status.COMPILATION_IN_PROGRESS) {
286 FileUtil.createIfDoesntExist(lockFile);
288 else {
289 deleteFile(lockFile);
292 catch (IOException e) {
293 context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1);
297 private File getLockFile() {
298 return new File(CompilerPaths.getCompilerSystemDirectory(myProject), LOCK_FILE_NAME);
301 private void doRebuild(CompileStatusNotification callback,
302 CompilerMessage message,
303 final boolean checkCachesVersion,
304 final CompileScope compileScope) {
305 if (validateCompilerConfiguration(compileScope, true)) {
306 startup(compileScope, true, false, callback, message, checkCachesVersion, false);
310 private CompileScope addAdditionalRoots(CompileScope originalScope, final CompilerFilter filter) {
311 CompileScope scope = attachIntermediateOutputDirectories(originalScope, filter);
313 final AdditionalCompileScopeProvider[] scopeProviders = Extensions.getExtensions(AdditionalCompileScopeProvider.EXTENSION_POINT_NAME);
314 CompileScope baseScope = scope;
315 for (AdditionalCompileScopeProvider scopeProvider : scopeProviders) {
316 final CompileScope additionalScope = scopeProvider.getAdditionalScope(baseScope);
317 if (additionalScope != null) {
318 scope = new CompositeScope(scope, additionalScope);
321 return scope;
324 private CompileScope attachIntermediateOutputDirectories(CompileScope originalScope, CompilerFilter filter) {
325 CompileScope scope = originalScope;
326 final Set<Module> affected = new HashSet<Module>(Arrays.asList(originalScope.getAffectedModules()));
327 for (Map.Entry<Pair<IntermediateOutputCompiler, Module>, Pair<VirtualFile, VirtualFile>> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) {
328 final Module module = entry.getKey().getSecond();
329 if (affected.contains(module) && filter.acceptCompiler(entry.getKey().getFirst())) {
330 final Pair<VirtualFile, VirtualFile> outputs = entry.getValue();
331 scope = new CompositeScope(scope, new FileSetCompileScope(Arrays.asList(outputs.getFirst(), outputs.getSecond()), new Module[]{module}));
334 return scope;
337 public static final Key<Long> COMPILATION_START_TIMESTAMP = Key.create("COMPILATION_START_TIMESTAMP");
339 private void startup(final CompileScope scope,
340 final boolean isRebuild,
341 final boolean forceCompile,
342 final CompileStatusNotification callback,
343 final CompilerMessage message,
344 final boolean checkCachesVersion,
345 final boolean trackDependencies) {
346 final CompilerTask compileTask = new CompilerTask(myProject, CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_IN_BACKGROUND,
347 forceCompile
348 ? CompilerBundle.message("compiler.content.name.compile")
349 : CompilerBundle.message("compiler.content.name.make"), false);
350 final WindowManager windowManager = WindowManager.getInstance();
351 if (windowManager != null) {
352 windowManager.getStatusBar(myProject).setInfo("");
355 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
356 FileDocumentManager.getInstance().saveAllDocuments();
358 final DependencyCache dependencyCache = createDependencyCache();
359 final CompileContextImpl compileContext =
360 new CompileContextImpl(myProject, compileTask, scope, dependencyCache, !isRebuild && !forceCompile, isRebuild);
361 compileContext.putUserData(COMPILATION_START_TIMESTAMP, LocalTimeCounter.currentTime());
362 for (Map.Entry<Pair<IntermediateOutputCompiler, Module>, Pair<VirtualFile, VirtualFile>> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) {
363 final Pair<VirtualFile, VirtualFile> outputs = entry.getValue();
364 final Module module = entry.getKey().getSecond();
365 compileContext.assignModule(outputs.getFirst(), module, false);
366 compileContext.assignModule(outputs.getSecond(), module, true);
369 compileTask.start(new Runnable() {
370 public void run() {
371 long start = System.currentTimeMillis();
372 try {
373 if (myProject.isDisposed()) {
374 return;
376 LOG.info("COMPILATION STARTED");
377 if (message != null) {
378 compileContext.addMessage(message);
380 TranslatingCompilerFilesMonitor.getInstance().ensureInitializationCompleted(myProject);
381 doCompile(compileContext, isRebuild, forceCompile, callback, checkCachesVersion, trackDependencies);
383 finally {
384 compileContext.commitZipFiles();
385 CompilerUtil.logDuration("Refreshing VFS in total", CompilerUtil.ourRefreshTime);
386 CompilerUtil.ourRefreshTime = 0L;
387 final long finish = System.currentTimeMillis();
388 CompilerUtil.logDuration(
389 "\tCOMPILATION FINISHED; Errors: " +
390 compileContext.getMessageCount(CompilerMessageCategory.ERROR) +
391 "; warnings: " +
392 compileContext.getMessageCount(CompilerMessageCategory.WARNING),
393 finish - start
395 //if (LOG.isDebugEnabled()) {
396 // LOG.debug("COMPILATION FINISHED");
400 }, new Runnable() {
401 public void run() {
402 if (isRebuild) {
403 final int rv = Messages.showDialog(
404 myProject, "You are about to rebuild the whole project.\nRun 'Make Project' instead?", "Confirm Project Rebuild",
405 new String[]{"Make", "Rebuild"}, 0, Messages.getQuestionIcon()
407 if (rv == 0 /*yes, please, do run make*/) {
408 startup(scope, false, false, callback, null, checkCachesVersion, trackDependencies);
409 return;
412 startup(scope, isRebuild, forceCompile, callback, message, checkCachesVersion, trackDependencies);
417 private void doCompile(final CompileContextImpl compileContext,
418 final boolean isRebuild,
419 final boolean forceCompile,
420 final CompileStatusNotification callback,
421 final boolean checkCachesVersion,
422 final boolean trackDependencies) {
423 ExitStatus status = ExitStatus.ERRORS;
424 boolean wereExceptions = false;
425 try {
426 if (checkCachesVersion) {
427 checkCachesVersion(compileContext);
428 if (compileContext.isRebuildRequested()) {
429 return;
432 writeStatus(new CompileStatus(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION, true), compileContext);
433 if (compileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
434 return;
437 myAllOutputDirectories = getAllOutputDirectories();
438 // need this for updating zip archives experiment, uncomment if the feature is turned on
439 //myOutputFinder = new OutputPathFinder(myAllOutputDirectories);
440 status = doCompile(compileContext, isRebuild, forceCompile, trackDependencies, false);
442 catch (Throwable ex) {
443 wereExceptions = true;
444 final PluginId pluginId = IdeErrorsDialog.findPluginId(ex);
446 final StringBuffer message = new StringBuffer();
447 message.append("Internal error");
448 if (pluginId != null) {
449 message.append(" (Plugin: ").append(pluginId).append(")");
451 message.append(": ").append(ex.getMessage());
452 compileContext.addMessage(CompilerMessageCategory.ERROR, message.toString(), null, -1, -1);
454 if (pluginId != null) {
455 throw new PluginException(ex, pluginId);
457 throw new RuntimeException(ex);
459 finally {
460 dropDependencyCache(compileContext);
461 final ExitStatus _status = status;
462 if (compileContext.isRebuildRequested()) {
463 ApplicationManager.getApplication().invokeLater(new Runnable() {
464 public void run() {
465 doRebuild(callback, new CompilerMessageImpl(myProject, CompilerMessageCategory.INFORMATION, compileContext.getRebuildReason(),
466 null, -1, -1, null), false, compileContext.getCompileScope());
468 }, ModalityState.NON_MODAL);
470 else {
471 final long duration = System.currentTimeMillis() - compileContext.getStartCompilationStamp();
472 writeStatus(new CompileStatus(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION, wereExceptions), compileContext);
473 ApplicationManager.getApplication().invokeLater(new Runnable() {
474 public void run() {
475 final int errorCount = compileContext.getMessageCount(CompilerMessageCategory.ERROR);
476 final int warningCount = compileContext.getMessageCount(CompilerMessageCategory.WARNING);
477 final String statusMessage = createStatusMessage(_status, warningCount, errorCount);
478 final StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject);
479 if (statusBar != null) { // because this code is in invoke later, the code may work for already closed project
480 // in case another project was opened in the frame while the compiler was working (See SCR# 28591)
481 statusBar.setInfo(statusMessage);
482 if (duration > ONE_MINUTE_MS) {
483 final MessageType messageType = errorCount > 0 ? MessageType.ERROR : warningCount > 0 ? MessageType.WARNING : MessageType.INFO;
484 ToolWindowManager.getInstance(myProject).notifyByBalloon(ToolWindowId.MESSAGES_WINDOW, messageType, statusMessage);
487 if (_status != ExitStatus.UP_TO_DATE && compileContext.getMessageCount(null) > 0) {
488 compileContext.addMessage(CompilerMessageCategory.INFORMATION, statusMessage, null, -1, -1);
490 if (callback != null) {
491 callback.finished(_status == ExitStatus.CANCELLED, errorCount, warningCount, compileContext);
494 }, ModalityState.NON_MODAL);
499 private void checkCachesVersion(final CompileContextImpl compileContext) {
500 final CompileStatus compileStatus = readStatus();
501 if (compileStatus == null) {
502 compileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted"));
504 else if (compileStatus.CACHE_FORMAT_VERSION != -1 &&
505 compileStatus.CACHE_FORMAT_VERSION != CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION) {
506 compileContext.requestRebuildNextTime(CompilerBundle.message("error.caches.old.format"));
508 else if (compileStatus.COMPILATION_IN_PROGRESS) {
509 compileContext.requestRebuildNextTime(CompilerBundle.message("error.previous.compilation.failed"));
513 private static String createStatusMessage(final ExitStatus status, final int warningCount, final int errorCount) {
514 if (status == ExitStatus.CANCELLED) {
515 return CompilerBundle.message("status.compilation.aborted");
517 if (status == ExitStatus.UP_TO_DATE) {
518 return CompilerBundle.message("status.all.up.to.date");
520 if (status == ExitStatus.SUCCESS) {
521 return warningCount > 0
522 ? CompilerBundle.message("status.compilation.completed.successfully.with.warnings", warningCount)
523 : CompilerBundle.message("status.compilation.completed.successfully");
525 return CompilerBundle.message("status.compilation.completed.successfully.with.warnings.and.errors", errorCount, warningCount);
528 private static class ExitStatus {
529 private final String myName;
531 private ExitStatus(@NonNls String name) {
532 myName = name;
535 public String toString() {
536 return myName;
539 public static final ExitStatus CANCELLED = new ExitStatus("CANCELLED");
540 public static final ExitStatus ERRORS = new ExitStatus("ERRORS");
541 public static final ExitStatus SUCCESS = new ExitStatus("SUCCESS");
542 public static final ExitStatus UP_TO_DATE = new ExitStatus("UP_TO_DATE");
545 private static class ExitException extends Exception {
546 private final ExitStatus myStatus;
548 private ExitException(ExitStatus status) {
549 myStatus = status;
552 public ExitStatus getExitStatus() {
553 return myStatus;
557 private ExitStatus doCompile(final CompileContextEx context,
558 boolean isRebuild,
559 final boolean forceCompile,
560 final boolean trackDependencies, final boolean onlyCheckStatus) {
561 try {
562 if (isRebuild) {
563 deleteAll(context);
564 if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
565 if (LOG.isDebugEnabled()) {
566 logErrorMessages(context);
568 return ExitStatus.ERRORS;
572 if (!onlyCheckStatus) {
573 if (!executeCompileTasks(context, true)) {
574 if (LOG.isDebugEnabled()) {
575 LOG.debug("Compilation cancelled");
577 return ExitStatus.CANCELLED;
581 if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
582 if (LOG.isDebugEnabled()) {
583 logErrorMessages(context);
585 return ExitStatus.ERRORS;
588 final long refreshStart = System.currentTimeMillis();
590 // need this to make sure the VFS is built
591 boolean needRecalcOutputDirs = false;
592 //final List<VirtualFile> outputsToRefresh = new ArrayList<VirtualFile>();
594 final VirtualFile[] all = context.getAllOutputDirectories();
596 final ProgressIndicator progressIndicator = context.getProgressIndicator();
598 final int totalCount = all.length + myGenerationCompilerModuleToOutputDirMap.size() * 2;
599 final CountDownLatch latch = new CountDownLatch(totalCount);
600 final Runnable decCount = new Runnable() {
601 public void run() {
602 latch.countDown();
603 progressIndicator.setFraction(((double)(totalCount - latch.getCount())) / totalCount);
606 progressIndicator.pushState();
607 progressIndicator.setText("Inspecting output directories...");
608 final boolean asyncMode = !ApplicationManager.getApplication().isDispatchThread(); // must not lock awt thread with latch.await()
609 try {
610 for (VirtualFile output : all) {
611 if (output.isValid()) {
612 walkChildren(output, context);
614 else {
615 needRecalcOutputDirs = true;
616 final File file = new File(output.getPath());
617 if (!file.exists()) {
618 final boolean created = file.mkdirs();
619 if (!created) {
620 context.addMessage(CompilerMessageCategory.ERROR, "Failed to create output directory " + file.getPath(), null, 0, 0);
621 return ExitStatus.ERRORS;
624 output = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
625 if (output == null) {
626 context.addMessage(CompilerMessageCategory.ERROR, "Failed to locate output directory " + file.getPath(), null, 0, 0);
627 return ExitStatus.ERRORS;
630 output.refresh(asyncMode, true, decCount);
631 //outputsToRefresh.add(output);
633 for (Pair<IntermediateOutputCompiler, Module> pair : myGenerationCompilerModuleToOutputDirMap.keySet()) {
634 final Pair<VirtualFile, VirtualFile> generated = myGenerationCompilerModuleToOutputDirMap.get(pair);
635 walkChildren(generated.getFirst(), context);
636 //outputsToRefresh.add(generated.getFirst());
637 generated.getFirst().refresh(asyncMode, true, decCount);
638 walkChildren(generated.getSecond(), context);
639 //outputsToRefresh.add(generated.getSecond());
640 generated.getSecond().refresh(asyncMode, true, decCount);
643 //RefreshQueue.getInstance().refresh(false, true, null, outputsToRefresh.toArray(new VirtualFile[outputsToRefresh.size()]));
644 try {
645 latch.await(); // wait until all threads are refreshed
647 catch (InterruptedException e) {
648 LOG.info(e);
651 finally {
652 progressIndicator.popState();
655 final long initialRefreshTime = System.currentTimeMillis() - refreshStart;
656 CompilerUtil.logDuration("Initial VFS refresh", initialRefreshTime);
657 CompilerUtil.ourRefreshTime += initialRefreshTime;
659 DumbService.getInstance(myProject).waitForSmartMode();
661 if (needRecalcOutputDirs) {
662 context.recalculateOutputDirs();
665 boolean didSomething = false;
667 final CompilerManager compilerManager = CompilerManager.getInstance(myProject);
669 try {
670 didSomething |= generateSources(compilerManager, context, forceCompile, onlyCheckStatus);
672 didSomething |= invokeFileProcessingCompilers(compilerManager, context, SourceInstrumentingCompiler.class,
673 FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, forceCompile, true, onlyCheckStatus);
675 didSomething |= invokeFileProcessingCompilers(compilerManager, context, SourceProcessingCompiler.class,
676 FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, forceCompile, true, onlyCheckStatus);
678 final CompileScope intermediateSources = attachIntermediateOutputDirectories(new CompositeScope(CompileScope.EMPTY_ARRAY) {
679 @NotNull
680 public Module[] getAffectedModules() {
681 return context.getCompileScope().getAffectedModules();
683 }, SOURCE_PROCESSING_ONLY);
684 context.addScope(intermediateSources);
686 didSomething |= translate(context, compilerManager, forceCompile, isRebuild, trackDependencies, onlyCheckStatus);
688 didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassInstrumentingCompiler.class,
689 FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, isRebuild, false, onlyCheckStatus);
691 // explicitly passing forceCompile = false because in scopes that is narrower than ProjectScope it is impossible
692 // to understand whether the class to be processed is in scope or not. Otherwise compiler may process its items even if
693 // there were changes in completely independent files.
694 didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassPostProcessingCompiler.class,
695 FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, isRebuild, false, onlyCheckStatus);
697 didSomething |= invokeFileProcessingCompilers(compilerManager, context, PackagingCompiler.class,
698 FILE_PACKAGING_COMPILER_ADAPTER_FACTORY,
699 isRebuild, false, onlyCheckStatus);
701 didSomething |= invokeFileProcessingCompilers(compilerManager, context, Validator.class, FILE_PROCESSING_COMPILER_ADAPTER_FACTORY,
702 forceCompile, true, onlyCheckStatus);
704 catch (ExitException e) {
705 if (LOG.isDebugEnabled()) {
706 LOG.debug(e);
707 logErrorMessages(context);
709 return e.getExitStatus();
711 finally {
712 // drop in case it has not been dropped yet.
713 dropDependencyCache(context);
715 final VirtualFile[] allOutputDirs = context.getAllOutputDirectories();
717 if (didSomething && GENERATE_CLASSPATH_INDEX) {
718 CompilerUtil.runInContext(context, "Generating classpath index...", new ThrowableRunnable<RuntimeException>(){
719 public void run() {
720 int count = 0;
721 for (VirtualFile file : allOutputDirs) {
722 context.getProgressIndicator().setFraction((double)++count / allOutputDirs.length);
723 createClasspathIndex(file);
729 if (!context.getProgressIndicator().isCanceled() && context.getMessageCount(CompilerMessageCategory.ERROR) == 0) {
730 RefreshQueue.getInstance().refresh(true, true, new Runnable() {
731 public void run() {
732 CompilerDirectoryTimestamp.updateTimestamp(Arrays.asList(allOutputDirs));
734 }, allOutputDirs);
738 if (!onlyCheckStatus) {
739 if (!executeCompileTasks(context, false)) {
740 return ExitStatus.CANCELLED;
744 if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
745 if (LOG.isDebugEnabled()) {
746 logErrorMessages(context);
748 return ExitStatus.ERRORS;
750 if (!didSomething) {
751 return ExitStatus.UP_TO_DATE;
753 return ExitStatus.SUCCESS;
755 catch (ProcessCanceledException e) {
756 return ExitStatus.CANCELLED;
760 private static void logErrorMessages(final CompileContext context) {
761 final CompilerMessage[] errors = context.getMessages(CompilerMessageCategory.ERROR);
762 if (errors.length > 0) {
763 LOG.debug("Errors reported: ");
764 for (CompilerMessage error : errors) {
765 LOG.debug("\t" + error.getMessage());
770 private static void walkChildren(VirtualFile from, final CompileContext context) {
771 final VirtualFile[] files = from.getChildren();
772 if (files != null && files.length > 0) {
773 context.getProgressIndicator().checkCanceled();
774 context.getProgressIndicator().setText2(from.getPresentableUrl());
775 for (VirtualFile file : files) {
776 walkChildren(file, context);
781 private static void createClasspathIndex(final VirtualFile file) {
782 try {
783 BufferedWriter writer = new BufferedWriter(new FileWriter(new File(VfsUtil.virtualToIoFile(file), "classpath.index")));
784 try {
785 writeIndex(writer, file, file);
787 finally {
788 writer.close();
791 catch (IOException e) {
792 // Ignore. Failed to create optional classpath index
796 private static void writeIndex(final BufferedWriter writer, final VirtualFile root, final VirtualFile file) throws IOException {
797 writer.write(VfsUtil.getRelativePath(file, root, '/'));
798 writer.write('\n');
800 for (VirtualFile child : file.getChildren()) {
801 writeIndex(writer, root, child);
805 private static void dropDependencyCache(final CompileContextEx context) {
806 CompilerUtil.runInContext(context, CompilerBundle.message("progress.saving.caches"), new ThrowableRunnable<RuntimeException>(){
807 public void run() {
808 context.getDependencyCache().resetState();
813 private boolean generateSources(final CompilerManager compilerManager,
814 CompileContextEx context,
815 final boolean forceCompile,
816 final boolean onlyCheckStatus) throws ExitException {
817 boolean didSomething = false;
819 final SourceGeneratingCompiler[] sourceGenerators = compilerManager.getCompilers(SourceGeneratingCompiler.class, myCompilerFilter);
820 for (final SourceGeneratingCompiler sourceGenerator : sourceGenerators) {
821 if (context.getProgressIndicator().isCanceled()) {
822 throw new ExitException(ExitStatus.CANCELLED);
825 final boolean generatedSomething = generateOutput(context, sourceGenerator, forceCompile, onlyCheckStatus);
827 if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
828 throw new ExitException(ExitStatus.ERRORS);
830 didSomething |= generatedSomething;
832 return didSomething;
835 private boolean translate(final CompileContextEx context,
836 final CompilerManager compilerManager,
837 final boolean forceCompile,
838 boolean isRebuild,
839 final boolean trackDependencies, final boolean onlyCheckStatus) throws ExitException {
841 boolean didSomething = false;
843 final TranslatingCompiler[] translators = compilerManager.getCompilers(TranslatingCompiler.class, myCompilerFilter);
846 final List<Chunk<Module>> sortedChunks = Collections.unmodifiableList(ApplicationManager.getApplication().runReadAction(new Computable<List<Chunk<Module>>>() {
847 public List<Chunk<Module>> compute() {
848 final ModuleManager moduleManager = ModuleManager.getInstance(myProject);
849 return ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(moduleManager.getModules()));
851 }));
853 try {
854 VirtualFile[] snapshot = null;
855 final Map<Chunk<Module>, Collection<VirtualFile>> chunkMap = new HashMap<Chunk<Module>, Collection<VirtualFile>>();
856 int total = 0;
857 int processed = 0;
858 for (final Chunk<Module> currentChunk : sortedChunks) {
859 final TranslatorsOutputSink sink = new TranslatorsOutputSink(context, translators);
860 final Set<FileType> generatedTypes = new HashSet<FileType>();
861 Collection<VirtualFile> chunkFiles = chunkMap.get(currentChunk);
862 try {
863 for (int currentCompiler = 0, translatorsLength = translators.length; currentCompiler < translatorsLength; currentCompiler++) {
864 sink.setCurrentCompilerIndex(currentCompiler);
865 final TranslatingCompiler compiler = translators[currentCompiler];
866 if (context.getProgressIndicator().isCanceled()) {
867 throw new ExitException(ExitStatus.CANCELLED);
870 DumbService.getInstance(myProject).waitForSmartMode();
872 if (snapshot == null || ContainerUtil.intersects(generatedTypes, compilerManager.getRegisteredInputTypes(compiler))) {
873 // rescan snapshot if previously generated files may influence the input of this compiler
874 snapshot = ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile[]>() {
875 public VirtualFile[] compute() {
876 return context.getCompileScope().getFiles(null, true);
879 final Map<Module, List<VirtualFile>> moduleToFilesMap = CompilerUtil.buildModuleToFilesMap(context, snapshot);
880 for (Chunk<Module> moduleChunk : sortedChunks) {
881 List<VirtualFile> files = Collections.emptyList();
882 for (Module module : moduleChunk.getNodes()) {
883 final List<VirtualFile> moduleFiles = moduleToFilesMap.get(module);
884 if (moduleFiles != null) {
885 files = ContainerUtil.concat(files, moduleFiles);
888 chunkMap.put(moduleChunk, files);
890 total = snapshot.length * translatorsLength;
891 chunkFiles = chunkMap.get(currentChunk);
894 final CompileContextEx _context;
895 if (compiler instanceof IntermediateOutputCompiler) {
896 // wrap compile context so that output goes into intermediate directories
897 final IntermediateOutputCompiler _compiler = (IntermediateOutputCompiler)compiler;
898 _context = new CompileContextExProxy(context) {
899 public VirtualFile getModuleOutputDirectory(final Module module) {
900 return getGenerationOutputDir(_compiler, module, false);
903 public VirtualFile getModuleOutputDirectoryForTests(final Module module) {
904 return getGenerationOutputDir(_compiler, module, true);
908 else {
909 _context = context;
911 final boolean compiledSomething =
912 compileSources(_context, currentChunk, compiler, chunkFiles, forceCompile, isRebuild, trackDependencies, onlyCheckStatus, sink);
914 processed += chunkFiles.size();
915 _context.getProgressIndicator().setFraction(((double)processed) / total);
917 if (compiledSomething) {
918 generatedTypes.addAll(compilerManager.getRegisteredOutputTypes(compiler));
921 if (_context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
922 throw new ExitException(ExitStatus.ERRORS);
925 didSomething |= compiledSomething;
928 finally {
929 if (context.getMessageCount(CompilerMessageCategory.ERROR) == 0) {
930 // perform update only if there were no errors, so it is guaranteed that the file was processd by all neccesary compilers
931 sink.flushPostponedItems();
936 catch (ProcessCanceledException e) {
937 ProgressManager.getInstance().executeNonCancelableSection(new Runnable() {
938 public void run() {
939 try {
940 final Collection<VirtualFile> deps = CacheUtils.findDependentFiles(context, Collections.<VirtualFile>emptySet(), null, null);
941 if (deps.size() > 0) {
942 TranslatingCompilerFilesMonitor.getInstance().update(context, null, Collections.<TranslatingCompiler.OutputItem>emptyList(), deps.toArray(new VirtualFile[deps.size()]));
945 catch (IOException ignored) {
946 LOG.info(ignored);
948 catch (CacheCorruptedException ignored) {
949 LOG.info(ignored);
953 throw e;
955 finally {
956 dropDependencyCache(context);
957 if (didSomething) {
958 TranslatingCompilerFilesMonitor.getInstance().updateOutputRootsLayout(myProject);
961 return didSomething;
964 private interface FileProcessingCompilerAdapterFactory {
965 FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler);
968 private boolean invokeFileProcessingCompilers(final CompilerManager compilerManager,
969 CompileContextEx context,
970 Class<? extends FileProcessingCompiler> fileProcessingCompilerClass,
971 FileProcessingCompilerAdapterFactory factory,
972 boolean forceCompile,
973 final boolean checkScope,
974 final boolean onlyCheckStatus) throws ExitException {
975 boolean didSomething = false;
976 final FileProcessingCompiler[] compilers = compilerManager.getCompilers(fileProcessingCompilerClass, myCompilerFilter);
977 if (compilers.length > 0) {
978 try {
979 CacheDeferredUpdater cacheUpdater = new CacheDeferredUpdater();
980 try {
981 for (final FileProcessingCompiler compiler : compilers) {
982 if (context.getProgressIndicator().isCanceled()) {
983 throw new ExitException(ExitStatus.CANCELLED);
986 CompileContextEx _context = context;
987 if (compiler instanceof IntermediateOutputCompiler) {
988 final IntermediateOutputCompiler _compiler = (IntermediateOutputCompiler)compiler;
989 _context = new CompileContextExProxy(context) {
990 public VirtualFile getModuleOutputDirectory(final Module module) {
991 return getGenerationOutputDir(_compiler, module, false);
994 public VirtualFile getModuleOutputDirectoryForTests(final Module module) {
995 return getGenerationOutputDir(_compiler, module, true);
1000 final boolean processedSomething = processFiles(factory.create(_context, compiler), forceCompile, checkScope, onlyCheckStatus, cacheUpdater);
1002 if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
1003 throw new ExitException(ExitStatus.ERRORS);
1006 didSomething |= processedSomething;
1009 finally {
1010 cacheUpdater.doUpdate();
1013 catch (IOException e) {
1014 LOG.info(e);
1015 context.requestRebuildNextTime(e.getMessage());
1016 throw new ExitException(ExitStatus.ERRORS);
1018 catch (ProcessCanceledException e) {
1019 throw e;
1021 catch (ExitException e) {
1022 throw e;
1024 catch (Exception e) {
1025 context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1);
1026 LOG.error(e);
1030 return didSomething;
1033 private static Map<Module, Set<GeneratingCompiler.GenerationItem>> buildModuleToGenerationItemMap(GeneratingCompiler.GenerationItem[] items) {
1034 final Map<Module, Set<GeneratingCompiler.GenerationItem>> map = new HashMap<Module, Set<GeneratingCompiler.GenerationItem>>();
1035 for (GeneratingCompiler.GenerationItem item : items) {
1036 Module module = item.getModule();
1037 LOG.assertTrue(module != null);
1038 Set<GeneratingCompiler.GenerationItem> itemSet = map.get(module);
1039 if (itemSet == null) {
1040 itemSet = new HashSet<GeneratingCompiler.GenerationItem>();
1041 map.put(module, itemSet);
1043 itemSet.add(item);
1045 return map;
1048 private void deleteAll(final CompileContextEx context) {
1049 CompilerUtil.runInContext(context, CompilerBundle.message("progress.clearing.output"), new ThrowableRunnable<RuntimeException>() {
1050 public void run() {
1051 final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode();
1052 final VirtualFile[] allSources = context.getProjectCompileScope().getFiles(null, true);
1053 if (myShouldClearOutputDirectory) {
1054 clearOutputDirectories(myAllOutputDirectories);
1056 else { // refresh is still required
1057 try {
1058 for (final Compiler compiler : CompilerManager.getInstance(myProject).getCompilers(Compiler.class)) {
1059 try {
1060 if (compiler instanceof GeneratingCompiler) {
1061 final StateCache<ValidityState> cache = getGeneratingCompilerCache((GeneratingCompiler)compiler);
1062 final Iterator<String> urlIterator = cache.getUrlsIterator();
1063 while (urlIterator.hasNext()) {
1064 context.getProgressIndicator().checkCanceled();
1065 deleteFile(new File(VirtualFileManager.extractPath(urlIterator.next())));
1068 else if (compiler instanceof TranslatingCompiler) {
1069 final ArrayList<Trinity<File, String, Boolean>> toDelete = new ArrayList<Trinity<File, String, Boolean>>();
1070 ApplicationManager.getApplication().runReadAction(new Runnable() {
1071 public void run() {
1072 TranslatingCompilerFilesMonitor.getInstance()
1073 .collectFiles(context, (TranslatingCompiler)compiler, Arrays.<VirtualFile>asList(allSources).iterator(), true
1074 /*pass true to make sure that every source in scope file is processed*/, false
1075 /*important! should pass false to enable collection of files to delete*/,
1076 new ArrayList<VirtualFile>(), toDelete);
1079 for (Trinity<File, String, Boolean> trinity : toDelete) {
1080 context.getProgressIndicator().checkCanceled();
1081 final File file = trinity.getFirst();
1082 deleteFile(file);
1083 if (isTestMode) {
1084 CompilerManagerImpl.addDeletedPath(file.getPath());
1089 catch (IOException e) {
1090 LOG.info(e);
1093 pruneEmptyDirectories(context.getProgressIndicator(), myAllOutputDirectories); // to avoid too much files deleted events
1095 finally {
1096 CompilerUtil.refreshIODirectories(myAllOutputDirectories);
1099 dropScopesCaches();
1101 clearCompilerSystemDirectory(context);
1106 private void dropScopesCaches() {
1107 // hack to be sure the classpath will include the output directories
1108 ApplicationManager.getApplication().runReadAction(new Runnable() {
1109 public void run() {
1110 ((ProjectRootManagerEx)ProjectRootManager.getInstance(myProject)).clearScopesCachesForModules();
1115 private static void pruneEmptyDirectories(ProgressIndicator progress, final Set<File> directories) {
1116 for (File directory : directories) {
1117 doPrune(progress, directory, directories);
1121 private static boolean doPrune(ProgressIndicator progress, final File directory, final Set<File> outPutDirectories) {
1122 progress.checkCanceled();
1123 final File[] files = directory.listFiles();
1124 boolean isEmpty = true;
1125 if (files != null) {
1126 for (File file : files) {
1127 if (!outPutDirectories.contains(file)) {
1128 if (doPrune(progress, file, outPutDirectories)) {
1129 deleteFile(file);
1131 else {
1132 isEmpty = false;
1135 else {
1136 isEmpty = false;
1140 else {
1141 isEmpty = false;
1144 return isEmpty;
1147 private Set<File> getAllOutputDirectories() {
1148 final Set<File> outputDirs = new OrderedSet<File>((TObjectHashingStrategy<File>)TObjectHashingStrategy.CANONICAL);
1149 for (final String path : CompilerPathsEx.getOutputPaths(ModuleManager.getInstance(myProject).getModules())) {
1150 outputDirs.add(new File(path));
1152 return outputDirs;
1155 private void clearOutputDirectories(final Set<File> _outputDirectories) {
1156 final long start = System.currentTimeMillis();
1157 // do not delete directories themselves, or we'll get rootsChanged() otherwise
1158 final List<File> outputDirectories = new ArrayList<File>(_outputDirectories);
1159 for (Pair<IntermediateOutputCompiler, Module> pair : myGenerationCompilerModuleToOutputDirMap.keySet()) {
1160 outputDirectories.add(new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), false)));
1161 outputDirectories.add(new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), true)));
1163 Collection<File> filesToDelete = new ArrayList<File>(outputDirectories.size() * 2);
1164 for (File outputDirectory : outputDirectories) {
1165 File[] files = outputDirectory.listFiles();
1166 if (files != null) {
1167 filesToDelete.addAll(Arrays.asList(files));
1170 FileUtil.asyncDelete(filesToDelete);
1172 // ensure output directories exist
1173 for (final File file : outputDirectories) {
1174 file.mkdirs();
1176 final long clearStop = System.currentTimeMillis();
1178 CompilerUtil.refreshIODirectories(outputDirectories);
1180 final long refreshStop = System.currentTimeMillis();
1182 CompilerUtil.logDuration("Clearing output dirs", clearStop - start);
1183 CompilerUtil.logDuration("Refreshing output directories", refreshStop - clearStop);
1186 private void clearCompilerSystemDirectory(final CompileContextEx context) {
1187 CompilerCacheManager.getInstance(myProject).clearCaches(context);
1188 FileUtil.delete(CompilerPathsEx.getZipStoreDirectory(myProject));
1189 dropDependencyCache(context);
1191 for (Pair<IntermediateOutputCompiler, Module> pair : myGenerationCompilerModuleToOutputDirMap.keySet()) {
1192 final File[] outputs = {
1193 new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), false)),
1194 new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), true))
1196 for (File output : outputs) {
1197 final File[] files = output.listFiles();
1198 if (files != null) {
1199 for (final File file : files) {
1200 final boolean deleteOk = deleteFile(file);
1201 if (!deleteOk) {
1202 context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.failed.to.delete", file.getPath()),
1203 null, -1, -1);
1212 * @param file a file to delete
1213 * @return true if and only if the file existed and was successfully deleted
1214 * Note: the behaviour is different from FileUtil.delete() which returns true if the file absent on the disk
1216 private static boolean deleteFile(final File file) {
1217 File[] files = file.listFiles();
1218 if (files != null) {
1219 for (File file1 : files) {
1220 deleteFile(file1);
1224 for (int i = 0; i < 10; i++){
1225 if (file.delete()) {
1226 return true;
1228 if (!file.exists()) {
1229 return false;
1231 try {
1232 Thread.sleep(50);
1234 catch (InterruptedException ignored) {
1237 return false;
1240 private VirtualFile getGenerationOutputDir(final IntermediateOutputCompiler compiler, final Module module, final boolean forTestSources) {
1241 final Pair<VirtualFile, VirtualFile> outputs =
1242 myGenerationCompilerModuleToOutputDirMap.get(new Pair<IntermediateOutputCompiler, Module>(compiler, module));
1243 return forTestSources? outputs.getSecond() : outputs.getFirst();
1246 private boolean generateOutput(final CompileContextEx context,
1247 final GeneratingCompiler compiler,
1248 final boolean forceGenerate,
1249 final boolean onlyCheckStatus) throws ExitException {
1250 final GeneratingCompiler.GenerationItem[] allItems = compiler.getGenerationItems(context);
1251 final List<GeneratingCompiler.GenerationItem> toGenerate = new ArrayList<GeneratingCompiler.GenerationItem>();
1252 final List<File> filesToRefresh = new ArrayList<File>();
1253 final List<File> generatedFiles = new ArrayList<File>();
1254 final List<Module> affectedModules = new ArrayList<Module>();
1255 try {
1256 final StateCache<ValidityState> cache = getGeneratingCompilerCache(compiler);
1257 final Set<String> pathsToRemove = new HashSet<String>(cache.getUrls());
1259 final Map<GeneratingCompiler.GenerationItem, String> itemToOutputPathMap = new HashMap<GeneratingCompiler.GenerationItem, String>();
1260 final IOException[] ex = {null};
1261 ApplicationManager.getApplication().runReadAction(new Runnable() {
1262 public void run() {
1263 for (final GeneratingCompiler.GenerationItem item : allItems) {
1264 final Module itemModule = item.getModule();
1265 final String outputDirPath = CompilerPaths.getGenerationOutputPath(compiler, itemModule, item.isTestSource());
1266 final String outputPath = outputDirPath + "/" + item.getPath();
1267 itemToOutputPathMap.put(item, outputPath);
1269 try {
1270 final ValidityState savedState = cache.getState(outputPath);
1272 if (forceGenerate || savedState == null || !savedState.equalsTo(item.getValidityState())) {
1273 final String outputPathUrl = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, outputPath);
1274 if (context.getCompileScope().belongs(outputPathUrl)) {
1275 toGenerate.add(item);
1277 else {
1278 pathsToRemove.remove(outputPath);
1281 else {
1282 pathsToRemove.remove(outputPath);
1285 catch (IOException e) {
1286 ex[0] = e;
1291 if (ex[0] != null) {
1292 throw ex[0];
1295 if (onlyCheckStatus) {
1296 if (toGenerate.isEmpty() && pathsToRemove.isEmpty()) {
1297 return false;
1299 if (LOG.isDebugEnabled()) {
1300 if (!toGenerate.isEmpty()) {
1301 LOG.debug("Found items to generate, compiler " + compiler.getDescription());
1303 if (!pathsToRemove.isEmpty()) {
1304 LOG.debug("Found paths to remove, compiler " + compiler.getDescription());
1307 throw new ExitException(ExitStatus.CANCELLED);
1310 if (!pathsToRemove.isEmpty()) {
1311 CompilerUtil.runInContext(context, CompilerBundle.message("progress.synchronizing.output.directory"), new ThrowableRunnable<IOException>(){
1312 public void run() throws IOException {
1313 for (final String path : pathsToRemove) {
1314 final File file = new File(path);
1315 final boolean deleted = deleteFile(file);
1316 if (deleted) {
1317 cache.remove(path);
1318 filesToRefresh.add(file);
1325 final Map<Module, Set<GeneratingCompiler.GenerationItem>> moduleToItemMap =
1326 buildModuleToGenerationItemMap(toGenerate.toArray(new GeneratingCompiler.GenerationItem[toGenerate.size()]));
1327 List<Module> modules = new ArrayList<Module>(moduleToItemMap.size());
1328 for (final Module module : moduleToItemMap.keySet()) {
1329 modules.add(module);
1331 ModuleCompilerUtil.sortModules(myProject, modules);
1333 for (final Module module : modules) {
1334 CompilerUtil.runInContext(context, "Generating output from "+compiler.getDescription(),new ThrowableRunnable<IOException>(){
1335 public void run() throws IOException {
1336 final Set<GeneratingCompiler.GenerationItem> items = moduleToItemMap.get(module);
1337 if (items != null && !items.isEmpty()) {
1338 final GeneratingCompiler.GenerationItem[][] productionAndTestItems = splitGenerationItems(items);
1339 for (GeneratingCompiler.GenerationItem[] _items : productionAndTestItems) {
1340 if (_items.length == 0) continue;
1341 final VirtualFile outputDir = getGenerationOutputDir(compiler, module, _items[0].isTestSource());
1342 final GeneratingCompiler.GenerationItem[] successfullyGenerated = compiler.generate(context, _items, outputDir);
1344 CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable<IOException>() {
1345 public void run() throws IOException {
1346 if (successfullyGenerated.length > 0) {
1347 affectedModules.add(module);
1349 for (final GeneratingCompiler.GenerationItem item : successfullyGenerated) {
1350 final String fullOutputPath = itemToOutputPathMap.get(item);
1351 cache.update(fullOutputPath, item.getValidityState());
1352 final File file = new File(fullOutputPath);
1353 filesToRefresh.add(file);
1354 generatedFiles.add(file);
1355 context.getProgressIndicator().setText2(file.getPath());
1365 catch (IOException e) {
1366 LOG.info(e);
1367 context.requestRebuildNextTime(e.getMessage());
1368 throw new ExitException(ExitStatus.ERRORS);
1370 finally {
1371 CompilerUtil.refreshIOFiles(filesToRefresh);
1372 if (!generatedFiles.isEmpty()) {
1373 DumbService.getInstance(myProject).waitForSmartMode();
1374 List<VirtualFile> vFiles = ApplicationManager.getApplication().runReadAction(new Computable<List<VirtualFile>>() {
1375 public List<VirtualFile> compute() {
1376 final ArrayList<VirtualFile> vFiles = new ArrayList<VirtualFile>(generatedFiles.size());
1377 for (File generatedFile : generatedFiles) {
1378 final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(generatedFile);
1379 if (vFile != null) {
1380 vFiles.add(vFile);
1383 return vFiles;
1386 if (forceGenerate) {
1387 context.addScope(new FileSetCompileScope(vFiles, affectedModules.toArray(new Module[affectedModules.size()])));
1389 context.markGenerated(vFiles);
1392 return !toGenerate.isEmpty() || !filesToRefresh.isEmpty();
1395 private static GeneratingCompiler.GenerationItem[][] splitGenerationItems(final Set<GeneratingCompiler.GenerationItem> items) {
1396 final List<GeneratingCompiler.GenerationItem> production = new ArrayList<GeneratingCompiler.GenerationItem>();
1397 final List<GeneratingCompiler.GenerationItem> tests = new ArrayList<GeneratingCompiler.GenerationItem>();
1398 for (GeneratingCompiler.GenerationItem item : items) {
1399 if (item.isTestSource()) {
1400 tests.add(item);
1402 else {
1403 production.add(item);
1406 return new GeneratingCompiler.GenerationItem[][]{
1407 production.toArray(new GeneratingCompiler.GenerationItem[production.size()]),
1408 tests.toArray(new GeneratingCompiler.GenerationItem[tests.size()])
1412 private boolean compileSources(final CompileContextEx context, final Chunk<Module> moduleChunk, final TranslatingCompiler compiler, final Collection<VirtualFile> srcSnapshot,
1413 final boolean forceCompile,
1414 final boolean isRebuild,
1415 final boolean trackDependencies,
1416 final boolean onlyCheckStatus,
1417 TranslatingCompiler.OutputSink sink) throws ExitException {
1419 final Set<VirtualFile> toCompile = new HashSet<VirtualFile>();
1420 final List<Trinity<File, String, Boolean>> toDelete = new ArrayList<Trinity<File, String, Boolean>>();
1421 context.getProgressIndicator().pushState();
1423 final boolean[] wereFilesDeleted = new boolean[]{false};
1424 try {
1425 ApplicationManager.getApplication().runReadAction(new Runnable() {
1426 public void run() {
1428 TranslatingCompilerFilesMonitor.getInstance().collectFiles(
1429 context, compiler, srcSnapshot.iterator(), forceCompile, isRebuild, toCompile, toDelete
1431 if (trackDependencies && !toCompile.isEmpty()) { // should add dependent files
1432 // todo: drop this?
1433 final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
1434 final PsiManager psiManager = PsiManager.getInstance(myProject);
1435 for (final VirtualFile file : toCompile.toArray(new VirtualFile[toCompile.size()])) {
1436 if (fileTypeManager.getFileTypeByFile(file) == StdFileTypes.JAVA) {
1437 final PsiFile psiFile = psiManager.findFile(file);
1438 if (psiFile != null) {
1439 addDependentFiles(psiFile, toCompile, compiler, context);
1447 if (onlyCheckStatus) {
1448 if (toDelete.isEmpty() && toCompile.isEmpty()) {
1449 return false;
1451 if (LOG.isDebugEnabled()) {
1452 if (!toDelete.isEmpty()) {
1453 LOG.debug("Found items to delete, compiler " + compiler.getDescription());
1455 if (!toCompile.isEmpty()) {
1456 LOG.debug("Found items to compile, compiler " + compiler.getDescription());
1459 throw new ExitException(ExitStatus.CANCELLED);
1462 if (!toDelete.isEmpty()) {
1463 try {
1464 wereFilesDeleted[0] = syncOutputDir(context, toDelete);
1466 catch (CacheCorruptedException e) {
1467 LOG.info(e);
1468 context.requestRebuildNextTime(e.getMessage());
1472 if ((wereFilesDeleted[0] || !toCompile.isEmpty()) && context.getMessageCount(CompilerMessageCategory.ERROR) == 0) {
1473 compiler.compile(context, moduleChunk, toCompile.toArray(new VirtualFile[toCompile.size()]), sink);
1476 finally {
1477 context.getProgressIndicator().popState();
1479 return !toCompile.isEmpty() || wereFilesDeleted[0];
1482 private static boolean syncOutputDir(final CompileContextEx context, final Collection<Trinity<File, String, Boolean>> toDelete) throws CacheCorruptedException {
1483 final int total = toDelete.size();
1484 final DependencyCache dependencyCache = context.getDependencyCache();
1485 final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode();
1487 final List<File> filesToRefresh = new ArrayList<File>();
1488 final boolean[] wereFilesDeleted = {false};
1489 CompilerUtil.runInContext(context, CompilerBundle.message("progress.synchronizing.output.directory"), new ThrowableRunnable<CacheCorruptedException>(){
1490 public void run() throws CacheCorruptedException {
1491 final long start = System.currentTimeMillis();
1492 try {
1493 int current = 0;
1494 for (final Trinity<File, String, Boolean> trinity : toDelete) {
1495 final File outputPath = trinity.getFirst();
1496 context.getProgressIndicator().checkCanceled();
1497 context.getProgressIndicator().setFraction((double)++current / total);
1498 context.getProgressIndicator().setText2(outputPath.getPath());
1499 filesToRefresh.add(outputPath);
1500 if (isTestMode) {
1501 LOG.assertTrue(outputPath.exists());
1503 if (!deleteFile(outputPath)) {
1504 if (isTestMode && outputPath.exists()) {
1505 LOG.error("Was not able to delete output file: " + outputPath.getPath());
1507 continue;
1509 wereFilesDeleted[0] = true;
1511 // update zip here
1512 //final String outputDir = myOutputFinder.lookupOutputPath(outputPath);
1513 //if (outputDir != null) {
1514 // try {
1515 // context.updateZippedOuput(outputDir, FileUtil.toSystemIndependentName(outputPath.getPath()).substring(outputDir.length() + 1));
1516 // }
1517 // catch (IOException e) {
1518 // LOG.info(e);
1519 // }
1522 final String className = trinity.getSecond();
1523 if (className != null) {
1524 final int id = dependencyCache.getSymbolTable().getId(className);
1525 dependencyCache.addTraverseRoot(id);
1526 final boolean sourcePresent = trinity.getThird().booleanValue();
1527 if (!sourcePresent) {
1528 dependencyCache.markSourceRemoved(id);
1531 if (isTestMode) {
1532 CompilerManagerImpl.addDeletedPath(outputPath.getPath());
1536 finally {
1537 CompilerUtil.logDuration("Sync output directory", System.currentTimeMillis() - start);
1538 CompilerUtil.refreshIOFiles(filesToRefresh);
1542 return wereFilesDeleted[0];
1545 private void addDependentFiles(final PsiFile psiFile, Set<VirtualFile> toCompile, TranslatingCompiler compiler, CompileContext context) {
1546 final DependenciesBuilder builder = new ForwardDependenciesBuilder(myProject, new AnalysisScope(psiFile));
1547 builder.analyze();
1548 final Map<PsiFile, Set<PsiFile>> dependencies = builder.getDependencies();
1549 final Set<PsiFile> dependentFiles = dependencies.get(psiFile);
1550 if (dependentFiles != null && !dependentFiles.isEmpty()) {
1551 final TranslatingCompilerFilesMonitor monitor = TranslatingCompilerFilesMonitor.getInstance();
1552 for (final PsiFile dependentFile : dependentFiles) {
1553 if (dependentFile instanceof PsiCompiledElement) {
1554 continue;
1556 final VirtualFile vFile = dependentFile.getVirtualFile();
1557 if (vFile == null || toCompile.contains(vFile)) {
1558 continue;
1560 if (!compiler.isCompilableFile(vFile, context)) {
1561 continue;
1563 if (!monitor.isMarkedForCompilation(myProject, vFile)) {
1564 continue; // no need to compile since already compiled
1566 toCompile.add(vFile);
1567 addDependentFiles(dependentFile, toCompile, compiler, context);
1572 // [mike] performance optimization - this method is accessed > 15,000 times in Aurora
1573 private String getModuleOutputPath(final Module module, boolean inTestSourceContent) {
1574 final Map<Module, String> map = inTestSourceContent ? myModuleTestOutputPaths : myModuleOutputPaths;
1575 String path = map.get(module);
1576 if (path == null) {
1577 path = CompilerPaths.getModuleOutputPath(module, inTestSourceContent);
1578 map.put(module, path);
1581 return path;
1584 private boolean processFiles(final FileProcessingCompilerAdapter adapter,
1585 final boolean forceCompile,
1586 final boolean checkScope,
1587 final boolean onlyCheckStatus, final CacheDeferredUpdater cacheUpdater) throws ExitException, IOException {
1588 final CompileContextEx context = (CompileContextEx)adapter.getCompileContext();
1589 final FileProcessingCompilerStateCache cache = getFileProcessingCompilerCache(adapter.getCompiler());
1590 final FileProcessingCompiler.ProcessingItem[] items = adapter.getProcessingItems();
1591 if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
1592 return false;
1594 final CompileScope scope = context.getCompileScope();
1595 final List<FileProcessingCompiler.ProcessingItem> toProcess = new ArrayList<FileProcessingCompiler.ProcessingItem>();
1596 final Set<String> allUrls = new HashSet<String>();
1597 final IOException[] ex = {null};
1598 DumbService.getInstance(myProject).waitForSmartMode();
1599 ApplicationManager.getApplication().runReadAction(new Runnable() {
1600 public void run() {
1601 try {
1602 for (FileProcessingCompiler.ProcessingItem item : items) {
1603 final VirtualFile file = item.getFile();
1604 if (file == null) {
1605 LOG.error("FileProcessingCompiler.ProcessingItem.getFile() must not return null: compiler " + adapter.getCompiler().getDescription());
1606 continue;
1608 final String url = file.getUrl();
1609 allUrls.add(url);
1610 if (!forceCompile && cache.getTimestamp(url) == file.getTimeStamp()) {
1611 final ValidityState state = cache.getExtState(url);
1612 final ValidityState itemState = item.getValidityState();
1613 if (state != null ? state.equalsTo(itemState) : itemState == null) {
1614 continue;
1617 if (LOG.isDebugEnabled()) {
1618 LOG.debug("Adding item to process: " + url + "; saved ts= " + cache.getTimestamp(url) + "; VFS ts=" + file.getTimeStamp());
1620 toProcess.add(item);
1623 catch (IOException e) {
1624 ex[0] = e;
1629 if (ex[0] != null) {
1630 throw ex[0];
1633 final Collection<String> urls = cache.getUrls();
1634 final List<String> urlsToRemove = new ArrayList<String>();
1635 if (!urls.isEmpty()) {
1636 CompilerUtil.runInContext(context, CompilerBundle.message("progress.processing.outdated.files"), new ThrowableRunnable<IOException>(){
1637 public void run() throws IOException {
1638 ApplicationManager.getApplication().runReadAction(new Runnable() {
1639 public void run() {
1640 for (final String url : urls) {
1641 if (!allUrls.contains(url)) {
1642 if (!checkScope || scope.belongs(url)) {
1643 urlsToRemove.add(url);
1649 if (!onlyCheckStatus && !urlsToRemove.isEmpty()) {
1650 for (final String url : urlsToRemove) {
1651 adapter.processOutdatedItem(context, url, cache.getExtState(url));
1652 cache.remove(url);
1659 if (onlyCheckStatus) {
1660 if (urlsToRemove.isEmpty() && toProcess.isEmpty()) {
1661 return false;
1663 if (LOG.isDebugEnabled()) {
1664 if (!urlsToRemove.isEmpty()) {
1665 LOG.debug("Found urls to remove, compiler " + adapter.getCompiler().getDescription());
1666 for (String url : urlsToRemove) {
1667 LOG.debug("\t" + url);
1670 if (!toProcess.isEmpty()) {
1671 LOG.debug("Found items to compile, compiler " + adapter.getCompiler().getDescription());
1672 for (FileProcessingCompiler.ProcessingItem item : toProcess) {
1673 LOG.debug("\t" + item.getFile().getPresentableUrl());
1677 throw new ExitException(ExitStatus.CANCELLED);
1680 if (toProcess.isEmpty()) {
1681 return false;
1684 final FileProcessingCompiler.ProcessingItem[] processed =
1685 adapter.process(toProcess.toArray(new FileProcessingCompiler.ProcessingItem[toProcess.size()]));
1687 if (processed.length == 0) {
1688 return true;
1690 CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable<IOException>() {
1691 public void run() throws IOException {
1692 final List<VirtualFile> vFiles = new ArrayList<VirtualFile>(processed.length);
1693 for (FileProcessingCompiler.ProcessingItem aProcessed : processed) {
1694 final VirtualFile file = aProcessed.getFile();
1695 vFiles.add(file);
1696 if (LOG.isDebugEnabled()) {
1697 LOG.debug("File processed by " + adapter.getCompiler().getDescription());
1698 LOG.debug("\tFile processed " + file.getPresentableUrl() + "; ts=" + file.getTimeStamp());
1701 //final String path = file.getPath();
1702 //final String outputDir = myOutputFinder.lookupOutputPath(path);
1703 //if (outputDir != null) {
1704 // context.updateZippedOuput(outputDir, path.substring(outputDir.length() + 1));
1707 LocalFileSystem.getInstance().refreshFiles(vFiles);
1708 if (LOG.isDebugEnabled()) {
1709 LOG.debug("Files after VFS refresh:");
1710 for (VirtualFile file : vFiles) {
1711 LOG.debug("\t" + file.getPresentableUrl() + "; ts=" + file.getTimeStamp());
1714 for (FileProcessingCompiler.ProcessingItem item : processed) {
1715 cacheUpdater.addFileForUpdate(item, cache);
1719 return true;
1722 private FileProcessingCompilerStateCache getFileProcessingCompilerCache(FileProcessingCompiler compiler) throws IOException {
1723 return CompilerCacheManager.getInstance(myProject).getFileProcessingCompilerCache(compiler);
1726 private StateCache<ValidityState> getGeneratingCompilerCache(final GeneratingCompiler compiler) throws IOException {
1727 return CompilerCacheManager.getInstance(myProject).getGeneratingCompilerCache(compiler);
1730 public void executeCompileTask(final CompileTask task, final CompileScope scope, final String contentName, final Runnable onTaskFinished) {
1731 final CompilerTask progressManagerTask =
1732 new CompilerTask(myProject, CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_IN_BACKGROUND, contentName, false);
1733 final CompileContextImpl compileContext = new CompileContextImpl(myProject, progressManagerTask, scope, null, false, false);
1735 FileDocumentManager.getInstance().saveAllDocuments();
1737 progressManagerTask.start(new Runnable() {
1738 public void run() {
1739 try {
1740 task.execute(compileContext);
1742 catch (ProcessCanceledException ex) {
1743 // suppressed
1745 finally {
1746 compileContext.commitZipFiles();
1747 if (onTaskFinished != null) {
1748 onTaskFinished.run();
1752 }, null);
1755 private boolean executeCompileTasks(final CompileContext context, final boolean beforeTasks) {
1756 final CompilerManager manager = CompilerManager.getInstance(myProject);
1757 final ProgressIndicator progressIndicator = context.getProgressIndicator();
1758 progressIndicator.pushState();
1759 try {
1760 CompileTask[] tasks = beforeTasks ? manager.getBeforeTasks() : manager.getAfterTasks();
1761 if (tasks.length > 0) {
1762 progressIndicator.setText(beforeTasks
1763 ? CompilerBundle.message("progress.executing.precompile.tasks")
1764 : CompilerBundle.message("progress.executing.postcompile.tasks"));
1765 for (CompileTask task : tasks) {
1766 if (!task.execute(context)) {
1767 return false;
1772 finally {
1773 progressIndicator.popState();
1774 WindowManager.getInstance().getStatusBar(myProject).setInfo("");
1775 if (progressIndicator instanceof CompilerTask) {
1776 ApplicationManager.getApplication().invokeLater(new Runnable() {
1777 public void run() {
1778 ((CompilerTask)progressIndicator).showCompilerContent();
1783 return true;
1786 private boolean validateCompilerConfiguration(final CompileScope scope, boolean checkOutputAndSourceIntersection) {
1787 final Module[] scopeModules = scope.getAffectedModules()/*ModuleManager.getInstance(myProject).getModules()*/;
1788 final List<String> modulesWithoutOutputPathSpecified = new ArrayList<String>();
1789 final List<String> modulesWithoutJdkAssigned = new ArrayList<String>();
1790 final Set<File> nonExistingOutputPaths = new HashSet<File>();
1792 for (final Module module : scopeModules) {
1793 final boolean hasSources = hasSources(module, false);
1794 final boolean hasTestSources = hasSources(module, true);
1795 if (!hasSources && !hasTestSources) {
1796 // If module contains no sources, shouldn't have to select JDK or output directory (SCR #19333)
1797 // todo still there may be problems with this approach if some generated files are attributed by this module
1798 continue;
1800 final Sdk jdk = ModuleRootManager.getInstance(module).getSdk();
1801 if (jdk == null) {
1802 modulesWithoutJdkAssigned.add(module.getName());
1804 final String outputPath = getModuleOutputPath(module, false);
1805 final String testsOutputPath = getModuleOutputPath(module, true);
1806 if (outputPath == null && testsOutputPath == null) {
1807 modulesWithoutOutputPathSpecified.add(module.getName());
1809 else {
1810 if (outputPath != null) {
1811 final File file = new File(outputPath.replace('/', File.separatorChar));
1812 if (!file.exists()) {
1813 nonExistingOutputPaths.add(file);
1816 else {
1817 if (hasSources) {
1818 modulesWithoutOutputPathSpecified.add(module.getName());
1821 if (testsOutputPath != null) {
1822 final File f = new File(testsOutputPath.replace('/', File.separatorChar));
1823 if (!f.exists()) {
1824 nonExistingOutputPaths.add(f);
1827 else {
1828 if (hasTestSources) {
1829 modulesWithoutOutputPathSpecified.add(module.getName());
1834 if (!modulesWithoutJdkAssigned.isEmpty()) {
1835 showNotSpecifiedError("error.jdk.not.specified", modulesWithoutJdkAssigned, ProjectBundle.message("modules.classpath.title"));
1836 return false;
1839 if (!modulesWithoutOutputPathSpecified.isEmpty()) {
1840 showNotSpecifiedError("error.output.not.specified", modulesWithoutOutputPathSpecified, CommonContentEntriesEditor.NAME);
1841 return false;
1844 if (!nonExistingOutputPaths.isEmpty()) {
1845 for (File file : nonExistingOutputPaths) {
1846 final boolean succeeded = file.mkdirs();
1847 if (!succeeded) {
1848 if (file.exists()) {
1849 // for overlapping paths, this one might have been created as an intermediate path on a previous iteration
1850 continue;
1852 Messages.showMessageDialog(myProject, CompilerBundle.message("error.failed.to.create.directory", file.getPath()),
1853 CommonBundle.getErrorTitle(), Messages.getErrorIcon());
1854 return false;
1857 final Boolean refreshSuccess = ApplicationManager.getApplication().runWriteAction(new Computable<Boolean>() {
1858 public Boolean compute() {
1859 LocalFileSystem.getInstance().refreshIoFiles(nonExistingOutputPaths);
1860 for (File file : nonExistingOutputPaths) {
1861 if (LocalFileSystem.getInstance().findFileByIoFile(file) == null) {
1862 return Boolean.FALSE;
1865 return Boolean.TRUE;
1868 if (!refreshSuccess.booleanValue()) {
1869 return false;
1871 dropScopesCaches();
1874 if (checkOutputAndSourceIntersection) {
1875 if (myShouldClearOutputDirectory) {
1876 if (!validateOutputAndSourcePathsIntersection()) {
1877 return false;
1881 final List<Chunk<Module>> chunks = ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(scopeModules));
1882 final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject);
1883 for (final Chunk<Module> chunk : chunks) {
1884 final Set<Module> chunkModules = chunk.getNodes();
1885 if (chunkModules.size() <= 1) {
1886 continue; // no need to check one-module chunks
1888 if (config.isAnnotationProcessorsEnabled()) {
1889 final Set<Module> excluded = config.getExcludedModules();
1890 for (Module chunkModule : chunkModules) {
1891 if (!excluded.contains(chunkModule)) {
1892 showCyclesNotSupportedForAnnotationProcessors(chunkModules.toArray(new Module[chunkModules.size()]));
1893 return false;
1897 Sdk jdk = null;
1898 LanguageLevel languageLevel = null;
1899 for (final Module module : chunkModules) {
1900 final Sdk moduleJdk = ModuleRootManager.getInstance(module).getSdk();
1901 if (jdk == null) {
1902 jdk = moduleJdk;
1904 else {
1905 if (!jdk.equals(moduleJdk)) {
1906 showCyclicModulesHaveDifferentJdksError(chunkModules.toArray(new Module[chunkModules.size()]));
1907 return false;
1911 LanguageLevel moduleLanguageLevel = LanguageLevelUtil.getEffectiveLanguageLevel(module);
1912 if (languageLevel == null) {
1913 languageLevel = moduleLanguageLevel;
1915 else {
1916 if (!languageLevel.equals(moduleLanguageLevel)) {
1917 showCyclicModulesHaveDifferentLanguageLevel(chunkModules.toArray(new Module[chunkModules.size()]));
1918 return false;
1923 final Compiler[] allCompilers = CompilerManager.getInstance(myProject).getCompilers(Compiler.class);
1924 for (Compiler compiler : allCompilers) {
1925 if (!compiler.validateConfiguration(scope)) {
1926 return false;
1929 return true;
1932 private void showCyclicModulesHaveDifferentLanguageLevel(Module[] modulesInChunk) {
1933 LOG.assertTrue(modulesInChunk.length > 0);
1934 String moduleNameToSelect = modulesInChunk[0].getName();
1935 final String moduleNames = getModulesString(modulesInChunk);
1936 Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.language.level", moduleNames),
1937 CommonBundle.getErrorTitle(), Messages.getErrorIcon());
1938 showConfigurationDialog(moduleNameToSelect, null);
1941 private void showCyclicModulesHaveDifferentJdksError(Module[] modulesInChunk) {
1942 LOG.assertTrue(modulesInChunk.length > 0);
1943 String moduleNameToSelect = modulesInChunk[0].getName();
1944 final String moduleNames = getModulesString(modulesInChunk);
1945 Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.jdk", moduleNames),
1946 CommonBundle.getErrorTitle(), Messages.getErrorIcon());
1947 showConfigurationDialog(moduleNameToSelect, null);
1950 private void showCyclesNotSupportedForAnnotationProcessors(Module[] modulesInChunk) {
1951 LOG.assertTrue(modulesInChunk.length > 0);
1952 String moduleNameToSelect = modulesInChunk[0].getName();
1953 final String moduleNames = getModulesString(modulesInChunk);
1954 Messages.showMessageDialog(myProject, CompilerBundle.message("error.annotation.processing.not.supported.for.module.cycles", moduleNames),
1955 CommonBundle.getErrorTitle(), Messages.getErrorIcon());
1956 showConfigurationDialog(moduleNameToSelect, null);
1959 private static String getModulesString(Module[] modulesInChunk) {
1960 final StringBuilder moduleNames = StringBuilderSpinAllocator.alloc();
1961 try {
1962 for (Module module : modulesInChunk) {
1963 if (moduleNames.length() > 0) {
1964 moduleNames.append("\n");
1966 moduleNames.append("\"").append(module.getName()).append("\"");
1968 return moduleNames.toString();
1970 finally {
1971 StringBuilderSpinAllocator.dispose(moduleNames);
1975 private static boolean hasSources(Module module, boolean checkTestSources) {
1976 final ContentEntry[] contentEntries = ModuleRootManager.getInstance(module).getContentEntries();
1977 for (final ContentEntry contentEntry : contentEntries) {
1978 final SourceFolder[] sourceFolders = contentEntry.getSourceFolders();
1979 for (final SourceFolder sourceFolder : sourceFolders) {
1980 if (sourceFolder.getFile() == null) {
1981 continue; // skip invalid source folders
1983 if (checkTestSources) {
1984 if (sourceFolder.isTestSource()) {
1985 return true;
1988 else {
1989 if (!sourceFolder.isTestSource()) {
1990 return true;
1995 return false;
1998 private void showNotSpecifiedError(@NonNls final String resourceId, List<String> modules, String tabNameToSelect) {
1999 String nameToSelect = null;
2000 final StringBuilder names = StringBuilderSpinAllocator.alloc();
2001 final String message;
2002 try {
2003 final int maxModulesToShow = 10;
2004 for (String name : modules.size() > maxModulesToShow ? modules.subList(0, maxModulesToShow) : modules) {
2005 if (nameToSelect == null) {
2006 nameToSelect = name;
2008 if (names.length() > 0) {
2009 names.append(",\n");
2011 names.append("\"");
2012 names.append(name);
2013 names.append("\"");
2015 if (modules.size() > maxModulesToShow) {
2016 names.append(",\n...");
2018 message = CompilerBundle.message(resourceId, modules.size(), names.toString());
2020 finally {
2021 StringBuilderSpinAllocator.dispose(names);
2024 if (ApplicationManager.getApplication().isUnitTestMode()) {
2025 LOG.error(message);
2028 Messages.showMessageDialog(myProject, message, CommonBundle.getErrorTitle(), Messages.getErrorIcon());
2029 showConfigurationDialog(nameToSelect, tabNameToSelect);
2032 private boolean validateOutputAndSourcePathsIntersection() {
2033 final Module[] allModules = ModuleManager.getInstance(myProject).getModules();
2034 final VirtualFile[] outputPaths = CompilerPathsEx.getOutputDirectories(allModules);
2035 final Set<VirtualFile> affectedOutputPaths = new HashSet<VirtualFile>();
2036 for (Module allModule : allModules) {
2037 final ModuleRootManager rootManager = ModuleRootManager.getInstance(allModule);
2038 final VirtualFile[] sourceRoots = rootManager.getSourceRoots();
2039 for (final VirtualFile outputPath : outputPaths) {
2040 for (VirtualFile sourceRoot : sourceRoots) {
2041 if (VfsUtil.isAncestor(outputPath, sourceRoot, true) || VfsUtil.isAncestor(sourceRoot, outputPath, false)) {
2042 affectedOutputPaths.add(outputPath);
2047 if (!affectedOutputPaths.isEmpty()) {
2048 final StringBuilder paths = new StringBuilder();
2049 for (final VirtualFile affectedOutputPath : affectedOutputPaths) {
2050 if (paths.length() < 0) {
2051 paths.append("\n");
2053 paths.append(affectedOutputPath.getPath().replace('/', File.separatorChar));
2055 final int answer = Messages.showOkCancelDialog(myProject,
2056 CompilerBundle.message("warning.sources.under.output.paths", paths.toString()),
2057 CommonBundle.getErrorTitle(), Messages.getWarningIcon());
2058 if (answer == 0) { // ok
2059 myShouldClearOutputDirectory = false;
2060 return true;
2062 else {
2063 return false;
2066 return true;
2069 private void showConfigurationDialog(String moduleNameToSelect, String tabNameToSelect) {
2070 ProjectSettingsService.getInstance(myProject).showModuleConfigurationDialog(moduleNameToSelect, tabNameToSelect, false);
2073 private static VirtualFile lookupVFile(final IntermediateOutputCompiler compiler, final Module module, final boolean forTestSources) {
2074 final File file = new File(CompilerPaths.getGenerationOutputPath(compiler, module, forTestSources));
2075 final LocalFileSystem lfs = LocalFileSystem.getInstance();
2077 VirtualFile vFile = lfs.findFileByIoFile(file);
2078 if (vFile != null) {
2079 return vFile;
2082 final boolean justCreated = file.mkdirs();
2083 vFile = lfs.refreshAndFindFileByIoFile(file);
2085 assert vFile != null: "Virtual file not found for " + file.getPath() + "; mkdirs() exit code is " + justCreated;
2087 return vFile;
2090 private static class CacheDeferredUpdater {
2091 private final Map<VirtualFile, List<Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem>>> myData = new java.util.HashMap<VirtualFile, List<Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem>>>();
2093 public void addFileForUpdate(final FileProcessingCompiler.ProcessingItem item, FileProcessingCompilerStateCache cache) {
2094 final VirtualFile file = item.getFile();
2095 List<Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem>> list = myData.get(file);
2096 if (list == null) {
2097 list = new ArrayList<Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem>>();
2098 myData.put(file, list);
2100 list.add(new Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem>(cache, item));
2103 public void doUpdate() throws IOException{
2104 final IOException[] ex = {null};
2105 ApplicationManager.getApplication().runReadAction(new Runnable() {
2106 public void run() {
2107 try {
2108 for (Map.Entry<VirtualFile, List<Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem>>> entry : myData.entrySet()) {
2109 for (Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem> pair : entry.getValue()) {
2110 final FileProcessingCompiler.ProcessingItem item = pair.getSecond();
2111 pair.getFirst().update(entry.getKey(), item.getValidityState());
2115 catch (IOException e) {
2116 ex[0] = e;
2120 if (ex[0] != null) {
2121 throw ex[0];
2126 private static class TranslatorsOutputSink implements TranslatingCompiler.OutputSink {
2127 final Map<String, Collection<TranslatingCompiler.OutputItem>> myPostponedItems = new HashMap<String, Collection<TranslatingCompiler.OutputItem>>();
2128 private final CompileContextEx myContext;
2129 private final TranslatingCompiler[] myCompilers;
2130 private int myCurrentCompilerIdx;
2131 //private LinkedBlockingQueue<Future> myFutures = new LinkedBlockingQueue<Future>();
2133 private TranslatorsOutputSink(CompileContextEx context, TranslatingCompiler[] compilers) {
2134 myContext = context;
2135 this.myCompilers = compilers;
2138 public void setCurrentCompilerIndex(int index) {
2139 myCurrentCompilerIdx = index;
2142 public void add(final String outputRoot, final Collection<TranslatingCompiler.OutputItem> items, final VirtualFile[] filesToRecompile) {
2143 final TranslatingCompiler compiler = myCompilers[myCurrentCompilerIdx];
2144 if (compiler instanceof IntermediateOutputCompiler) {
2145 final LocalFileSystem lfs = LocalFileSystem.getInstance();
2146 final List<VirtualFile> outputs = new ArrayList<VirtualFile>();
2147 for (TranslatingCompiler.OutputItem item : items) {
2148 final VirtualFile vFile = lfs.findFileByPath(item.getOutputPath());
2149 if (vFile != null) {
2150 outputs.add(vFile);
2153 myContext.markGenerated(outputs);
2155 final int nextCompilerIdx = myCurrentCompilerIdx + 1;
2156 try {
2157 if (nextCompilerIdx < myCompilers.length ) {
2158 final Map<String, Collection<TranslatingCompiler.OutputItem>> updateNow = new java.util.HashMap<String, Collection<TranslatingCompiler.OutputItem>>();
2159 // process postponed
2160 for (Map.Entry<String, Collection<TranslatingCompiler.OutputItem>> entry : myPostponedItems.entrySet()) {
2161 final String outputDir = entry.getKey();
2162 final Collection<TranslatingCompiler.OutputItem> postponed = entry.getValue();
2163 for (Iterator<TranslatingCompiler.OutputItem> it = postponed.iterator(); it.hasNext();) {
2164 TranslatingCompiler.OutputItem item = it.next();
2165 boolean shouldPostpone = false;
2166 for (int idx = nextCompilerIdx; idx < myCompilers.length; idx++) {
2167 if (shouldPostpone = myCompilers[idx].isCompilableFile(item.getSourceFile(), myContext)) {
2168 break;
2171 if (!shouldPostpone) {
2172 // the file is not compilable by the rest of compilers, so it is safe to update it now
2173 it.remove();
2174 addItemToMap(updateNow, outputDir, item);
2178 // process items from current compilation
2179 for (TranslatingCompiler.OutputItem item : items) {
2180 boolean shouldPostpone = false;
2181 for (int idx = nextCompilerIdx; idx < myCompilers.length; idx++) {
2182 if (shouldPostpone = myCompilers[idx].isCompilableFile(item.getSourceFile(), myContext)) {
2183 break;
2186 if (shouldPostpone) {
2187 // the file is compilable by the next compiler in row, update should be postponed
2188 addItemToMap(myPostponedItems, outputRoot, item);
2190 else {
2191 addItemToMap(updateNow, outputRoot, item);
2195 if (updateNow.size() == 1) {
2196 final Map.Entry<String, Collection<TranslatingCompiler.OutputItem>> entry = updateNow.entrySet().iterator().next();
2197 final String outputDir = entry.getKey();
2198 final Collection<TranslatingCompiler.OutputItem> itemsToUpdate = entry.getValue();
2199 TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputDir, itemsToUpdate, filesToRecompile);
2201 else {
2202 for (Map.Entry<String, Collection<TranslatingCompiler.OutputItem>> entry : updateNow.entrySet()) {
2203 final String outputDir = entry.getKey();
2204 final Collection<TranslatingCompiler.OutputItem> itemsToUpdate = entry.getValue();
2205 TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputDir, itemsToUpdate, VirtualFile.EMPTY_ARRAY);
2207 if (filesToRecompile.length > 0) {
2208 TranslatingCompilerFilesMonitor.getInstance().update(myContext, null, Collections.<TranslatingCompiler.OutputItem>emptyList(), filesToRecompile);
2212 else {
2213 TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputRoot, items, filesToRecompile);
2216 catch (IOException e) {
2217 LOG.info(e);
2218 myContext.requestRebuildNextTime(e.getMessage());
2222 private void addItemToMap(Map<String, Collection<TranslatingCompiler.OutputItem>> map, String outputDir, TranslatingCompiler.OutputItem item) {
2223 Collection<TranslatingCompiler.OutputItem> collection = map.get(outputDir);
2224 if (collection == null) {
2225 collection = new ArrayList<TranslatingCompiler.OutputItem>();
2226 map.put(outputDir, collection);
2228 collection.add(item);
2231 public void flushPostponedItems() {
2232 final TranslatingCompilerFilesMonitor filesMonitor = TranslatingCompilerFilesMonitor.getInstance();
2233 try {
2234 for (Map.Entry<String, Collection<TranslatingCompiler.OutputItem>> entry : myPostponedItems.entrySet()) {
2235 final String outputDir = entry.getKey();
2236 final Collection<TranslatingCompiler.OutputItem> items = entry.getValue();
2237 filesMonitor.update(myContext, outputDir, items, VirtualFile.EMPTY_ARRAY);
2240 catch (IOException e) {
2241 LOG.info(e);
2242 myContext.requestRebuildNextTime(e.getMessage());