PY-60971 Create new API in PythonHelpersLocator that alerts about I/O

The deprecated `PythonHelpersLocator.getHelperPath` used to be cheap and had no I/O, but since bd5183320a43878de861fd11ae1f9bf79710290a it may start to copy many files to another directory. It becomes dangerous to call it from EDT.

GitOrigin-RevId: 98a913e68671b38299791fde7f267a396bd4faa0
This commit is contained in:
Vladimir Lagunov
2023-06-12 14:51:35 +02:00
committed by intellij-monorepo-bot
parent c67b9317a0
commit a277ed82d3

View File

@@ -17,11 +17,12 @@ package com.jetbrains.python;
import com.intellij.execution.process.ProcessIOExecutorService;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationInfo;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
import com.intellij.openapi.util.BuildNumber;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.NioFiles;
import com.intellij.util.LazyInitializer;
@@ -41,29 +42,100 @@ public final class PythonHelpersLocator {
private static final Logger LOG = Logger.getInstance(PythonHelpersLocator.class);
private static final String PROPERTY_HELPERS_LOCATION = "idea.python.helpers.path";
private static final LazyInitializer.LazyValue<@Nullable File> maybeCopiedHelpersRoot = new LazyInitializer.LazyValue<>(() -> {
try {
if (ApplicationInfo.getInstance().getBuild().isSnapshot()) {
// Although it runs not directly from the IDE, it's still built locally from sources, and it's supposed that
// copied code may change between runs.
return FileUtil.createTempDirectory("python-helpers", null, true);
}
else {
return new File(PathManager.getSystemPath());
}
}
catch (IOException e) {
throw new UncheckedIOException("Failed to create temporary directory for helpers", e);
}
});
private PythonHelpersLocator() { }
/**
* @return the base directory under which various scripts, etc. are stored.
* @return the base directory under which various Python scripts and other auxiliary files are stored.
* @deprecated This method used to be cheap, but now it may invoke I/O. Consider choosing between
* {@link #predictHelpersPath()} and {@link #getCopiedHelpersPath()}. If you're not sure, prefer {@link #getCopiedHelpersPath()} and watch
* for exceptions.
*/
@Deprecated
public static @NotNull File getHelpersRoot() {
String property = System.getProperty(PROPERTY_HELPERS_LOCATION);
if (property != null) {
return new File(property);
}
return assertHelpersLayout(getHelperRoot(ModuleHelpers.COMMUNITY));
return assertHelpersLayout(getCopiedHelpersPath(ModuleHelpers.COMMUNITY));
}
/**
* @return the base directory under which various scripts, various Python scripts and other auxiliary files are supposed to be stored.
* The helper scripts may not be stored there at the time of calling this function.
*/
public static @NotNull File predictHelpersPath() {
String property = System.getProperty(PROPERTY_HELPERS_LOCATION);
if (property != null) {
return new File(property);
}
return predictHelpersPath(ModuleHelpers.COMMUNITY);
}
/**
* @return the base directory under which various scripts, various Python scripts and other auxiliary files are stored.
* If the files haven't been copied yet, the functions does that blocking the current thread.
*/
public static @NotNull File getCopiedHelpersPath() {
logErrorOnEdt();
String property = System.getProperty(PROPERTY_HELPERS_LOCATION);
if (property != null) {
return new File(property);
}
return assertHelpersLayout(getCopiedHelpersPath(ModuleHelpers.COMMUNITY));
}
/**
* @deprecated This method used to be cheap, but now it may invoke I/O. Consider choosing between
* {@link #predictHelpersProPath()} and {@link #getCopiedHelpersProPath()}. If you're unsure, prefer {@link #getCopiedHelpersProPath()}
* and watch for exceptions.
*/
@Deprecated
public static @NotNull Path getHelpersProRoot() {
return assertHelpersProLayout(getHelperRoot(ModuleHelpers.PRO)).toPath().normalize();
return assertHelpersProLayout(getCopiedHelpersPath(ModuleHelpers.PRO)).toPath().normalize();
}
private static @NotNull File getHelperRoot(@NotNull ModuleHelpers moduleHelpers) {
/**
* See {@link #predictHelpersPath()}.
*/
public static @NotNull Path predictHelpersProPath() {
return predictHelpersPath(ModuleHelpers.PRO).toPath().normalize();
}
/**
* See {@link #getCopiedHelpersPath()}.
*/
public static @NotNull Path getCopiedHelpersProPath() {
logErrorOnEdt();
return assertHelpersProLayout(getCopiedHelpersPath(ModuleHelpers.PRO)).toPath().normalize();
}
@NotNull
private static File predictHelpersPath(@NotNull ModuleHelpers moduleHelpers) {
return new File(maybeCopiedHelpersRoot.get(), moduleHelpers.getSubDirectory());
}
private static @NotNull File getCopiedHelpersPath(@NotNull ModuleHelpers moduleHelpers) {
if (PluginManagerCore.isRunningFromSources()) {
return new File(PathManager.getCommunityHomePath(), moduleHelpers.myCommunityRepoRelativePath);
}
else {
@Nullable File helpersRootDir = ProgressIndicatorUtils.awaitWithCheckCanceled(moduleHelpers.copyRoot.get());
@Nullable File helpersRootDir = ProgressIndicatorUtils.awaitWithCheckCanceled(moduleHelpers.copiedHelpersRoot.get());
if (helpersRootDir != null) {
return helpersRootDir;
}
@@ -74,6 +146,18 @@ public final class PythonHelpersLocator {
}
}
private static void logErrorOnEdt() {
try {
Application app = ApplicationManager.getApplication();
if (app != null) {
app.assertIsNonDispatchThread();
}
}
catch (AssertionError err) {
Logger.getInstance(PythonHelpersLocator.class).error(err);
}
}
private static @Nullable File getPluginBaseDir(@NonNls String jarPath) {
if (jarPath.endsWith(".jar")) {
final File jarFile = new File(jarPath);
@@ -145,51 +229,36 @@ public final class PythonHelpersLocator {
* There is no check for macOS though, since such problems may appear in other operating systems as well, and since it's a bad
* idea to modify the IDE distributive during running in general.
*/
final LazyInitializer.LazyValue<@NotNull CompletableFuture<@Nullable File>> copyRoot;
final LazyInitializer.LazyValue<@NotNull CompletableFuture<@Nullable File>> copiedHelpersRoot;
ModuleHelpers(@NotNull String moduleName, @NotNull String pluginRelativePath, @NotNull String communityRelativePath) {
myModuleName = moduleName;
myCommunityRepoRelativePath = communityRelativePath;
copyRoot = new LazyInitializer.LazyValue<>(() -> {
copiedHelpersRoot = new LazyInitializer.LazyValue<>(() -> {
String jarPath = PathUtil.getJarPathForClass(PythonHelpersLocator.class);
final File pluginBaseDir = getPluginBaseDir(jarPath);
if (pluginBaseDir == null) {
return CompletableFuture.completedFuture(null);
}
return CompletableFuture.supplyAsync(
() -> {
try {
BuildNumber build = ApplicationInfo.getInstance().getBuild();
File systemRootDir;
if (build.isSnapshot()) {
// Although it runs not directly from the IDE, it's still built locally from sources, and it's supposed that
// copied code may change between runs.
systemRootDir = FileUtil.createTempDirectory("python-helpers", null, true);
}
else {
systemRootDir = new File(PathManager.getSystemPath());
}
File helpersRootDir = new File(
new File(
systemRootDir,
"python-helpers-" + build.asStringWithoutProductCode()
),
moduleName
);
if (!helpersRootDir.isDirectory()) {
NioFiles.createDirectories(helpersRootDir.toPath().getParent());
FileUtil.copyDir(new File(pluginBaseDir, pluginRelativePath), helpersRootDir, true);
}
return helpersRootDir;
return CompletableFuture.supplyAsync(() -> {
try {
final File helpersRootDir = predictHelpersPath(this);
if (!helpersRootDir.isDirectory()) {
NioFiles.createDirectories(helpersRootDir.toPath().getParent());
FileUtil.copyDir(new File(pluginBaseDir, pluginRelativePath), helpersRootDir, true);
}
catch (IOException e) {
throw new UncheckedIOException("Failed to create temporary directory for helpers", e);
}
},
ProcessIOExecutorService.INSTANCE
);
return helpersRootDir;
}
catch (IOException e) {
throw new UncheckedIOException("Failed to create temporary directory for helpers", e);
}
}, ProcessIOExecutorService.INSTANCE);
});
}
@NotNull String getSubDirectory() {
return "python-helpers-" + ApplicationInfo.getInstance().getBuild().asStringWithoutProductCode() + File.separator + myModuleName;
}
}
}