From 6b1720baee7137da77fb4e79b892dce9e05a685e Mon Sep 17 00:00:00 2001 From: "Nikita.Ashihmin" Date: Thu, 26 Jun 2025 12:10:13 +0400 Subject: [PATCH] PY-82244 Packages: Venv interpreter is not configured automatically Signed-off-by: Nikita.Ashihmin GitOrigin-RevId: 7dd5f77452afb28c500b70c525f7cdc007991a9c --- .../ide/impl/PythonSdkConfigurator.kt | 165 +++++++++--------- .../src/com/jetbrains/python/sdk/PySdkExt.kt | 43 ++--- python/src/com/jetbrains/python/sdk/sdk.kt | 3 +- .../python/target/ui/PyAddVirtualEnvPanel.kt | 23 +-- 4 files changed, 115 insertions(+), 119 deletions(-) diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/PythonSdkConfigurator.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/PythonSdkConfigurator.kt index 4cb01ab301e6..c58abbcaa0a4 100644 --- a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/PythonSdkConfigurator.kt +++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/PythonSdkConfigurator.kt @@ -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) + } } } } diff --git a/python/src/com/jetbrains/python/sdk/PySdkExt.kt b/python/src/com/jetbrains/python/sdk/PySdkExt.kt index 9c20d3f73835..2bea1673f59e 100644 --- a/python/src/com/jetbrains/python/sdk/PySdkExt.kt +++ b/python/src/com/jetbrains/python/sdk/PySdkExt.kt @@ -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? { // For Java only internal fun PyDetectedSdk.setupAssociatedLogged(existingSdks: List, 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, associatedModulePath: String?, doAssociate: Boolean): PyResult { +suspend fun PyDetectedSdk.setupAssociated( + existingSdks: List, + associatedModulePath: String?, + doAssociate: Boolean, +): PyResult = 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, 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 diff --git a/python/src/com/jetbrains/python/sdk/sdk.kt b/python/src/com/jetbrains/python/sdk/sdk.kt index 1947412790cf..a415f7b05b46 100644 --- a/python/src/com/jetbrains/python/sdk/sdk.kt +++ b/python/src/com/jetbrains/python/sdk/sdk.kt @@ -20,8 +20,7 @@ suspend fun createSdk( existingSdks: Array, ): 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) diff --git a/python/src/com/jetbrains/python/target/ui/PyAddVirtualEnvPanel.kt b/python/src/com/jetbrains/python/target/ui/PyAddVirtualEnvPanel.kt index 3561bacfe96e..0664a6ebf84d 100644 --- a/python/src/com/jetbrains/python/target/ui/PyAddVirtualEnvPanel.kt +++ b/python/src/com/jetbrains/python/target/ui/PyAddVirtualEnvPanel.kt @@ -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)