| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @author: Eugene Zhuravlev |
| * Date: Jan 17, 2003 |
| * Time: 1:42:26 PM |
| */ |
| package com.intellij.compiler.impl; |
| |
| import com.intellij.CommonBundle; |
| import com.intellij.compiler.CompilerConfiguration; |
| import com.intellij.compiler.CompilerWorkspaceConfiguration; |
| import com.intellij.compiler.ModuleCompilerUtil; |
| import com.intellij.compiler.ProblemsView; |
| import com.intellij.compiler.progress.CompilerTask; |
| import com.intellij.compiler.server.BuildManager; |
| import com.intellij.compiler.server.DefaultMessageHandler; |
| import com.intellij.notification.Notification; |
| import com.intellij.notification.NotificationListener; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.compiler.*; |
| import com.intellij.openapi.compiler.ex.CompilerPathsEx; |
| import com.intellij.openapi.deployment.DeploymentUtil; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.module.LanguageLevelUtil; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.progress.ProcessCanceledException; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.project.ProjectBundle; |
| import com.intellij.openapi.projectRoots.Sdk; |
| import com.intellij.openapi.roots.ModuleRootManager; |
| import com.intellij.openapi.roots.ui.configuration.CommonContentEntriesEditor; |
| import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; |
| import com.intellij.openapi.ui.MessageType; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VirtualFileManager; |
| import com.intellij.openapi.wm.*; |
| import com.intellij.packaging.artifacts.Artifact; |
| import com.intellij.packaging.impl.compiler.ArtifactCompilerUtil; |
| import com.intellij.packaging.impl.compiler.ArtifactsCompiler; |
| import com.intellij.pom.java.LanguageLevel; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.util.Chunk; |
| import com.intellij.util.StringBuilderSpinAllocator; |
| import com.intellij.util.ThrowableRunnable; |
| import com.intellij.util.containers.HashMap; |
| import com.intellij.util.containers.MultiMap; |
| import com.intellij.util.messages.MessageBus; |
| import com.intellij.util.text.DateFormatUtil; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.TestOnly; |
| import org.jetbrains.jps.api.CmdlineProtoUtil; |
| import org.jetbrains.jps.api.CmdlineRemoteProto; |
| import org.jetbrains.jps.api.RequestFuture; |
| import org.jetbrains.jps.model.java.JavaSourceRootType; |
| |
| import javax.swing.*; |
| import javax.swing.event.HyperlinkEvent; |
| import java.io.File; |
| import java.lang.ref.WeakReference; |
| import java.util.*; |
| import java.util.concurrent.TimeUnit; |
| |
| import static org.jetbrains.jps.api.CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope; |
| |
| public class CompileDriver { |
| |
| private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileDriver"); |
| |
| private final Project myProject; |
| |
| private final Map<Module, String> myModuleOutputPaths = new HashMap<Module, String>(); |
| private final Map<Module, String> myModuleTestOutputPaths = new HashMap<Module, String>(); |
| |
| private static final Key<Boolean> COMPILATION_STARTED_AUTOMATICALLY = Key.create("compilation_started_automatically"); |
| |
| private CompilerFilter myCompilerFilter = CompilerFilter.ALL; |
| |
| private static final long ONE_MINUTE_MS = 60L /*sec*/ * 1000L /*millisec*/; |
| |
| public CompileDriver(Project project) { |
| myProject = project; |
| } |
| |
| public void setCompilerFilter(CompilerFilter compilerFilter) { |
| myCompilerFilter = compilerFilter == null? CompilerFilter.ALL : compilerFilter; |
| } |
| |
| public void rebuild(CompileStatusNotification callback) { |
| doRebuild(callback, null, new ProjectCompileScope(myProject)); |
| } |
| |
| public void make(CompileScope scope, CompileStatusNotification callback) { |
| if (validateCompilerConfiguration(scope)) { |
| startup(scope, false, false, callback, null); |
| } |
| else { |
| callback.finished(true, 0, 0, DummyCompileContext.getInstance()); |
| } |
| } |
| |
| public boolean isUpToDate(CompileScope scope) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("isUpToDate operation started"); |
| } |
| |
| final CompilerTask task = new CompilerTask(myProject, "Classes up-to-date check", true, false, false, isCompilationStartedAutomatically(scope)); |
| final CompileContextImpl compileContext = new CompileContextImpl(myProject, task, scope, true, false); |
| |
| final Ref<ExitStatus> result = new Ref<ExitStatus>(); |
| |
| task.start(new Runnable() { |
| public void run() { |
| final ProgressIndicator indicator = compileContext.getProgressIndicator(); |
| if (indicator.isCanceled() || myProject.isDisposed()) { |
| return; |
| } |
| try { |
| final RequestFuture future = compileInExternalProcess(compileContext, true); |
| if (future != null) { |
| while (!future.waitFor(200L, TimeUnit.MILLISECONDS)) { |
| if (indicator.isCanceled()) { |
| future.cancel(false); |
| } |
| } |
| } |
| } |
| catch (Throwable e) { |
| LOG.error(e); |
| } |
| finally { |
| result.set(COMPILE_SERVER_BUILD_STATUS.get(compileContext)); |
| if (!myProject.isDisposed()) { |
| CompilerCacheManager.getInstance(myProject).flushCaches(); |
| } |
| } |
| } |
| }, null); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("isUpToDate operation finished"); |
| } |
| |
| return ExitStatus.UP_TO_DATE.equals(result.get()); |
| } |
| |
| public void compile(CompileScope scope, CompileStatusNotification callback) { |
| if (validateCompilerConfiguration(scope)) { |
| startup(scope, false, true, callback, null); |
| } |
| else { |
| callback.finished(true, 0, 0, DummyCompileContext.getInstance()); |
| } |
| } |
| |
| private void doRebuild(CompileStatusNotification callback, CompilerMessage message, final CompileScope compileScope) { |
| if (validateCompilerConfiguration(compileScope)) { |
| startup(compileScope, true, false, callback, message); |
| } |
| else { |
| callback.finished(true, 0, 0, DummyCompileContext.getInstance()); |
| } |
| } |
| |
| public static void setCompilationStartedAutomatically(CompileScope scope) { |
| //todo[nik] pass this option as a parameter to compile/make methods instead |
| scope.putUserData(COMPILATION_STARTED_AUTOMATICALLY, Boolean.TRUE); |
| } |
| |
| private static boolean isCompilationStartedAutomatically(CompileScope scope) { |
| return Boolean.TRUE.equals(scope.getUserData(COMPILATION_STARTED_AUTOMATICALLY)); |
| } |
| |
| @Nullable |
| private RequestFuture compileInExternalProcess(final @NotNull CompileContextImpl compileContext, final boolean onlyCheckUpToDate) |
| throws Exception { |
| final CompileScope scope = compileContext.getCompileScope(); |
| final Collection<String> paths = CompileScopeUtil.fetchFiles(compileContext); |
| List<TargetTypeBuildScope> scopes = new ArrayList<TargetTypeBuildScope>(); |
| final boolean forceBuild = !compileContext.isMake(); |
| List<TargetTypeBuildScope> explicitScopes = CompileScopeUtil.getBaseScopeForExternalBuild(scope); |
| if (explicitScopes != null) { |
| scopes.addAll(explicitScopes); |
| } |
| else if (!compileContext.isRebuild() && !CompileScopeUtil.allProjectModulesAffected(compileContext)) { |
| CompileScopeUtil.addScopesForModules(Arrays.asList(scope.getAffectedModules()), scopes, forceBuild); |
| } |
| else { |
| scopes.addAll(CmdlineProtoUtil.createAllModulesScopes(forceBuild)); |
| } |
| if (paths.isEmpty()) { |
| for (BuildTargetScopeProvider provider : BuildTargetScopeProvider.EP_NAME.getExtensions()) { |
| scopes = CompileScopeUtil.mergeScopes(scopes, provider.getBuildTargetScopes(scope, myCompilerFilter, myProject, forceBuild)); |
| } |
| } |
| |
| // need to pass scope's user data to server |
| final Map<String, String> builderParams; |
| if (onlyCheckUpToDate) { |
| builderParams = Collections.emptyMap(); |
| } |
| else { |
| final Map<Key, Object> exported = scope.exportUserData(); |
| if (!exported.isEmpty()) { |
| builderParams = new HashMap<String, String>(); |
| for (Map.Entry<Key, Object> entry : exported.entrySet()) { |
| final String _key = entry.getKey().toString(); |
| final String _value = entry.getValue().toString(); |
| builderParams.put(_key, _value); |
| } |
| } |
| else { |
| builderParams = Collections.emptyMap(); |
| } |
| } |
| |
| final MessageBus messageBus = myProject.getMessageBus(); |
| final MultiMap<String, Artifact> outputToArtifact = ArtifactCompilerUtil.containsArtifacts(scopes) ? ArtifactCompilerUtil.createOutputToArtifactMap(myProject) : null; |
| final BuildManager buildManager = BuildManager.getInstance(); |
| buildManager.cancelAutoMakeTasks(myProject); |
| return buildManager.scheduleBuild(myProject, compileContext.isRebuild(), compileContext.isMake(), onlyCheckUpToDate, scopes, paths, builderParams, new DefaultMessageHandler(myProject) { |
| |
| @Override |
| public void buildStarted(UUID sessionId) { |
| } |
| |
| @Override |
| public void sessionTerminated(final UUID sessionId) { |
| if (compileContext.shouldUpdateProblemsView()) { |
| final ProblemsView view = ProblemsView.SERVICE.getInstance(myProject); |
| view.clearProgress(); |
| view.clearOldMessages(compileContext.getCompileScope(), compileContext.getSessionId()); |
| } |
| } |
| |
| @Override |
| public void handleFailure(UUID sessionId, CmdlineRemoteProto.Message.Failure failure) { |
| compileContext.addMessage(CompilerMessageCategory.ERROR, failure.hasDescription()? failure.getDescription() : "", null, -1, -1); |
| final String trace = failure.hasStacktrace()? failure.getStacktrace() : null; |
| if (trace != null) { |
| LOG.info(trace); |
| } |
| compileContext.putUserData(COMPILE_SERVER_BUILD_STATUS, ExitStatus.ERRORS); |
| } |
| |
| @Override |
| protected void handleCompileMessage(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) { |
| final CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind kind = message.getKind(); |
| //System.out.println(compilerMessage.getText()); |
| final String messageText = message.getText(); |
| if (kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.PROGRESS) { |
| final ProgressIndicator indicator = compileContext.getProgressIndicator(); |
| indicator.setText(messageText); |
| if (message.hasDone()) { |
| indicator.setFraction(message.getDone()); |
| } |
| } |
| else { |
| final CompilerMessageCategory category = kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.ERROR ? CompilerMessageCategory.ERROR |
| : kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.WARNING ? CompilerMessageCategory.WARNING : CompilerMessageCategory.INFORMATION; |
| |
| String sourceFilePath = message.hasSourceFilePath() ? message.getSourceFilePath() : null; |
| if (sourceFilePath != null) { |
| sourceFilePath = FileUtil.toSystemIndependentName(sourceFilePath); |
| } |
| final long line = message.hasLine() ? message.getLine() : -1; |
| final long column = message.hasColumn() ? message.getColumn() : -1; |
| final String srcUrl = sourceFilePath != null ? VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, sourceFilePath) : null; |
| compileContext.addMessage(category, messageText, srcUrl, (int)line, (int)column); |
| } |
| } |
| |
| @Override |
| protected void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) { |
| final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Type eventType = event.getEventType(); |
| switch (eventType) { |
| case FILES_GENERATED: |
| final List<CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.GeneratedFile> generated = event.getGeneratedFilesList(); |
| final CompilationStatusListener publisher = messageBus.syncPublisher(CompilerTopics.COMPILATION_STATUS); |
| Set<String> writtenArtifactOutputPaths = outputToArtifact != null ? new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY) : null; |
| for (CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.GeneratedFile generatedFile : generated) { |
| final String root = FileUtil.toSystemIndependentName(generatedFile.getOutputRoot()); |
| final String relativePath = FileUtil.toSystemIndependentName(generatedFile.getRelativePath()); |
| publisher.fileGenerated(root, relativePath); |
| if (outputToArtifact != null) { |
| Collection<Artifact> artifacts = outputToArtifact.get(root); |
| if (!artifacts.isEmpty()) { |
| for (Artifact artifact : artifacts) { |
| ArtifactsCompiler.addChangedArtifact(compileContext, artifact); |
| } |
| writtenArtifactOutputPaths.add(FileUtil.toSystemDependentName(DeploymentUtil.appendToPath(root, relativePath))); |
| } |
| } |
| } |
| if (writtenArtifactOutputPaths != null && !writtenArtifactOutputPaths.isEmpty()) { |
| ArtifactsCompiler.addWrittenPaths(compileContext, writtenArtifactOutputPaths); |
| } |
| break; |
| case BUILD_COMPLETED: |
| ExitStatus status = ExitStatus.SUCCESS; |
| if (event.hasCompletionStatus()) { |
| final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status completionStatus = event.getCompletionStatus(); |
| switch (completionStatus) { |
| case CANCELED: |
| status = ExitStatus.CANCELLED; |
| break; |
| case ERRORS: |
| status = ExitStatus.ERRORS; |
| break; |
| case SUCCESS: |
| status = ExitStatus.SUCCESS; |
| break; |
| case UP_TO_DATE: |
| status = ExitStatus.UP_TO_DATE; |
| break; |
| } |
| } |
| compileContext.putUserDataIfAbsent(COMPILE_SERVER_BUILD_STATUS, status); |
| break; |
| } |
| } |
| }); |
| } |
| |
| |
| private static final Key<ExitStatus> COMPILE_SERVER_BUILD_STATUS = Key.create("COMPILE_SERVER_BUILD_STATUS"); |
| |
| private void startup(final CompileScope scope, |
| final boolean isRebuild, |
| final boolean forceCompile, |
| final CompileStatusNotification callback, |
| final CompilerMessage message) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| |
| final String contentName = |
| forceCompile ? CompilerBundle.message("compiler.content.name.compile") : CompilerBundle.message("compiler.content.name.make"); |
| final boolean isUnitTestMode = ApplicationManager.getApplication().isUnitTestMode(); |
| final CompilerTask compileTask = new CompilerTask(myProject, contentName, isUnitTestMode, true, true, isCompilationStartedAutomatically(scope)); |
| |
| StatusBar.Info.set("", myProject, "Compiler"); |
| // ensure the project model seen by build process is up-to-date |
| myProject.save(); |
| if (!isUnitTestMode) { |
| ApplicationManager.getApplication().saveSettings(); |
| } |
| PsiDocumentManager.getInstance(myProject).commitAllDocuments(); |
| FileDocumentManager.getInstance().saveAllDocuments(); |
| |
| final CompileContextImpl compileContext = new CompileContextImpl(myProject, compileTask, scope, !isRebuild && !forceCompile, isRebuild); |
| |
| final Runnable compileWork = new Runnable() { |
| public void run() { |
| final ProgressIndicator indicator = compileContext.getProgressIndicator(); |
| if (indicator.isCanceled() || myProject.isDisposed()) { |
| if (callback != null) { |
| callback.finished(true, 0, 0, compileContext); |
| } |
| return; |
| } |
| try { |
| LOG.info("COMPILATION STARTED (BUILD PROCESS)"); |
| if (message != null) { |
| compileContext.addMessage(message); |
| } |
| if (isRebuild) { |
| CompilerUtil.runInContext(compileContext, "Clearing build system data...", new ThrowableRunnable<Throwable>() { |
| @Override |
| public void run() throws Throwable { |
| CompilerCacheManager.getInstance(myProject).clearCaches(compileContext); |
| } |
| }); |
| } |
| final boolean beforeTasksOk = executeCompileTasks(compileContext, true); |
| |
| final int errorCount = compileContext.getMessageCount(CompilerMessageCategory.ERROR); |
| if (!beforeTasksOk || errorCount > 0) { |
| COMPILE_SERVER_BUILD_STATUS.set(compileContext, errorCount > 0 ? ExitStatus.ERRORS : ExitStatus.CANCELLED); |
| return; |
| } |
| |
| final RequestFuture future = compileInExternalProcess(compileContext, false); |
| if (future != null) { |
| while (!future.waitFor(200L, TimeUnit.MILLISECONDS)) { |
| if (indicator.isCanceled()) { |
| future.cancel(false); |
| } |
| } |
| if (!executeCompileTasks(compileContext, false)) { |
| COMPILE_SERVER_BUILD_STATUS.set(compileContext, ExitStatus.CANCELLED); |
| } |
| if (compileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) { |
| COMPILE_SERVER_BUILD_STATUS.set(compileContext, ExitStatus.ERRORS); |
| } |
| } |
| } |
| catch (Throwable e) { |
| LOG.error(e); // todo |
| } |
| finally { |
| CompilerCacheManager.getInstance(myProject).flushCaches(); |
| |
| final long duration = notifyCompilationCompleted(compileContext, callback, COMPILE_SERVER_BUILD_STATUS.get(compileContext)); |
| CompilerUtil.logDuration( |
| "\tCOMPILATION FINISHED (BUILD PROCESS); Errors: " + |
| compileContext.getMessageCount(CompilerMessageCategory.ERROR) + |
| "; warnings: " + |
| compileContext.getMessageCount(CompilerMessageCategory.WARNING), |
| duration |
| ); |
| } |
| } |
| }; |
| |
| compileTask.start(compileWork, new Runnable() { |
| public void run() { |
| if (isRebuild) { |
| final int rv = Messages.showOkCancelDialog( |
| myProject, "You are about to rebuild the whole project.\nRun 'Make Project' instead?", "Confirm Project Rebuild", |
| "Make", "Rebuild", Messages.getQuestionIcon() |
| ); |
| if (rv == Messages.OK /*yes, please, do run make*/) { |
| startup(scope, false, false, callback, null); |
| return; |
| } |
| } |
| startup(scope, isRebuild, forceCompile, callback, message); |
| } |
| }); |
| } |
| |
| @Nullable @TestOnly |
| public static ExitStatus getExternalBuildExitStatus(CompileContext context) { |
| return context.getUserData(COMPILE_SERVER_BUILD_STATUS); |
| } |
| |
| /** @noinspection SSBasedInspection*/ |
| private long notifyCompilationCompleted(final CompileContextImpl compileContext, final CompileStatusNotification callback, final ExitStatus _status) { |
| final long duration = System.currentTimeMillis() - compileContext.getStartCompilationStamp(); |
| if (!myProject.isDisposed()) { |
| // refresh on output roots is required in order for the order enumerator to see all roots via VFS |
| final Set<File> outputs = new HashSet<File>(); |
| final Module[] affectedModules = compileContext.getCompileScope().getAffectedModules(); |
| for (final String path : CompilerPathsEx.getOutputPaths(affectedModules)) { |
| outputs.add(new File(path)); |
| } |
| final LocalFileSystem lfs = LocalFileSystem.getInstance(); |
| if (!outputs.isEmpty()) { |
| final ProgressIndicator indicator = compileContext.getProgressIndicator(); |
| indicator.setText("Synchronizing output directories..."); |
| CompilerUtil.refreshOutputDirectories(outputs, _status == ExitStatus.CANCELLED); |
| indicator.setText(""); |
| } |
| if (compileContext.isAnnotationProcessorsEnabled() && !myProject.isDisposed()) { |
| final Set<File> genSourceRoots = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY); |
| final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); |
| for (Module module : affectedModules) { |
| if (config.getAnnotationProcessingConfiguration(module).isEnabled()) { |
| final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); |
| if (path != null) { |
| genSourceRoots.add(new File(path)); |
| } |
| } |
| } |
| if (!genSourceRoots.isEmpty()) { |
| // refresh generates source roots asynchronously; needed for error highlighting update |
| lfs.refreshIoFiles(genSourceRoots, true, true, null); |
| } |
| } |
| } |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| int errorCount = 0; |
| int warningCount = 0; |
| try { |
| errorCount = compileContext.getMessageCount(CompilerMessageCategory.ERROR); |
| warningCount = compileContext.getMessageCount(CompilerMessageCategory.WARNING); |
| if (!myProject.isDisposed()) { |
| final String statusMessage = createStatusMessage(_status, warningCount, errorCount, duration); |
| final MessageType messageType = errorCount > 0 ? MessageType.ERROR : warningCount > 0 ? MessageType.WARNING : MessageType.INFO; |
| if (duration > ONE_MINUTE_MS && CompilerWorkspaceConfiguration.getInstance(myProject).DISPLAY_NOTIFICATION_POPUP) { |
| ToolWindowManager.getInstance(myProject).notifyByBalloon(ToolWindowId.MESSAGES_WINDOW, messageType, statusMessage); |
| } |
| |
| final String wrappedMessage = _status != ExitStatus.UP_TO_DATE? "<a href='#'>" + statusMessage + "</a>" : statusMessage; |
| final Notification notification = CompilerManager.NOTIFICATION_GROUP.createNotification( |
| "", wrappedMessage, |
| messageType.toNotificationType(), |
| new MessagesActivationListener(compileContext) |
| ); |
| compileContext.getBuildSession().registerCloseAction(new Runnable() { |
| @Override |
| public void run() { |
| notification.expire(); |
| } |
| }); |
| notification.notify(myProject); |
| |
| if (_status != ExitStatus.UP_TO_DATE && compileContext.getMessageCount(null) > 0) { |
| final String msg = DateFormatUtil.formatDateTime(new Date()) + " - " + statusMessage; |
| compileContext.addMessage(CompilerMessageCategory.INFORMATION, msg, null, -1, -1); |
| } |
| } |
| } |
| finally { |
| if (callback != null) { |
| callback.finished(_status == ExitStatus.CANCELLED, errorCount, warningCount, compileContext); |
| } |
| } |
| } |
| }); |
| return duration; |
| } |
| |
| private static String createStatusMessage(final ExitStatus status, final int warningCount, final int errorCount, long duration) { |
| String message; |
| if (status == ExitStatus.CANCELLED) { |
| message = CompilerBundle.message("status.compilation.aborted"); |
| } |
| else if (status == ExitStatus.UP_TO_DATE) { |
| message = CompilerBundle.message("status.all.up.to.date"); |
| } |
| else { |
| if (status == ExitStatus.SUCCESS) { |
| message = warningCount > 0 |
| ? CompilerBundle.message("status.compilation.completed.successfully.with.warnings", warningCount) |
| : CompilerBundle.message("status.compilation.completed.successfully"); |
| } |
| else { |
| message = CompilerBundle.message("status.compilation.completed.successfully.with.warnings.and.errors", errorCount, warningCount); |
| } |
| message = message + " in " + StringUtil.formatDuration(duration); |
| } |
| return message; |
| } |
| |
| /* |
| private void dropScopesCaches() { |
| // hack to be sure the classpath will include the output directories |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| ((ProjectRootManagerEx)ProjectRootManager.getInstance(myProject)).clearScopesCachesForModules(); |
| } |
| }); |
| } |
| */ |
| |
| // [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; |
| } |
| |
| public void executeCompileTask(final CompileTask task, final CompileScope scope, final String contentName, final Runnable onTaskFinished) { |
| final CompilerTask progressManagerTask = new CompilerTask(myProject, contentName, false, false, true, isCompilationStartedAutomatically(scope)); |
| final CompileContextImpl compileContext = new CompileContextImpl(myProject, progressManagerTask, scope, false, false); |
| |
| FileDocumentManager.getInstance().saveAllDocuments(); |
| |
| progressManagerTask.start(new Runnable() { |
| public void run() { |
| try { |
| task.execute(compileContext); |
| } |
| catch (ProcessCanceledException ex) { |
| // suppressed |
| } |
| finally { |
| if (onTaskFinished != null) { |
| onTaskFinished.run(); |
| } |
| } |
| } |
| }, null); |
| } |
| |
| private boolean executeCompileTasks(final CompileContext context, final boolean beforeTasks) { |
| if (myProject.isDisposed()) { |
| return false; |
| } |
| final CompilerManager manager = CompilerManager.getInstance(myProject); |
| final ProgressIndicator progressIndicator = context.getProgressIndicator(); |
| progressIndicator.pushState(); |
| try { |
| CompileTask[] tasks = beforeTasks ? manager.getBeforeTasks() : manager.getAfterTasks(); |
| if (tasks.length > 0) { |
| progressIndicator.setText(beforeTasks |
| ? CompilerBundle.message("progress.executing.precompile.tasks") |
| : CompilerBundle.message("progress.executing.postcompile.tasks")); |
| for (CompileTask task : tasks) { |
| if (!task.execute(context)) { |
| return false; |
| } |
| } |
| } |
| } |
| finally { |
| progressIndicator.popState(); |
| WindowManager.getInstance().getStatusBar(myProject).setInfo(""); |
| if (progressIndicator instanceof CompilerTask) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| public void run() { |
| ((CompilerTask)progressIndicator).showCompilerContent(); |
| } |
| }); |
| } |
| } |
| return true; |
| } |
| |
| private boolean validateCompilerConfiguration(final CompileScope scope) { |
| try { |
| final Module[] scopeModules = scope.getAffectedModules(); |
| final List<String> modulesWithoutOutputPathSpecified = new ArrayList<String>(); |
| final List<String> modulesWithoutJdkAssigned = new ArrayList<String>(); |
| final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); |
| final CompilerManager compilerManager = CompilerManager.getInstance(myProject); |
| for (final Module module : scopeModules) { |
| if (!compilerManager.isValidationEnabled(module)) { |
| continue; |
| } |
| final boolean hasSources = hasSources(module, JavaSourceRootType.SOURCE); |
| final boolean hasTestSources = hasSources(module, JavaSourceRootType.TEST_SOURCE); |
| 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 Sdk jdk = ModuleRootManager.getInstance(module).getSdk(); |
| 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) { |
| if (hasSources) { |
| modulesWithoutOutputPathSpecified.add(module.getName()); |
| } |
| } |
| if (testsOutputPath == null) { |
| if (hasTestSources) { |
| modulesWithoutOutputPathSpecified.add(module.getName()); |
| } |
| } |
| } |
| } |
| if (!modulesWithoutJdkAssigned.isEmpty()) { |
| showNotSpecifiedError("error.jdk.not.specified", modulesWithoutJdkAssigned, ProjectBundle.message("modules.classpath.title")); |
| return false; |
| } |
| |
| if (!modulesWithoutOutputPathSpecified.isEmpty()) { |
| showNotSpecifiedError("error.output.not.specified", modulesWithoutOutputPathSpecified, CommonContentEntriesEditor.NAME); |
| return false; |
| } |
| |
| final List<Chunk<Module>> chunks = ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(scopeModules)); |
| for (final Chunk<Module> chunk : chunks) { |
| final Set<Module> chunkModules = chunk.getNodes(); |
| if (chunkModules.size() <= 1) { |
| continue; // no need to check one-module chunks |
| } |
| Sdk jdk = null; |
| LanguageLevel languageLevel = null; |
| for (final Module module : chunkModules) { |
| final Sdk moduleJdk = ModuleRootManager.getInstance(module).getSdk(); |
| if (jdk == null) { |
| jdk = moduleJdk; |
| } |
| else { |
| if (!jdk.equals(moduleJdk)) { |
| showCyclicModulesHaveDifferentJdksError(chunkModules.toArray(new Module[chunkModules.size()])); |
| return false; |
| } |
| } |
| |
| LanguageLevel moduleLanguageLevel = LanguageLevelUtil.getEffectiveLanguageLevel(module); |
| if (languageLevel == null) { |
| languageLevel = moduleLanguageLevel; |
| } |
| else { |
| if (!languageLevel.equals(moduleLanguageLevel)) { |
| showCyclicModulesHaveDifferentLanguageLevel(chunkModules.toArray(new Module[chunkModules.size()])); |
| return false; |
| } |
| } |
| } |
| } |
| return true; |
| } |
| catch (Throwable e) { |
| LOG.info(e); |
| return false; |
| } |
| } |
| |
| private void showCyclicModulesHaveDifferentLanguageLevel(Module[] modulesInChunk) { |
| LOG.assertTrue(modulesInChunk.length > 0); |
| String moduleNameToSelect = modulesInChunk[0].getName(); |
| final String moduleNames = getModulesString(modulesInChunk); |
| Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.language.level", moduleNames), |
| CommonBundle.getErrorTitle(), Messages.getErrorIcon()); |
| showConfigurationDialog(moduleNameToSelect, null); |
| } |
| |
| private void showCyclicModulesHaveDifferentJdksError(Module[] modulesInChunk) { |
| LOG.assertTrue(modulesInChunk.length > 0); |
| String moduleNameToSelect = modulesInChunk[0].getName(); |
| final String moduleNames = getModulesString(modulesInChunk); |
| Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.jdk", moduleNames), |
| CommonBundle.getErrorTitle(), Messages.getErrorIcon()); |
| showConfigurationDialog(moduleNameToSelect, null); |
| } |
| |
| private static String getModulesString(Module[] modulesInChunk) { |
| final StringBuilder moduleNames = StringBuilderSpinAllocator.alloc(); |
| try { |
| for (Module module : modulesInChunk) { |
| if (moduleNames.length() > 0) { |
| moduleNames.append("\n"); |
| } |
| moduleNames.append("\"").append(module.getName()).append("\""); |
| } |
| return moduleNames.toString(); |
| } |
| finally { |
| StringBuilderSpinAllocator.dispose(moduleNames); |
| } |
| } |
| |
| private static boolean hasSources(Module module, final JavaSourceRootType rootType) { |
| return !ModuleRootManager.getInstance(module).getSourceRoots(rootType).isEmpty(); |
| } |
| |
| private void showNotSpecifiedError(@NonNls final String resourceId, List<String> modules, String editorNameToSelect) { |
| String nameToSelect = null; |
| final StringBuilder names = StringBuilderSpinAllocator.alloc(); |
| final String message; |
| try { |
| final int maxModulesToShow = 10; |
| for (String name : modules.size() > maxModulesToShow ? modules.subList(0, maxModulesToShow) : modules) { |
| if (nameToSelect == null) { |
| nameToSelect = name; |
| } |
| if (names.length() > 0) { |
| names.append(",\n"); |
| } |
| names.append("\""); |
| names.append(name); |
| names.append("\""); |
| } |
| if (modules.size() > maxModulesToShow) { |
| names.append(",\n..."); |
| } |
| message = CompilerBundle.message(resourceId, modules.size(), names.toString()); |
| } |
| finally { |
| StringBuilderSpinAllocator.dispose(names); |
| } |
| |
| if (ApplicationManager.getApplication().isUnitTestMode()) { |
| LOG.error(message); |
| } |
| |
| Messages.showMessageDialog(myProject, message, CommonBundle.getErrorTitle(), Messages.getErrorIcon()); |
| showConfigurationDialog(nameToSelect, editorNameToSelect); |
| } |
| |
| private void showConfigurationDialog(String moduleNameToSelect, String tabNameToSelect) { |
| ProjectSettingsService.getInstance(myProject).showModuleConfigurationDialog(moduleNameToSelect, tabNameToSelect); |
| } |
| |
| private static class MessagesActivationListener extends NotificationListener.Adapter { |
| private final WeakReference<Project> myProjectRef; |
| private final Object myContentId; |
| |
| public MessagesActivationListener(CompileContextImpl compileContext) { |
| myProjectRef = new WeakReference<Project>(compileContext.getProject()); |
| myContentId = compileContext.getBuildSession().getContentId(); |
| } |
| |
| @Override |
| protected void hyperlinkActivated(@NotNull Notification notification, @NotNull HyperlinkEvent e) { |
| final Project project = myProjectRef.get(); |
| if (project != null && !project.isDisposed() && CompilerTask.showCompilerContent(project, myContentId)) { |
| final ToolWindow tw = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.MESSAGES_WINDOW); |
| if (tw != null) { |
| tw.activate(null, false); |
| } |
| } |
| else { |
| notification.expire(); |
| } |
| } |
| } |
| } |