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 948cf66de4d7..53e1248d748f 100644 --- a/java/compiler/impl/src/com/intellij/task/impl/JpsProjectTaskRunner.java +++ b/java/compiler/impl/src/com/intellij/task/impl/JpsProjectTaskRunner.java @@ -76,6 +76,11 @@ public class JpsProjectTaskRunner extends ProjectTaskRunner { (projectTask instanceof ProjectModelBuildTask && ((ProjectModelBuildTask)projectTask).getBuildableElement() instanceof Artifact); } + @Override + public boolean isFileGeneratedEventsSupported() { + return true; + } + public static Map, List> groupBy(@NotNull Collection tasks) { return tasks.stream().collect(Collectors.groupingBy(o -> { if (o instanceof ModuleFilesBuildTask) return ModuleFilesBuildTask.class; diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/HotSwapManager.java b/java/debugger/impl/src/com/intellij/debugger/impl/HotSwapManager.java index 11af6c7ef72f..d6e26a4f2ade 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/HotSwapManager.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/HotSwapManager.java @@ -15,9 +15,12 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.JBIterable; import gnu.trove.THashMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class HotSwapManager { private final Map myTimeStamps = new HashMap<>(); @@ -48,17 +51,20 @@ public class HotSwapManager { myTimeStamps.put(session, Long.valueOf(tStamp)); } - public Map scanForModifiedClasses(final DebuggerSession session, final HotSwapProgress progress) { + public Map scanForModifiedClasses(@NotNull DebuggerSession session, + @Nullable Stream outputPaths, + @NotNull HotSwapProgress progress) { DebuggerManagerThreadImpl.assertIsManagerThread(); final long timeStamp = getTimeStamp(session); final Map modifiedClasses = new HashMap<>(); - List outputPaths = ReadAction.compute( - () -> JBIterable.of(OrderEnumerator.orderEntries(myProject).classes().getRoots()) - .filterMap(o -> o.isDirectory() && !o.getFileSystem().isReadOnly() ? o.getPath() : null) - .toList()); - for (String path : outputPaths) { + List paths = outputPaths != null ? outputPaths.collect(Collectors.toList()) : + ReadAction.compute(() -> JBIterable.of(OrderEnumerator.orderEntries(myProject).classes().getRoots()) + .filterMap(o -> o.isDirectory() && !o.getFileSystem().isReadOnly() ? o.getPath() : null) + .toList() + ); + for (String path : paths) { String rootPath = FileUtil.toCanonicalPath(path); collectModifiedClasses(new File(path), rootPath, rootPath + "/", modifiedClasses, progress, timeStamp); } @@ -131,9 +137,16 @@ public class HotSwapManager { return result; } + @NotNull + public static Map> scanForModifiedClasses(@NotNull List sessions, + @NotNull HotSwapProgress swapProgress) { + return scanForModifiedClasses(sessions, null, swapProgress); + } + @NotNull public static Map> scanForModifiedClasses(@NotNull List sessions, + @Nullable Stream outputPaths, @NotNull HotSwapProgress swapProgress) { final Map> modifiedClasses = new THashMap<>(); final MultiProcessCommand scanClassesCommand = new MultiProcessCommand(); @@ -147,7 +160,8 @@ public class HotSwapManager { @Override protected void action() { swapProgress.setDebuggerSession(debuggerSession); - Map sessionClasses = getInstance(swapProgress.getProject()).scanForModifiedClasses(debuggerSession, swapProgress); + Map sessionClasses = + getInstance(swapProgress.getProject()).scanForModifiedClasses(debuggerSession, outputPaths, swapProgress); if (!sessionClasses.isEmpty()) { modifiedClasses.put(debuggerSession, sessionClasses); } 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 577b16deda14..dedfcb5f69db 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUIImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUIImpl.java @@ -38,6 +38,7 @@ import org.jetbrains.jps.util.JpsPathUtil; import java.io.File; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; public class HotSwapUIImpl extends HotSwapUI { /** @@ -100,7 +101,14 @@ public class HotSwapUIImpl extends HotSwapUI { return sessions.stream().anyMatch(DebuggerSession::isPaused); } - private void hotSwapSessions(final List sessions, @Nullable final Map> generatedPaths, + private void hotSwapSessions(@NotNull List sessions, + @Nullable HotSwapStatusListener callback) { + hotSwapSessions(sessions, null, null, callback); + } + + private void hotSwapSessions(@NotNull final List sessions, + @Nullable final Map> generatedPaths, + @Nullable final Stream outputPaths, @Nullable final HotSwapStatusListener callback) { final boolean shouldAskBeforeHotswap = myAskBeforeHotswap; myAskBeforeHotswap = true; @@ -141,19 +149,11 @@ public class HotSwapUIImpl extends HotSwapUI { final HotSwapProgressImpl findClassesProgress; if (shouldPerformScan) { - findClassesProgress = new HotSwapProgressImpl(myProject); + findClassesProgress = createHotSwapProgress(callbackWrapper, sessions); } else { boolean createProgress = sessions.stream().anyMatch(DebuggerSession::isModifiedClassesScanRequired); - findClassesProgress = createProgress ? new HotSwapProgressImpl(myProject) : null; - } - if (findClassesProgress != null) { - findClassesProgress.addProgressListener(new HotSwapProgressImpl.HotSwapProgressListener() { - @Override - public void onCancel() { - callbackWrapper.onCancel(sessions); - } - }); + findClassesProgress = createProgress ? createHotSwapProgress(callbackWrapper, sessions) : null; } ApplicationManager.getApplication().executeOnPooledThread(() -> { @@ -171,6 +171,15 @@ public class HotSwapUIImpl extends HotSwapUI { modifiedClasses = new HashMap<>(); if (!toUseGenerated.isEmpty()) { modifiedClasses.putAll(HotSwapManager.findModifiedClasses(toUseGenerated, generatedPaths)); + if (outputPaths != null) { + scanForModifiedClassesWithProgress(toUseGenerated, outputPaths, createHotSwapProgress(callbackWrapper, sessions)) + .forEach( + (session, map) -> modifiedClasses.merge(session, map, (map1, map2) -> { + map1.putAll(map2); + return map1; + }) + ); + } } if (!toScan.isEmpty()) { modifiedClasses.putAll(scanForModifiedClassesWithProgress(toScan, Objects.requireNonNull(findClassesProgress))); @@ -253,12 +262,32 @@ public class HotSwapUIImpl extends HotSwapUI { }); } + @NotNull + private HotSwapProgressImpl createHotSwapProgress(@NotNull HotSwapStatusListener callbackWrapper, + @NotNull List sessions) { + HotSwapProgressImpl progress = new HotSwapProgressImpl(myProject); + progress.addProgressListener(new HotSwapProgressImpl.HotSwapProgressListener() { + @Override + public void onCancel() { + callbackWrapper.onCancel(sessions); + } + }); + return progress; + } + @NotNull private static Map> scanForModifiedClassesWithProgress(@NotNull List sessions, @NotNull HotSwapProgressImpl progress) { + return scanForModifiedClassesWithProgress(sessions, null, progress); + } + + @NotNull + private static Map> scanForModifiedClassesWithProgress(@NotNull List sessions, + @Nullable Stream outputPaths, + @NotNull HotSwapProgressImpl progress) { return ProgressManager.getInstance().runProcess(() -> { try { - return HotSwapManager.scanForModifiedClasses(sessions, progress); + return HotSwapManager.scanForModifiedClasses(sessions, outputPaths, progress); } finally { progress.finished(); @@ -297,7 +326,7 @@ public class HotSwapUIImpl extends HotSwapUI { } else { if (session.isAttached()) { - hotSwapSessions(Collections.singletonList(session), null, callback); + hotSwapSessions(Collections.singletonList(session), callback); } else if (callback != null) { callback.onFailure(ContainerUtil.newSmartList(session)); @@ -364,16 +393,14 @@ public class HotSwapUIImpl extends HotSwapUI { generatedPaths.put(outputRoot, relativePaths); } } - if (generatedPaths.isEmpty()) { - generatedPaths = null; - } } else { - generatedPaths = null; + generatedPaths = Collections.emptyMap(); } HotSwapStatusListener callback = context.getUserData(HOT_SWAP_CALLBACK_KEY); - hotSwapSessions(sessions, generatedPaths, callback); + Stream dirtyOutputRoots = context.getDirtyOutputPaths().orElse(null); + hotSwapSessions(sessions, generatedPaths, dirtyOutputRoots, callback); } } } diff --git a/platform/lang-api/src/com/intellij/task/ProjectTaskContext.java b/platform/lang-api/src/com/intellij/task/ProjectTaskContext.java index cd383976f839..11f90c05c1da 100644 --- a/platform/lang-api/src/com/intellij/task/ProjectTaskContext.java +++ b/platform/lang-api/src/com/intellij/task/ProjectTaskContext.java @@ -18,12 +18,17 @@ package com.intellij.task; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.UserDataHolderBase; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; /** * @author Vladislav.Soroka @@ -35,6 +40,7 @@ public class ProjectTaskContext extends UserDataHolderBase { private final RunConfiguration myRunConfiguration; private final boolean myAutoRun; private final MultiMap myGeneratedFiles; + private final List>> myDirtyOutputPaths; private volatile boolean myCollectGeneratedFiles; public ProjectTaskContext() { @@ -58,6 +64,7 @@ public class ProjectTaskContext extends UserDataHolderBase { myRunConfiguration = runConfiguration; myAutoRun = autoRun; myGeneratedFiles = MultiMap.createConcurrentSet(); + myDirtyOutputPaths = ContainerUtil.createConcurrentList(); } @Nullable @@ -120,6 +127,35 @@ public class ProjectTaskContext extends UserDataHolderBase { } } + /** + * This method isn't supposed to be used directly. + * {@link ProjectTaskRunner}s can use it to report output paths of generated files produced during some task execution. + *

+ * Note, generated files collecting is disabled by default. + * It can be requested using the {@link #enableCollectionOfGeneratedFiles()} method by the task initiator, see {@link ProjectTaskManager#run(ProjectTaskContext, ProjectTask, ProjectTaskNotification)}. + * Or using the {@link ProjectTaskListener#started(ProjectTaskContext)} event. + *

+ * The method should be used ONLY if the {@link ProjectTaskRunner} doesn't support {@link #fileGenerated} events. + */ + @ApiStatus.Experimental + public void dirtyOutputPathsProvider(@NotNull Supplier> outputPathsProvider) { + if (myCollectGeneratedFiles) { + myDirtyOutputPaths.add(outputPathsProvider); + } + } + + /** + * Provides output paths that can be used for generated files by some tasks. + * The intended usage is to scan those directories for modified files. + *

+ * Can be useful for {@link ProjectTaskRunner}s which doesn't support {@link #fileGenerated} events. + */ + @ApiStatus.OverrideOnly + public Optional> getDirtyOutputPaths() { + return myDirtyOutputPaths.isEmpty() ? Optional.empty() : + Optional.of(myDirtyOutputPaths.stream().map(supplier -> supplier.get()).flatMap(Collection::stream).distinct()); + } + public ProjectTaskContext withUserData(@NotNull Key key, @Nullable T value) { putUserData(key, value); return this; diff --git a/platform/lang-api/src/com/intellij/task/ProjectTaskRunner.java b/platform/lang-api/src/com/intellij/task/ProjectTaskRunner.java index 89976181026f..e14a5f2a3734 100644 --- a/platform/lang-api/src/com/intellij/task/ProjectTaskRunner.java +++ b/platform/lang-api/src/com/intellij/task/ProjectTaskRunner.java @@ -19,6 +19,7 @@ import com.intellij.execution.Executor; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -56,4 +57,9 @@ public abstract class ProjectTaskRunner { @Nullable Executor executor) { return null; } + + @ApiStatus.Experimental + public boolean isFileGeneratedEventsSupported() { + return false; + } } 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 3931c2a68803..b781d02e6baf 100644 --- a/platform/lang-impl/src/com/intellij/task/impl/ProjectTaskManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/task/impl/ProjectTaskManagerImpl.java @@ -4,11 +4,13 @@ package com.intellij.task.impl; import com.intellij.execution.ExecutionException; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.OrderEnumerator; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectModelBuildableElement; import com.intellij.openapi.util.Pair; @@ -18,6 +20,7 @@ import com.intellij.ui.GuiUtils; import com.intellij.util.Consumer; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.JBIterable; import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,6 +30,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import java.util.function.Supplier; import static com.intellij.util.containers.ContainerUtil.map; import static java.util.Arrays.stream; @@ -190,7 +194,14 @@ public class ProjectTaskManagerImpl extends ProjectTaskManager { sendSuccessNotify(context, notification); } else { - pair.first.run(myProject, context, notification, pair.second); + ProjectTaskRunner runner = pair.first; + if (!runner.isFileGeneratedEventsSupported()) { + pair.second.stream() + .filter(ModuleBuildTask.class::isInstance) + .map(task -> ((ModuleBuildTask)task).getModule()) + .forEach(module -> context.dirtyOutputPathsProvider(moduleOutputPathsProvider(module))); + } + runner.run(myProject, context, notification, pair.second); } } }; @@ -203,6 +214,14 @@ public class ProjectTaskManagerImpl extends ProjectTaskManager { } } + @NotNull + private static Supplier> moduleOutputPathsProvider(@NotNull Module module) { + return () -> ReadAction.compute(() -> { + return JBIterable.of(OrderEnumerator.orderEntries(module).getClassesRoots()) + .filterMap(file -> file.isDirectory() && !file.getFileSystem().isReadOnly() ? file.getPath() : null).toList(); + }); + } + public final void addListener(@NotNull ProjectTaskManagerListener listener) { myListeners.add(listener); }