IDEA-222088 Hotswap takes a long time when nothing to reload

GitOrigin-RevId: 43f925ca50def11bf20ee5052575a5b79130d26f
This commit is contained in:
Vladislav.Soroka
2019-09-17 11:30:43 +00:00
committed by intellij-monorepo-bot
parent f385726405
commit 28f298ec0e
6 changed files with 133 additions and 26 deletions
@@ -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<Class<? extends ProjectTask>, List<ProjectTask>> groupBy(@NotNull Collection<? extends ProjectTask> tasks) {
return tasks.stream().collect(Collectors.groupingBy(o -> {
if (o instanceof ModuleFilesBuildTask) return ModuleFilesBuildTask.class;
@@ -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<DebuggerSession, Long> myTimeStamps = new HashMap<>();
@@ -48,17 +51,20 @@ public class HotSwapManager {
myTimeStamps.put(session, Long.valueOf(tStamp));
}
public Map<String, HotSwapFile> scanForModifiedClasses(final DebuggerSession session, final HotSwapProgress progress) {
public Map<String, HotSwapFile> scanForModifiedClasses(@NotNull DebuggerSession session,
@Nullable Stream<String> outputPaths,
@NotNull HotSwapProgress progress) {
DebuggerManagerThreadImpl.assertIsManagerThread();
final long timeStamp = getTimeStamp(session);
final Map<String, HotSwapFile> modifiedClasses = new HashMap<>();
List<String> 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<String> 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<DebuggerSession, Map<String, HotSwapFile>> scanForModifiedClasses(@NotNull List<? extends DebuggerSession> sessions,
@NotNull HotSwapProgress swapProgress) {
return scanForModifiedClasses(sessions, null, swapProgress);
}
@NotNull
public static Map<DebuggerSession, Map<String, HotSwapFile>> scanForModifiedClasses(@NotNull List<? extends DebuggerSession> sessions,
@Nullable Stream<String> outputPaths,
@NotNull HotSwapProgress swapProgress) {
final Map<DebuggerSession, Map<String, HotSwapFile>> modifiedClasses = new THashMap<>();
final MultiProcessCommand scanClassesCommand = new MultiProcessCommand();
@@ -147,7 +160,8 @@ public class HotSwapManager {
@Override
protected void action() {
swapProgress.setDebuggerSession(debuggerSession);
Map<String, HotSwapFile> sessionClasses = getInstance(swapProgress.getProject()).scanForModifiedClasses(debuggerSession, swapProgress);
Map<String, HotSwapFile> sessionClasses =
getInstance(swapProgress.getProject()).scanForModifiedClasses(debuggerSession, outputPaths, swapProgress);
if (!sessionClasses.isEmpty()) {
modifiedClasses.put(debuggerSession, sessionClasses);
}
@@ -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<DebuggerSession> sessions, @Nullable final Map<String, Collection<String>> generatedPaths,
private void hotSwapSessions(@NotNull List<DebuggerSession> sessions,
@Nullable HotSwapStatusListener callback) {
hotSwapSessions(sessions, null, null, callback);
}
private void hotSwapSessions(@NotNull final List<DebuggerSession> sessions,
@Nullable final Map<String, Collection<String>> generatedPaths,
@Nullable final Stream<String> 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<DebuggerSession> sessions) {
HotSwapProgressImpl progress = new HotSwapProgressImpl(myProject);
progress.addProgressListener(new HotSwapProgressImpl.HotSwapProgressListener() {
@Override
public void onCancel() {
callbackWrapper.onCancel(sessions);
}
});
return progress;
}
@NotNull
private static Map<DebuggerSession, Map<String, HotSwapFile>> scanForModifiedClassesWithProgress(@NotNull List<DebuggerSession> sessions,
@NotNull HotSwapProgressImpl progress) {
return scanForModifiedClassesWithProgress(sessions, null, progress);
}
@NotNull
private static Map<DebuggerSession, Map<String, HotSwapFile>> scanForModifiedClassesWithProgress(@NotNull List<DebuggerSession> sessions,
@Nullable Stream<String> 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<String> dirtyOutputRoots = context.getDirtyOutputPaths().orElse(null);
hotSwapSessions(sessions, generatedPaths, dirtyOutputRoots, callback);
}
}
}
@@ -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<String, String> myGeneratedFiles;
private final List<Supplier<List<String>>> 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.
* <p>
* 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.
* <p>
* The method should be used ONLY if the {@link ProjectTaskRunner} doesn't support {@link #fileGenerated} events.
*/
@ApiStatus.Experimental
public void dirtyOutputPathsProvider(@NotNull Supplier<List<String>> 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.
* <p>
* Can be useful for {@link ProjectTaskRunner}s which doesn't support {@link #fileGenerated} events.
*/
@ApiStatus.OverrideOnly
public Optional<Stream<String>> getDirtyOutputPaths() {
return myDirtyOutputPaths.isEmpty() ? Optional.empty() :
Optional.of(myDirtyOutputPaths.stream().map(supplier -> supplier.get()).flatMap(Collection::stream).distinct());
}
public <T> ProjectTaskContext withUserData(@NotNull Key<T> key, @Nullable T value) {
putUserData(key, value);
return this;
@@ -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;
}
}
@@ -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<List<String>> 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);
}