mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-222088 Hotswap takes a long time when nothing to reload
GitOrigin-RevId: 43f925ca50def11bf20ee5052575a5b79130d26f
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f385726405
commit
28f298ec0e
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user