From 72ebbbe011be8a26f3962331371fbc0359eaf179 Mon Sep 17 00:00:00 2001 From: nik Date: Mon, 9 Oct 2017 20:26:38 +0300 Subject: [PATCH] compiler: add 'Compile affected unloaded modules before commit' option Now the build process can optionally load unloaded modules and compile them. It's used to check before committing that changes don't break compilation of unloaded modules (IDEA-180275). --- .../compiler/CompilerManagerImpl.java | 5 + .../intellij/compiler/impl/CompileDriver.java | 30 ++-- .../compiler/impl/CompileScopeUtil.java | 10 +- .../compiler/impl/CompositeScope.java | 10 ++ .../compiler/impl/ModuleCompileScope.java | 30 +++- ...oadedModulesCompilationCheckinHandler.java | 154 ++++++++++++++++++ .../compiler/progress/CompilerTask.java | 14 +- .../compiler/server/BuildManager.java | 3 +- .../ArtifactBuildTargetScopeProvider.java | 2 +- .../CompilerWorkspaceConfiguration.java | 1 + .../openapi/compiler/CompileScope.java | 11 ++ .../openapi/compiler/CompilerManager.java | 5 + .../UnloadedModulesCompilationTest.java | 39 +++++ .../jps/api/BuildParametersKeys.java | 1 + .../org/jetbrains/jps/cmdline/BuildMain.java | 2 +- .../jetbrains/jps/cmdline/BuildSession.java | 22 ++- .../jps/cmdline/JpsModelLoaderImpl.java | 9 +- .../model/serialization/JpsProjectLoader.java | 19 ++- .../JpsSerializationManager.java | 7 +- .../impl/JpsSerializationManagerImpl.java | 4 +- .../org/jetbrains/jps/build/Standalone.java | 2 +- .../src/messages/CompilerBundle.properties | 7 + resources/src/idea/RichPlatformPlugin.xml | 2 + 23 files changed, 353 insertions(+), 36 deletions(-) create mode 100644 java/compiler/impl/src/com/intellij/compiler/impl/vcs/UnloadedModulesCompilationCheckinHandler.java create mode 100644 java/compiler/tests/com/intellij/compiler/UnloadedModulesCompilationTest.java diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java b/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java index fdcf95c68e90..3c9650fa4742 100644 --- a/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java @@ -263,6 +263,11 @@ public class CompilerManagerImpl extends CompilerManager { new CompileDriver(myProject).make(scope, new ListenerNotificator(callback)); } + @Override + public void makeWithModalProgress(@NotNull CompileScope scope, @Nullable CompileStatusNotification callback) { + new CompileDriver(myProject).make(scope, true, new ListenerNotificator(callback)); + } + @Override public void make(@NotNull CompileScope scope, CompilerFilter filter, @Nullable CompileStatusNotification callback) { final CompileDriver compileDriver = new CompileDriver(myProject); diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java index 6e1e76260c67..adebcbe898c9 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java @@ -66,10 +66,7 @@ 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.GlobalOptions; -import org.jetbrains.jps.api.TaskFuture; +import org.jetbrains.jps.api.*; import org.jetbrains.jps.model.java.JavaSourceRootType; import javax.swing.*; @@ -107,8 +104,12 @@ public class CompileDriver { } public void make(CompileScope scope, CompileStatusNotification callback) { + make(scope, false, callback); + } + + public void make(CompileScope scope, boolean withModalProgress, CompileStatusNotification callback) { if (validateCompilerConfiguration(scope)) { - startup(scope, false, false, callback, null); + startup(scope, false, false, withModalProgress, callback, null); } else { callback.finished(true, 0, 0, DummyCompileContext.getInstance()); @@ -195,7 +196,7 @@ public class CompileDriver { scopes.addAll(explicitScopes); } else if (!compileContext.isRebuild() && !CompileScopeUtil.allProjectModulesAffected(compileContext)) { - CompileScopeUtil.addScopesForModules(Arrays.asList(scope.getAffectedModules()), scopes, forceBuild); + CompileScopeUtil.addScopesForModules(Arrays.asList(scope.getAffectedModules()), scope.getAffectedUnloadedModules(), scopes, forceBuild); } else { scopes.addAll(CmdlineProtoUtil.createAllModulesScopes(forceBuild)); @@ -228,7 +229,7 @@ public class CompileDriver { // need to pass scope's user data to server final Map builderParams; if (onlyCheckUpToDate) { - builderParams = Collections.emptyMap(); + builderParams = new HashMap<>(); } else { final Map exported = scope.exportUserData(); @@ -241,9 +242,12 @@ public class CompileDriver { } } else { - builderParams = Collections.emptyMap(); + builderParams = new HashMap<>(); } } + if (!scope.getAffectedUnloadedModules().isEmpty()) { + builderParams.put(BuildParametersKeys.LOAD_UNLOADED_MODULES, Boolean.TRUE.toString()); + } final MessageBus messageBus = myProject.getMessageBus(); final MultiMap outputToArtifact = ArtifactCompilerUtil.containsArtifacts(scopes) ? ArtifactCompilerUtil.createOutputToArtifactMap(myProject) : null; @@ -372,16 +376,22 @@ public class CompileDriver { }); } + private void startup(final CompileScope scope, final boolean isRebuild, final boolean forceCompile, + final CompileStatusNotification callback, final CompilerMessage message) { + startup(scope, isRebuild, forceCompile, false, callback, message); + } + private void startup(final CompileScope scope, final boolean isRebuild, final boolean forceCompile, - final CompileStatusNotification callback, + boolean withModalProgress, final CompileStatusNotification callback, final CompilerMessage message) { ApplicationManager.getApplication().assertIsDispatchThread(); final String contentName = CompilerBundle.message(forceCompile ? "compiler.content.name.compile" : "compiler.content.name.make"); final boolean isUnitTestMode = ApplicationManager.getApplication().isUnitTestMode(); - final CompilerTask compileTask = new CompilerTask(myProject, contentName, isUnitTestMode, true, true, isCompilationStartedAutomatically(scope)); + final CompilerTask compileTask = new CompilerTask(myProject, contentName, isUnitTestMode, !withModalProgress, true, + isCompilationStartedAutomatically(scope), withModalProgress); StatusBar.Info.set("", myProject, "Compiler"); // ensure the project model seen by build process is up-to-date diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileScopeUtil.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileScopeUtil.java index ce83b1755bb7..d3db11d32957 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileScopeUtil.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileScopeUtil.java @@ -36,13 +36,19 @@ public class CompileScopeUtil { scope.putUserData(BASE_SCOPE_FOR_EXTERNAL_BUILD, scopes); } - public static void addScopesForModules(Collection modules, List scopes, boolean forceBuild) { - if (!modules.isEmpty()) { + public static void addScopesForModules(Collection modules, + Collection unloadedModules, + List scopes, + boolean forceBuild) { + if (!modules.isEmpty() || !unloadedModules.isEmpty()) { for (JavaModuleBuildTargetType type : JavaModuleBuildTargetType.ALL_TYPES) { TargetTypeBuildScope.Builder builder = TargetTypeBuildScope.newBuilder().setTypeId(type.getTypeId()).setForceBuild(forceBuild); for (Module module : modules) { builder.addTargetId(module.getName()); } + for (String unloadedModule : unloadedModules) { + builder.addTargetId(unloadedModule); + } scopes.add(builder.build()); } } diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompositeScope.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompositeScope.java index df6de5d10bd4..fbbf769a558a 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompositeScope.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompositeScope.java @@ -90,6 +90,16 @@ public class CompositeScope extends ExportableUserDataHolderBase implements Comp return modules.toArray(new Module[modules.size()]); } + @NotNull + @Override + public Collection getAffectedUnloadedModules() { + Set unloadedModules = new LinkedHashSet<>(); + for (final CompileScope compileScope : myScopes) { + ContainerUtil.addAll(unloadedModules, compileScope.getAffectedUnloadedModules()); + } + return unloadedModules; + } + public T getUserData(@NotNull Key key) { for (CompileScope compileScope : myScopes) { T userData = compileScope.getUserData(key); diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/ModuleCompileScope.java b/java/compiler/impl/src/com/intellij/compiler/impl/ModuleCompileScope.java index ff79b0721ac9..80098ea5106d 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/ModuleCompileScope.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/ModuleCompileScope.java @@ -24,11 +24,13 @@ package com.intellij.compiler.impl; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.module.UnloadedModuleDescription; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.pointers.VirtualFilePointer; import com.intellij.util.CommonProcessors; import org.jetbrains.annotations.NotNull; @@ -38,9 +40,10 @@ public class ModuleCompileScope extends FileIndexCompileScope { private final Project myProject; private final Set myScopeModules; private final Module[] myModules; + private final Collection myIncludedUnloadedModules; public ModuleCompileScope(final Module module, boolean includeDependentModules) { - this(module.getProject(), Collections.singleton(module), includeDependentModules, false); + this(module.getProject(), Collections.singleton(module), Collections.emptyList(), includeDependentModules, false); } public ModuleCompileScope(Project project, final Module[] modules, boolean includeDependentModules) { @@ -48,11 +51,12 @@ public class ModuleCompileScope extends FileIndexCompileScope { } public ModuleCompileScope(Project project, final Module[] modules, boolean includeDependentModules, boolean includeRuntimeDependencies) { - this(project, Arrays.asList(modules), includeDependentModules, includeRuntimeDependencies); + this(project, Arrays.asList(modules), Collections.emptyList(), includeDependentModules, includeRuntimeDependencies); } - private ModuleCompileScope(Project project, final Collection modules, boolean includeDependentModules, boolean includeRuntimeDeps) { + public ModuleCompileScope(Project project, final Collection modules, Collection includedUnloadedModules, boolean includeDependentModules, boolean includeRuntimeDeps) { myProject = project; + myIncludedUnloadedModules = includedUnloadedModules; myScopeModules = new HashSet<>(); for (Module module : modules) { if (module == null) { @@ -77,6 +81,12 @@ public class ModuleCompileScope extends FileIndexCompileScope { return myScopeModules.toArray(new Module[myScopeModules.size()]); } + @NotNull + @Override + public Collection getAffectedUnloadedModules() { + return Collections.unmodifiableCollection(myIncludedUnloadedModules); + } + protected FileIndex[] getFileIndices() { final FileIndex[] indices = new FileIndex[myScopeModules.size()]; int idx = 0; @@ -87,7 +97,7 @@ public class ModuleCompileScope extends FileIndexCompileScope { } public boolean belongs(final String url) { - if (myScopeModules.isEmpty()) { + if (myScopeModules.isEmpty() && myIncludedUnloadedModules.isEmpty()) { return false; // optimization } Module candidateModule = null; @@ -142,6 +152,18 @@ public class ModuleCompileScope extends FileIndexCompileScope { } } + ModuleManager moduleManager = ModuleManager.getInstance(myProject); + for (String unloadedModule : myIncludedUnloadedModules) { + UnloadedModuleDescription moduleDescription = moduleManager.getUnloadedModuleDescription(unloadedModule); + if (moduleDescription != null) { + for (VirtualFilePointer pointer : moduleDescription.getContentRoots()) { + if (isUrlUnderRoot(url, pointer.getUrl())) { + return true; + } + } + } + } + return false; } diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/vcs/UnloadedModulesCompilationCheckinHandler.java b/java/compiler/impl/src/com/intellij/compiler/impl/vcs/UnloadedModulesCompilationCheckinHandler.java new file mode 100644 index 000000000000..40619d3d9d29 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/compiler/impl/vcs/UnloadedModulesCompilationCheckinHandler.java @@ -0,0 +1,154 @@ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.compiler.impl.vcs; + +import com.intellij.CommonBundle; +import com.intellij.compiler.CompilerWorkspaceConfiguration; +import com.intellij.compiler.impl.ModuleCompileScope; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.compiler.CompileStatusNotification; +import com.intellij.openapi.compiler.CompilerBundle; +import com.intellij.openapi.compiler.CompilerManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.impl.DirectoryIndex; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vcs.CheckinProjectPanel; +import com.intellij.openapi.vcs.changes.CommitContext; +import com.intellij.openapi.vcs.changes.CommitExecutor; +import com.intellij.openapi.vcs.checkin.CheckinHandler; +import com.intellij.openapi.vcs.checkin.CheckinHandlerFactory; +import com.intellij.openapi.vcs.ui.RefreshableOnComponent; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.ui.NonFocusableCheckBox; +import com.intellij.util.PairConsumer; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.JBUI; +import com.intellij.xml.util.XmlStringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author nik + */ +public class UnloadedModulesCompilationCheckinHandler extends CheckinHandler { + private final Project myProject; + private final CheckinProjectPanel myCheckinPanel; + + public UnloadedModulesCompilationCheckinHandler(Project project, CheckinProjectPanel checkinPanel) { + myProject = project; + myCheckinPanel = checkinPanel; + } + + @Nullable + @Override + public RefreshableOnComponent getBeforeCheckinConfigurationPanel() { + if (ModuleManager.getInstance(myProject).getUnloadedModuleDescriptions().isEmpty()) { + return null; + } + + JCheckBox checkBox = new NonFocusableCheckBox(CompilerBundle.message("checkbox.text.compile.affected.unloaded.modules")); + return new RefreshableOnComponent() { + @Override + public JComponent getComponent() { + return JBUI.Panels.simplePanel().addToLeft(checkBox); + } + + @Override + public void refresh() { + } + + @Override + public void saveState() { + CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_AFFECTED_UNLOADED_MODULES_BEFORE_COMMIT = checkBox.isSelected(); + } + + @Override + public void restoreState() { + checkBox.setSelected(CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_AFFECTED_UNLOADED_MODULES_BEFORE_COMMIT); + } + }; + } + + @Override + public ReturnResult beforeCheckin(@Nullable CommitExecutor executor, PairConsumer additionalDataConsumer) { + if (!CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_AFFECTED_UNLOADED_MODULES_BEFORE_COMMIT + || ModuleManager.getInstance(myProject).getUnloadedModuleDescriptions().isEmpty()) { + return ReturnResult.COMMIT; + } + + ProjectFileIndex fileIndex = ProjectFileIndex.getInstance(myProject); + CompilerManager compilerManager = CompilerManager.getInstance(myProject); + Set affectedModules = new LinkedHashSet<>(); + for (VirtualFile file : myCheckinPanel.getVirtualFiles()) { + if (compilerManager.isCompilableFileType(file.getFileType())) { + ContainerUtil.addIfNotNull(affectedModules, fileIndex.getModuleForFile(file)); + } + } + + Set affectedUnloadedModules = new LinkedHashSet<>(); + for (Module module : affectedModules) { + affectedUnloadedModules.addAll(DirectoryIndex.getInstance(myProject).getDependentUnloadedModules(module)); + } + + if (affectedUnloadedModules.isEmpty()) { + return ReturnResult.COMMIT; + } + + AtomicReference result = new AtomicReference<>(); + compilerManager.makeWithModalProgress(new ModuleCompileScope(myProject, affectedModules, affectedUnloadedModules, true, false), + new CompileStatusNotification() { + @Override + public void finished(boolean aborted, int errors, int warnings, CompileContext compileContext) { + result.set( + aborted ? BuildResult.CANCELED : errors > 0 ? BuildResult.FAILED : BuildResult.SUCCESSFUL); + } + }); + + if (result.get() == BuildResult.SUCCESSFUL) { + return ReturnResult.COMMIT; + } + String message = CompilerBundle.message("dialog.message.compilation.of.unloaded.modules.failed"); + int answer = Messages.showYesNoCancelDialog(myProject, XmlStringUtil.wrapInHtml(message), CompilerBundle.message("dialog.title.compilation.failed"), + CompilerBundle.message("button.text.checkin.handler.commit"), + CompilerBundle.message("button.text.checkin.handler.show.errors"), + CommonBundle.getCancelButtonText(), null); + + if (answer == Messages.CANCEL) { + return ReturnResult.CANCEL; + } + else if (answer == Messages.YES) { + return ReturnResult.COMMIT; + } + else { + ApplicationManager.getApplication().invokeLater(() -> { + final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.MESSAGES_WINDOW); + if (toolWindow != null) { + toolWindow.activate(null, false); + } + }, ModalityState.NON_MODAL); + return ReturnResult.CLOSE_WINDOW; + } + } + + private enum BuildResult { SUCCESSFUL, FAILED, CANCELED } + + public static class Factory extends CheckinHandlerFactory { + @NotNull + @Override + public CheckinHandler createHandler(@NotNull CheckinProjectPanel panel, @NotNull CommitContext commitContext) { + return new UnloadedModulesCompilationCheckinHandler(panel.getProject(), panel); + } + } +} diff --git a/java/compiler/impl/src/com/intellij/compiler/progress/CompilerTask.java b/java/compiler/impl/src/com/intellij/compiler/progress/CompilerTask.java index 4a5afe186608..c274fe5ceb12 100644 --- a/java/compiler/impl/src/com/intellij/compiler/progress/CompilerTask.java +++ b/java/compiler/impl/src/com/intellij/compiler/progress/CompilerTask.java @@ -78,6 +78,7 @@ public class CompilerTask extends Task.Backgroundable { private static final String APP_ICON_ID = "compiler"; @NotNull private final Object myContentId = new IDObject("content_id"); + private final boolean myModal; @NotNull private Object mySessionId = myContentId; // by default sessionID should be unique, just as content ID @@ -105,12 +106,18 @@ public class CompilerTask extends Task.Backgroundable { public CompilerTask(@NotNull Project project, String contentName, final boolean headlessMode, boolean forceAsync, boolean waitForPreviousSession, boolean compilationStartedAutomatically) { + this(project, contentName, headlessMode, forceAsync, waitForPreviousSession, compilationStartedAutomatically, false); + } + + public CompilerTask(@NotNull Project project, String contentName, final boolean headlessMode, boolean forceAsync, + boolean waitForPreviousSession, boolean compilationStartedAutomatically, boolean modal) { super(project, contentName); myContentName = contentName; myHeadlessMode = headlessMode; myForceAsyncExecution = forceAsync; myWaitForPreviousSession = waitForPreviousSession; myCompilationStartedAutomatically = compilationStartedAutomatically; + myModal = modal; } @NotNull @@ -149,7 +156,12 @@ public class CompilerTask extends Task.Backgroundable { @Override public boolean shouldStartInBackground() { - return true; + return !myModal; + } + + @Override + public boolean isConditionalModal() { + return myModal; } public ProgressIndicator getIndicator() { diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java index 71925988312b..49726270fc2c 100644 --- a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java +++ b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java @@ -522,7 +522,8 @@ public class BuildManager implements Disposable { final List scopes = CmdlineProtoUtil.createAllModulesScopes(false); final AutoMakeMessageHandler handler = new AutoMakeMessageHandler(project); final TaskFuture future = scheduleBuild( - project, false, true, false, scopes, Collections.emptyList(), Collections.emptyMap(), handler + project, false, true, false, scopes, Collections.emptyList(), Collections.emptyMap(), + handler ); if (future != null) { myAutomakeFutures.put(future, project); diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactBuildTargetScopeProvider.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactBuildTargetScopeProvider.java index 22783212fc2b..22a4325311fe 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactBuildTargetScopeProvider.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactBuildTargetScopeProvider.java @@ -52,7 +52,7 @@ public class ArtifactBuildTargetScopeProvider extends BuildTargetScopeProvider { final Set artifacts = ArtifactCompileScope.getArtifactsToBuild(project, baseScope, false); if (ArtifactCompileScope.getArtifacts(baseScope) == null) { Set modules = ArtifactUtil.getModulesIncludedInArtifacts(artifacts, project); - CompileScopeUtil.addScopesForModules(modules, scopes, forceBuild); + CompileScopeUtil.addScopesForModules(modules, Collections.emptyList(), scopes, forceBuild); } if (!artifacts.isEmpty()) { TargetTypeBuildScope.Builder builder = TargetTypeBuildScope.newBuilder() diff --git a/java/compiler/openapi/src/com/intellij/compiler/CompilerWorkspaceConfiguration.java b/java/compiler/openapi/src/com/intellij/compiler/CompilerWorkspaceConfiguration.java index ac62b1a2dbc0..09ed72257c6c 100644 --- a/java/compiler/openapi/src/com/intellij/compiler/CompilerWorkspaceConfiguration.java +++ b/java/compiler/openapi/src/com/intellij/compiler/CompilerWorkspaceConfiguration.java @@ -46,6 +46,7 @@ public class CompilerWorkspaceConfiguration implements PersistentStateComponent< public int COMPILER_PROCESS_HEAP_SIZE = 700; public String COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS = ""; public boolean REBUILD_ON_DEPENDENCY_CHANGE = true; + public boolean COMPILE_AFFECTED_UNLOADED_MODULES_BEFORE_COMMIT = true; public static CompilerWorkspaceConfiguration getInstance(Project project) { return ServiceManager.getService(project, CompilerWorkspaceConfiguration.class); diff --git a/java/compiler/openapi/src/com/intellij/openapi/compiler/CompileScope.java b/java/compiler/openapi/src/com/intellij/openapi/compiler/CompileScope.java index 5e1c30445f89..1517120bce6c 100644 --- a/java/compiler/openapi/src/com/intellij/openapi/compiler/CompileScope.java +++ b/java/compiler/openapi/src/com/intellij/openapi/compiler/CompileScope.java @@ -21,6 +21,9 @@ import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; +import java.util.Collections; + /** * Interface describing the current compilation scope. * Only sources that belong to the scope are compiled. @@ -56,4 +59,12 @@ public interface CompileScope extends ExportableUserDataHolder { */ @NotNull Module[] getAffectedModules(); + + /** + * @return list of names of unloaded modules this scope affects. + */ + @NotNull + default Collection getAffectedUnloadedModules() { + return Collections.emptyList(); + } } diff --git a/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerManager.java b/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerManager.java index 947d3c3bf29e..f09e88fbb9f2 100644 --- a/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerManager.java +++ b/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerManager.java @@ -221,6 +221,11 @@ public abstract class CompilerManager { */ public abstract void make(@NotNull CompileScope scope, @Nullable CompileStatusNotification callback); + /** + * Same as {@link #make(CompileScope, CompileStatusNotification)} but with modal progress window instead of background progress + */ + public abstract void makeWithModalProgress(@NotNull CompileScope scope, @Nullable CompileStatusNotification callback); + /** * Compile all modified files and all files that depend on them from the scope given. * Files are compiled according to dependencies between the modules they belong to. Compiler excludes are honored. All modules must belong to the same project diff --git a/java/compiler/tests/com/intellij/compiler/UnloadedModulesCompilationTest.java b/java/compiler/tests/com/intellij/compiler/UnloadedModulesCompilationTest.java new file mode 100644 index 000000000000..5b9092bdd28d --- /dev/null +++ b/java/compiler/tests/com/intellij/compiler/UnloadedModulesCompilationTest.java @@ -0,0 +1,39 @@ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.compiler; + +import com.intellij.compiler.impl.ModuleCompileScope; +import com.intellij.openapi.compiler.CompilerFilter; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.vfs.VirtualFile; + +import java.io.File; +import java.util.Collections; +import java.util.List; + +import static com.intellij.util.io.TestFileSystemBuilder.fs; + +/** + * @author nik + */ +public class UnloadedModulesCompilationTest extends BaseCompilerTestCase { + public void testDoNotCompileUnloadedModulesByDefault() { + VirtualFile a = createFile("unloaded/src/A.java", "class A{ error }"); + Module unloaded = addModule("unloaded", a.getParent()); + List unloadedList = Collections.singletonList(unloaded.getName()); + ModuleManager.getInstance(myProject).setUnloadedModules(unloadedList); + buildAllModules().assertUpToDate(); + } + + public void testCompileUnloadedModulesIfExplicitlySpecified() { + VirtualFile a = createFile("unloaded/src/A.java", "class A{}"); + Module unloaded = addModule("unloaded", a.getParent()); + File outputDir = getOutputDir(unloaded, false); + + List unloadedList = Collections.singletonList(unloaded.getName()); + ModuleManager.getInstance(myProject).setUnloadedModules(unloadedList); + + make(new ModuleCompileScope(myProject, Collections.emptyList(), unloadedList, true, false), CompilerFilter.ALL); + fs().file("A.class").build().assertDirectoryEqual(outputDir); + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/BuildParametersKeys.java b/jps/jps-builders/src/org/jetbrains/jps/api/BuildParametersKeys.java index 3621bd00e726..de6f1496bd3b 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/api/BuildParametersKeys.java +++ b/jps/jps-builders/src/org/jetbrains/jps/api/BuildParametersKeys.java @@ -5,4 +5,5 @@ package org.jetbrains.jps.api; */ public interface BuildParametersKeys { String FORCE_MODEL_LOADING = "_force_model_loading"; + String LOAD_UNLOADED_MODULES = "load_unloaded_modules"; } diff --git a/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildMain.java b/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildMain.java index cbfdf0dab651..46fe593bffc9 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildMain.java +++ b/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildMain.java @@ -129,7 +129,7 @@ public class BuildMain { try { FileSystemUtil.getAttributes(projectPathToPreload); // this will pre-load all FS optimizations - final BuildRunner runner = new BuildRunner(new JpsModelLoaderImpl(projectPathToPreload, globalsPathToPreload, null)); + final BuildRunner runner = new BuildRunner(new JpsModelLoaderImpl(projectPathToPreload, globalsPathToPreload, false, null)); data.setRunner(runner); final File dataStorageRoot = Utils.getDataStorageRoot(projectPathToPreload); diff --git a/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildSession.java b/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildSession.java index 510055b0f276..e5b12b4ec4c9 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildSession.java +++ b/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildSession.java @@ -86,8 +86,7 @@ final class BuildSession implements Runnable, CanceledStatus { @Nullable CmdlineRemoteProto.Message.ControllerMessage.FSEvent delta, @Nullable PreloadedData preloaded) { mySessionId = sessionId; myChannel = channel; - myPreloadedData = preloaded; - + final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals = params.getGlobalSettings(); myProjectPath = FileUtil.toCanonicalPath(params.getProjectId()); String globalOptionsPath = FileUtil.toCanonicalPath(globals.getGlobalOptionsPath()); @@ -99,11 +98,24 @@ final class BuildSession implements Runnable, CanceledStatus { builderParams.put(pair.getKey(), pair.getValue()); } myInitialFSDelta = delta; - if (preloaded == null || preloaded.getRunner() == null) { - myBuildRunner = new BuildRunner(new JpsModelLoaderImpl(myProjectPath, globalOptionsPath, null)); + boolean loadUnloadedModules = Boolean.parseBoolean(builderParams.get(BuildParametersKeys.LOAD_UNLOADED_MODULES)); + if (loadUnloadedModules && preloaded != null) { + myPreloadedData = null; + ProjectDescriptor projectDescriptor = preloaded.getProjectDescriptor(); + if (projectDescriptor != null) { + projectDescriptor.release(); + preloaded.setProjectDescriptor(null); + } } else { - myBuildRunner = preloaded.getRunner(); + myPreloadedData = preloaded; + } + + if (myPreloadedData == null || myPreloadedData.getRunner() == null) { + myBuildRunner = new BuildRunner(new JpsModelLoaderImpl(myProjectPath, globalOptionsPath, loadUnloadedModules, null)); + } + else { + myBuildRunner = myPreloadedData.getRunner(); } myBuildRunner.setFilePaths(filePaths); myBuildRunner.setBuilderParams(builderParams); diff --git a/jps/jps-builders/src/org/jetbrains/jps/cmdline/JpsModelLoaderImpl.java b/jps/jps-builders/src/org/jetbrains/jps/cmdline/JpsModelLoaderImpl.java index 5ad11fdaf8b6..e6712c4f1e6e 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/cmdline/JpsModelLoaderImpl.java +++ b/jps/jps-builders/src/org/jetbrains/jps/cmdline/JpsModelLoaderImpl.java @@ -30,11 +30,16 @@ public class JpsModelLoaderImpl implements JpsModelLoader { private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.cmdline.JpsModelLoaderImpl"); private final String myProjectPath; private final String myGlobalOptionsPath; + private final boolean myLoadUnloadedModules; private final ParameterizedRunnable myModelInitializer; - public JpsModelLoaderImpl(String projectPath, String globalOptionsPath, @Nullable ParameterizedRunnable initializer) { + public JpsModelLoaderImpl(String projectPath, + String globalOptionsPath, + boolean loadUnloadedModules, + @Nullable ParameterizedRunnable initializer) { myProjectPath = projectPath; myGlobalOptionsPath = globalOptionsPath; + myLoadUnloadedModules = loadUnloadedModules; myModelInitializer = initializer; } @@ -42,7 +47,7 @@ public class JpsModelLoaderImpl implements JpsModelLoader { public JpsModel loadModel() throws IOException { final long start = System.currentTimeMillis(); LOG.info("Loading model: project path = " + myProjectPath + ", global options path = " + myGlobalOptionsPath); - final JpsModel model = JpsSerializationManager.getInstance().loadModel(myProjectPath, myGlobalOptionsPath); + final JpsModel model = JpsSerializationManager.getInstance().loadModel(myProjectPath, myGlobalOptionsPath, myLoadUnloadedModules); if (myModelInitializer != null) { myModelInitializer.run(model); } diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java index 1eace6fd9610..d277587f761e 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java @@ -67,12 +67,14 @@ public class JpsProjectLoader extends JpsLoaderBase { public static final String CLASSPATH_DIR_ATTRIBUTE = "classpath-dir"; private final JpsProject myProject; private final Map myPathVariables; + private final boolean myLoadUnloadedModules; - private JpsProjectLoader(JpsProject project, Map pathVariables, Path baseDir) { + private JpsProjectLoader(JpsProject project, Map pathVariables, Path baseDir, boolean loadUnloadedModules) { super(createProjectMacroExpander(pathVariables, baseDir)); myProject = project; myPathVariables = pathVariables; myProject.getContainer().setChild(JpsProjectSerializationDataExtensionImpl.ROLE, new JpsProjectSerializationDataExtensionImpl(baseDir)); + myLoadUnloadedModules = loadUnloadedModules; } static JpsMacroExpander createProjectMacroExpander(Map pathVariables, @NotNull Path baseDir) { @@ -81,10 +83,17 @@ public class JpsProjectLoader extends JpsLoaderBase { return expander; } - public static void loadProject(final JpsProject project, Map pathVariables, String projectPath) throws IOException { + public static void loadProject(final JpsProject project, + Map pathVariables, + String projectPath) throws IOException { + loadProject(project, pathVariables, projectPath, false); + } + + public static void loadProject(final JpsProject project, Map pathVariables, String projectPath, + boolean loadUnloadedModules) throws IOException { Path file = Paths.get(FileUtil.toCanonicalPath(projectPath)); if (Files.isRegularFile(file) && projectPath.endsWith(".ipr")) { - new JpsProjectLoader(project, pathVariables, file.getParent()).loadFromIpr(file); + new JpsProjectLoader(project, pathVariables, file.getParent(), loadUnloadedModules).loadFromIpr(file); } else { Path dotIdea = file.resolve(PathMacroUtil.DIRECTORY_STORE_NAME); @@ -98,7 +107,7 @@ public class JpsProjectLoader extends JpsLoaderBase { else { throw new IOException("Cannot find IntelliJ IDEA project files at " + projectPath); } - new JpsProjectLoader(project, pathVariables, directory.getParent()).loadFromDirectory(directory); + new JpsProjectLoader(project, pathVariables, directory.getParent(), loadUnloadedModules).loadFromDirectory(directory); } } @@ -236,7 +245,7 @@ public class JpsProjectLoader extends JpsLoaderBase { if (componentRoot == null) return; Set unloadedModules = new HashSet<>(); - if (Files.exists(workspaceFile)) { + if (!myLoadUnloadedModules && Files.exists(workspaceFile)) { Element unloadedModulesList = JDomSerializationUtil.findComponent(loadRootElement(workspaceFile), "UnloadedModulesList"); for (Element element : JDOMUtil.getChildren(unloadedModulesList, "module")) { unloadedModules.add(element.getAttributeValue("name")); diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsSerializationManager.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsSerializationManager.java index 2261d3ccea77..aa89e4cde73c 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsSerializationManager.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsSerializationManager.java @@ -34,7 +34,12 @@ public abstract class JpsSerializationManager { } @NotNull - public abstract JpsModel loadModel(@NotNull String projectPath, @Nullable String optionsPath) throws IOException; + public JpsModel loadModel(@NotNull String projectPath, @Nullable String optionsPath) throws IOException { + return loadModel(projectPath, optionsPath, false); + } + + @NotNull + public abstract JpsModel loadModel(@NotNull String projectPath, @Nullable String optionsPath, boolean loadUnloadedModules) throws IOException; @NotNull public abstract JpsProject loadProject(@NotNull String projectPath, @NotNull Map pathVariables) throws IOException; diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/impl/JpsSerializationManagerImpl.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/impl/JpsSerializationManagerImpl.java index c77b1647e16c..06f08f121ab5 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/impl/JpsSerializationManagerImpl.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/impl/JpsSerializationManagerImpl.java @@ -32,14 +32,14 @@ import java.util.Map; public class JpsSerializationManagerImpl extends JpsSerializationManager { @NotNull @Override - public JpsModel loadModel(@NotNull String projectPath, @Nullable String optionsPath) + public JpsModel loadModel(@NotNull String projectPath, @Nullable String optionsPath, boolean loadUnloadedModules) throws IOException { JpsModel model = JpsElementFactory.getInstance().createModel(); if (optionsPath != null) { JpsGlobalLoader.loadGlobalSettings(model.getGlobal(), optionsPath); } Map pathVariables = JpsModelSerializationDataService.computeAllPathVariables(model.getGlobal()); - JpsProjectLoader.loadProject(model.getProject(), pathVariables, projectPath); + JpsProjectLoader.loadProject(model.getProject(), pathVariables, projectPath, loadUnloadedModules); return model; } diff --git a/jps/standalone-builder/src/org/jetbrains/jps/build/Standalone.java b/jps/standalone-builder/src/org/jetbrains/jps/build/Standalone.java index f6abfaac104d..261f4bf3ad19 100644 --- a/jps/standalone-builder/src/org/jetbrains/jps/build/Standalone.java +++ b/jps/standalone-builder/src/org/jetbrains/jps/build/Standalone.java @@ -127,7 +127,7 @@ public class Standalone { return 1; } - JpsModelLoaderImpl loader = new JpsModelLoaderImpl(projectPath, globalOptionsPath, initializer); + JpsModelLoaderImpl loader = new JpsModelLoaderImpl(projectPath, globalOptionsPath, false, initializer); Set modulesSet = new HashSet<>(Arrays.asList(modules)); List artifactsList = Arrays.asList(artifacts); File dataStorageRoot; diff --git a/resources-en/src/messages/CompilerBundle.properties b/resources-en/src/messages/CompilerBundle.properties index cfcbaf7092c9..80c7abf9ca8e 100644 --- a/resources-en/src/messages/CompilerBundle.properties +++ b/resources-en/src/messages/CompilerBundle.properties @@ -140,6 +140,13 @@ mesage.text.deployment.descriptor.file.not.exist=Deployment descriptor file ''{0 message.text.deployment.description.invalid.file=Invalid file warning.text.file.has.been.changed=File has been changed during compilation, inspection validation skipped +dialog.message.compilation.of.unloaded.modules.failed=There are unloaded modules in the project which depend on changed files.
\ + Compilation of these modules finished with errors. +dialog.title.compilation.failed=Compilation Failed +button.text.checkin.handler.commit=&Commit +button.text.checkin.handler.show.errors=&Show Errors +checkbox.text.compile.affected.unloaded.modules=Compile affected &unloaded modules + #artifacts dialog.title.output.directory.for.artifact=Output Directory for Artifact chooser.description.select.output.directory.for.0.artifact=Select output directory for ''{0}'' artifact diff --git a/resources/src/idea/RichPlatformPlugin.xml b/resources/src/idea/RichPlatformPlugin.xml index a016af51cc54..7b5ccfdb6061 100644 --- a/resources/src/idea/RichPlatformPlugin.xml +++ b/resources/src/idea/RichPlatformPlugin.xml @@ -356,6 +356,8 @@ + +