Files
openide/source/com/intellij/compiler/impl/CompileDriver.java
2005-01-19 16:35:14 +03:00

1595 lines
65 KiB
Java

/*
* @author: Eugene Zhuravlev
* Date: Jan 17, 2003
* Time: 1:42:26 PM
*/
package com.intellij.compiler.impl;
import com.intellij.analysis.AnalysisScope;
import com.intellij.compiler.*;
import com.intellij.compiler.make.CacheCorruptedException;
import com.intellij.compiler.make.DependencyCache;
import com.intellij.compiler.make.MakeUtil;
import com.intellij.compiler.progress.CompilerProgressIndicator;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.compiler.*;
import com.intellij.openapi.compiler.Compiler;
import com.intellij.openapi.compiler.ex.CompilerPathsEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.ProjectJdk;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.ui.configuration.ContentEntriesEditor;
import com.intellij.openapi.roots.ui.configuration.LibrariesEditor;
import com.intellij.openapi.roots.ui.configuration.ModulesConfigurator;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.packageDependencies.ForwardDependenciesBuilder;
import com.intellij.packageDependencies.DependenciesBuilder;
import com.intellij.psi.PsiCompiledElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.ProfilingUtil;
import com.intellij.j2ee.make.impl.MakeUtilImpl;
import java.io.*;
import java.util.*;
public class CompileDriver {
private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileDriver");
private final Project myProject;
private final Map myCompilerToCacheMap = new com.intellij.util.containers.HashMap();
private Map<Pair<Compiler, Module>, VirtualFile> myGenerationCompilerModuleToOutputDirMap;
private String myCachesDirectoryPath;
private final ProjectCompileScope myProjectCompileScope;
private Set<String> myOutputFilesOnDisk = null;
private boolean myShouldClearOutputDirectory;
private Map<Module, String> myModuleOutputPaths = new HashMap<Module, String>();
private Map<Module, String> myModuleTestOutputPaths = new HashMap<Module, String>();
private CompileDriver.ExitStatus myExitStatus = null;
private ProjectRootManager myProjectRootManager;
private static final String VERSION_FILE_NAME = "version.dat";
private static final String LOCK_FILE_NAME = "in_progress.dat";
private final FileProcessingCompilerAdapterFactory myProcessingCompilerAdapterFactory;
private final FileProcessingCompilerAdapterFactory myPackagingCompilerAdapterFactory;
public CompileDriver(Project project) {
myProject = project;
myCachesDirectoryPath = CompilerPaths.getCacheStoreDirectory(myProject).getPath().replace('/', File.separatorChar);
myShouldClearOutputDirectory = CompilerConfiguration.getInstance(myProject).isClearOutputDirectory();
myGenerationCompilerModuleToOutputDirMap =
new com.intellij.util.containers.HashMap<Pair<Compiler, Module>, VirtualFile>();
final Compiler[] compilers = CompilerManager.getInstance(myProject).getCompilers(GeneratingCompiler.class);
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
final Module[] allModules = ModuleManager.getInstance(myProject).getModules();
for (int idx = 0; idx < compilers.length; idx++) {
GeneratingCompiler compiler = (GeneratingCompiler)compilers[idx];
for (int i = 0; i < allModules.length; i++) {
final Module module = allModules[i];
final String path = getGenerationOutputPath(compiler, module);
final File file = new File(path);
final VirtualFile vFile;
if (file.mkdirs()) {
vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
}
else {
vFile = LocalFileSystem.getInstance().findFileByPath(path);
}
Pair<Compiler, Module> pair = new Pair<Compiler, Module>(compiler, module);
myGenerationCompilerModuleToOutputDirMap.put(pair, vFile);
}
}
}
});
myProjectCompileScope = new ProjectCompileScope(myProject);
myProjectRootManager = ProjectRootManager.getInstance(myProject);
myProcessingCompilerAdapterFactory = new FileProcessingCompilerAdapterFactory() {
public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) {
return new FileProcessingCompilerAdapter(context, compiler);
}
};
myPackagingCompilerAdapterFactory = new FileProcessingCompilerAdapterFactory() {
public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) {
return new PackagingCompilerAdapter(context, (PackagingCompiler)compiler);
}
};
}
public void rebuild(CompileStatusNotification callback) {
doRebuild(callback, null, true, addAdditionalRoots(myProjectCompileScope));
}
public void make(CompileStatusNotification callback) {
make(myProjectCompileScope, callback);
}
public void make(Project project, Module[] modules, CompileStatusNotification callback) {
make(new ModuleCompileScope(project, modules, true), callback);
}
public void make(Module module, CompileStatusNotification callback) {
make(new ModuleCompileScope(module, true), callback);
}
public void make(CompileScope scope, CompileStatusNotification callback) {
final CompileScope scope1 = addAdditionalRoots(scope);
if (validateCompilerConfiguration(scope1, false)) {
startup(scope1, false, false, callback, null, true);
}
}
public void compile(CompileScope scope, CompileStatusNotification callback, boolean trackDependencies) {
if (validateCompilerConfiguration(scope, false)) {
if (trackDependencies) {
scope = new TrackDependenciesScope(scope);
}
startup(scope, false, true, callback, null, true);
}
}
private static class CompileStatus {
final int CACHE_FORMAT_VERSION;
final boolean COMPILATION_IN_PROGRESS;
public CompileStatus(int cacheVersion, boolean isCompilationInProgress) {
this.CACHE_FORMAT_VERSION = cacheVersion;
this.COMPILATION_IN_PROGRESS = isCompilationInProgress;
}
}
private CompileStatus readStatus() {
final boolean isInProgress = new File(myCachesDirectoryPath, LOCK_FILE_NAME).exists();
int version = -1;
try {
final File versionFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME);
if (versionFile.exists()) {
DataInputStream in = new DataInputStream(new FileInputStream(versionFile));
try {
version = in.readInt();
}
finally {
in.close();
}
}
}
catch (IOException e) {
LOG.info(e); // may happen in case of IDEA crashed and the file is not written properly
return null;
}
return new CompileStatus(version, isInProgress);
}
private void writeStatus(CompileStatus status, CompileContext context) {
final File statusFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME);
final File lockFile = new File(myCachesDirectoryPath, LOCK_FILE_NAME);
try {
if (!statusFile.exists()) {
statusFile.createNewFile();
}
DataOutputStream out = new DataOutputStream(new FileOutputStream(statusFile));
try {
out.writeInt(status.CACHE_FORMAT_VERSION);
}
finally {
out.close();
}
if (status.COMPILATION_IN_PROGRESS) {
lockFile.createNewFile();
}
else {
lockFile.delete();
}
}
catch (IOException e) {
context.addMessage(CompilerMessageCategory.ERROR, "Error: " + e.getMessage(), null, -1, -1);
}
}
private void doRebuild(CompileStatusNotification callback, CompilerMessage message, final boolean checkCachesVersion, final CompileScope compileScope) {
if (validateCompilerConfiguration(compileScope, true)) {
startup(compileScope, true, false, callback, message, checkCachesVersion);
}
}
private CompileScope addAdditionalRoots(CompileScope originalScope) {
CompileScope scope = originalScope;
for (Iterator<Pair<Compiler, Module>> it = myGenerationCompilerModuleToOutputDirMap.keySet().iterator(); it.hasNext();) {
final Pair<Compiler, Module> pair = it.next();
final VirtualFile outputDir = myGenerationCompilerModuleToOutputDirMap.get(pair);
scope = new CompositeScope(scope, new FileSetCompileScope(new VirtualFile[] {outputDir}, new Module[]{pair.getSecond()}));
}
CompileScope additionalJ2eeScope = MakeUtilImpl.getOutOfSourceJ2eeCompileScope(scope);
if (additionalJ2eeScope != null) {
scope = new CompositeScope(scope, additionalJ2eeScope);
}
return scope;
}
private void startup(final CompileScope scope, final boolean isRebuild, final boolean forceCompile, final CompileStatusNotification callback, CompilerMessage message, final boolean checkCachesVersion) {
final CompilerProgressIndicator indicator = new CompilerProgressIndicator(
myProject,
CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_IN_BACKGROUND,
forceCompile ? "Compile" : "Make"
);
WindowManager.getInstance().getStatusBar(myProject).setInfo("");
final CompileContextImpl compileContext = new CompileContextImpl(myProject, indicator, scope, new DependencyCache(myCachesDirectoryPath), this);
for (Iterator<Pair<Compiler, Module>> it = myGenerationCompilerModuleToOutputDirMap.keySet().iterator(); it.hasNext();) {
Pair<Compiler, Module> pair = it.next();
compileContext.assignModule(myGenerationCompilerModuleToOutputDirMap.get(pair), pair.getSecond());
}
if (message != null) {
compileContext.addMessage(message);
}
FileDocumentManager.getInstance().saveAllDocuments();
new Thread("Compile Thread") {
public void run() {
synchronized (CompilerManager.getInstance(myProject)) {
ProgressManager.getInstance().runProcess(new Runnable() {
public void run() {
doCompile(compileContext, isRebuild, forceCompile, callback, checkCachesVersion);
}
}, compileContext.getProgressIndicator());
}
}
}.start();
}
private void doCompile(final CompileContextImpl compileContext,
final boolean isRebuild,
final boolean forceCompile,
final CompileStatusNotification callback, final boolean checkCachesVersion) {
ExitStatus status = ExitStatus.ERRORS;
boolean wereExceptions = false;
try {
compileContext.getProgressIndicator().pushState();
if (checkCachesVersion) {
final CompileStatus compileStatus = readStatus();
if (compileStatus == null) {
compileContext.requestRebuildNextTime("Compiler caches are corrupted. Starting rebuild...");
}
else if (compileStatus.CACHE_FORMAT_VERSION != -1 && compileStatus.CACHE_FORMAT_VERSION != CompilerConfiguration.DEPENDENCY_FORMAT_VERSION) {
compileContext.requestRebuildNextTime("Compiler caches on disk have old format. Starting rebuild...");
}
else if (compileStatus.COMPILATION_IN_PROGRESS) {
compileContext.requestRebuildNextTime("Previous compilation did not terminate properly. Caches may have been corrupted. Starting rebuild...");
}
if (compileContext.isRebuildRequested()) {
return;
}
}
writeStatus(new CompileStatus(CompilerConfiguration.DEPENDENCY_FORMAT_VERSION, true), compileContext);
if (compileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
return;
}
if (!isRebuild) {
compileContext.getProgressIndicator().setText("Scanning output directories...");
myOutputFilesOnDisk = CompilerPathsEx.getOutputFiles(myProject);
}
status = doCompile(compileContext, isRebuild, forceCompile);
}
catch (Throwable ex) {
wereExceptions = true;
throw new RuntimeException(ex);
}
finally {
compileContext.getProgressIndicator().popState();
final ExitStatus _status = status;
if (compileContext.isRebuildRequested()) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
doRebuild(
callback,
new CompilerMessageImpl(myProject, CompilerMessageCategory.INFORMATION, compileContext.getRebuildReason(), null, -1, -1), false, compileContext.getCompileScope()
);
}
});
}
else {
writeStatus(new CompileStatus(CompilerConfiguration.DEPENDENCY_FORMAT_VERSION, wereExceptions), compileContext);
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
final int errorCount = compileContext.getMessageCount(CompilerMessageCategory.ERROR);
final int warningCount = compileContext.getMessageCount(CompilerMessageCategory.WARNING);
final String statusMessage = createStatusMessage(_status, warningCount, errorCount);
final StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject);
if (statusBar != null) { // because this code is in invoke later, the code may work for already closed project
// in case another project was opened in the frame while the compiler was working (See SCR# 28591)
statusBar.setInfo(statusMessage);
}
if (_status != ExitStatus.UP_TO_DATE && compileContext.getMessageCount(null) > 0) {
compileContext.addMessage(CompilerMessageCategory.INFORMATION, statusMessage, null, -1, -1);
}
if (callback != null) {
callback.finished(_status == ExitStatus.CANCELLED, errorCount, warningCount);
}
ProfilingUtil.operationFinished("make");
}
}, ModalityState.NON_MMODAL);
}
}
}
private static String createStatusMessage(final ExitStatus _status, final int warningCount, final int errorCount) {
if (_status == ExitStatus.CANCELLED) {
return "Compilation aborted";
}
if (_status == ExitStatus.UP_TO_DATE) {
return "All files are up-to-date";
}
if (_status == ExitStatus.SUCCESS) {
return (warningCount > 0)
? "Compilation completed successfully with " + warningCount + " warnings"
: "Compilation completed successfully";
}
return "Compilation completed with " + errorCount + " errors and " + warningCount +
(warningCount == 1 ? " warning" : " warnings");
}
private static class ExitStatus {
private String myName;
private ExitStatus(String name) {
myName = name;
}
public String toString() {
return myName;
}
public static final ExitStatus CANCELLED = new ExitStatus("CANCELLED");
public static final ExitStatus ERRORS = new ExitStatus("ERRORS");
public static final ExitStatus SUCCESS = new ExitStatus("SUCCESS");
public static final ExitStatus UP_TO_DATE = new ExitStatus("UP_TO_DATE");
}
private ExitStatus doCompile(CompileContextImpl context, boolean isRebuild, final boolean forceCompile) {
try {
if (isRebuild) {
deleteAll(context);
if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
return ExitStatus.ERRORS;
}
}
try {
context.getProgressIndicator().pushState();
if (!executeCompileTasks(context, true)) {
return ExitStatus.CANCELLED;
}
}
finally {
context.getProgressIndicator().popState();
}
if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
return ExitStatus.ERRORS;
}
boolean didSomething = false;
final CompilerManager compilerManager = CompilerManager.getInstance(myProject);
didSomething |= generateSources(compilerManager, context, forceCompile);
if (myExitStatus != null) {
return myExitStatus;
}
didSomething |= invokeFileProcessingCompilers(compilerManager, context, SourceInstrumentingCompiler.class, myProcessingCompilerAdapterFactory, forceCompile);
if (myExitStatus != null) {
return myExitStatus;
}
try {
didSomething |= translate(context, compilerManager, forceCompile, isRebuild);
if (myExitStatus != null) {
return myExitStatus;
}
didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassInstrumentingCompiler.class, myProcessingCompilerAdapterFactory, forceCompile);
if (myExitStatus != null) {
return myExitStatus;
}
didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassPostProcessingCompiler.class, myProcessingCompilerAdapterFactory, forceCompile);
if (myExitStatus != null) {
return myExitStatus;
}
didSomething |= invokeFileProcessingCompilers(compilerManager, context, PackagingCompiler.class, myPackagingCompilerAdapterFactory, forceCompile);
if (myExitStatus != null) {
return myExitStatus;
}
didSomething |= invokeFileProcessingCompilers(compilerManager, context, Validator.class, myProcessingCompilerAdapterFactory, forceCompile);
if (myExitStatus != null) {
return myExitStatus;
}
}
finally {
context.getProgressIndicator().setText("Saving caches...");
context.getDependencyCache().dispose();
}
try {
context.getProgressIndicator().pushState();
if (!executeCompileTasks(context, false)) {
return ExitStatus.CANCELLED;
}
}
finally {
context.getProgressIndicator().popState();
}
if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
return ExitStatus.ERRORS;
}
if (!didSomething) {
return ExitStatus.UP_TO_DATE;
}
return ExitStatus.SUCCESS;
}
catch (ProcessCanceledException e) {
return ExitStatus.CANCELLED;
}
}
private boolean generateSources(final CompilerManager compilerManager, CompileContextImpl context, final boolean forceCompile) {
boolean didSomething = false;
final Compiler[] sourceGenerators = compilerManager.getCompilers(SourceGeneratingCompiler.class);
for (int idx = 0; idx < sourceGenerators.length; idx++) {
if (context.getProgressIndicator().isCanceled()) {
myExitStatus = ExitStatus.CANCELLED;
return false;
}
Compiler compiler = sourceGenerators[idx];
final boolean generatedSomething = generateOutput(context, (SourceGeneratingCompiler)compiler, forceCompile);
if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
myExitStatus = ExitStatus.ERRORS;
return false;
}
didSomething |= generatedSomething;
}
return didSomething;
}
private boolean translate(CompileContextImpl context, final CompilerManager compilerManager, final boolean forceCompile, boolean isRebuild) {
boolean didSomething = false;
final Compiler[] translators = compilerManager.getCompilers(TranslatingCompiler.class);
final VfsSnapshot snapshot = new VfsSnapshot(context.getCompileScope().getFiles(null, true));
for (int idx = 0; idx < translators.length; idx++) {
if (context.getProgressIndicator().isCanceled()) {
myExitStatus = ExitStatus.CANCELLED;
return false;
}
final boolean compiledSomething;
compiledSomething = compileSources(context, snapshot, (TranslatingCompiler)translators[idx], forceCompile, isRebuild);
if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
myExitStatus = ExitStatus.ERRORS;
return false;
}
didSomething |= compiledSomething;
}
return didSomething;
}
private static interface FileProcessingCompilerAdapterFactory {
FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler);
}
private boolean invokeFileProcessingCompilers(final CompilerManager compilerManager, CompileContextImpl context, Class fileProcessingCompilerClass, FileProcessingCompilerAdapterFactory factory, boolean forceCompile) {
LOG.assertTrue(FileProcessingCompiler.class.isAssignableFrom(fileProcessingCompilerClass));
boolean didSomething = false;
final Compiler[] classPostProcessors = compilerManager.getCompilers(fileProcessingCompilerClass);
if (classPostProcessors.length > 0) {
try {
for (int idx = 0; idx < classPostProcessors.length; idx++) {
if (context.getProgressIndicator().isCanceled()) {
myExitStatus = ExitStatus.CANCELLED;
return false;
}
final boolean processedSomething = processFiles(factory.create(context, (FileProcessingCompiler)classPostProcessors[idx]), forceCompile);
if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
myExitStatus = ExitStatus.ERRORS;
return false;
}
didSomething |= processedSomething;
}
}
catch(ProcessCanceledException e) {
throw e;
}
catch (Exception e) {
context.addMessage(CompilerMessageCategory.ERROR, "Error processing classes: " + e.getMessage(), null, -1, -1);
LOG.error(e);
}
}
return didSomething;
}
private Map<Module, Set<GeneratingCompiler.GenerationItem>> buildModuleToGenerationItemMap(
GeneratingCompiler.GenerationItem[] items) {
final Map<Module, Set<GeneratingCompiler.GenerationItem>> map = new com.intellij.util.containers.HashMap<Module, Set<GeneratingCompiler.GenerationItem>>();
for (int idx = 0; idx < items.length; idx++) {
GeneratingCompiler.GenerationItem item = items[idx];
Module module = item.getModule();
LOG.assertTrue(module != null);
Set<GeneratingCompiler.GenerationItem> itemSet = map.get(module);
if (itemSet == null) {
itemSet = new HashSet<GeneratingCompiler.GenerationItem>();
map.put(module, itemSet);
}
itemSet.add(item);
}
return map;
}
private void deleteAll(final CompileContext context) {
context.getProgressIndicator().pushState();
try {
final com.intellij.openapi.compiler.Compiler[] allCompilers = CompilerManager.getInstance(myProject).getCompilers(Compiler.class);
context.getProgressIndicator().setText("Clearing output directories...");
for (int idx = 0; idx < allCompilers.length; idx++) {
final Compiler compiler = allCompilers[idx];
if (compiler instanceof GeneratingCompiler) {
final StateCache<ValidityState> cache = getGeneratingCompilerCache((GeneratingCompiler)compiler);
if (!myShouldClearOutputDirectory) {
deleteUrls(cache.getUrlsIterator());
}
cache.wipe();
}
else if (compiler instanceof FileProcessingCompiler) {
final FileProcessingCompilerStateCache cache = getFileProcessingCompilerCache((FileProcessingCompiler)compiler);
cache.wipe();
}
else if (compiler instanceof TranslatingCompiler) {
final TranslatingCompilerStateCache cache = getTranslatingCompilerCache((TranslatingCompiler)compiler);
if (!myShouldClearOutputDirectory) {
deleteUrls(cache.getOutputUrlsIterator());
}
cache.wipe();
}
}
if (myShouldClearOutputDirectory) {
final File[] files = getAllOutputDirectories();
for (int i = 0; i < files.length; i++) {
deleteAllFilesIn(files[i]);
}
// ensure output directories exist, create and refresh if not exist
final List<File> createdFiles = new ArrayList<File>(files.length);
for (int idx = 0; idx < files.length; idx++) {
final File file = files[idx];
if (file.mkdirs()) {
createdFiles.add(file);
}
}
if (createdFiles.size() > 0) {
CompilerUtil.refreshIOFiles(createdFiles.toArray(new File[createdFiles.size()]));
}
}
clearCompilerSystemDirectory(context);
}
finally {
context.getProgressIndicator().popState();
}
}
private void deleteUrls(final Iterator<String> urlIterator) {
while(urlIterator.hasNext()) {
final String url = urlIterator.next();
new File(VirtualFileManager.extractPath(url)).delete();
}
}
private File[] getAllOutputDirectories() {
final List<File> outputDirs = new ArrayList<File>();
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
final VirtualFile[] outputDirectories = CompilerPathsEx.getOutputDirectories(
ModuleManager.getInstance(myProject).getModules());
for (int idx = 0; idx < outputDirectories.length; idx++) {
final File directory = VfsUtil.virtualToIoFile(outputDirectories[idx]);
outputDirs.add(directory);
}
}
});
return outputDirs.toArray(new File[outputDirs.size()]);
}
private void deleteAllFilesIn(File directory) {
FileUtil.asyncDelete(directory);
directory.mkdir();
}
private void clearCompilerSystemDirectory(final CompileContext context) {
final File[] children = new File(myCachesDirectoryPath).listFiles();
if (children != null) {
for (int idx = 0; idx < children.length; idx++) {
final File child = children[idx];
final boolean deleteOk = FileUtil.delete(child);
if (!deleteOk) {
context.addMessage(CompilerMessageCategory.ERROR, "Failed to delete " + child.getPath(), null, -1, -1);
}
}
}
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
for (Iterator<Pair<Compiler, Module>> it = myGenerationCompilerModuleToOutputDirMap.keySet().iterator(); it.hasNext();) {
Pair<Compiler, Module> pair = it.next();
final VirtualFile dir = myGenerationCompilerModuleToOutputDirMap.get(pair);
final File[] files = VfsUtil.virtualToIoFile(dir).listFiles();
if (files != null) {
for (int idx = 0; idx < files.length; idx++) {
final File file = files[idx];
final boolean deleteOk = FileUtil.delete(file);
if (!deleteOk) {
context.addMessage(CompilerMessageCategory.ERROR, "Failed to delete " + file.getPath(), null, -1, -1);
}
}
}
}
}
});
}
private VirtualFile getGenerationOutputDir(final GeneratingCompiler compiler, final Module module) {
return myGenerationCompilerModuleToOutputDirMap.get(new Pair<Compiler, Module>(compiler, module));
}
private static String getGenerationOutputPath(GeneratingCompiler compiler, Module module) {
final String generatedCompilerDirectoryPath = CompilerPaths.getGeneratedDataDirectory(module.getProject(), compiler).getPath();
return generatedCompilerDirectoryPath.replace(File.separatorChar, '/') + "/" +
(module.getName().replace(' ', '_') + "." + Integer.toHexString(module.getModuleFilePath().hashCode()));
}
private boolean generateOutput(final CompileContextImpl context, final GeneratingCompiler compiler, final boolean forceGenerate) {
final GeneratingCompiler.GenerationItem[] allItems = compiler.getGenerationItems(context);
final List<GeneratingCompiler.GenerationItem> toGenerate = new ArrayList<GeneratingCompiler.GenerationItem>();
final StateCache<ValidityState> cache = getGeneratingCompilerCache(compiler);
final Set<String> pathsToRemove = new HashSet<String>(Arrays.asList(cache.getUrls()));
final Map<GeneratingCompiler.GenerationItem, String> itemToOutputPathMap = new com.intellij.util.containers.HashMap<GeneratingCompiler.GenerationItem, String>();
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
for (int idx = 0; idx < allItems.length; idx++) {
final GeneratingCompiler.GenerationItem item = allItems[idx];
final Module itemModule = item.getModule();
final String outputDirPath = getGenerationOutputPath(compiler, itemModule);
final String outputPath = outputDirPath + "/" + item.getPath();
itemToOutputPathMap.put(item, outputPath);
final ValidityState savedState = cache.getState(outputPath);
if (forceGenerate || savedState == null || !savedState.equalsTo(item.getValidityState())) {
toGenerate.add(item);
}
else {
pathsToRemove.remove(outputPath);
}
}
}
});
final List<File> filesToRefresh = new ArrayList<File>();
try {
if (pathsToRemove.size() > 0) {
context.getProgressIndicator().pushState();
context.getProgressIndicator().setText("Synchronizing output directory...");
for (Iterator<String> it = pathsToRemove.iterator(); it.hasNext();) {
String path = it.next();
final File file = new File(path);
final boolean deleted = file.delete();
if (deleted) {
cache.remove(path);
filesToRefresh.add(file);
}
}
context.getProgressIndicator().popState();
}
Map<Module, Set<GeneratingCompiler.GenerationItem>> moduleToItemMap =
buildModuleToGenerationItemMap(toGenerate.toArray(new GeneratingCompiler.GenerationItem[toGenerate.size()]));
List<Module> modules = new ArrayList<Module>(moduleToItemMap.size());
for (Iterator<Module> it = moduleToItemMap.keySet().iterator(); it.hasNext();) {
modules.add(it.next());
}
ModuleCompilerUtil.sortModules(myProject, modules);
for (Iterator<Module> it = modules.iterator(); it.hasNext();) {
context.getProgressIndicator().pushState();
try {
final Module module = it.next();
final Set<GeneratingCompiler.GenerationItem> items = moduleToItemMap.get(module);
if (items != null && items.size() > 0) {
final VirtualFile outputDir = getGenerationOutputDir(compiler, module);
final GeneratingCompiler.GenerationItem[] successfullyGenerated =
compiler.generate(context, items.toArray(new GeneratingCompiler.GenerationItem[items.size()]), outputDir);
context.getProgressIndicator().setText("Updating caches...");
for (int idx = 0; idx < successfullyGenerated.length; idx++) {
GeneratingCompiler.GenerationItem item = successfullyGenerated[idx];
cache.update(itemToOutputPathMap.get(item), item.getValidityState());
filesToRefresh.add(new File(item.getPath()));
}
}
}
finally {
context.getProgressIndicator().popState();
}
}
}
finally {
context.getProgressIndicator().pushState();
CompilerUtil.refreshIOFiles(filesToRefresh.toArray(new File[filesToRefresh.size()]));
if (cache.isDirty()) {
context.getProgressIndicator().setText("Saving caches...");
cache.save();
}
context.getProgressIndicator().popState();
}
return toGenerate.size() > 0 || filesToRefresh.size() > 0;
}
private boolean compileSources(final CompileContextImpl context, final VfsSnapshot snapshot, final TranslatingCompiler compiler, final boolean forceCompile, final boolean isRebuild) {
final TranslatingCompilerStateCache cache = getTranslatingCompilerCache(compiler);
final Set<VirtualFile> toCompile = new HashSet<VirtualFile>();
final Set<String> toDelete = new HashSet<String>();
final boolean wereFilesDeleted[] = new boolean[] {false};
final CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(myProject);
context.getProgressIndicator().pushState();
try {
final Set<String> urlsWithSourceRemoved = new HashSet<String>();
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
findOutOfDateFiles(compiler, snapshot, forceCompile, cache, toCompile, context);
if (context.getCompileScope() instanceof TrackDependenciesScope && toCompile.size() > 0) { // should add dependent files
final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
final PsiManager psiManager = PsiManager.getInstance(myProject);
final VirtualFile[] filesToCompile = toCompile.toArray(new VirtualFile[toCompile.size()]);
Set<String> sourcesWithOutputRemoved = getSourcesWithOutputRemoved(cache);
for (int i = 0; i < filesToCompile.length; i++) {
final VirtualFile file = filesToCompile[i];
if (fileTypeManager.getFileTypeByFile(file) == StdFileTypes.JAVA) {
final PsiFile psiFile = psiManager.findFile(file);
if (psiFile != null) {
addDependentFiles(psiFile, toCompile, cache, snapshot, sourcesWithOutputRemoved);
}
}
}
}
context.getProgressIndicator().setText("Searching for files to delete...");
if (!isRebuild) {
findFilesToDelete(context.getCompileScope(), snapshot, urlsWithSourceRemoved, cache, toCompile, context, toDelete, compilerConfiguration);
}
}
});
if (toDelete.size() > 0) {
try {
wereFilesDeleted[0] = syncOutputDir(urlsWithSourceRemoved, context, toDelete, cache);
}
catch (CacheCorruptedException e) {
LOG.info(e);
context.requestRebuildNextTime(e.getMessage());
}
}
if (wereFilesDeleted[0] && toDelete.size() > 0) {
CompilerUtil.refreshPaths(toDelete.toArray(new String[toDelete.size()]));
}
if ((wereFilesDeleted[0] || toCompile.size() > 0) && context.getMessageCount(CompilerMessageCategory.ERROR) == 0) {
final TranslatingCompiler.ExitStatus exitStatus = compiler.compile(context, toCompile.toArray(new VirtualFile[toCompile.size()]));
updateInternalCaches(cache, context, exitStatus.getSuccessfullyCompiled(), exitStatus.getFilesToRecompile());
}
}
finally {
if (cache.isDirty()) {
context.getProgressIndicator().setText("Saving caches...");
if (cache.isDirty()) {
cache.save();
}
}
context.getProgressIndicator().popState();
}
return toCompile.size() > 0 || wereFilesDeleted[0];
}
private Set<String> getSourcesWithOutputRemoved(TranslatingCompilerStateCache cache) {
//final String[] outputUrls = cache.getOutputUrls();
final Set<String> set = new HashSet<String>();
for (Iterator<String> it = cache.getOutputUrlsIterator(); it.hasNext();) {
String outputUrl = it.next();
if (!myOutputFilesOnDisk.contains(outputUrl)) {
set.add(cache.getSourceUrl(outputUrl));
}
}
return set;
}
private void findFilesToDelete(final CompileScope scope,
VfsSnapshot snapshot, final Set<String> urlsWithSourceRemoved,
final TranslatingCompilerStateCache cache,
final Set<VirtualFile> toCompile,
final CompileContextImpl context,
final Set<String> toDelete,
final CompilerConfiguration compilerConfiguration) {
final List<String> toRemove = new ArrayList<String>();
for (Iterator<String> it = cache.getOutputUrlsIterator(); it.hasNext();) {
final String outputPath = it.next();
final String sourceUrl = cache.getSourceUrl(outputPath);
final VirtualFile sourceFile = snapshot.getFileByUrl(sourceUrl);
boolean needRecompile = false;
boolean shouldDelete = false;
if (myOutputFilesOnDisk.contains(outputPath)) {
if (sourceFile == null) {
shouldDelete = scope.belongs(sourceUrl);
}
else {
if (toCompile.contains(sourceFile)) {
shouldDelete = true;
}
else {
final String currentOutputDir = getModuleOutputDirForFile(context, sourceFile);
if (currentOutputDir != null) {
final String className = cache.getClassName(outputPath);
final String cachedOutputDir = (className == null) ?
currentOutputDir :
outputPath.substring(0, outputPath.length() - className.length() - ".class".length() - 1);
if (CompilerUtil.pathsEqual(cachedOutputDir, currentOutputDir)) {
shouldDelete = false;
}
else {
// output for this source has been changed or the output dir was changed, need to recompile to the new output dir
shouldDelete = true;
needRecompile = true;
}
}
else {
shouldDelete = true;
}
}
}
}
else {
// output for this source has been deleted or the output dir was changed, need to recompile
needRecompile = true;
shouldDelete = true; // in case the output dir was changed, should delete from the previous location
}
if (shouldDelete) {
toDelete.add(outputPath);
}
if (needRecompile) {
if (sourceFile != null && scope.belongs(sourceUrl)) {
if (!compilerConfiguration.isExcludedFromCompilation(sourceFile)) {
toCompile.add(sourceFile);
toRemove.add(outputPath);
}
}
}
if (sourceFile == null) {
urlsWithSourceRemoved.add(outputPath);
}
}
for (Iterator<String> it = toRemove.iterator(); it.hasNext();) {
cache.remove(it.next());
}
}
private void updateInternalCaches(final TranslatingCompilerStateCache cache, final CompileContextImpl context, final TranslatingCompiler.OutputItem[] successfullyCompiled, final VirtualFile[] filesToRecompile) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
context.getProgressIndicator().setText("Updating caches...");
final FileTypeManager typeManager = FileTypeManager.getInstance();
for (int idx = 0; idx < successfullyCompiled.length; idx++) {
final TranslatingCompiler.OutputItem item = successfullyCompiled[idx];
final String outputPath = item.getOutputPath();
final VirtualFile sourceFile = item.getSourceFile();
final String className;
if (StdFileTypes.JAVA.equals(typeManager.getFileTypeByFile(sourceFile))) {
final String outputDir = item.getOutputRootDirectory();
if (!CompilerUtil.startsWith(outputPath, outputDir)) {
LOG.assertTrue(false, outputPath + " does not start with " + outputDir);
}
className = MakeUtil.relativeClassPathToQName(outputPath.substring(outputDir.length(), outputPath.length()), '/');
}
else {
className = null;
}
cache.update(outputPath, className, sourceFile);
}
for (int idx = 0; idx < filesToRecompile.length; idx++) {
cache.markAsModified(filesToRecompile[idx]);
}
}
});
}
private boolean syncOutputDir(final Set<String> urlsWithSourceRemoved, final CompileContextImpl context, final Set<String> toDelete,
final TranslatingCompilerStateCache cache) throws CacheCorruptedException {
boolean wereFilesDeleted = false;
DeleteHelper deleteHelper = new DeleteHelper(myProject);
int current = 0;
int total = toDelete.size();
final DependencyCache dependencyCache = context.getDependencyCache();
final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode();
context.getProgressIndicator().pushState();
try {
context.getProgressIndicator().setText("Synchronizing output directory...");
for (Iterator it = toDelete.iterator(); it.hasNext();) {
final String outputPath = (String)it.next();
context.getProgressIndicator().setFraction(((double)(++current)) / total);
if (deleteHelper.delete(outputPath)) {
wereFilesDeleted = true;
String qName = cache.getClassName(outputPath);
if (qName != null) {
final int id = dependencyCache.getSymbolTable().getId(qName);
dependencyCache.addTraverseRoot(id);
if (urlsWithSourceRemoved.contains(outputPath)) {
dependencyCache.markSourceRemoved(id);
}
}
if (isTestMode) {
CompilerManagerImpl.addDeletedPath(outputPath);
}
cache.remove(outputPath);
}
}
deleteHelper.finish();
return wereFilesDeleted;
}
finally {
context.getProgressIndicator().popState();
}
}
private void findOutOfDateFiles(final TranslatingCompiler compiler,
VfsSnapshot snapshot,
final boolean forceCompile,
final TranslatingCompilerStateCache cache,
final Set<VirtualFile> toCompile,
CompileContext context) {
final CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(myProject);
VirtualFile[] compilableFiles = getCompilableFiles(compiler, snapshot, context);
for (int idx = 0; idx < compilableFiles.length; idx++) {
final VirtualFile file = compilableFiles[idx];
if (!forceCompile) {
if (compilerConfiguration.isExcludedFromCompilation(file)) {
continue;
}
}
if (forceCompile || file.getTimeStamp() != cache.getSourceTimestamp(snapshot.getUrlByFile(file))) {
toCompile.add(file);
}
}
}
private void addDependentFiles(final PsiFile psiFile,
Set<VirtualFile> toCompile,
final TranslatingCompilerStateCache cache,
VfsSnapshot snapshot,
Set<String> sourcesWithOutputRemoved) {
final DependenciesBuilder builder = new ForwardDependenciesBuilder(myProject, new AnalysisScope(psiFile, AnalysisScope.SOURCE_JAVA_FILES));
builder.analyze();
final Map<PsiFile, Set<PsiFile>> dependencies = builder.getDependencies();
final Set<PsiFile> dependentFiles = dependencies.get(psiFile);
if (dependentFiles != null && dependentFiles.size() > 0) {
for (Iterator it = dependentFiles.iterator(); it.hasNext();) {
final PsiFile dependentFile = (PsiFile)it.next();
if (dependentFile instanceof PsiCompiledElement) {
continue;
}
final VirtualFile vFile = dependentFile.getVirtualFile();
if (toCompile.contains(vFile)) {
continue;
}
String url = snapshot.getUrlByFile(vFile);
if (url == null) { // the file does not belong to this snapshot
url = vFile.getUrl();
}
if (!sourcesWithOutputRemoved.contains(url)) {
if (vFile.getTimeStamp() == cache.getSourceTimestamp(url)) {
continue;
}
}
toCompile.add(vFile);
addDependentFiles(dependentFile, toCompile, cache, snapshot, sourcesWithOutputRemoved);
}
}
}
private VirtualFile[] getCompilableFiles(final TranslatingCompiler compiler, VfsSnapshot snapshot, CompileContext context) {
final Set<VirtualFile> result = new HashSet<VirtualFile>();
for (Iterator<String> iterator = snapshot.getUrlsIterator(); iterator.hasNext();) {
final String url = iterator.next();
final VirtualFile file = snapshot.getFileByUrl(url);
if (compiler.isCompilableFile(file, context)) {
result.add(file);
}
}
return result.toArray(new VirtualFile[result.size()]);
}
private String getModuleOutputDirForFile(CompileContext context, VirtualFile file) {
final Module module = context.getModuleByFile(file);
if (module == null) {
return null; // looks like file invalidated
}
final ProjectFileIndex fileIndex = myProjectRootManager.getFileIndex();
return getModuleOutputPath(module, fileIndex.isInTestSourceContent(file));
}
// [mike] performance optimization - this method is accessed > 15,000 times in Aurora
private String getModuleOutputPath(final Module module, boolean inTestSourceContent) {
final Map<Module, String> map = inTestSourceContent? myModuleTestOutputPaths : myModuleOutputPaths;
String path = map.get(module);
if (path == null) {
path = CompilerPaths.getModuleOutputPath(module, inTestSourceContent);
map.put(module, path);
}
return path;
}
private boolean processFiles(final FileProcessingCompilerAdapter adapter, final boolean forceCompile) {
final CompileContext context = adapter.getCompileContext();
final FileProcessingCompilerStateCache cache = getFileProcessingCompilerCache(adapter.getCompiler());
final FileProcessingCompiler.ProcessingItem[] items = adapter.getProcessingItems();
if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
return false;
}
final List<FileProcessingCompiler.ProcessingItem> toProcess = new ArrayList<FileProcessingCompiler.ProcessingItem>();
final Set<String> allUrls = new HashSet<String>();
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
for (int idx = 0; idx < items.length; idx++) {
FileProcessingCompiler.ProcessingItem item = items[idx];
final VirtualFile file = item.getFile();
final String url = file.getUrl();
allUrls.add(url);
if (!forceCompile && cache.getTimestamp(url) == file.getTimeStamp()) {
final ValidityState state = cache.getExtState(url);
final ValidityState itemState = item.getValidityState();
if (state != null? state.equalsTo(itemState) : itemState == null) {
continue;
}
}
toProcess.add(item);
}
}
});
final String[] urls = cache.getUrls();
if (urls.length > 0) {
context.getProgressIndicator().pushState();
context.getProgressIndicator().setText("Processing outdated files...");
final CompileScope scope = context.getCompileScope();
final List<String> urlsToRemove = new ArrayList<String>();
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
for (int idx = 0; idx < urls.length; idx++) {
final String url = urls[idx];
if (!allUrls.contains(url)) {
if (scope.belongs(url)) {
urlsToRemove.add(url);
}
}
}
}
});
if (urlsToRemove.size() > 0) {
for (Iterator<String> it = urlsToRemove.iterator(); it.hasNext();) {
final String url = it.next();
adapter.processOutdatedItem(context, url, cache.getExtState(url));
cache.remove(url);
}
}
context.getProgressIndicator().popState();
}
if (toProcess.size() == 0) {
return false;
}
context.getProgressIndicator().pushState();
final FileProcessingCompiler.ProcessingItem[] processed =
adapter.process(toProcess.toArray(new FileProcessingCompiler.ProcessingItem[toProcess.size()]));
context.getProgressIndicator().popState();
if (processed.length > 0) {
context.getProgressIndicator().pushState();
context.getProgressIndicator().setText("Updating caches...");
try {
final VirtualFile[] vFiles = new VirtualFile[processed.length];
for (int idx = 0; idx < processed.length; idx++) {
vFiles[idx] = processed[idx].getFile();
}
CompilerUtil.refreshVirtualFiles(vFiles);
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
for (int idx = 0; idx < processed.length; idx++) {
FileProcessingCompiler.ProcessingItem item = processed[idx];
cache.update(item.getFile(), item.getValidityState());
}
}
});
}
finally {
if (cache.isDirty()) {
context.getProgressIndicator().setText("Saving caches...");
cache.save();
}
context.getProgressIndicator().popState();
}
}
return true;
}
public TranslatingCompilerStateCache getTranslatingCompilerCache(TranslatingCompiler compiler) {
Object cache = myCompilerToCacheMap.get(compiler);
if (cache == null) {
cache = new TranslatingCompilerStateCache(myCachesDirectoryPath, getIdPrefix(compiler));
myCompilerToCacheMap.put(compiler, cache);
}
else {
LOG.assertTrue(cache instanceof TranslatingCompilerStateCache);
}
return (TranslatingCompilerStateCache)cache;
}
private FileProcessingCompilerStateCache getFileProcessingCompilerCache(FileProcessingCompiler compiler) {
Object cache = myCompilerToCacheMap.get(compiler);
if (cache == null) {
cache = new FileProcessingCompilerStateCache(myCachesDirectoryPath, getIdPrefix(compiler), compiler);
myCompilerToCacheMap.put(compiler, cache);
}
else {
LOG.assertTrue(cache instanceof FileProcessingCompilerStateCache);
}
return (FileProcessingCompilerStateCache)cache;
}
private StateCache<ValidityState> getGeneratingCompilerCache(final GeneratingCompiler compiler) {
Object cache = myCompilerToCacheMap.get(compiler);
if (cache == null) {
cache = new StateCache<ValidityState>(myCachesDirectoryPath + File.separator + getIdPrefix(compiler) + "_timestamp.dat") {
public ValidityState read(DataInputStream stream) throws IOException {
return compiler.createValidityState(stream);
}
public void write(ValidityState validityState, DataOutputStream stream) throws IOException {
validityState.save(stream);
}
};
myCompilerToCacheMap.put(compiler, cache);
}
return (StateCache<ValidityState>)cache;
}
private String getIdPrefix(Compiler compiler) {
String description = compiler.getDescription();
return description.toLowerCase().replaceAll("\\s+", "_");
}
public void executeCompileTask(final CompileTask task,
final CompileScope scope,
final String contentName,
final Runnable onTaskFinished) {
final CompilerProgressIndicator indicator = new CompilerProgressIndicator(
myProject,
CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_IN_BACKGROUND,
contentName
);
final CompileContextImpl compileContext = new CompileContextImpl(myProject, indicator, scope, null, this);
FileDocumentManager.getInstance().saveAllDocuments();
new Thread("Compile Task Thread") {
public void run() {
synchronized (CompilerManager.getInstance(myProject)) {
ProgressManager.getInstance().runProcess(new Runnable() {
public void run() {
try {
task.execute(compileContext);
}
catch (ProcessCanceledException ex) {
// suppressed
}
finally {
if (onTaskFinished != null) {
onTaskFinished.run();
}
}
}
}, compileContext.getProgressIndicator());
}
}
}.start();
}
private boolean executeCompileTasks(CompileContext context, boolean beforeTasks) {
final CompilerManager manager = CompilerManager.getInstance(myProject);
final ProgressIndicator progressIndicator = context.getProgressIndicator();
try {
CompileTask[] tasks = beforeTasks ? (CompileTask[])manager.getBeforeTasks() : manager.getAfterTasks();
if (tasks.length > 0) {
progressIndicator.setText(beforeTasks ? "Executing pre-compile tasks..." : "Executing post-compile tasks...");
for (int idx = 0; idx < tasks.length; idx++) {
CompileTask task = tasks[idx];
if (!task.execute(context)) {
return false;
}
}
}
}
finally {
WindowManager.getInstance().getStatusBar(myProject).setInfo("");
if (progressIndicator instanceof CompilerProgressIndicator) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
((CompilerProgressIndicator)progressIndicator).showCompilerContent();
}
});
}
}
return true;
}
// todo: add validation for module chunks: all modules that form a chunk must have the same JDK
private boolean validateCompilerConfiguration(final CompileScope scope, boolean checkOutputAndSourceIntersection) {
final Module[] scopeModules = scope.getAffectedModules()/*ModuleManager.getInstance(myProject).getModules()*/;
final List<String> modulesWithoutOutputPathSpecified = new ArrayList<String>();
final List<String> modulesWithoutJdkAssigned = new ArrayList<String>();
final Set<File> nonExistingOutputPaths = new HashSet<File>();
for (int idx = 0; idx < scopeModules.length; idx++) {
final Module module = scopeModules[idx];
if (ModuleType.J2EE_APPLICATION.equals(module.getModuleType())) {
continue; // makes no sence to demand jdk & output paths for such modules
}
final boolean hasSources = hasSources(module, false);
final boolean hasTestSources = hasSources(module, true);
if (!hasSources && !hasTestSources) {
// If module contains no sources, shouldn't have to select JDK or output directory (SCR #19333)
// todo still there may be problems with this approach if some generated files are attributed by this module
continue;
}
final ProjectJdk jdk = ModuleRootManager.getInstance(module).getJdk();
if (jdk == null) {
modulesWithoutJdkAssigned.add(module.getName());
}
final String outputPath = getModuleOutputPath(module, false);
final String testsOutputPath = getModuleOutputPath(module, true);
if (outputPath == null && testsOutputPath == null) {
modulesWithoutOutputPathSpecified.add(module.getName());
}
else {
if (outputPath != null) {
final File file = new File(outputPath.replace('/', File.separatorChar));
if (!file.exists()) {
nonExistingOutputPaths.add(file);
}
}
else {
if (hasSources) {
modulesWithoutOutputPathSpecified.add(module.getName());
}
}
if (testsOutputPath != null) {
final File f = new File(testsOutputPath.replace('/', File.separatorChar));
if (!f.exists()) {
nonExistingOutputPaths.add(f);
}
}
else {
if (hasTestSources) {
modulesWithoutOutputPathSpecified.add(module.getName());
}
}
}
}
if (modulesWithoutJdkAssigned.size() > 0) {
showNotSpecifiedError(modulesWithoutJdkAssigned, "the JDK", LibrariesEditor.NAME);
return false;
}
if (modulesWithoutOutputPathSpecified.size() > 0) {
showNotSpecifiedError(modulesWithoutOutputPathSpecified, "the output path", ContentEntriesEditor.NAME);
return false;
}
if (nonExistingOutputPaths.size() > 0) {
StringBuffer paths = new StringBuffer();
for (Iterator<File> it = nonExistingOutputPaths.iterator(); it.hasNext();) {
File file = it.next();
if (paths.length() > 0) {
paths.append(",\n");
}
paths.append(file.getPath());
}
final String notExistsMessage = (nonExistingOutputPaths.size() > 1 ?
"The following output paths do not exist:\n" :
"The following output path does not exist:\n") +
paths.toString() +
"\n\nWould you like to create" + (nonExistingOutputPaths.size() > 1 ? " them " : " it ") + "and continue?";
final int answer = Messages.showYesNoDialog(myProject, notExistsMessage, "Output Path Does Not Exist", Messages.getQuestionIcon());
if (answer == 0) { // yes
for (Iterator<File> it = nonExistingOutputPaths.iterator(); it.hasNext();) {
File file = it.next();
final boolean succeeded = file.mkdirs();
if (!succeeded) {
Messages.showMessageDialog(myProject, "Failed to create directory " + file.getPath(), "Unable To Create Directory", Messages.getErrorIcon());
return false;
}
}
final Boolean refreshSuccess = ApplicationManager.getApplication().runWriteAction(new Computable<Boolean>() {
public Boolean compute() {
for (Iterator<File> it = nonExistingOutputPaths.iterator(); it.hasNext();) {
File file = it.next();
final VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
if (vFile == null) {
return Boolean.FALSE;
}
}
return Boolean.TRUE;
}
});
if (!refreshSuccess.booleanValue()) {
return false;
}
}
else {
return false;
}
}
if (checkOutputAndSourceIntersection) {
if (myShouldClearOutputDirectory) {
if (!validateOutputAndSourcePathsIntersection()) {
return false;
}
}
}
final List<Chunk<Module>> chunks = ModuleCompilerUtil.getSortedModuleChunks(myProject, scopeModules);
for (Iterator<Chunk<Module>> it = chunks.iterator(); it.hasNext();) {
final Set<Module> chunkModules = it.next().getNodes();
if (chunkModules.size() <= 1) {
continue; // no need to check one-module chunks
}
ProjectJdk jdk = null;
for (Iterator<Module> iterator = chunkModules.iterator(); iterator.hasNext();) {
final Module module = iterator.next();
final ProjectJdk moduleJdk = ModuleRootManager.getInstance(module).getJdk();
if (jdk == null) {
jdk = moduleJdk;
}
else {
if (!jdk.equals(moduleJdk)) {
showCyclicModulesHaveDifferentJdksError(chunkModules.iterator());
return false;
}
}
}
}
final Compiler[] allCompilers = CompilerManager.getInstance(myProject).getCompilers(Compiler.class);
for (int idx = 0; idx < allCompilers.length; idx++) {
Compiler compiler = allCompilers[idx];
if (!compiler.validateConfiguration(scope)) {
return false;
}
}
return true;
}
private void showCyclicModulesHaveDifferentJdksError(Iterator<Module> modulesIterator) {
String moduleNameToSelect = null;
final StringBuffer message = new StringBuffer();
message.append("The following modules must have the same JDK assigned because of cyclic dependencies between them:");
while (modulesIterator.hasNext()) {
final Module module = modulesIterator.next();
if (moduleNameToSelect == null) {
moduleNameToSelect = module.getName();
}
message.append("\n").append("\"").append(module.getName()).append("\"");
}
message.append("\nPlease update modules configuration");
Messages.showMessageDialog(myProject, message.toString(), "Cannot Start Compiler", Messages.getErrorIcon());
showConfigurationDialog(moduleNameToSelect, null);
}
private boolean hasSources(Module module, boolean checkTestSources) {
final ContentEntry[] contentEntries = ModuleRootManager.getInstance(module).getContentEntries();
for (int idx = 0; idx < contentEntries.length; idx++) {
final ContentEntry contentEntry = contentEntries[idx];
final SourceFolder[] sourceFolders = contentEntry.getSourceFolders();
for (int i = 0; i < sourceFolders.length; i++) {
final SourceFolder sourceFolder = sourceFolders[i];
if (sourceFolder.getFile() == null) {
continue; // skip invalid source folders
}
if (checkTestSources) {
if (sourceFolder.isTestSource()) {
return true;
}
}
else {
if (!sourceFolder.isTestSource()) {
return true;
}
}
}
}
return false;
}
private void showNotSpecifiedError(List<String> modules, final String whatNotSpecified, String tabNameToSelect) {
StringBuffer names = new StringBuffer();
String nameToSelect = null;
for (Iterator<String> it = modules.iterator(); it.hasNext();) {
String name = it.next();
if (nameToSelect == null) {
nameToSelect = name;
}
if (names.length() > 0) {
names.append(",\n");
}
names.append("\"");
names.append(name);
names.append("\"");
}
String message = "Cannot start compiler: " + whatNotSpecified + " is not specified for" +
(modules.size() > 1 ? " modules\n" : " module ") + names.toString() +
".\nSpecify " + whatNotSpecified + " in Configure Project.";
if(ApplicationManager.getApplication().isUnitTestMode()) LOG.assertTrue(false, message);
Messages.showMessageDialog(
myProject,
message,
"Cannot Start Compiler",
Messages.getErrorIcon()
);
showConfigurationDialog(nameToSelect, tabNameToSelect);
}
private boolean validateOutputAndSourcePathsIntersection() {
final Module[] allModules = ModuleManager.getInstance(myProject).getModules();
final VirtualFile[] outputPaths = CompilerPathsEx.getOutputDirectories(allModules);
final Set<VirtualFile> affectedOutputPaths = new HashSet<VirtualFile>();
for (int idx = 0; idx < allModules.length; idx++) {
final ModuleRootManager rootManager = ModuleRootManager.getInstance(allModules[idx]);
final VirtualFile[] sourceRoots = rootManager.getSourceRoots();
for (int j = 0; j < outputPaths.length; j++) {
VirtualFile outputPath = outputPaths[j];
for (int i = 0; i < sourceRoots.length; i++) {
VirtualFile sourceRoot = sourceRoots[i];
if (VfsUtil.isAncestor(outputPath, sourceRoot, true) || VfsUtil.isAncestor(sourceRoot, outputPath, false)) {
affectedOutputPaths.add(outputPath);
}
}
}
}
if (affectedOutputPaths.size() > 0) {
final StringBuffer message = new StringBuffer();
message.append("Compiler option \"Clear output directory on rebuild\" is currently on.\nHowever, source files may exist in the following output path");
if (affectedOutputPaths.size() > 1) {
message.append("s:\n");
}
else {
message.append(":\n");
}
for (Iterator<VirtualFile> it = affectedOutputPaths.iterator(); it.hasNext();) {
message.append(it.next().getPath().replace('/', File.separatorChar));
message.append("\n");
}
message.append("\nCompilation will proceed without clearing output directories.");
final int answer = Messages.showOkCancelDialog(myProject, message.toString(), "Clear Output Files", Messages.getWarningIcon());
if (answer == 0) { // ok
myShouldClearOutputDirectory = false;
return true;
}
else {
return false;
}
}
return true;
}
private void showConfigurationDialog(String moduleNameToSelect, String tabNameToSelect) {
ModulesConfigurator.showDialog(myProject, moduleNameToSelect, tabNameToSelect, false);
}
private static class VfsSnapshot {
private Map<String, VirtualFile> myUrlToFile = new HashMap<String, VirtualFile>();
private Map<VirtualFile, String> myFileToUrl = new HashMap<VirtualFile, String>();
public VfsSnapshot(final VirtualFile[] files) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
myUrlToFile = new HashMap<String, VirtualFile>(files.length);
for (int i = 0; i < files.length; i++) {
final VirtualFile file = files[i];
final String url = file.getUrl();
myUrlToFile.put(url, file);
myFileToUrl.put(file, url);
}
}
});
}
public VirtualFile getFileByUrl(final String url) {
return myUrlToFile.get(url);
}
public String getUrlByFile(final VirtualFile file) {
return myFileToUrl.get(file);
}
public Iterator<String> getUrlsIterator() {
return myUrlToFile.keySet().iterator();
}
}
}