PY-82244 Packages: Venv interpreter is not configured automatically

Signed-off-by: Nikita.Ashihmin <nikita.ashihmin@jetbrains.com>

GitOrigin-RevId: 7dd5f77452afb28c500b70c525f7cdc007991a9c
This commit is contained in:
Nikita.Ashihmin
2025-06-27 03:38:29 +00:00
committed by intellij-monorepo-bot
parent d04453827d
commit 6b1720baee
4 changed files with 115 additions and 119 deletions
@@ -3,7 +3,7 @@ package com.intellij.pycharm.community.ide.impl
import com.intellij.ide.trustedProjects.TrustedProjects
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.application.EDT
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.module.Module
@@ -36,7 +36,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus
import kotlin.coroutines.coroutineContext
/**
* @see [PyConfigureSdkOnWslTest]
@@ -96,105 +95,107 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
project: Project,
module: Module,
extension: PyProjectSdkConfigurationExtension?,
): Unit = reportRawProgress { indicator ->
// please keep this method in sync with com.jetbrains.python.inspections.PyInterpreterInspection.Visitor.getSuitableSdkFix
): Unit = withContext(Dispatchers.Default) {
reportRawProgress { indicator ->
// please keep this method in sync with com.jetbrains.python.inspections.PyInterpreterInspection.Visitor.getSuitableSdkFix
indicator.fraction(null)
indicator.fraction(null)
val context = UserDataHolderBase()
val context = UserDataHolderBase()
if (!coroutineContext.isActive) return@reportRawProgress
if (!coroutineContext.isActive) return@reportRawProgress
indicator.text(PyBundle.message("looking.for.inner.venvs"))
LOGGER.debug("Looking for inner virtual environments")
detectAssociatedEnvironments(module, emptyList(), context).filter { it.isLocatedInsideModule(module) }.takeIf { it.isNotEmpty() }?.let {
runInEdt { it.forEach { module.excludeInnerVirtualEnv(it) } }
}
if (!TrustedProjects.isProjectTrusted(project)) {
// com.jetbrains.python.inspections.PyInterpreterInspection will ask for confirmation
LOGGER.info("Python interpreter has not been configured since project is not trusted")
return@reportRawProgress
}
val existingSdks = ProjectSdksModel().apply { reset(project) }.sdks.filter { it.sdkType is PythonSdkType }
if (!coroutineContext.isActive) return@reportRawProgress
indicator.text(PyBundle.message("looking.for.previous.interpreter"))
LOGGER.debug("Looking for the previously used interpreter")
mostPreferred(filterAssociatedSdks(module, existingSdks))?.let {
LOGGER.debug { "The previously used interpreter: $it" }
setReadyToUseSdk(project, module, it)
return@reportRawProgress
}
if (!coroutineContext.isActive) return@reportRawProgress
indicator.text(PyBundle.message("looking.for.related.venv"))
LOGGER.debug("Looking for a virtual environment related to the project")
detectAssociatedEnvironments(module, existingSdks, context).firstOrNull()?.let {
LOGGER.debug { "Detected virtual environment related to the project: $it" }
val newSdk = it.setupAssociated(existingSdks, module.basePath, true).orLogException(LOGGER) ?: return
LOGGER.debug { "Created virtual environment related to the project: $newSdk" }
runInEdt {
SdkConfigurationUtil.addSdk(newSdk)
setReadyToUseSdk(project, module, newSdk)
indicator.text(PyBundle.message("looking.for.inner.venvs"))
LOGGER.debug("Looking for inner virtual environments")
detectAssociatedEnvironments(module, emptyList(), context).filter { it.isLocatedInsideModule(module) }.takeIf { it.isNotEmpty() }?.let {
withContext(Dispatchers.EDT) { it.forEach { module.excludeInnerVirtualEnv(it) } }
}
return@reportRawProgress
}
if (!TrustedProjects.isProjectTrusted(project)) {
// com.jetbrains.python.inspections.PyInterpreterInspection will ask for confirmation
LOGGER.info("Python interpreter has not been configured since project is not trusted")
return@reportRawProgress
}
if (!coroutineContext.isActive) return@reportRawProgress
val existingSdks = ProjectSdksModel().apply { reset(project) }.sdks.filter { it.sdkType is PythonSdkType }
if (extension != null) {
indicator.text("")
setSdkUsingExtension(module, extension) { extension.createAndAddSdkForConfigurator(module) }
return@reportRawProgress
}
if (!coroutineContext.isActive) return@reportRawProgress
if (!coroutineContext.isActive) return@reportRawProgress
if (PyCondaSdkCustomizer.instance.suggestSharedCondaEnvironments) {
indicator.text(PyBundle.message("looking.for.shared.conda.environment"))
mostPreferred(filterSharedCondaEnvs(module, existingSdks))?.let {
indicator.text(PyBundle.message("looking.for.previous.interpreter"))
LOGGER.debug("Looking for the previously used interpreter")
mostPreferred(filterAssociatedSdks(module, existingSdks))?.let {
LOGGER.debug { "The previously used interpreter: $it" }
setReadyToUseSdk(project, module, it)
return@reportRawProgress
}
if (!coroutineContext.isActive) return@reportRawProgress
}
indicator.text(PyBundle.message("looking.for.default.interpreter"))
LOGGER.debug("Looking for the default interpreter setting for a new project")
getDefaultProjectSdk()?.let {
LOGGER.debug { "Default interpreter setting for a new project: $it" }
setReadyToUseSdk(project, module, it)
return@reportRawProgress
}
indicator.text(PyBundle.message("looking.for.related.venv"))
LOGGER.debug("Looking for a virtual environment related to the project")
detectAssociatedEnvironments(module, existingSdks, context).firstOrNull()?.let {
LOGGER.debug { "Detected virtual environment related to the project: $it" }
val newSdk = it.setupAssociated(existingSdks, module.basePath, true).orLogException(LOGGER) ?: return@withContext
if (!coroutineContext.isActive) return@reportRawProgress
LOGGER.debug { "Created virtual environment related to the project: $newSdk" }
indicator.text(PyBundle.message("looking.for.previous.system.interpreter"))
LOGGER.debug("Looking for the previously used system-wide interpreter")
mostPreferred(filterSystemWideSdks(existingSdks))?.let {
LOGGER.debug { "Previously used system-wide interpreter: $it" }
setReadyToUseSdk(project, module, it)
return@reportRawProgress
}
withContext(Dispatchers.EDT) {
SdkConfigurationUtil.addSdk(newSdk)
setReadyToUseSdk(project, module, newSdk)
}
if (!coroutineContext.isActive) return@reportRawProgress
return@reportRawProgress
}
indicator.text(PyBundle.message("looking.for.system.interpreter"))
LOGGER.debug("Looking for a system-wide interpreter")
detectSystemWideSdks(module, existingSdks, context).firstOrNull()?.let {
LOGGER.debug { "Detected system-wide interpreter: $it" }
runInEdt {
SdkConfigurationUtil.createAndAddSDK(it.homePath!!, PythonSdkType.getInstance())?.apply {
LOGGER.debug { "Created system-wide interpreter: $this" }
setReadyToUseSdk(project, module, this)
if (!coroutineContext.isActive) return@reportRawProgress
if (extension != null) {
indicator.text("")
setSdkUsingExtension(module, extension) { extension.createAndAddSdkForConfigurator(module) }
return@reportRawProgress
}
if (!coroutineContext.isActive) return@reportRawProgress
if (PyCondaSdkCustomizer.instance.suggestSharedCondaEnvironments) {
indicator.text(PyBundle.message("looking.for.shared.conda.environment"))
mostPreferred(filterSharedCondaEnvs(module, existingSdks))?.let {
setReadyToUseSdk(project, module, it)
return@reportRawProgress
}
if (!coroutineContext.isActive) return@reportRawProgress
}
indicator.text(PyBundle.message("looking.for.default.interpreter"))
LOGGER.debug("Looking for the default interpreter setting for a new project")
getDefaultProjectSdk()?.let {
LOGGER.debug { "Default interpreter setting for a new project: $it" }
setReadyToUseSdk(project, module, it)
return@reportRawProgress
}
if (!coroutineContext.isActive) return@reportRawProgress
indicator.text(PyBundle.message("looking.for.previous.system.interpreter"))
LOGGER.debug("Looking for the previously used system-wide interpreter")
mostPreferred(filterSystemWideSdks(existingSdks))?.let {
LOGGER.debug { "Previously used system-wide interpreter: $it" }
setReadyToUseSdk(project, module, it)
return@reportRawProgress
}
if (!coroutineContext.isActive) return@reportRawProgress
indicator.text(PyBundle.message("looking.for.system.interpreter"))
LOGGER.debug("Looking for a system-wide interpreter")
detectSystemWideSdks(module, existingSdks, context).firstOrNull()?.let {
LOGGER.debug { "Detected system-wide interpreter: $it" }
withContext(Dispatchers.EDT) {
SdkConfigurationUtil.createAndAddSDK(it.homePath!!, PythonSdkType.getInstance())?.apply {
LOGGER.debug { "Created system-wide interpreter: $this" }
setReadyToUseSdk(project, module, this)
}
}
}
}
+23 -20
View File
@@ -10,6 +10,7 @@ import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
@@ -26,7 +27,6 @@ import com.intellij.openapi.vfs.ex.temp.TempFileSystem
import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.util.PathUtil
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.intellij.webcore.packaging.PackagesNotificationPanel
import com.jetbrains.python.PyBundle
import com.jetbrains.python.errorProcessing.PyResult
@@ -200,7 +200,9 @@ fun createSdkByGenerateTask(
}
}
else {
suggestAssociatedSdkName(homeFile.path, associatedProjectPath)
runBlockingMaybeCancellable {
suggestAssociatedSdkName(homeFile.path, associatedProjectPath)
}
}
return SdkConfigurationUtil.setupSdk(
existingSdks.toTypedArray(),
@@ -221,10 +223,7 @@ suspend fun createSdk(
val homeFile = withContext(Dispatchers.IO) { StandardFileSystems.local().refreshAndFindFileByPath(sdkHomePath.pathString) }
?: return PyResult.localizedError(PyBundle.message("python.sdk.directory.not.found", sdkHomePath.pathString))
val sdkName = suggestedSdkName ?: withContext(Dispatchers.IO) {
suggestAssociatedSdkName(homeFile.path, associatedProjectPath)
}
val sdkName = suggestedSdkName ?: suggestAssociatedSdkName(homeFile.path, associatedProjectPath)
val sdk = SdkConfigurationUtil.setupSdk(
existingSdks.toTypedArray(),
homeFile,
@@ -289,25 +288,30 @@ fun PyDetectedSdk.setup(existingSdks: List<Sdk>): Sdk? {
// For Java only
internal fun PyDetectedSdk.setupAssociatedLogged(existingSdks: List<Sdk>, associatedModulePath: String?, doAssociate: Boolean): Sdk? {
return setupAssociated(existingSdks, associatedModulePath, doAssociate).orLogException(LOGGER)
return runBlockingMaybeCancellable {
setupAssociated(existingSdks, associatedModulePath, doAssociate).orLogException(LOGGER)
}
}
@Internal
fun PyDetectedSdk.setupAssociated(existingSdks: List<Sdk>, associatedModulePath: String?, doAssociate: Boolean): PyResult<Sdk> {
suspend fun PyDetectedSdk.setupAssociated(
existingSdks: List<Sdk>,
associatedModulePath: String?,
doAssociate: Boolean,
): PyResult<Sdk> = withContext(Dispatchers.IO) {
if (!sdkSeemsValid) {
return PyResult.localizedError(PyBundle.message("python.sdk.error.invalid.interpreter.selected", homePath))
return@withContext PyResult.localizedError(PyBundle.message("python.sdk.error.invalid.interpreter.selected", homePath))
}
val homePath = this.homePath
val homePath = homePath
if (homePath == null) {
// e.g. directory is not there anymore
return PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid", null))
return@withContext PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid", null))
}
val homeDir = this.homeDirectory
val homeDir = homeDirectory
if (homeDir == null) {
return PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid", null))
return@withContext PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid", null))
}
val suggestedName = if (doAssociate) {
@@ -332,7 +336,7 @@ fun PyDetectedSdk.setupAssociated(existingSdks: List<Sdk>, associatedModulePath:
data,
suggestedName)
return PyResult.success(sdk)
PyResult.success(sdk)
}
var Module.pythonSdk: Sdk?
@@ -408,12 +412,11 @@ fun getInnerVirtualEnvRoot(sdk: Sdk): VirtualFile? {
}
}
@RequiresBackgroundThread(generateAssertion = false)
internal fun suggestAssociatedSdkName(sdkHome: String, associatedPath: String?): String? {
internal suspend fun suggestAssociatedSdkName(sdkHome: String, associatedPath: String?): String? = withContext(Dispatchers.IO) {
// please don't forget to update com.jetbrains.python.inspections.PyInterpreterInspection.Visitor#getSuitableSdkFix
// after changing this method
val baseSdkName = PythonSdkType.suggestBaseSdkName(sdkHome) ?: return null
val baseSdkName = PythonSdkType.suggestBaseSdkName(sdkHome) ?: return@withContext null
val venvRoot = PythonSdkUtil.getVirtualEnvRoot(sdkHome)?.path
val condaRoot = CondaEnvSdkFlavor.getCondaEnvRoot(sdkHome)?.path
val associatedName = when {
@@ -424,9 +427,9 @@ internal fun suggestAssociatedSdkName(sdkHome: String, associatedPath: String?):
PythonSdkUtil.isBaseConda(sdkHome) ->
"base"
else ->
associatedPath?.let { PathUtil.getFileName(associatedPath) } ?: return null
associatedPath?.let { PathUtil.getFileName(associatedPath) } ?: return@withContext null
}
return "$baseSdkName ($associatedName)"
return@withContext "$baseSdkName ($associatedName)"
}
internal val Sdk.isSystemWide: Boolean
+1 -2
View File
@@ -20,8 +20,7 @@ suspend fun createSdk(
existingSdks: Array<Sdk>,
): Sdk {
val newSdk = withContext(Dispatchers.IO) {
// "suggest name" calls external process and can't be called from EDT
val suggestedName = /*suggestedSdkName ?:*/ suggestAssociatedSdkName(pythonBinaryPath.path, projectPath?.toString())
val suggestedName = suggestAssociatedSdkName(pythonBinaryPath.path, projectPath?.toString())
SdkConfigurationUtil.setupSdk(existingSdks, pythonBinaryPath,
PythonSdkType.getInstance(),
null, suggestedName)
@@ -6,6 +6,7 @@ import com.intellij.execution.target.TargetEnvironmentConfiguration
import com.intellij.execution.target.joinTargetPaths
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
@@ -14,11 +15,7 @@ import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.io.FileUtil
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.bind
import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.builder.selected
import com.intellij.ui.dsl.builder.*
import com.intellij.ui.layout.ComponentPredicate
import com.intellij.ui.layout.not
import com.intellij.util.PathUtil
@@ -29,25 +26,19 @@ import com.jetbrains.python.icons.PythonIcons
import com.jetbrains.python.pathValidation.PlatformAndRoot.Companion.getPlatformAndRoot
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory.Companion.extendWithTargetSpecificFields
import com.jetbrains.python.sdk.PyDetectedSdk
import com.jetbrains.python.sdk.PySdkSettings
import com.jetbrains.python.sdk.PythonSdkType
import com.jetbrains.python.venvReader.VirtualEnvReader
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.add.ExistingPySdkComboBoxItem
import com.jetbrains.python.sdk.add.PySdkPathChoosingComboBox
import com.jetbrains.python.sdk.add.addBaseInterpretersAsync
import com.jetbrains.python.sdk.add.addInterpretersAsync
import com.jetbrains.python.sdk.configuration.createSdkForTarget
import com.jetbrains.python.sdk.configuration.createVirtualEnvAndSdkSynchronously
import com.jetbrains.python.sdk.detectVirtualEnvs
import com.jetbrains.python.sdk.flavors.PyFlavorAndData
import com.jetbrains.python.sdk.flavors.PyFlavorData
import com.jetbrains.python.sdk.isAssociatedWithAnotherModule
import com.jetbrains.python.sdk.isAssociatedWithModule
import com.jetbrains.python.sdk.setupAssociated
import com.jetbrains.python.target.PyTargetAwareAdditionalData
import com.jetbrains.python.target.PythonLanguageRuntimeConfiguration
import com.jetbrains.python.target.createDetectedSdk
import com.jetbrains.python.venvReader.VirtualEnvReader
import java.awt.BorderLayout
import java.util.function.Supplier
@@ -199,7 +190,9 @@ internal class PyAddVirtualEnvPanel(
val item = interpreterCombobox.selectedItem as ExistingPySdkComboBoxItem
// there should *not* be other items other than `ExistingPySdkComboBoxItem`
return configureExistingVirtualenvSdk(targetEnvironmentConfiguration, item.sdk)
return runBlockingMaybeCancellable {
configureExistingVirtualenvSdk(targetEnvironmentConfiguration, item.sdk)
}
}
/**
@@ -228,7 +221,7 @@ internal class PyAddVirtualEnvPanel(
isInheritSitePackages, false, targetPanelExtension)
}
private fun configureExistingVirtualenvSdk(targetEnvironmentConfiguration: TargetEnvironmentConfiguration?, selectedSdk: Sdk): Sdk {
private suspend fun configureExistingVirtualenvSdk(targetEnvironmentConfiguration: TargetEnvironmentConfiguration?, selectedSdk: Sdk): Sdk {
if (targetEnvironmentConfiguration == null) {
return when (selectedSdk) {
is PyDetectedSdk -> selectedSdk.setupAssociated(existingSdks, newProjectPath ?: project?.basePath, true)