PY-80388: Exclude conda from system pythons.

What happened: `WinPythonSdkFlavor` returned all pythons from the `Path` environment variable.
Conda might be installed there.

How did we fix it: all flavors now report paths using protected functions. The parent class then filters results to drop pythons with incorrect flavor.

See `com.jetbrains.python.sdk.flavors.PythonSdkFlavor.suggestLocalHomePaths`.

GitOrigin-RevId: 7f2ef52a427ac07a625305c8371d985e2a7e0575
This commit is contained in:
Ilya.Kazakevich
2025-04-16 23:49:14 +02:00
committed by intellij-monorepo-bot
parent 9dd749c19b
commit 2d1ff1fec7
6 changed files with 42 additions and 24 deletions

View File

@@ -17,7 +17,6 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.PatternUtil;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.icons.PythonPsiApiIcons;
import com.jetbrains.python.run.CommandLinePatcher;
@@ -33,10 +32,11 @@ import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.jetbrains.python.venvReader.ResolveUtilKt.tryResolvePath;
import static com.jetbrains.python.sdk.flavors.PySdkFlavorUtilKt.getFileExecutionError;
import static com.jetbrains.python.sdk.flavors.PySdkFlavorUtilKt.getFileExecutionErrorOnEdt;
import static com.jetbrains.python.venvReader.ResolveUtilKt.tryResolvePath;
/**
@@ -97,21 +97,31 @@ public abstract class PythonSdkFlavor<D extends PyFlavorData> {
}
/**
* On local targets some flavours could be detected. It returns path to python interpreters for such cases.
* Flavors that are aware of some system pythons must return them there.
*/
@RequiresBackgroundThread
public @NotNull Collection<@NotNull Path> suggestLocalHomePaths(final @Nullable Module module, final @Nullable UserDataHolder context) {
return ContainerUtil.map(suggestHomePaths(module, context), Path::of);
}
/**
* @deprecated use {@link #suggestLocalHomePaths(Module, UserDataHolder)}
*/
@Deprecated(forRemoval = true)
public Collection<String> suggestHomePaths(final @Nullable Module module, final @Nullable UserDataHolder context) {
protected @NotNull Collection<@NotNull Path> suggestLocalHomePathsImpl(final @Nullable Module module,
final @Nullable UserDataHolder context) {
return Collections.emptyList();
}
/**
* On local targets some flavors could be detected. It returns a path to python interpreters for such cases.
*/
@RequiresBackgroundThread
public final @NotNull Collection<@NotNull Path> suggestLocalHomePaths(final @Nullable Module module,
final @Nullable UserDataHolder context) {
return suggestLocalHomePathsImpl(module, context).stream().filter(path -> {
var flavor = tryDetectFlavorByLocalPath(path.toString());
boolean correctFlavor = flavor != null && flavor.getClass().equals(getClass());
// Some flavors might report foreign pythons: i.e Windows might find conda on PATH.
if (!correctFlavor) {
LOG.info(String.format("Path %s has a wrong flavor, not %s, skipping", path, this));
return false;
}
return true;
}).collect(Collectors.toSet()).stream().sorted().toList();
}
/**
* Flavor is added to result in {@link #getApplicableFlavors()} if this method returns true.
@@ -275,7 +285,7 @@ public abstract class PythonSdkFlavor<D extends PyFlavorData> {
}
/**
* Detects {@link PythonSdkFlavor} for local python path
* Detects {@link PythonSdkFlavor} for a local python path
*/
@RequiresBackgroundThread(generateAssertion = false) //No warning yet as there are usages: to be fixed
public static @Nullable PythonSdkFlavor<?> tryDetectFlavorByLocalPath(@NotNull String sdkPath) {

View File

@@ -13,6 +13,7 @@ import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.text.HtmlBuilder;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.sdk.PyDetectedSdk;
import org.jetbrains.annotations.NotNull;
@@ -45,8 +46,9 @@ public final class MacPythonSdkFlavor extends CPythonSdkFlavor<PyFlavorData.Empt
return PyFlavorData.Empty.class;
}
@RequiresBackgroundThread
@Override
public @NotNull Collection<@NotNull Path> suggestLocalHomePaths(@Nullable Module module, @Nullable UserDataHolder context) {
protected @NotNull Collection<@NotNull Path> suggestLocalHomePathsImpl(@Nullable Module module, @Nullable UserDataHolder context) {
Set<Path> candidates = new HashSet<>();
collectPythonInstallations(Path.of("/Library/Frameworks/Python.framework/Versions"), candidates);
collectPythonInstallations(Path.of("/System/Library/Frameworks/Python.framework/Versions"), candidates);

View File

@@ -5,6 +5,7 @@ import com.google.common.collect.Sets;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.venvReader.VirtualEnvReader;
import org.jetbrains.annotations.ApiStatus;
@@ -42,8 +43,9 @@ public final class UnixPythonSdkFlavor extends CPythonSdkFlavor<PyFlavorData.Emp
return PyFlavorData.Empty.class;
}
@RequiresBackgroundThread
@Override
public @NotNull Collection<@NotNull Path> suggestLocalHomePaths(@Nullable Module module, @Nullable UserDataHolder context) {
protected @NotNull Collection<@NotNull Path> suggestLocalHomePathsImpl(@Nullable Module module, @Nullable UserDataHolder context) {
return getDefaultUnixPythons();
}

View File

@@ -6,6 +6,7 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.icons.PythonIcons;
import com.jetbrains.python.sdk.BasePySdkExtKt;
@@ -42,8 +43,9 @@ public final class VirtualEnvSdkFlavor extends CPythonSdkFlavor<PyFlavorData.Emp
return PyFlavorData.Empty.class;
}
@RequiresBackgroundThread
@Override
public @NotNull Collection<@NotNull Path> suggestLocalHomePaths(@Nullable Module module, @Nullable UserDataHolder context) {
protected @NotNull Collection<@NotNull Path> suggestLocalHomePathsImpl(@Nullable Module module, @Nullable UserDataHolder context) {
return ReadAction.compute(() -> {
final var candidates = new ArrayList<Path>();
@@ -69,10 +71,6 @@ public final class VirtualEnvSdkFlavor extends CPythonSdkFlavor<PyFlavorData.Emp
});
}
public static @NotNull Path getDefaultLocation() {
return VirtualEnvReader.getInstance().getVEnvRootDir();
}
@Override
public boolean isValidSdkPath(@NotNull String pathStr) {
if (!super.isValidSdkPath(pathStr)) {

View File

@@ -2,6 +2,7 @@
package com.jetbrains.python.sdk.flavors;
import com.google.common.collect.ImmutableMap;
import com.intellij.execution.configurations.PathEnvironmentVariableUtil;
import com.intellij.execution.target.TargetEnvironmentConfiguration;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
@@ -11,6 +12,7 @@ import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.EnvironmentUtil;
import com.intellij.util.concurrency.SynchronizedClearableLazy;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.intellij.util.containers.ContainerUtil;
@@ -84,7 +86,8 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor<PyFlavorData.Empty> {
@RequiresBackgroundThread
@Override
public @NotNull Collection<@NotNull Path> suggestLocalHomePaths(final @Nullable Module module, final @Nullable UserDataHolder context) {
protected final @NotNull Collection<@NotNull Path> suggestLocalHomePathsImpl(final @Nullable Module module,
final @Nullable UserDataHolder context) {
Set<String> candidates = new TreeSet<>();
findInCandidatePaths(candidates, "python.exe", "pypy.exe");
findInstallations(candidates, "python.exe", PythonHelpersLocator.getCommunityHelpersRoot().getParent().toString());
@@ -127,6 +130,7 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor<PyFlavorData.Empty> {
return path != null && isPythonFromStore(path); // Python from store might be non-executable, but still usable
}
@RequiresBackgroundThread
private boolean isPythonFromStore(@NotNull Path path) {
String pathStr = path.toString();
if (myAppxCache.getValue().contains(pathStr)) {
@@ -161,8 +165,9 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor<PyFlavorData.Empty> {
}
}
public static void findInPath(Collection<? super String> candidates, String exeName) {
final String path = System.getenv("PATH");
@RequiresBackgroundThread
private static void findInPath(@NotNull Collection<? super @NotNull String> candidates, @NotNull String exeName) {
final String path = PathEnvironmentVariableUtil.getPathVariableValue(); //can be Path or PATH
if (path == null) return;
for (String pathEntry : StringUtil.split(path, ";")) {
if (pathEntry.startsWith("\"") && pathEntry.endsWith("\"")) {

View File

@@ -58,8 +58,9 @@ public final class CondaEnvSdkFlavor extends CPythonSdkFlavor<PyCondaFlavorData>
return PyCondaFlavorData.class;
}
@RequiresBackgroundThread
@Override
public @NotNull Collection<@NotNull Path> suggestLocalHomePaths(@Nullable Module module, @Nullable UserDataHolder context) {
protected @NotNull Collection<@NotNull Path> suggestLocalHomePathsImpl(@Nullable Module module, @Nullable UserDataHolder context) {
// There is no such thing as "conda homepath" since conda doesn't store python path
return Collections.emptyList();
}