mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[python] PY-84315: Use SDK configurators for inspection
There was a problem that we can detect uv environment as a virtualenv in PyInterpreterInspection. The problem is that we don't use the knowledge about the existing environment anyhow. This change relies on SDK configurators to tell whether environment is present or can be configured. GitOrigin-RevId: 27f55c7d1b4712b0b8c3d39b1b8f7d8643680910
This commit is contained in:
committed by
intellij-monorepo-bot
parent
033d7cc704
commit
9140cb4e7b
+1
-1
@@ -85,7 +85,7 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
|
||||
emptyList()
|
||||
}
|
||||
else {
|
||||
PyProjectSdkConfigurationExtension.EP_NAME.extensionsIfPointIsRegistered.mapNotNull { it.checkEnvironmentAndPrepareSdkCreator(module) }.sorted()
|
||||
PyProjectSdkConfigurationExtension.findAllSortedForModule(module)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,12 +91,6 @@
|
||||
</extensions>
|
||||
|
||||
<projectListeners>
|
||||
<listener
|
||||
class="com.jetbrains.python.inspections.PyInterpreterInspection$Visitor$CacheCleaner"
|
||||
topic="com.intellij.platform.backend.workspace.WorkspaceModelChangeListener"/>
|
||||
<listener
|
||||
class="com.jetbrains.python.inspections.PyInterpreterInspection$Visitor$CacheCleaner"
|
||||
topic="com.intellij.openapi.projectRoots.ProjectJdkTable$Listener"/>
|
||||
<listener class="com.jetbrains.python.packaging.PyDependencyCollectorListener"
|
||||
topic="com.jetbrains.python.packaging.common.PythonPackageManagementListener"/>
|
||||
<listener class="com.jetbrains.python.statistics.PyPackageDaemonListener"
|
||||
|
||||
+3
-5
@@ -36,14 +36,12 @@ private class AutoconfigSelectSdkProvider() : EvoSelectSdkProvider {
|
||||
text = PySdkUiBundle.message("evo.sdk.status.bar.popup.shortcuts.best.options"),
|
||||
icon = AllIcons.General.Layout
|
||||
) {
|
||||
val createSdkInfos = PyProjectSdkConfigurationExtension.EP_NAME.extensionsIfPointIsRegistered.mapNotNull {
|
||||
it.checkEnvironmentAndPrepareSdkCreator(evoModuleSdk.module)
|
||||
}
|
||||
val createSdkInfoWithTools = PyProjectSdkConfigurationExtension.findAllSortedForModule(evoModuleSdk.module)
|
||||
|
||||
val section = EvoTreeSection(
|
||||
label = null,
|
||||
elements = createSdkInfos.mapIndexed { idx, createSdkInfo ->
|
||||
EvoTreeLeafElement(RunConfiguratorAction(createSdkInfo.intentionName, idx))
|
||||
elements = createSdkInfoWithTools.mapIndexed { idx, createSdkInfoWithTool ->
|
||||
EvoTreeLeafElement(RunConfiguratorAction(createSdkInfoWithTool.intentionName, idx))
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
+11
@@ -27,6 +27,17 @@ interface PyProjectSdkConfigurationExtension {
|
||||
fun findForModule(module: Module): CreateSdkInfo? = runBlockingMaybeCancellable {
|
||||
EP_NAME.extensionsIfPointIsRegistered.firstNotNullOfOrNull { ext -> ext.checkEnvironmentAndPrepareSdkCreator(module) }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@RequiresBackgroundThread
|
||||
/**
|
||||
* We return all configurators in a sorted order. The order is determined by extensions order, but existing environments have a
|
||||
* higher priority. That means we first have all existing envs, and only after SDK creators that extensions can manage.
|
||||
*/
|
||||
fun findAllSortedForModule(module: Module): List<CreateSdkInfo> = runBlockingMaybeCancellable {
|
||||
EP_NAME.extensionsIfPointIsRegistered
|
||||
.mapNotNull { e -> e.checkEnvironmentAndPrepareSdkCreator(module) }.sorted()
|
||||
}
|
||||
}
|
||||
|
||||
val toolInfo: PyToolUIInfo
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.inspections;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
|
||||
import com.intellij.codeInspection.LocalInspectionToolSession;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
@@ -13,32 +11,23 @@ import com.intellij.codeInspection.util.IntentionFamilyName;
|
||||
import com.intellij.codeInspection.util.IntentionName;
|
||||
import com.intellij.ide.actions.ShowSettingsUtilImpl;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.options.ex.ConfigurableExtensionPointUtil;
|
||||
import com.intellij.openapi.options.ex.ConfigurableVisitor;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
|
||||
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
|
||||
import com.intellij.openapi.util.UserDataHolderBase;
|
||||
import com.intellij.platform.backend.workspace.WorkspaceModelChangeListener;
|
||||
import com.intellij.platform.workspace.jps.entities.ModuleEntity;
|
||||
import com.intellij.platform.workspace.storage.EntityChange;
|
||||
import com.intellij.platform.workspace.storage.VersionedStorageChange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.util.PathUtil;
|
||||
import com.intellij.util.PlatformUtils;
|
||||
import com.intellij.util.concurrency.AppExecutorUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.workspaceModel.ide.impl.legacyBridge.module.ModuleEntityUtils;
|
||||
import com.jetbrains.python.PyPsiBundle;
|
||||
import com.jetbrains.python.PythonIdeLanguageCustomization;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
@@ -61,13 +50,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -77,8 +63,6 @@ import static com.intellij.python.sdkConfigurator.common.PublicApiKt.detectSdkFo
|
||||
|
||||
public final class PyInterpreterInspection extends PyInspection {
|
||||
|
||||
private static final @NotNull Logger LOGGER = Logger.getInstance(PyInterpreterInspection.class);
|
||||
|
||||
private static final @NotNull Pattern NAME = Pattern.compile("Python (?<version>\\d\\.\\d+)\\s*(\\((?<name>.+?)\\))?");
|
||||
|
||||
@Override
|
||||
@@ -89,21 +73,6 @@ public final class PyInterpreterInspection extends PyInspection {
|
||||
}
|
||||
|
||||
public static class Visitor extends PyInspectionVisitor {
|
||||
/** Invalidated by {@link CacheCleaner}. */
|
||||
private static final AsyncLoadingCache<@NotNull Module, @NotNull List<PyDetectedSdk>> DETECTED_ASSOCIATED_ENVS_CACHE =
|
||||
Caffeine.newBuilder().executor(AppExecutorUtil.getAppExecutorService())
|
||||
|
||||
// Even though various listeners invalidate the cache on many actions, it's unfeasible to track for venv/conda interpreters
|
||||
// creation performed outside the IDE.
|
||||
// 20 seconds timeout is taken at random.
|
||||
.expireAfterWrite(Duration.ofSeconds(20))
|
||||
|
||||
.weakKeys()
|
||||
.buildAsync(module -> {
|
||||
final List<Sdk> existingSdks = getExistingSdks();
|
||||
final UserDataHolderBase context = new UserDataHolderBase();
|
||||
return PySdkExtKt.detectAssociatedEnvironments(module, existingSdks, context);
|
||||
});
|
||||
|
||||
public Visitor(@Nullable ProblemsHolder holder,
|
||||
@NotNull TypeEvalContext context) {
|
||||
@@ -161,30 +130,9 @@ public final class PyInterpreterInspection extends PyInspection {
|
||||
|
||||
final UserDataHolderBase context = new UserDataHolderBase();
|
||||
|
||||
List<PyDetectedSdk> detectedAssociatedEnvs = Collections.emptyList();
|
||||
while (true) {
|
||||
try {
|
||||
// Beware that this thread holds the read lock. Shouldn't wait too much.
|
||||
detectedAssociatedEnvs = DETECTED_ASSOCIATED_ENVS_CACHE.get(module).get(10, TimeUnit.MILLISECONDS);
|
||||
break;
|
||||
}
|
||||
catch (InterruptedException | TimeoutException ignored) {
|
||||
ProgressManager.checkCanceled();
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.warn("Failed to get suitable sdk fix for name " + name + " and module " + module, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final var detectedAssociatedSdk = ContainerUtil.getFirstItem(detectedAssociatedEnvs);
|
||||
if (detectedAssociatedSdk != null) {
|
||||
return new UseDetectedInterpreterFix(detectedAssociatedSdk, existingSdks, true, module);
|
||||
}
|
||||
|
||||
final CreateSdkInfo createSdkInfo = PyProjectSdkConfigurationExtension.findForModule(module);
|
||||
if (createSdkInfo != null) {
|
||||
return new UseProvidedInterpreterFix(module, createSdkInfo);
|
||||
final List<CreateSdkInfo> createSdkInfos = PyProjectSdkConfigurationExtension.findAllSortedForModule(module);
|
||||
if (!createSdkInfos.isEmpty()) {
|
||||
return new UseProvidedInterpreterFix(module, createSdkInfos.getFirst());
|
||||
}
|
||||
|
||||
if (name != null) {
|
||||
@@ -290,44 +238,6 @@ public final class PyInterpreterInspection extends PyInspection {
|
||||
private static @Nullable String getEnvRootName(@Nullable File envRoot) {
|
||||
return envRoot == null ? null : PathUtil.getFileName(envRoot.getPath());
|
||||
}
|
||||
|
||||
private static class CacheCleaner implements WorkspaceModelChangeListener, ProjectJdkTable.Listener {
|
||||
@Override
|
||||
public void jdkAdded(@NotNull Sdk jdk) {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jdkRemoved(@NotNull Sdk jdk) {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jdkNameChanged(@NotNull Sdk jdk, @NotNull String previousName) {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the cache for the modules that were changed in any way. Especially interesting are
|
||||
* content roots changes and current Python interpreter changes.
|
||||
*/
|
||||
@Override
|
||||
public void beforeChanged(@NotNull VersionedStorageChange event) {
|
||||
for (EntityChange<ModuleEntity> change : event.getChanges(ModuleEntity.class)) {
|
||||
ModuleEntity entity = change.getOldEntity();
|
||||
if (entity != null) {
|
||||
var module = ModuleEntityUtils.findModule(entity, event.getStorageBefore());
|
||||
if (module != null) {
|
||||
DETECTED_ASSOCIATED_ENVS_CACHE.synchronous().invalidate(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void invalidate() {
|
||||
DETECTED_ASSOCIATED_ENVS_CACHE.synchronous().invalidateAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isFileIgnored(@NotNull PyFile pyFile) {
|
||||
|
||||
Reference in New Issue
Block a user