From 20e27b7d8efb3dbe3e4db4c59758517461b3aa4a Mon Sep 17 00:00:00 2001 From: "Vladislav.Soroka" Date: Mon, 1 Jul 2019 12:24:55 +0300 Subject: [PATCH] Build: do not use JPS compilation directly when building modules/artifacts of running JEE applications IDEA-210338 GitOrigin-RevId: 7e065541303104d3e8a8e5fd17a29554e8068d8d --- .../actions/CompileProjectAction.java | 19 +- .../compiler/impl/CompileContextImpl.java | 23 +-- .../impl/compiler/ArtifactsCompiler.java | 25 +-- ...ildArtifactsBeforeRunTaskProviderBase.java | 8 +- .../com/intellij/task/impl/JpsBuildData.java | 26 +++ .../task/impl/JpsProjectTaskRunner.java | 184 +++++++++++++----- .../com/intellij/debugger/ui/HotSwapUI.java | 18 +- .../intellij/debugger/ui/HotSwapUIImpl.java | 82 ++++---- .../debugger/ui/HotSwapVetoableListener.java | 27 +-- .../options/CompileStepBeforeRun.java | 25 ++- .../test/ExternalSystemTestCase.java | 13 +- .../task/ProjectTaskNotification.java | 28 ++- .../intellij/task/impl/ProjectTaskList.java | 29 ++- .../task/impl/ProjectTaskManagerImpl.java | 45 ++--- 14 files changed, 300 insertions(+), 252 deletions(-) create mode 100644 java/compiler/impl/src/com/intellij/task/impl/JpsBuildData.java diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/CompileProjectAction.java b/java/compiler/impl/src/com/intellij/compiler/actions/CompileProjectAction.java index ea10b2157f13..0c8e44201052 100644 --- a/java/compiler/impl/src/com/intellij/compiler/actions/CompileProjectAction.java +++ b/java/compiler/impl/src/com/intellij/compiler/actions/CompileProjectAction.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2016 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. - */ +// Copyright 2000-2019 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.actions; import com.intellij.history.LocalHistory; @@ -21,6 +7,7 @@ import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.compiler.CompilerBundle; import com.intellij.openapi.project.Project; +import com.intellij.task.ProjectTaskContext; import com.intellij.task.ProjectTaskManager; import com.intellij.task.ProjectTaskNotification; import com.intellij.task.ProjectTaskResult; @@ -31,7 +18,7 @@ public class CompileProjectAction extends CompileActionBase { protected void doAction(DataContext dataContext, final Project project) { ProjectTaskManager.getInstance(project).rebuildAllModules(new ProjectTaskNotification() { @Override - public void finished(@NotNull ProjectTaskResult executionResult) { + public void finished(@NotNull ProjectTaskContext context, @NotNull ProjectTaskResult executionResult) { if (executionResult.isAborted() || project.isDisposed()) { return; } diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java index 17da48dd3e59..48a36e0d3711 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2015 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. - */ +// Copyright 2000-2019 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. /* * @author: Eugene Zhuravlev @@ -25,7 +11,10 @@ import com.intellij.compiler.ProblemsView; import com.intellij.compiler.progress.CompilerTask; import com.intellij.execution.impl.ExecutionManagerImpl; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.compiler.*; +import com.intellij.openapi.compiler.CompileScope; +import com.intellij.openapi.compiler.CompilerMessage; +import com.intellij.openapi.compiler.CompilerMessageCategory; +import com.intellij.openapi.compiler.CompilerPaths; import com.intellij.openapi.compiler.ex.CompileContextEx; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; @@ -33,7 +22,6 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.util.Key; import com.intellij.openapi.util.UserDataHolderBase; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.Navigatable; @@ -43,7 +31,6 @@ import org.jetbrains.annotations.Nullable; import java.util.UUID; public class CompileContextImpl extends UserDataHolderBase implements CompileContextEx { - public static final Key CONTEXT_KEY = Key.create("jps_compile_context"); private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileContextImpl"); private final Project myProject; private final CompilerTask myBuildSession; diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsCompiler.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsCompiler.java index 4f3f9e565eda..cbf60632e0d9 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsCompiler.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsCompiler.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2010 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. - */ +// Copyright 2000-2019 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.packaging.impl.compiler; import com.intellij.openapi.compiler.CompileContext; @@ -21,6 +7,9 @@ import com.intellij.openapi.compiler.Compiler; import com.intellij.openapi.compiler.CompilerManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; +import com.intellij.task.ProjectTaskContext; +import com.intellij.task.impl.JpsBuildData; +import com.intellij.task.impl.JpsProjectTaskRunner; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -66,4 +55,10 @@ public class ArtifactsCompiler implements Compiler { public static Set getWrittenPaths(@NotNull CompileContext context) { return context.getUserData(WRITTEN_PATHS_KEY); } + + @Nullable + public static Set getWrittenPaths(@NotNull ProjectTaskContext context) { + JpsBuildData jpsBuildData = context.getUserData(JpsProjectTaskRunner.JPS_BUILD_DATA_KEY); + return jpsBuildData == null ? null : jpsBuildData.getArtifactsWrittenPaths(); + } } diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTaskProviderBase.java b/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTaskProviderBase.java index fa90018ff997..f4ab7b7bb463 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTaskProviderBase.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTaskProviderBase.java @@ -1,4 +1,4 @@ -// Copyright 2000-2018 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. +// Copyright 2000-2019 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.packaging.impl.run; import com.intellij.execution.BeforeRunTaskProvider; @@ -125,7 +125,7 @@ public abstract class BuildArtifactsBeforeRunTaskProviderBase getArtifactsWrittenPaths(); + + List getFinishedBuildsContexts(); +} diff --git a/java/compiler/impl/src/com/intellij/task/impl/JpsProjectTaskRunner.java b/java/compiler/impl/src/com/intellij/task/impl/JpsProjectTaskRunner.java index d751eef3624a..962fa1f1cdbf 100644 --- a/java/compiler/impl/src/com/intellij/task/impl/JpsProjectTaskRunner.java +++ b/java/compiler/impl/src/com/intellij/task/impl/JpsProjectTaskRunner.java @@ -1,21 +1,6 @@ -/* - * Copyright 2000-2016 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. - */ +// Copyright 2000-2019 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.task.impl; -import com.intellij.compiler.impl.CompileContextImpl; import com.intellij.compiler.impl.CompileDriver; import com.intellij.compiler.impl.CompileScopeUtil; import com.intellij.compiler.impl.CompositeScope; @@ -29,23 +14,23 @@ import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectModelBuildableElement; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.KeyWithDefaultValue; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.packaging.artifacts.Artifact; import com.intellij.packaging.impl.compiler.ArtifactCompileScope; +import com.intellij.packaging.impl.compiler.ArtifactsCompiler; import com.intellij.packaging.impl.compiler.ArtifactsWorkspaceSettings; import com.intellij.task.*; import com.intellij.ui.GuiUtils; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBusConnection; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -54,6 +39,9 @@ import java.util.stream.Stream; */ public class JpsProjectTaskRunner extends ProjectTaskRunner { private static final Logger LOG = Logger.getInstance(JpsProjectTaskRunner.class); + @ApiStatus.Internal + public static final Key JPS_BUILD_DATA_KEY = KeyWithDefaultValue.create("jps_build_data", MyJpsBuildData::new); + @ApiStatus.Internal public static final Key EXECUTION_SESSION_ID_KEY = ExecutionManagerImpl.EXECUTION_SESSION_ID_KEY; @Override @@ -61,6 +49,7 @@ public class JpsProjectTaskRunner extends ProjectTaskRunner { @NotNull ProjectTaskContext context, @Nullable ProjectTaskNotification callback, @NotNull Collection tasks) { + context.putUserData(JPS_BUILD_DATA_KEY, new MyJpsBuildData()); MessageBusConnection connection = project.getMessageBus().connect(project); connection.subscribe(CompilerTopics.COMPILATION_STATUS, new CompilationStatusListener() { @Override @@ -68,27 +57,22 @@ public class JpsProjectTaskRunner extends ProjectTaskRunner { context.fileGenerated(outputRoot, relativePath); } }); - CompileStatusNotification compileNotification = (aborted, errors, warnings, compileContext) -> { - context.putUserData(CompileContextImpl.CONTEXT_KEY, compileContext); - if (callback != null) { - callback.finished(new ProjectTaskResult(aborted, errors, warnings)); - } - connection.disconnect(); - }; Map, List> taskMap = groupBy(tasks); GuiUtils.invokeLaterIfNeeded(() -> { - runModulesResourcesBuildTasks(project, context, compileNotification, taskMap); - runModulesBuildTasks(project, context, compileNotification, taskMap); - runFilesBuildTasks(project, compileNotification, taskMap); - runEmptyBuildTask(project, context, compileNotification, taskMap); - runArtifactsBuildTasks(project, context, compileNotification, taskMap); + try (MyNotificationCollector notificationCollector = new MyNotificationCollector(context, callback, () -> connection.disconnect())) { + runModulesResourcesBuildTasks(project, context, notificationCollector, taskMap); + runModulesBuildTasks(project, context, notificationCollector, taskMap); + runFilesBuildTasks(project, notificationCollector, taskMap); + runEmptyBuildTask(project, context, notificationCollector, taskMap); + runArtifactsBuildTasks(project, context, notificationCollector, taskMap); + } }, ModalityState.defaultModalityState(), project.getDisposed()); } @Override public boolean canRun(@NotNull ProjectTask projectTask) { - return projectTask instanceof ModuleBuildTask || projectTask instanceof EmptyCompileScopeBuildTask || + return projectTask instanceof ModuleBuildTask || projectTask instanceof EmptyCompileScopeBuildTask || (projectTask instanceof ProjectModelBuildTask && ((ProjectModelBuildTask)projectTask).getBuildableElement() instanceof Artifact); } @@ -105,33 +89,33 @@ public class JpsProjectTaskRunner extends ProjectTaskRunner { private static void runModulesBuildTasks(@NotNull Project project, @NotNull ProjectTaskContext context, - @NotNull CompileStatusNotification compileNotification, + @NotNull MyNotificationCollector notificationCollector, @NotNull Map, List> tasksMap) { Collection buildTasks = tasksMap.get(ModuleBuildTask.class); if (ContainerUtil.isEmpty(buildTasks)) return; ModulesBuildSettings modulesBuildSettings = assembleModulesBuildSettings(buildTasks); CompilerManager compilerManager = CompilerManager.getInstance(project); - + if (modulesBuildSettings.isRebuild()){ - compilerManager.rebuild(compileNotification); + compilerManager.rebuild(new MyCompileStatusNotification(notificationCollector)); } else { CompileScope scope = createScope( compilerManager, context, modulesBuildSettings.modules, modulesBuildSettings.includeDependentModules, modulesBuildSettings.includeRuntimeDependencies ); if (modulesBuildSettings.isIncrementalBuild) { - compilerManager.make(scope, compileNotification); + compilerManager.make(scope, new MyCompileStatusNotification(notificationCollector)); } else { - compilerManager.compile(scope, compileNotification); + compilerManager.compile(scope, new MyCompileStatusNotification(notificationCollector)); } } } private static void runEmptyBuildTask(@NotNull Project project, - @NotNull ProjectTaskContext context, - @NotNull CompileStatusNotification compileNotification, - @NotNull Map, List> tasksMap) { + @NotNull ProjectTaskContext context, + @NotNull MyNotificationCollector notificationCollector, + @NotNull Map, List> tasksMap) { Collection buildTasks = tasksMap.get(EmptyCompileScopeBuildTask.class); if (ContainerUtil.isEmpty(buildTasks)) return; @@ -140,16 +124,16 @@ public class JpsProjectTaskRunner extends ProjectTaskRunner { // this will effectively run all before- and after- compilation tasks registered within CompilerManager EmptyCompileScopeBuildTask task = (EmptyCompileScopeBuildTask)buildTasks.iterator().next(); if (task.isIncrementalBuild()) { - compilerManager.make(scope, compileNotification); + compilerManager.make(scope, new MyCompileStatusNotification(notificationCollector)); } else { - compilerManager.compile(scope, compileNotification); + compilerManager.compile(scope, new MyCompileStatusNotification(notificationCollector)); } } private static void runModulesResourcesBuildTasks(@NotNull Project project, @NotNull ProjectTaskContext context, - @NotNull CompileStatusNotification compileNotification, + @NotNull MyNotificationCollector notificationCollector, @NotNull Map, List> tasksMap) { Collection buildTasks = tasksMap.get(ModuleResourcesBuildTask.class); if (ContainerUtil.isEmpty(buildTasks)) return; @@ -165,10 +149,10 @@ public class JpsProjectTaskRunner extends ProjectTaskRunner { CompileScopeUtil.setResourcesScopeForExternalBuild(scope, moduleNames); if (modulesBuildSettings.isIncrementalBuild) { - compilerManager.make(scope, compileNotification); + compilerManager.make(scope, new MyCompileStatusNotification(notificationCollector)); } else { - compilerManager.compile(scope, compileNotification); + compilerManager.compile(scope, new MyCompileStatusNotification(notificationCollector)); } } @@ -263,20 +247,20 @@ public class JpsProjectTaskRunner extends ProjectTaskRunner { } private static void runFilesBuildTasks(@NotNull Project project, - @NotNull CompileStatusNotification compileNotification, + @NotNull MyNotificationCollector notificationCollector, @NotNull Map, List> tasksMap) { Collection filesTargets = tasksMap.get(ModuleFilesBuildTask.class); if (!ContainerUtil.isEmpty(filesTargets)) { VirtualFile[] files = filesTargets.stream() .flatMap(target -> Stream.of(((ModuleFilesBuildTask)target).getFiles())) .toArray(VirtualFile[]::new); - CompilerManager.getInstance(project).compile(files, compileNotification); + CompilerManager.getInstance(project).compile(files, new MyCompileStatusNotification(notificationCollector)); } } private static void runArtifactsBuildTasks(@NotNull Project project, @NotNull ProjectTaskContext context, - @NotNull CompileStatusNotification compileNotification, + @NotNull MyNotificationCollector notificationCollector, @NotNull Map, List> tasksMap) { Collection buildTasks = tasksMap.get(ProjectModelBuildTask.class); @@ -296,22 +280,116 @@ public class JpsProjectTaskRunner extends ProjectTaskRunner { } } - buildArtifacts(project, toMake, context.getSessionId(), compileNotification, false); - buildArtifacts(project, toCompile, context.getSessionId(), compileNotification, true); + buildArtifacts(project, toMake, context.getSessionId(), notificationCollector, false); + buildArtifacts(project, toCompile, context.getSessionId(), notificationCollector, true); } } private static void buildArtifacts(@NotNull Project project, @NotNull List artifacts, @Nullable Object sessionId, - @Nullable CompileStatusNotification compileNotification, + @NotNull MyNotificationCollector notificationCollector, boolean forceArtifactBuild) { if (!artifacts.isEmpty()) { final CompileScope scope = ArtifactCompileScope.createArtifactsScope(project, artifacts, forceArtifactBuild); ArtifactsWorkspaceSettings.getInstance(project).setArtifactsToBuild(artifacts); ExecutionManagerImpl.EXECUTION_SESSION_ID_KEY.set(scope, sessionId); //in external build we can set 'rebuild' flag per target type - CompilerManager.getInstance(project).make(scope, compileNotification); + CompilerManager.getInstance(project).make(scope, new MyCompileStatusNotification(notificationCollector)); + } + } + + private static class MyNotificationCollector implements AutoCloseable { + @NotNull private final ProjectTaskContext myContext; + @Nullable private final ProjectTaskNotification myTaskNotification; + @NotNull private final Runnable myOnFinished; + private boolean myCollectingStopped; + + private final Set myNotifications = ContainerUtil.newIdentityTroveSet(); + private int myErrors; + private int myWarnings; + private boolean myAborted; + + + private MyNotificationCollector(@NotNull ProjectTaskContext context, + @Nullable ProjectTaskNotification taskNotification, + @NotNull Runnable onFinished) { + myContext = context; + myTaskNotification = taskNotification; + myOnFinished = onFinished; + } + + @Override + synchronized public void close() { + myCollectingStopped = true; + } + + synchronized private void notifyFinished() { + if (myTaskNotification != null) { + myTaskNotification.finished(myContext, new ProjectTaskResult(myAborted, myErrors, myWarnings)); + } + myOnFinished.run(); + } + + synchronized private void appendJpsBuildResult(boolean aborted, int errors, int warnings, + @NotNull CompileContext compileContext, + @NotNull MyCompileStatusNotification notification) { + if (!myNotifications.remove(notification)) { + LOG.error("Multiple invocation of the same callback"); + } + myErrors += errors; + myWarnings += warnings; + if (aborted) myAborted = true; + MyJpsBuildData jpsBuildData = (MyJpsBuildData)JPS_BUILD_DATA_KEY.get(myContext); + jpsBuildData.add(compileContext); + + if (myCollectingStopped && myNotifications.isEmpty()) { + notifyFinished(); + } + } + + synchronized private void add(@NotNull MyCompileStatusNotification notification) { + assert !myCollectingStopped; + if (!myNotifications.add(notification)) { + LOG.error("Do not use the same callback for different JPS invocations"); + } + } + } + + private static class MyCompileStatusNotification implements CompileStatusNotification { + + private final MyNotificationCollector myCollector; + + private MyCompileStatusNotification(@NotNull MyNotificationCollector collector) { + myCollector = collector; + myCollector.add(this); + } + + @Override + public void finished(boolean aborted, int errors, int warnings, @NotNull CompileContext compileContext) { + myCollector.appendJpsBuildResult(aborted, errors, warnings, compileContext, this); + } + } + + private static class MyJpsBuildData implements JpsBuildData { + private final List myContexts = new ArrayList<>(); + + @Override + public Set getArtifactsWrittenPaths() { + return myContexts.stream() + .map(ctx -> ArtifactsCompiler.getWrittenPaths(ctx)) + .filter(Objects::nonNull) + .flatMap(set -> set.stream()) + .collect(Collectors.toSet()); + } + + @Override + public List getFinishedBuildsContexts() { + return Collections.unmodifiableList(myContexts); + } + + private void add(@NotNull CompileContext context) { + myContexts.add(context); } } } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUI.java b/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUI.java index f89a4d6d075b..91f40db57941 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUI.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUI.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2009 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. - */ +// Copyright 2000-2019 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.debugger.ui; @@ -34,8 +20,6 @@ public abstract class HotSwapUI { public abstract void reloadChangedClasses(@NotNull DebuggerSession session, boolean compileBeforeHotswap, @Nullable HotSwapStatusListener callback); - public abstract void dontPerformHotswapAfterThisCompilation(); - public abstract void addListener(HotSwapVetoableListener listener); public abstract void removeListener(HotSwapVetoableListener listener); diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUIImpl.java b/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUIImpl.java index 5ee368675423..f46359a06c2b 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUIImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUIImpl.java @@ -21,6 +21,7 @@ import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.KeyWithDefaultValue; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.task.*; @@ -38,12 +39,12 @@ import java.util.*; import java.util.stream.Collectors; public class HotSwapUIImpl extends HotSwapUI { + public static final Key SKIP_HOT_SWAP_KEY = KeyWithDefaultValue.create("skip_hotswap_after_this_compilation", false); private static final Key HOT_SWAP_CALLBACK_KEY = Key.create("hot_swap_callback"); private final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private boolean myAskBeforeHotswap = true; private final Project myProject; - private boolean myPerformHotswapAfterThisCompilation = true; public HotSwapUIImpl(@NotNull Project project) { myProject = project; @@ -294,11 +295,6 @@ public class HotSwapUIImpl extends HotSwapUI { } } - @Override - public void dontPerformHotswapAfterThisCompilation() { - myPerformHotswapAfterThisCompilation = false; - } - public void dontAskHotswapAfterThisCompilation() { myAskBeforeHotswap = false; } @@ -324,49 +320,45 @@ public class HotSwapUIImpl extends HotSwapUI { if (myProject.isDisposed()) { return; } - try { - if (!hasCompilationResults(executionResult)) return; - int errors = executionResult.getErrors(); - boolean aborted = executionResult.isAborted(); - if (errors == 0 && !aborted && myPerformHotswapAfterThisCompilation) { - for (HotSwapVetoableListener listener : myListeners) { - if (!listener.shouldHotSwap(context, executionResult)) { - return; - } - } + if (!hasCompilationResults(executionResult)) return; - List sessions = getHotSwappableDebugSessions(); - if (!sessions.isEmpty()) { - Map> generatedPaths; - Collection generatedFilesRoots = context.getGeneratedFilesRoots(); - if (!generatedFilesRoots.isEmpty()) { - generatedPaths = new HashMap<>(); - for (String outputRoot : generatedFilesRoots) { - // collect only classes under IDE output roots - if (!JpsPathUtil.isUnder(myOutputRoots, new File(outputRoot))) continue; - Collection relativePaths = context.getGeneratedFilesRelativePaths(outputRoot).stream() - .filter(relativePath -> StringUtil.endsWith(relativePath, ".class")) - .collect(Collectors.toCollection(SmartList::new)); - if (!relativePaths.isEmpty()) { - generatedPaths.put(outputRoot, relativePaths); - } - } - if (generatedPaths.isEmpty()) { - generatedPaths = null; - } - } - else { - generatedPaths = null; - } - - HotSwapStatusListener callback = context.getUserData(HOT_SWAP_CALLBACK_KEY); - hotSwapSessions(sessions, generatedPaths, callback); + int errors = executionResult.getErrors(); + boolean aborted = executionResult.isAborted(); + if (errors == 0 && !aborted && !SKIP_HOT_SWAP_KEY.getRequired(context)) { + for (HotSwapVetoableListener listener : myListeners) { + if (!listener.shouldHotSwap(context, executionResult)) { + return; } } - } - finally { - myPerformHotswapAfterThisCompilation = true; + + List sessions = getHotSwappableDebugSessions(); + if (!sessions.isEmpty()) { + Map> generatedPaths; + Collection generatedFilesRoots = context.getGeneratedFilesRoots(); + if (!generatedFilesRoots.isEmpty()) { + generatedPaths = new HashMap<>(); + for (String outputRoot : generatedFilesRoots) { + // collect only classes under IDE output roots + if (!JpsPathUtil.isUnder(myOutputRoots, new File(outputRoot))) continue; + Collection relativePaths = context.getGeneratedFilesRelativePaths(outputRoot).stream() + .filter(relativePath -> StringUtil.endsWith(relativePath, ".class")) + .collect(Collectors.toCollection(SmartList::new)); + if (!relativePaths.isEmpty()) { + generatedPaths.put(outputRoot, relativePaths); + } + } + if (generatedPaths.isEmpty()) { + generatedPaths = null; + } + } + else { + generatedPaths = null; + } + + HotSwapStatusListener callback = context.getUserData(HOT_SWAP_CALLBACK_KEY); + hotSwapSessions(sessions, generatedPaths, callback); + } } } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapVetoableListener.java b/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapVetoableListener.java index 6a7887a86323..133b11ffc80a 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapVetoableListener.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapVetoableListener.java @@ -1,26 +1,13 @@ -/* - * Copyright 2000-2009 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. - */ +// Copyright 2000-2019 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.debugger.ui; -import com.intellij.compiler.impl.CompileContextImpl; import com.intellij.openapi.compiler.CompileContext; import com.intellij.openapi.compiler.DummyCompileContext; import com.intellij.task.ProjectTaskContext; import com.intellij.task.ProjectTaskResult; +import com.intellij.task.impl.JpsBuildData; +import com.intellij.task.impl.JpsProjectTaskRunner; import org.jetbrains.annotations.NotNull; /** @@ -40,7 +27,11 @@ public interface HotSwapVetoableListener { * Returns {@code false} if Hot Swap shouldn't be invoked after the given compilation session. */ default boolean shouldHotSwap(@NotNull ProjectTaskContext context, @NotNull ProjectTaskResult finishedTasksResult) { - CompileContext compileContext = context.getUserData(CompileContextImpl.CONTEXT_KEY); - return shouldHotSwap(compileContext != null ? compileContext : DummyCompileContext.getInstance()); + CompileContext compileContext = DummyCompileContext.getInstance(); + JpsBuildData jpsBuildData = context.getUserData(JpsProjectTaskRunner.JPS_BUILD_DATA_KEY); + if(jpsBuildData != null && jpsBuildData.getFinishedBuildsContexts().size() == 1) { + compileContext = jpsBuildData.getFinishedBuildsContexts().get(0); + } + return shouldHotSwap(compileContext); } } diff --git a/java/execution/impl/src/com/intellij/compiler/options/CompileStepBeforeRun.java b/java/execution/impl/src/com/intellij/compiler/options/CompileStepBeforeRun.java index 6149e51625f3..3222d0bb1b07 100644 --- a/java/execution/impl/src/com/intellij/compiler/options/CompileStepBeforeRun.java +++ b/java/execution/impl/src/com/intellij/compiler/options/CompileStepBeforeRun.java @@ -1,4 +1,4 @@ -// Copyright 2000-2018 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. +// Copyright 2000-2019 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.options; import com.intellij.execution.BeforeRunTask; @@ -21,9 +21,7 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Ref; -import com.intellij.task.ProjectTask; -import com.intellij.task.ProjectTaskContext; -import com.intellij.task.ProjectTaskManager; +import com.intellij.task.*; import com.intellij.task.impl.EmptyCompileScopeBuildTaskImpl; import com.intellij.util.concurrency.Semaphore; import org.jetbrains.annotations.ApiStatus; @@ -160,12 +158,19 @@ public class CompileStepBeforeRun extends BeforeRunTaskProvider { - if ((executionResult.getErrors() == 0 || ignoreErrors) && !executionResult.isAborted()) { - result.set(Boolean.TRUE); - } - done.up(); - }); + ProjectTaskContext context = new ProjectTaskContext(sessionId, configuration); + env.copyUserDataTo(context); + projectTaskManager.run(context, projectTask, + new ProjectTaskNotification() { + @Override + public void finished(@NotNull ProjectTaskContext context, @NotNull ProjectTaskResult executionResult) { + if ((executionResult.getErrors() == 0 || ignoreErrors) && !executionResult.isAborted()) { + result.set(Boolean.TRUE); + } + done.up(); + } + } + ); } else { done.up(); diff --git a/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/test/ExternalSystemTestCase.java b/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/test/ExternalSystemTestCase.java index 846ccda63f1d..595f6df688ea 100644 --- a/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/test/ExternalSystemTestCase.java +++ b/platform/external-system-impl/testSrc/com/intellij/openapi/externalSystem/test/ExternalSystemTestCase.java @@ -30,7 +30,10 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; import com.intellij.packaging.artifacts.Artifact; import com.intellij.packaging.impl.compiler.ArtifactCompileScope; +import com.intellij.task.ProjectTaskContext; import com.intellij.task.ProjectTaskManager; +import com.intellij.task.ProjectTaskNotification; +import com.intellij.task.ProjectTaskResult; import com.intellij.testFramework.*; import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; @@ -412,11 +415,17 @@ public abstract class ExternalSystemTestCase extends UsefulTestCase { private void build(@NotNull Object[] buildableElements) { final Semaphore semaphore = new Semaphore(); semaphore.down(); + ProjectTaskNotification callback = new ProjectTaskNotification() { + @Override + public void finished(@NotNull ProjectTaskContext context, @NotNull ProjectTaskResult executionResult) { + semaphore.up(); + } + }; if (buildableElements instanceof Module[]) { - ProjectTaskManager.getInstance(myProject).build((Module[])buildableElements, executionResult -> semaphore.up()); + ProjectTaskManager.getInstance(myProject).build((Module[])buildableElements, callback); } else if (buildableElements instanceof Artifact[]) { - ProjectTaskManager.getInstance(myProject).build((Artifact[])buildableElements, executionResult -> semaphore.up()); + ProjectTaskManager.getInstance(myProject).build((Artifact[])buildableElements, callback); } else { assert false : "Unsupported buildableElements: " + Arrays.toString(buildableElements); diff --git a/platform/lang-api/src/com/intellij/task/ProjectTaskNotification.java b/platform/lang-api/src/com/intellij/task/ProjectTaskNotification.java index 1c8121be454f..3dca02b616b1 100644 --- a/platform/lang-api/src/com/intellij/task/ProjectTaskNotification.java +++ b/platform/lang-api/src/com/intellij/task/ProjectTaskNotification.java @@ -1,20 +1,7 @@ -/* - * Copyright 2000-2016 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. - */ +// Copyright 2000-2019 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.task; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; /** @@ -23,6 +10,15 @@ import org.jetbrains.annotations.NotNull; public interface ProjectTaskNotification { /** * @param executionResult provides aggregated information about the {@link ProjectTask} execution + * @deprecated use {@link #finished(ProjectTaskContext, ProjectTaskResult)} instead */ - void finished(@NotNull ProjectTaskResult executionResult); + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2020.1") + default void finished(@NotNull ProjectTaskResult executionResult) {} + + /** + * @param context tasks execution context + * @param executionResult provides aggregated information about the {@link ProjectTask} execution + */ + default void finished(@NotNull ProjectTaskContext context, @NotNull ProjectTaskResult executionResult) { finished(executionResult); } } diff --git a/platform/lang-impl/src/com/intellij/task/impl/ProjectTaskList.java b/platform/lang-impl/src/com/intellij/task/impl/ProjectTaskList.java index 9eb3e7e73695..545017d69d32 100644 --- a/platform/lang-impl/src/com/intellij/task/impl/ProjectTaskList.java +++ b/platform/lang-impl/src/com/intellij/task/impl/ProjectTaskList.java @@ -1,30 +1,20 @@ -/* - * Copyright 2000-2016 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. - */ +// Copyright 2000-2019 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.task.impl; import com.intellij.task.ProjectTask; +import com.intellij.util.SmartList; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; /** * @author Vladislav.Soroka */ -public class ProjectTaskList extends ArrayList implements ProjectTask { +public class ProjectTaskList extends SmartList implements ProjectTask { + public ProjectTaskList() { + } + public ProjectTaskList(@NotNull Collection c) { super(c); } @@ -34,4 +24,9 @@ public class ProjectTaskList extends ArrayList implements ProjectTa public String getPresentableName() { return toString(); } + + @NotNull + public static ProjectTaskList asList(ProjectTask... tasks) { + return new ProjectTaskList(Arrays.asList(tasks)); + } } diff --git a/platform/lang-impl/src/com/intellij/task/impl/ProjectTaskManagerImpl.java b/platform/lang-impl/src/com/intellij/task/impl/ProjectTaskManagerImpl.java index d77c92cb712e..4a26b430c445 100644 --- a/platform/lang-impl/src/com/intellij/task/impl/ProjectTaskManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/task/impl/ProjectTaskManagerImpl.java @@ -168,22 +168,22 @@ public class ProjectTaskManagerImpl extends ProjectTaskManager { listener.beforeRun(context); } catch (ExecutionException e) { - sendAbortedNotify(new ListenerNotificator(context, callback)); + sendAbortedNotify(context, new ListenerNotificator(callback)); return; } } if (toRun.isEmpty()) { - sendSuccessNotify(new ListenerNotificator(context, callback)); + sendSuccessNotify(context, new ListenerNotificator(callback)); return; } ProjectTaskResultsAggregator callbacksCollector = - new ProjectTaskResultsAggregator(new ListenerNotificator(context, callback), toRun.size()); + new ProjectTaskResultsAggregator(context, new ListenerNotificator(callback), toRun.size()); for (Pair> pair : toRun) { ProjectTaskRunnerNotification notification = new ProjectTaskRunnerNotification(pair.second, callbacksCollector); if (pair.second.isEmpty()) { - sendSuccessNotify(notification); + sendSuccessNotify(context, notification); } else { pair.first.run(myProject, context, notification, pair.second); @@ -203,15 +203,15 @@ public class ProjectTaskManagerImpl extends ProjectTaskManager { myListeners.add(listener); } - private static void sendSuccessNotify(@Nullable ProjectTaskNotification notification) { + private static void sendSuccessNotify(@NotNull ProjectTaskContext context, @Nullable ProjectTaskNotification notification) { if (notification != null) { - notification.finished(new ProjectTaskResult(false, 0, 0)); + notification.finished(context, new ProjectTaskResult(false, 0, 0)); } } - private static void sendAbortedNotify(@Nullable ProjectTaskNotification notification) { + private static void sendAbortedNotify(@NotNull ProjectTaskContext context, @Nullable ProjectTaskNotification notification) { if (notification != null) { - notification.finished(new ProjectTaskResult(true, 0, 0)); + notification.finished(context, new ProjectTaskResult(true, 0, 0)); } } @@ -251,7 +251,7 @@ public class ProjectTaskManagerImpl extends ProjectTaskManager { @NotNull ProjectTaskContext context, @Nullable ProjectTaskNotification callback, @NotNull Collection tasks) { - sendSuccessNotify(callback); + sendSuccessNotify(context, callback); } @Override @@ -262,34 +262,31 @@ public class ProjectTaskManagerImpl extends ProjectTaskManager { private class ListenerNotificator implements ProjectTaskNotification { @Nullable private final ProjectTaskNotification myDelegate; - @NotNull private final ProjectTaskContext myContext; - private ListenerNotificator(@NotNull ProjectTaskContext context, - @Nullable ProjectTaskNotification delegate) { - myContext = context; + private ListenerNotificator(@Nullable ProjectTaskNotification delegate) { myDelegate = delegate; } @Override - public void finished(@NotNull ProjectTaskResult executionResult) { + public void finished(@NotNull ProjectTaskContext context, @NotNull ProjectTaskResult executionResult) { if (!executionResult.isAborted() && executionResult.getErrors() == 0) { // do not run after tasks on EDT ApplicationManager.getApplication().executeOnPooledThread(() -> { try { for (ProjectTaskManagerListener listener : myListeners) { - listener.afterRun(myContext, executionResult); + listener.afterRun(context, executionResult); } - notify(myContext, executionResult); + notify(context, executionResult); } catch (ExecutionException e) { LOG.debug(e); - notify(myContext, new ProjectTaskResult( + notify(context, new ProjectTaskResult( false, executionResult.getErrors() + 1, executionResult.getWarnings(), executionResult.getTasksState())); } }); } else { - notify(myContext, executionResult); + notify(context, executionResult); } } @@ -299,7 +296,7 @@ public class ProjectTaskManagerImpl extends ProjectTaskManager { myEventPublisher.finished(context, executionResult); } if (myDelegate != null) { - myDelegate.finished(executionResult); + myDelegate.finished(context, executionResult); } }, ModalityState.defaultModalityState()); } @@ -316,7 +313,7 @@ public class ProjectTaskManagerImpl extends ProjectTaskManager { } @Override - public void finished(@NotNull ProjectTaskResult result) { + public void finished(@NotNull ProjectTaskContext context, @NotNull ProjectTaskResult result) { if (result.getTasksState().isEmpty()) { final boolean aborted = result.isAborted(); final int errors = result.getErrors(); @@ -339,6 +336,7 @@ public class ProjectTaskManagerImpl extends ProjectTaskManager { } private static class ProjectTaskResultsAggregator { + private final ProjectTaskContext myContext; private final ProjectTaskNotification myDelegate; private final AtomicInteger myProgressCounter; private final AtomicInteger myErrorsCounter; @@ -346,7 +344,10 @@ public class ProjectTaskManagerImpl extends ProjectTaskManager { private final AtomicBoolean myAbortedFlag; private final Map myTasksState = ContainerUtil.newConcurrentMap(); - private ProjectTaskResultsAggregator(@NotNull ProjectTaskNotification delegate, int expectedResults) { + private ProjectTaskResultsAggregator(@NotNull ProjectTaskContext context, + @NotNull ProjectTaskNotification delegate, + int expectedResults) { + myContext = context; myDelegate = delegate; myProgressCounter = new AtomicInteger(expectedResults); myErrorsCounter = new AtomicInteger(); @@ -364,7 +365,7 @@ public class ProjectTaskManagerImpl extends ProjectTaskManager { } if (inProgress <= 0) { ProjectTaskResult result = new ProjectTaskResult(myAbortedFlag.get(), allErrors, allWarnings, myTasksState); - myDelegate.finished(result); + myDelegate.finished(myContext, result); } } }