diff --git a/python/src/com/jetbrains/python/inspections/PyInterpreterInspection.java b/python/src/com/jetbrains/python/inspections/PyInterpreterInspection.java index a5fc9461ccca..052b740052ce 100644 --- a/python/src/com/jetbrains/python/inspections/PyInterpreterInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyInterpreterInspection.java @@ -29,7 +29,9 @@ import com.intellij.util.PlatformUtils; import com.jetbrains.python.PyBundle; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.sdk.PySdkExtKt; import com.jetbrains.python.sdk.PythonSdkType; +import com.jetbrains.python.sdk.flavors.PipenvKt; import com.jetbrains.python.sdk.flavors.UsePipEnvQuickFix; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -66,34 +68,43 @@ public class PyInterpreterInspection extends PyInspection { public void visitPyFile(PyFile node) { final Module module = ModuleUtilCore.findModuleForPsiElement(node); if (module == null) return; + final Sdk sdk = PythonSdkType.findPythonSdk(module); final boolean pyCharm = PlatformUtils.isPyCharm(); final String interpreterOwner = pyCharm ? "project" : "module"; final List fixes = new ArrayList<>(); + if (UsePipEnvQuickFix.Companion.isApplicable(module)) { + fixes.add(new UsePipEnvQuickFix(sdk, module)); + } if (pyCharm) { - if (UsePipEnvQuickFix.Companion.isApplicable(module)) { - fixes.add(new UsePipEnvQuickFix()); - } fixes.add(new ConfigureInterpreterFix()); } final String product = pyCharm ? "PyCharm" : "Python plugin"; - final Sdk sdk = PythonSdkType.findPythonSdk(module); - if (sdk == null) { registerProblem(node, "No Python interpreter configured for the " + interpreterOwner, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); } - else if (PythonSdkType.isInvalid(sdk)) { - registerProblem(node, "Invalid Python interpreter selected for the " + interpreterOwner, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); - } else { - final LanguageLevel languageLevel = PythonSdkType.getLanguageLevelForSdk(sdk); - if (!LanguageLevel.SUPPORTED_LEVELS.contains(languageLevel)) { - registerProblem(node, - "Python " + languageLevel + " has reached its end-of-life and is no longer supported by " + product, - fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); + final Module associatedModule = PySdkExtKt.getAssociatedModule(sdk); + final String associatedName = associatedModule != null ? associatedModule.getName() : PySdkExtKt.getAssociatedModulePath(sdk); + if (PipenvKt.isPipEnv(sdk) && associatedModule != module) { + final String message = associatedName != null ? + "Pipenv interpreter is associated with another " + interpreterOwner + ": '" + associatedName + "'" : + "Pipenv interpreter is not associated with any " + interpreterOwner; + registerProblem(node, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); + } + else if (PythonSdkType.isInvalid(sdk)) { + registerProblem(node, "Invalid Python interpreter selected for the " + interpreterOwner, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); + } + else { + final LanguageLevel languageLevel = PythonSdkType.getLanguageLevelForSdk(sdk); + if (!LanguageLevel.SUPPORTED_LEVELS.contains(languageLevel)) { + registerProblem(node, + "Python " + languageLevel + " has reached its end-of-life and is no longer supported by " + product, + fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); + } } } } diff --git a/python/src/com/jetbrains/python/sdk/PySdkExt.kt b/python/src/com/jetbrains/python/sdk/PySdkExt.kt index 1cb4a51889dd..bcec6819aae1 100644 --- a/python/src/com/jetbrains/python/sdk/PySdkExt.kt +++ b/python/src/com/jetbrains/python/sdk/PySdkExt.kt @@ -18,9 +18,11 @@ package com.jetbrains.python.sdk import com.intellij.execution.ExecutionException import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.project.rootManager import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil @@ -46,6 +48,7 @@ import java.nio.file.Paths */ fun findBaseSdks(existingSdks: List): List { + // TODO: Filter out non-Python SDKs val existing = existingSdks.filter { it.isSystemWide } val detected = detectSystemWideSdks(existingSdks) return existing + detected @@ -117,6 +120,15 @@ val Sdk.associatedModulePath: String? // TODO: Support .project associations get() = associatedPathFromAdditionalData /*?: associatedPathFromDotProject*/ +val Sdk.associatedModule: Module? + get() { + val associatedPath = associatedModulePath + return ProjectManager.getInstance().openProjects + .asSequence() + .flatMap { ModuleManager.getInstance(it).modules.asSequence() } + .firstOrNull { it?.basePath == associatedPath } + } + fun Sdk.adminPermissionsNeeded(): Boolean { val homePath = homePath ?: return false return !Files.isWritable(Paths.get(homePath)) diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkAdditionalData.java b/python/src/com/jetbrains/python/sdk/PythonSdkAdditionalData.java index 003fe78e4e4a..242f86803960 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkAdditionalData.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkAdditionalData.java @@ -133,12 +133,13 @@ public class PythonSdkAdditionalData implements SdkAdditionalData { if (myAssociatedModulePath != null) { rootElement.setAttribute(ASSOCIATED_PROJECT_PATH, myAssociatedModulePath); - // XXX: We have to persist the pipenv flag since pipenv is no different from a regular - // virtualenv and currently we want to handle pipenvs differently. Consider adding an SDK - // extension mechanism for that - if (myIsPipEnv) { - rootElement.setAttribute(IS_PIPENV, "true"); - } + } + + // XXX: We have to persist the pipenv flag since pipenv is no different from a regular + // virtualenv and currently we want to handle pipenvs differently. Consider adding an SDK + // extension mechanism for that + if (myIsPipEnv) { + rootElement.setAttribute(IS_PIPENV, "true"); } } diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkType.java b/python/src/com/jetbrains/python/sdk/PythonSdkType.java index 590547dde973..2a0e738a21f7 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkType.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkType.java @@ -64,6 +64,7 @@ import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase; import com.jetbrains.python.remote.PythonRemoteInterpreterManager; import com.jetbrains.python.run.PyVirtualEnvReader; import com.jetbrains.python.sdk.flavors.CPythonSdkFlavor; +import com.jetbrains.python.sdk.flavors.PipenvKt; import com.jetbrains.python.sdk.flavors.PythonSdkFlavor; import icons.PythonIcons; import org.jdom.Element; @@ -220,6 +221,9 @@ public final class PythonSdkType extends SdkType { return false; } final VirtualFile interpreter = sdk.getHomeDirectory(); + if (PipenvKt.isPipEnv(sdk) && PySdkExtKt.getAssociatedModule(sdk) == null) { + return true; + } return interpreter == null || !interpreter.exists(); } diff --git a/python/src/com/jetbrains/python/sdk/flavors/pipenv.kt b/python/src/com/jetbrains/python/sdk/flavors/pipenv.kt index bd2453a9b1e4..6a3c54e457b3 100644 --- a/python/src/com/jetbrains/python/sdk/flavors/pipenv.kt +++ b/python/src/com/jetbrains/python/sdk/flavors/pipenv.kt @@ -218,7 +218,12 @@ private val Sdk.packageManager: PyPackageManager /** * A quick-fix for setting up the pipenv for the module of the current PSI element. */ -class UsePipEnvQuickFix : LocalQuickFix { +class UsePipEnvQuickFix(sdk: Sdk?, module: Module) : LocalQuickFix { + private val quickFixName = when { + sdk != null && sdk.associatedModule != module -> "Fix Pipenv interpreter" + else -> "Use Pipenv interpreter" + } + companion object { fun isApplicable(module: Module): Boolean = module.pipFile != null @@ -234,12 +239,15 @@ class UsePipEnvQuickFix : LocalQuickFix { if (sdk == newSdk) { SdkConfigurationUtil.addSdk(newSdk) } + else { + sdk.associateWithModule(module, false) + } project.pythonSdk = sdk module.pythonSdk = sdk } } - override fun getFamilyName() = "Use Pipenv interpreter" + override fun getFamilyName() = quickFixName override fun applyFix(project: Project, descriptor: ProblemDescriptor) { val element = descriptor.psiElement ?: return @@ -259,6 +267,7 @@ class PipEnvInstallQuickFix : LocalQuickFix { companion object { fun pipEnvInstall(project: Project, module: Module) { val sdk = module.pythonSdk ?: return + if (!sdk.isPipEnv) return val listener = PyPackageRequirementsInspection.RunningPackagingTasksListener(module) val ui = PyPackageManagerUI(project, sdk, listener) ui.install(null, listOf("--dev"))