mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
[python] PY-83881 Detect existing environments when creating SDK
Before the changes, there wasn't any mechanism to detect that environment was already created (for example, .venv exists in the project). In these situations, during SDK creation we could've created another environment which was not expected by users. With these changes, it's now possible to detect in the configurator that environment already exists, and use it when creating SDK. Merge-request: IJ-MR-177317 Merged-by: Alexey Katsman <alexey.katsman@jetbrains.com> GitOrigin-RevId: dd0cf0c02b18e90022e9ec828b7f9ad2282cd5b3
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d511ce0919
commit
cc191a617f
@@ -57,6 +57,7 @@ jvm_library(
|
||||
"//platform/eel-provider",
|
||||
"//python/services/shared",
|
||||
"//python/poetry",
|
||||
"//python/pipenv",
|
||||
"//python/python-venv:community-impl-venv",
|
||||
"@lib//:jetbrains-annotations",
|
||||
"//python/python-pyproject:pyproject",
|
||||
@@ -67,6 +68,7 @@ jvm_library(
|
||||
"//platform/non-modal-welcome-screen/backend",
|
||||
"//python/python-exec-service:community-execService",
|
||||
"//python/python-sdk-configurator/common",
|
||||
"//python/python-sdk-ui:sdk-ui",
|
||||
],
|
||||
runtime_deps = ["//python/python-features-trainer:featuresTrainer"],
|
||||
plugins = ["@lib//:compose-plugin"]
|
||||
@@ -124,6 +126,7 @@ jvm_library(
|
||||
"//python/services/shared",
|
||||
"//python/services/shared:shared_test_lib",
|
||||
"//python/poetry",
|
||||
"//python/pipenv",
|
||||
"//python/python-venv:community-impl-venv",
|
||||
"//python/python-venv:community-impl-venv_test_lib",
|
||||
"@lib//:jetbrains-annotations",
|
||||
@@ -140,6 +143,7 @@ jvm_library(
|
||||
"//python/python-exec-service:community-execService",
|
||||
"//python/python-exec-service:community-execService_test_lib",
|
||||
"//python/python-sdk-configurator/common",
|
||||
"//python/python-sdk-ui:sdk-ui",
|
||||
],
|
||||
plugins = ["@lib//:compose-plugin"]
|
||||
)
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.eel.provider" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.services.shared" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.impl.poetry" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.impl.pipenv" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.impl.venv" />
|
||||
<orderEntry type="library" name="jetbrains-annotations" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.python.pyproject" />
|
||||
@@ -81,5 +82,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.nonModalWelcomeScreen.backend" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.execService" />
|
||||
<orderEntry type="module" module-name="intellij.python.sdkConfigurator.common" />
|
||||
<orderEntry type="module" module-name="intellij.python.sdk.ui" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -7,6 +7,7 @@
|
||||
<module name="intellij.platform.ide.nonModalWelcomeScreen"/>
|
||||
<module name="intellij.platform.ide.nonModalWelcomeScreen.backend"/>
|
||||
<module name="intellij.python.sdkConfigurator.common"/>
|
||||
<module name="intellij.python.sdk.ui"/>
|
||||
</dependencies>
|
||||
|
||||
<projectListeners>
|
||||
@@ -143,7 +144,7 @@
|
||||
<extensions defaultExtensionNs="Pythonid">
|
||||
<projectSdkConfigurationExtension
|
||||
implementation="com.intellij.pycharm.community.ide.impl.configuration.PyRequirementsTxtOrSetupPySdkConfiguration"
|
||||
id="requirementsTxtOrSetupPy" order="last"/>
|
||||
id="requirementsTxtOrSetupPy" order="before uv"/>
|
||||
<projectSdkConfigurationExtension
|
||||
implementation="com.intellij.pycharm.community.ide.impl.conda.PyEnvironmentYmlSdkConfiguration"
|
||||
id="environmentYml"/>
|
||||
@@ -154,7 +155,7 @@
|
||||
<projectSdkConfigurationExtension implementation="com.intellij.pycharm.community.ide.impl.configuration.PyHatchSdkConfiguration"
|
||||
id="hatch" order="after poetry"/>
|
||||
<projectSdkConfigurationExtension implementation="com.intellij.pycharm.community.ide.impl.configuration.PyUvSdkConfiguration"
|
||||
id="uv" order="after hatch"/>
|
||||
id="uv" order="last"/>
|
||||
<projectSdkConfigurationExtension implementation="com.intellij.pycharm.community.ide.impl.configuration.PyVenvSdkConfiguration"
|
||||
id="venv" order="before requirementsTxtOrSetupPy"/>
|
||||
</extensions>
|
||||
|
||||
@@ -35,32 +35,30 @@ feature.remoteSsh.sync=Synchronize code, data, and other project files, keeping
|
||||
temporarily.ignored.file.provider.description=Temporarily ignored files
|
||||
|
||||
|
||||
sdk.use.existing.venv=Use existing virtual environment {0}
|
||||
sdk.create.venv.suggestion=Create a virtual environment using {0}
|
||||
sdk.create.venv.permission=File {0} contains project dependencies. Would you like to create a virtual environment using it?
|
||||
|
||||
|
||||
sdk.create.condaenv.suggestion=Create a conda environment using environment.yml
|
||||
sdk.create.condaenv.permission=File environment.yml contains project dependencies. Would you like to create a conda environment using it?
|
||||
sdk.create.condaenv.exception.dialog.title=Failed To Create Conda Environment
|
||||
|
||||
|
||||
sdk.detect.condaenv.exception.dialog.title=Failed To Get Conda Environments
|
||||
|
||||
|
||||
sdk.create.pipenv.suggestion=Create a pipenv environment using {0}
|
||||
sdk.create.pipenv.permission=File Pipfile contains project dependencies. Would you like to create a pipenv environment using it?
|
||||
sdk.create.pipenv.exception.dialog.title=Failed To Create Pipenv Environment
|
||||
|
||||
sdk.set.up.poetry.environment=Set up Poetry environment
|
||||
sdk.progress.text.setting.up.poetry.environment=Setting up poetry environment
|
||||
sdk.dialog.title.failed.to.set.up.poetry.environment=Failed To Set Up Poetry Environment
|
||||
sdk.dialog.title.setting.up.poetry.environment=Setting Up Poetry Environment
|
||||
sdk.notification.label.set.up.poetry.environment.from.pyproject.toml.dependencies=File pyproject.toml contains project dependencies. Would you like to set up a poetry environment?
|
||||
sdk.progress.text.setting.up.poetry.environment=Setting up Poetry environment
|
||||
notification.group.pro.advertiser=PyCharm recommended
|
||||
|
||||
sdk.could.not.find.valid.hatch.environment=Could not find a valid Hatch environment
|
||||
|
||||
sdk.set.up.hatch.environment=Set up Hatch 'default' environment
|
||||
sdk.set.up.hatch.project.analysis=Hatch project analysis
|
||||
sdk.set.up.uv.environment=Set up an uv {0} environment
|
||||
sdk.set.up.uv.environment=Set up a uv {0} environment
|
||||
|
||||
sdk.cannot.use.existing.conda.environment=Cannot use existing Conda environment
|
||||
sdk.remote.target.are.not.supported.for.conda.environment=Remote target are not supported for Conda environment
|
||||
|
||||
new.project.python.group.name=Python
|
||||
new.project.other.group.name=Other
|
||||
|
||||
+19
-20
@@ -28,11 +28,13 @@ import com.intellij.python.community.services.systemPython.SystemPython
|
||||
import com.intellij.python.community.services.systemPython.SystemPythonService
|
||||
import com.intellij.python.sdkConfigurator.common.enableSDKAutoConfigurator
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.getOrLogException
|
||||
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||
import com.jetbrains.python.sdk.*
|
||||
import com.jetbrains.python.sdk.conda.PyCondaSdkCustomizer
|
||||
import com.jetbrains.python.sdk.configuration.CreateSdkInfo
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.setReadyToUseSdk
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.setSdkUsingExtension
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.setSdkUsingCreateSdkInfo
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.suppressTipAndInspectionsFor
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.impl.PySdkBundle
|
||||
@@ -65,22 +67,21 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
|
||||
StartupManager.getInstance(project).runWhenProjectIsInitialized {
|
||||
PyPackageCoroutine.launch(project) {
|
||||
if (module.isDisposed) return@launch
|
||||
val extension = findExtension(module)
|
||||
val title = extension?.getIntention(module) ?: PySdkBundle.message("python.configuring.interpreter.progress")
|
||||
withBackgroundProgress(project, title, true) {
|
||||
val lifetime = extension?.let { suppressTipAndInspectionsFor(module, it) }
|
||||
lifetime.use { configureSdk(project, module, extension) }
|
||||
val sdkInfos = findSuitableCreateSdkInfos(module)
|
||||
withBackgroundProgress(project, PySdkBundle.message("python.configuring.interpreter.progress"), true) {
|
||||
val lifetime = suppressTipAndInspectionsFor(module, "all suitable extensions")
|
||||
lifetime.use { configureSdk(project, module, sdkInfos) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun findExtension(module: Module): PyProjectSdkConfigurationExtension? = withContext(Dispatchers.Default) {
|
||||
private suspend fun findSuitableCreateSdkInfos(module: Module): List<CreateSdkInfo> = withContext(Dispatchers.Default) {
|
||||
if (!TrustedProjects.isProjectTrusted(module.project) || ApplicationManager.getApplication().isUnitTestMode) {
|
||||
null
|
||||
emptyList()
|
||||
}
|
||||
else PyProjectSdkConfigurationExtension.EP_NAME.extensionsIfPointIsRegistered.firstOrNull {
|
||||
it.getIntention(module) != null && (!ApplicationManager.getApplication().isHeadlessEnvironment || it.supportsHeadlessModel())
|
||||
else {
|
||||
PyProjectSdkConfigurationExtension.EP_NAME.extensionsIfPointIsRegistered.mapNotNull { it.checkEnvironmentAndPrepareSdkCreator(module) }.sorted()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +90,7 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
|
||||
suspend fun configureSdk(
|
||||
project: Project,
|
||||
module: Module,
|
||||
extension: PyProjectSdkConfigurationExtension?,
|
||||
createSdkInfos: List<CreateSdkInfo>,
|
||||
): Unit = withContext(Dispatchers.Default) {
|
||||
val context = UserDataHolderBase()
|
||||
|
||||
@@ -107,13 +108,8 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
|
||||
if (searchPreviousUsed(module, existingSdks, project))
|
||||
return@withContext
|
||||
|
||||
if (extension != null) {
|
||||
val isExtensionSetup = setSdkUsingExtension(module, extension) {
|
||||
withContext(Dispatchers.Default) {
|
||||
extension.createAndAddSdkForConfigurator(module)
|
||||
}
|
||||
}
|
||||
if (isExtensionSetup) return@withContext
|
||||
for (createSdkInfo in createSdkInfos) {
|
||||
if (setSdkUsingCreateSdkInfo(module, createSdkInfo, true)) return@withContext
|
||||
}
|
||||
|
||||
if (setupSharedCondaEnv(module, existingSdks, project)) {
|
||||
@@ -205,8 +201,11 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
|
||||
if (fallback == null) {
|
||||
return false
|
||||
}
|
||||
fallback.createAndAddSdkForConfigurator(module)
|
||||
return true
|
||||
val sdkCreator = fallback.checkEnvironmentAndPrepareSdkCreator(module)?.sdkCreator
|
||||
if (sdkCreator == null) {
|
||||
return false
|
||||
}
|
||||
return sdkCreator(true).getOrLogException(thisLogger()) != null
|
||||
}
|
||||
|
||||
private suspend fun searchPreviousUsed(
|
||||
|
||||
+76
-27
@@ -1,7 +1,6 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.pycharm.community.ide.impl.conda
|
||||
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.module.Module
|
||||
@@ -10,6 +9,7 @@ import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
import com.intellij.openapi.ui.ValidationInfo
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.CondaEnvResult
|
||||
@@ -17,13 +17,16 @@ import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationC
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.Source
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.ui.PyAddNewCondaEnvFromFilePanel
|
||||
import com.intellij.python.community.execService.BinOnEel
|
||||
import com.intellij.python.sdk.ui.icons.PythonSdkUIIcons
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.getOrNull
|
||||
import com.jetbrains.python.onSuccess
|
||||
import com.jetbrains.python.packaging.conda.environmentYml.CondaEnvironmentYmlSdkUtils
|
||||
import com.jetbrains.python.packaging.conda.environmentYml.format.CondaEnvironmentYmlParser
|
||||
import com.jetbrains.python.pathValidation.PlatformAndRoot
|
||||
import com.jetbrains.python.pathValidation.ValidationRequest
|
||||
import com.jetbrains.python.pathValidation.validateExecutableFile
|
||||
@@ -32,14 +35,15 @@ import com.jetbrains.python.sdk.PythonSdkUpdater
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.conda.PyCondaSdkCustomizer
|
||||
import com.jetbrains.python.sdk.conda.createCondaSdkAlongWithNewEnv
|
||||
import com.jetbrains.python.sdk.conda.createCondaSdkFromExistingEnv
|
||||
import com.jetbrains.python.sdk.conda.suggestCondaPath
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.configuration.*
|
||||
import com.jetbrains.python.sdk.findAmongRoots
|
||||
import com.jetbrains.python.sdk.flavors.conda.NewCondaEnvRequest
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaCommand
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
|
||||
import com.jetbrains.python.sdk.setAssociationToModuleAsync
|
||||
import com.jetbrains.python.util.ShowingMessageErrorSync
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
@@ -53,43 +57,58 @@ import java.nio.file.Path
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): PyResult<Sdk?> = createAndAddSdk(module, Source.CONFIGURATOR)
|
||||
|
||||
override suspend fun getIntention(module: Module): @IntentionName String? {
|
||||
val isReadyToSetup = withContext(Dispatchers.IO) {
|
||||
getEnvironmentYml(module) != null &&
|
||||
suggestCondaPath()?.let { LocalFileSystem.getInstance().findFileByPath(it) } != null
|
||||
}
|
||||
override val toolInfo: PyToolUIInfo = PyToolUIInfo("Conda", PythonSdkUIIcons.Tools.Anaconda)
|
||||
|
||||
return if (isReadyToSetup) PyCharmCommunityCustomizationBundle.message("sdk.create.condaenv.suggestion") else null
|
||||
override suspend fun checkEnvironmentAndPrepareSdkCreator(module: Module): CreateSdkInfo? = prepareSdkCreator(
|
||||
toolInfo, { checkManageableEnv(module, it) }
|
||||
) { envExists ->
|
||||
{ needsConfirmation -> createAndAddSdk(module, if (needsConfirmation) Source.CONFIGURATOR else Source.INSPECTION, envExists) }
|
||||
}
|
||||
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): PyResult<Sdk?> = createAndAddSdk(module, Source.INSPECTION)
|
||||
override fun asPyProjectTomlSdkConfigurationExtension(): PyProjectTomlConfigurationExtension? = null
|
||||
|
||||
private suspend fun checkManageableEnv(module: Module, checkExistence: CheckExistence): EnvCheckerResult = withBackgroundProgress(module.project, PyBundle.message("python.sdk.validating.environment")) {
|
||||
val condaPath = withContext(Dispatchers.IO) {
|
||||
if (getEnvironmentYml(module) != null) {
|
||||
suggestCondaPath()?.let { LocalFileSystem.getInstance().findFileByPath(it) }
|
||||
}
|
||||
else null
|
||||
}
|
||||
val canManage = condaPath != null
|
||||
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.create.condaenv.suggestion")
|
||||
|
||||
when {
|
||||
canManage && checkExistence && getCondaEnvIdentity(module, condaPath.path) != null -> EnvCheckerResult.EnvFound("", intentionName)
|
||||
canManage -> EnvCheckerResult.EnvNotFound(intentionName)
|
||||
else -> EnvCheckerResult.CannotConfigure
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEnvironmentYml(module: Module) = listOf(
|
||||
CondaEnvironmentYmlSdkUtils.ENV_YAML_FILE_NAME,
|
||||
CondaEnvironmentYmlSdkUtils.ENV_YML_FILE_NAME,
|
||||
).firstNotNullOfOrNull { findAmongRoots(module, it) }
|
||||
|
||||
private suspend fun createAndAddSdk(module: Module, source: Source): PyResult<Sdk?> {
|
||||
private suspend fun createAndAddSdk(module: Module, source: Source, envExists: Boolean): PyResult<Sdk?> {
|
||||
val targetConfig = PythonInterpreterTargetEnvironmentFactory.getTargetModuleResidesOn(module)
|
||||
if (targetConfig != null) {
|
||||
// Remote targets aren't supported yet
|
||||
return PyResult.success(null)
|
||||
return PyResult.localizedError(PyCharmCommunityCustomizationBundle.message("sdk.remote.target.are.not.supported.for.conda.environment"))
|
||||
}
|
||||
|
||||
val (condaExecutable, environmentYml) = askForEnvData(module, source) ?: return PyResult.success(null)
|
||||
return createAndAddCondaEnv(module, condaExecutable, environmentYml).onSuccess { sdk ->
|
||||
sdk?.let { PythonSdkUpdater.scheduleUpdate(it, module.project) }
|
||||
val (condaExecutable, environmentYml) = askForEnvData(module, source, envExists) ?: return PyResult.success(null)
|
||||
return createAndAddCondaEnv(module, condaExecutable, environmentYml, envExists).onSuccess { sdk ->
|
||||
sdk.let { PythonSdkUpdater.scheduleUpdate(it, module.project) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun askForEnvData(module: Module, source: Source) = withContext(Dispatchers.Default) {
|
||||
private suspend fun askForEnvData(module: Module, source: Source, envExists: Boolean) = withContext(Dispatchers.Default) {
|
||||
val environmentYml = getEnvironmentYml(module) ?: return@withContext null
|
||||
// Again: only local conda is supported for now
|
||||
val condaExecutable = suggestCondaPath()?.let { LocalFileSystem.getInstance().findFileByPath(it) }
|
||||
|
||||
if (source == Source.INSPECTION && validateCondaPath(condaExecutable?.path, PlatformAndRoot.local) == null) {
|
||||
if ((envExists || source == Source.INSPECTION) && validateCondaPath(condaExecutable?.path, PlatformAndRoot.local) == null) {
|
||||
PySdkConfigurationCollector.logCondaEnvDialogSkipped(module.project, source, executableToEventField(condaExecutable?.path))
|
||||
return@withContext PyAddNewCondaEnvFromFilePanel.Data(condaExecutable!!.path, environmentYml.path)
|
||||
}
|
||||
@@ -110,11 +129,19 @@ class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
if (permitted) envData else null
|
||||
}
|
||||
|
||||
private suspend fun createAndAddCondaEnv(module: Module, condaExecutable: String, environmentYml: String): PyResult<Sdk?> {
|
||||
private suspend fun createAndAddCondaEnv(
|
||||
module: Module, condaExecutable: String, environmentYml: String, envExists: Boolean,
|
||||
): PyResult<Sdk> {
|
||||
thisLogger().debug("Creating conda environment")
|
||||
|
||||
val sdk = createCondaEnv(module.project, condaExecutable, environmentYml) ?: return PyResult.success(null)
|
||||
PySdkConfigurationCollector.logCondaEnv(module.project, CondaEnvResult.CREATED)
|
||||
val sdk = if (envExists) {
|
||||
useExistingCondaEnv(module, condaExecutable)
|
||||
}
|
||||
else {
|
||||
createCondaEnv(module.project, condaExecutable, environmentYml).also {
|
||||
PySdkConfigurationCollector.logCondaEnv(module.project, CondaEnvResult.CREATED)
|
||||
}
|
||||
}.getOr { return it }
|
||||
|
||||
val shared = PyCondaSdkCustomizer.instance.sharedEnvironmentsByDefault
|
||||
val basePath = module.basePath
|
||||
@@ -135,7 +162,31 @@ class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
return if (condaExecutable.isNullOrBlank()) InputData.NOT_FILLED else InputData.SPECIFIED
|
||||
}
|
||||
|
||||
private suspend fun createCondaEnv(project: Project, condaExecutable: String, environmentYml: String): Sdk? {
|
||||
private suspend fun useExistingCondaEnv(module: Module, condaExecutable: String): PyResult<Sdk> {
|
||||
val project = module.project
|
||||
return PyResult.success(PyCondaCommand(condaExecutable, null).createCondaSdkFromExistingEnv(
|
||||
getCondaEnvIdentity(module, condaExecutable)
|
||||
?: return PyResult.localizedError(PyCharmCommunityCustomizationBundle.message("sdk.cannot.use.existing.conda.environment")),
|
||||
PyConfigurableInterpreterList.getInstance(project).model.sdks.toList(),
|
||||
project
|
||||
))
|
||||
}
|
||||
|
||||
private suspend fun getCondaEnvIdentity(module: Module, condaExecutable: String): PyCondaEnvIdentity? {
|
||||
val environmentYml = getEnvironmentYml(module) ?: return null
|
||||
val envName = CondaEnvironmentYmlParser.readNameFromFile(environmentYml)
|
||||
val envPrefix = CondaEnvironmentYmlParser.readPrefixFromFile(environmentYml)
|
||||
val binaryToExec = BinOnEel(Path.of(condaExecutable))
|
||||
return PyCondaEnv.getEnvs(binaryToExec).getOr { return null }.firstOrNull {
|
||||
val envIdentity = it.envIdentity
|
||||
when (envIdentity) {
|
||||
is PyCondaEnvIdentity.NamedEnv -> envIdentity.envName == envName
|
||||
is PyCondaEnvIdentity.UnnamedEnv -> envIdentity.envPath == envPrefix
|
||||
}
|
||||
}?.envIdentity
|
||||
}
|
||||
|
||||
private suspend fun createCondaEnv(project: Project, condaExecutable: String, environmentYml: String): PyResult<Sdk> {
|
||||
val binaryToExec = BinOnEel(Path.of(condaExecutable))
|
||||
val existingEnvs = PyCondaEnv.getEnvs(binaryToExec).getOrNull() ?: emptyList()
|
||||
|
||||
@@ -146,13 +197,11 @@ class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
.createCondaSdkAlongWithNewEnv(newCondaEnvInfo, Dispatchers.EDT, existingSdks.toList(), project).getOr {
|
||||
PySdkConfigurationCollector.logCondaEnv(project, CondaEnvResult.CREATION_FAILURE)
|
||||
thisLogger().warn("Exception during creating conda environment $it")
|
||||
|
||||
ShowingMessageErrorSync.emit(it.error)
|
||||
return null
|
||||
return it
|
||||
}
|
||||
|
||||
PySdkConfigurationCollector.logCondaEnv(project, CondaEnvResult.CREATED)
|
||||
return sdk
|
||||
return PyResult.success(sdk)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,4 +218,4 @@ fun validateCondaPath(
|
||||
platformAndRoot,
|
||||
null
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
+61
-20
@@ -1,56 +1,97 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.pycharm.community.ide.impl.configuration
|
||||
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.platform.util.progress.reportRawProgress
|
||||
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
||||
import com.intellij.python.hatch.HatchVirtualEnvironment
|
||||
import com.intellij.python.hatch.PythonVirtualEnvironment
|
||||
import com.intellij.python.hatch.cli.HatchEnvironment
|
||||
import com.intellij.python.hatch.getHatchService
|
||||
import com.intellij.python.sdk.ui.icons.PythonSdkUIIcons
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.ToolId
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.getOrLogException
|
||||
import com.jetbrains.python.hatch.sdk.createSdk
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.projectModel.hatch.HATCH_TOOL_ID
|
||||
import com.jetbrains.python.sdk.configuration.*
|
||||
import com.jetbrains.python.util.runWithModalBlockingOrInBackground
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
class PyHatchSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
class PyHatchSdkConfiguration : PyProjectTomlConfigurationExtension {
|
||||
companion object {
|
||||
private val LOGGER = Logger.getInstance(PyHatchSdkConfiguration::class.java)
|
||||
}
|
||||
|
||||
override suspend fun getIntention(module: Module): @IntentionName String? {
|
||||
val isReadyAndHaveOwnership = reportRawProgress {
|
||||
it.text(PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.project.analysis"))
|
||||
val hatchService = module.getHatchService().getOr { return@reportRawProgress false }
|
||||
hatchService.isHatchManagedProject().getOrLogException(LOGGER) == true
|
||||
}
|
||||
override val toolInfo: PyToolUIInfo = PyToolUIInfo("Hatch", PythonSdkUIIcons.Tools.Hatch)
|
||||
override val toolId: ToolId = HATCH_TOOL_ID
|
||||
|
||||
val intention = when {
|
||||
isReadyAndHaveOwnership -> PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.environment")
|
||||
else -> null
|
||||
override suspend fun checkEnvironmentAndPrepareSdkCreator(module: Module): CreateSdkInfo? = prepareSdkCreator(
|
||||
toolInfo,
|
||||
{ checkExistence -> checkManageableEnv(module, checkExistence, true) },
|
||||
) { envExists -> { createSdk(module, envExists) } }
|
||||
|
||||
override suspend fun createSdkWithoutPyProjectTomlChecks(module: Module): CreateSdkInfo? = prepareSdkCreator(
|
||||
toolInfo,
|
||||
{ checkExistence -> checkManageableEnv(module, checkExistence, false) },
|
||||
) { envExists -> { createSdk(module, envExists) } }
|
||||
|
||||
override fun asPyProjectTomlSdkConfigurationExtension(): PyProjectTomlConfigurationExtension = this
|
||||
|
||||
private suspend fun checkManageableEnv(
|
||||
module: Module, checkExistence: CheckExistence, checkToml: CheckToml,
|
||||
): EnvCheckerResult = reportRawProgress {
|
||||
it.text(PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.project.analysis"))
|
||||
val hatchService = module.getHatchService().getOr { return EnvCheckerResult.CannotConfigure }
|
||||
val canManage = if (checkToml) hatchService.isHatchManagedProject().getOrLogException(LOGGER) == true else true
|
||||
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.environment")
|
||||
val envNotFound = EnvCheckerResult.EnvNotFound(intentionName)
|
||||
|
||||
when {
|
||||
canManage && checkExistence -> {
|
||||
val defaultEnv = hatchService.findDefaultVirtualEnvironmentOrNull().getOrLogException(LOGGER)?.pythonVirtualEnvironment
|
||||
when (defaultEnv) {
|
||||
is PythonVirtualEnvironment.Existing -> EnvCheckerResult.EnvFound("", intentionName)
|
||||
is PythonVirtualEnvironment.NotExisting, null -> envNotFound
|
||||
}
|
||||
}
|
||||
canManage -> envNotFound
|
||||
else -> EnvCheckerResult.CannotConfigure
|
||||
}
|
||||
return intention
|
||||
}
|
||||
|
||||
private fun createSdk(module: Module): PyResult<Sdk> = runWithModalBlockingOrInBackground(
|
||||
/**
|
||||
* Creates SDK for Hatch, it will also create a new Hatch environment and use an existing one.
|
||||
*
|
||||
* @param module module used to create SDK
|
||||
* @param envExists shows whether the environment already exists or a new one should be created
|
||||
*/
|
||||
private fun createSdk(module: Module, envExists: EnvExists): PyResult<Sdk> = runWithModalBlockingOrInBackground(
|
||||
project = module.project,
|
||||
msg = PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.environment")
|
||||
) {
|
||||
val hatchService = module.getHatchService().getOr { return@runWithModalBlockingOrInBackground it }
|
||||
val createdEnvironment = hatchService.createVirtualEnvironment().getOr { return@runWithModalBlockingOrInBackground it }
|
||||
|
||||
val hatchVenv = HatchVirtualEnvironment(HatchEnvironment.DEFAULT, createdEnvironment)
|
||||
val environment = if (envExists) {
|
||||
val defaultEnv = hatchService.findDefaultVirtualEnvironmentOrNull()
|
||||
.mapSuccess { it?.pythonVirtualEnvironment }
|
||||
.getOr { return@runWithModalBlockingOrInBackground it }
|
||||
when (defaultEnv) {
|
||||
is PythonVirtualEnvironment.Existing -> defaultEnv
|
||||
is PythonVirtualEnvironment.NotExisting, null -> return@runWithModalBlockingOrInBackground PyResult.localizedError(PyCharmCommunityCustomizationBundle.message("sdk.could.not.find.valid.hatch.environment"))
|
||||
}
|
||||
}
|
||||
else {
|
||||
hatchService.createVirtualEnvironment().getOr { return@runWithModalBlockingOrInBackground it }
|
||||
}
|
||||
|
||||
val hatchVenv = HatchVirtualEnvironment(HatchEnvironment.DEFAULT, environment)
|
||||
val sdk = hatchVenv.createSdk(hatchService.getWorkingDirectoryPath())
|
||||
sdk
|
||||
}
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): PyResult<Sdk> = createSdk(module)
|
||||
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): PyResult<Sdk> = createSdk(module)
|
||||
|
||||
override fun supportsHeadlessModel(): Boolean = true
|
||||
}
|
||||
+52
-19
@@ -1,7 +1,6 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.pycharm.community.ide.impl.configuration
|
||||
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
@@ -10,23 +9,31 @@ import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.openapi.ui.ValidationInfo
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.InputData
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.PipEnvResult
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.Source
|
||||
import com.intellij.python.community.impl.pipenv.pipenvPath
|
||||
import com.intellij.python.sdk.ui.icons.PythonSdkUIIcons
|
||||
import com.intellij.ui.IdeBorderFactory
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.getOrLogException
|
||||
import com.jetbrains.python.sdk.*
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.PythonSdkType
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.configuration.*
|
||||
import com.jetbrains.python.sdk.findAmongRoots
|
||||
import com.jetbrains.python.sdk.impl.resolvePythonBinary
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.pipenv.*
|
||||
import com.jetbrains.python.sdk.pipenv.ui.PyAddNewPipEnvFromFilePanel
|
||||
import com.jetbrains.python.sdk.setAssociationToModule
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -43,22 +50,48 @@ private val LOGGER = Logger.getInstance(PyPipfileSdkConfiguration::class.java)
|
||||
@ApiStatus.Internal
|
||||
class PyPipfileSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): PyResult<Sdk?> = createAndAddSDk(module, Source.CONFIGURATOR)
|
||||
override val toolInfo: PyToolUIInfo = PyToolUIInfo("Pipenv", PythonSdkUIIcons.Tools.Pip)
|
||||
|
||||
override suspend fun getIntention(module: Module): @IntentionName String? = findAmongRoots(module, PipEnvFileHelper.PIP_FILE)?.let { PyCharmCommunityCustomizationBundle.message("sdk.create.pipenv.suggestion", it.name) }
|
||||
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): PyResult<Sdk?> = createAndAddSDk(module, Source.INSPECTION)
|
||||
|
||||
private suspend fun createAndAddSDk(module: Module, source: Source): PyResult<Sdk?> {
|
||||
val pipEnvExecutable = askForEnvData(module, source) ?: return PyResult.success(null)
|
||||
PropertiesComponent.getInstance().pipEnvPath = pipEnvExecutable.pipEnvPath.pathString
|
||||
return createPipEnv(module)
|
||||
override suspend fun checkEnvironmentAndPrepareSdkCreator(module: Module): CreateSdkInfo? = prepareSdkCreator(
|
||||
toolInfo, { checkManageableEnv(module, it) }
|
||||
) { envExists ->
|
||||
{ needsConfirmation -> createAndAddSdk(module, if (needsConfirmation) Source.CONFIGURATOR else Source.INSPECTION, envExists) }
|
||||
}
|
||||
|
||||
private suspend fun askForEnvData(module: Module, source: Source): PyAddNewPipEnvFromFilePanel.Data? {
|
||||
override fun asPyProjectTomlSdkConfigurationExtension(): PyProjectTomlConfigurationExtension? = null
|
||||
|
||||
private suspend fun checkManageableEnv(
|
||||
module: Module, checkExistence: CheckExistence,
|
||||
): EnvCheckerResult = withBackgroundProgress(module.project, PyBundle.message("python.sdk.validating.environment")) {
|
||||
val pipfile = findAmongRoots(module, PipEnvFileHelper.PIP_FILE)?.name ?: return@withBackgroundProgress EnvCheckerResult.CannotConfigure
|
||||
val pipEnvExecutable = getPipEnvExecutable().getOrLogException(LOGGER) ?: return@withBackgroundProgress EnvCheckerResult.CannotConfigure
|
||||
val canManage = pipEnvExecutable.isExecutable()
|
||||
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.create.pipenv.suggestion", pipfile)
|
||||
val envNotFound = EnvCheckerResult.EnvNotFound(intentionName)
|
||||
|
||||
when {
|
||||
canManage && checkExistence -> {
|
||||
PropertiesComponent.getInstance().pipenvPath = pipEnvExecutable.pathString
|
||||
val envPath = runPipEnv(module.basePath?.toNioPathOrNull(), "--venv").mapSuccess { Path.of(it) }.successOrNull
|
||||
val path = envPath?.resolvePythonBinary()
|
||||
val envExists = path?.let { LocalFileSystem.getInstance().refreshAndFindFileByPath(it.pathString) != null } ?: false
|
||||
if (envExists) EnvCheckerResult.EnvFound("", intentionName) else envNotFound
|
||||
}
|
||||
canManage -> envNotFound
|
||||
else -> EnvCheckerResult.CannotConfigure
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createAndAddSdk(module: Module, source: Source, envExists: Boolean): PyResult<Sdk?> {
|
||||
val pipEnvExecutable = askForEnvData(module, source, envExists) ?: return PyResult.success(null)
|
||||
PropertiesComponent.getInstance().pipenvPath = pipEnvExecutable.pipEnvPath.pathString
|
||||
return createOrUsePipEnv(module)
|
||||
}
|
||||
|
||||
private suspend fun askForEnvData(module: Module, source: Source, envExists: Boolean): PyAddNewPipEnvFromFilePanel.Data? {
|
||||
val pipEnvExecutable = getPipEnvExecutable().getOrLogException(LOGGER)
|
||||
|
||||
if (source == Source.INSPECTION && pipEnvExecutable?.isExecutable() == true) {
|
||||
if ((envExists || source == Source.INSPECTION) && pipEnvExecutable?.isExecutable() == true) {
|
||||
return PyAddNewPipEnvFromFilePanel.Data(pipEnvExecutable)
|
||||
}
|
||||
|
||||
@@ -83,11 +116,11 @@ class PyPipfileSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
return if (permitted) envData else null
|
||||
}
|
||||
|
||||
private suspend fun createPipEnv(module: Module): PyResult<Sdk> {
|
||||
private suspend fun createOrUsePipEnv(module: Module): PyResult<Sdk> {
|
||||
LOGGER.debug("Creating pipenv environment")
|
||||
return withBackgroundProgress(module.project, PyBundle.message("python.sdk.setting.up.pipenv.sentence")) {
|
||||
return withBackgroundProgress(module.project, PyBundle.message("python.sdk.using.pipenv.sentence")) {
|
||||
val basePath = module.basePath
|
||||
?: return@withBackgroundProgress PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid",module.basePath))
|
||||
?: return@withBackgroundProgress PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid", module.basePath))
|
||||
val pipEnv = setupPipEnv(Path.of(basePath), null, true).getOr {
|
||||
PySdkConfigurationCollector.logPipEnv(module.project, PipEnvResult.CREATION_FAILURE)
|
||||
return@withBackgroundProgress it
|
||||
@@ -95,12 +128,12 @@ class PyPipfileSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
|
||||
val path = withContext(Dispatchers.IO) { VirtualEnvReader.Instance.findPythonInPythonRoot(Path.of(pipEnv)) }
|
||||
if (path == null) {
|
||||
return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable","python", pipEnv))
|
||||
return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable", "python", pipEnv))
|
||||
}
|
||||
|
||||
val file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path.toString())
|
||||
if (file == null) {
|
||||
return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable","python", path))
|
||||
return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable", "python", path))
|
||||
}
|
||||
|
||||
PySdkConfigurationCollector.logPipEnv(module.project, PipEnvResult.CREATED)
|
||||
|
||||
+48
-27
@@ -6,62 +6,85 @@ import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.platform.util.progress.reportRawProgress
|
||||
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
||||
import com.intellij.python.pyproject.PyProjectToml
|
||||
import com.intellij.python.sdk.ui.icons.PythonSdkUIIcons
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.ToolId
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.poetry.findPoetryLock
|
||||
import com.jetbrains.python.poetry.getPyProjectTomlForPoetry
|
||||
import com.jetbrains.python.projectModel.poetry.POETRY_TOOL_ID
|
||||
import com.jetbrains.python.sdk.PythonSdkType
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.configuration.*
|
||||
import com.jetbrains.python.sdk.impl.resolvePythonBinary
|
||||
import com.jetbrains.python.sdk.poetry.PyPoetrySdkAdditionalData
|
||||
import com.jetbrains.python.sdk.poetry.getPoetryExecutable
|
||||
import com.jetbrains.python.sdk.poetry.setupPoetry
|
||||
import com.jetbrains.python.sdk.poetry.suggestedSdkName
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.poetry.*
|
||||
import com.jetbrains.python.sdk.setAssociationToModule
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
@ApiStatus.Internal
|
||||
class PyPoetrySdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
override val toolId: ToolId = POETRY_TOOL_ID
|
||||
class PyPoetrySdkConfiguration : PyProjectTomlConfigurationExtension {
|
||||
|
||||
companion object {
|
||||
private val LOGGER = Logger.getInstance(PyPoetrySdkConfiguration::class.java)
|
||||
}
|
||||
|
||||
@NlsSafe
|
||||
override suspend fun getIntention(module: Module): String? = reportRawProgress {
|
||||
override val toolInfo: PyToolUIInfo = PyToolUIInfo("Poetry", PythonSdkUIIcons.Tools.Poetry)
|
||||
override val toolId: ToolId = POETRY_TOOL_ID
|
||||
|
||||
override suspend fun checkEnvironmentAndPrepareSdkCreator(module: Module): CreateSdkInfo? = prepareSdkCreator(
|
||||
toolInfo,
|
||||
{ checkExistence -> checkManageableEnv(module, checkExistence, true) },
|
||||
) { { createPoetry(module) } }
|
||||
|
||||
override suspend fun createSdkWithoutPyProjectTomlChecks(module: Module): CreateSdkInfo? = prepareSdkCreator(
|
||||
toolInfo,
|
||||
{ checkExistence -> checkManageableEnv(module, checkExistence, false) },
|
||||
) { { createPoetry(module) } }
|
||||
|
||||
override fun asPyProjectTomlSdkConfigurationExtension(): PyProjectTomlConfigurationExtension = this
|
||||
|
||||
private suspend fun checkManageableEnv(
|
||||
module: Module, checkExistence: CheckExistence, checkToml: CheckToml,
|
||||
): EnvCheckerResult = reportRawProgress {
|
||||
it.text(PyBundle.message("python.sdk.validating.environment"))
|
||||
|
||||
val isPoetryProject = withContext(Dispatchers.IO) {
|
||||
PyProjectToml.findFile(module)?.let { toml -> getPyProjectTomlForPoetry(toml) } != null ||
|
||||
findPoetryLock(module) != null
|
||||
val isPoetryProject = if (checkToml) {
|
||||
withContext(Dispatchers.IO) {
|
||||
PyProjectToml.findFile(module)?.let { toml -> getPyProjectTomlForPoetry(toml) } != null || findPoetryLock(module) != null
|
||||
}
|
||||
}
|
||||
else true
|
||||
|
||||
val isReadyToSetup = isPoetryProject && getPoetryExecutable().successOrNull != null
|
||||
val canManage = isPoetryProject && getPoetryExecutable().successOrNull != null
|
||||
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.set.up.poetry.environment")
|
||||
val envNotFound = EnvCheckerResult.EnvNotFound(intentionName)
|
||||
|
||||
return if (isReadyToSetup) PyCharmCommunityCustomizationBundle.message("sdk.set.up.poetry.environment") else null
|
||||
when {
|
||||
canManage && checkExistence -> {
|
||||
val basePath = module.basePath?.toNioPathOrNull()
|
||||
runPoetry(basePath, "check", "--lock").getOr { return@reportRawProgress envNotFound }
|
||||
val envPath = runPoetry(basePath, "env", "info", "-p")
|
||||
.mapSuccess { it.toNioPathOrNull() }
|
||||
.getOr { return@reportRawProgress envNotFound }
|
||||
envPath?.resolvePythonBinary()?.let { EnvCheckerResult.EnvFound("", intentionName) } ?: return@reportRawProgress envNotFound
|
||||
}
|
||||
canManage -> envNotFound
|
||||
else -> EnvCheckerResult.CannotConfigure
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): PyResult<Sdk> = createPoetry(module)
|
||||
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): PyResult<Sdk> = createPoetry(module)
|
||||
|
||||
override fun supportsHeadlessModel(): Boolean = true
|
||||
|
||||
private suspend fun createPoetry(module: Module): PyResult<Sdk> =
|
||||
withBackgroundProgress(module.project, PyCharmCommunityCustomizationBundle.message("sdk.progress.text.setting.up.poetry.environment")) {
|
||||
LOGGER.debug("Creating poetry environment")
|
||||
@@ -76,9 +99,7 @@ class PyPoetrySdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
?: return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable", "python", poetry))
|
||||
|
||||
val file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path.pathString)
|
||||
if (file == null) {
|
||||
return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable", "python", path))
|
||||
}
|
||||
?: return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable", "python", path))
|
||||
|
||||
LOGGER.debug("Setting up associated poetry environment: $path, $basePath")
|
||||
val sdk = SdkConfigurationUtil.setupSdk(
|
||||
@@ -90,7 +111,7 @@ class PyPoetrySdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
)
|
||||
|
||||
withContext(Dispatchers.EDT) {
|
||||
LOGGER.debug("Adding associated poetry environment: ${path}, $basePath")
|
||||
LOGGER.debug("Adding associated poetry environment: $path, $basePath")
|
||||
sdk.setAssociationToModule(module)
|
||||
SdkConfigurationUtil.addSdk(sdk)
|
||||
}
|
||||
|
||||
+17
-9
@@ -2,7 +2,6 @@
|
||||
package com.intellij.pycharm.community.ide.impl.configuration
|
||||
|
||||
import com.intellij.CommonBundle
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx
|
||||
@@ -22,19 +21,20 @@ import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationC
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.Source
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.VirtualEnvResult
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.ui.PyAddNewVirtualEnvFromFilePanel
|
||||
import com.intellij.python.sdk.ui.icons.PythonSdkUIIcons
|
||||
import com.intellij.ui.IdeBorderFactory
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.jetbrains.python.sdk.impl.PySdkBundle
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.packaging.PyPackageUtil
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import com.jetbrains.python.packaging.requirementsTxt.PythonRequirementTxtSdkUtils
|
||||
import com.jetbrains.python.packaging.setupPy.SetupPyManager
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.configuration.createVirtualEnvAndSdkSynchronously
|
||||
import com.jetbrains.python.sdk.configuration.*
|
||||
import com.jetbrains.python.sdk.impl.PySdkBundle
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
@@ -48,12 +48,20 @@ private val LOGGER = fileLogger()
|
||||
|
||||
@ApiStatus.Internal
|
||||
class PyRequirementsTxtOrSetupPySdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): PyResult<Sdk?> = createAndAddSdk(module, Source.CONFIGURATOR)
|
||||
|
||||
override suspend fun getIntention(module: Module): @IntentionName String? =
|
||||
getRequirementsTxtOrSetupPy(module)?.let { PyCharmCommunityCustomizationBundle.message("sdk.create.venv.suggestion", it.name) }
|
||||
override val toolInfo: PyToolUIInfo = PyToolUIInfo("venv", PythonSdkUIIcons.Tools.Pip)
|
||||
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): PyResult<Sdk?> = createAndAddSdk(module, Source.INSPECTION)
|
||||
override suspend fun checkEnvironmentAndPrepareSdkCreator(module: Module): CreateSdkInfo? = prepareSdkCreator(
|
||||
toolInfo,
|
||||
{ checkManageableEnv(module) },
|
||||
) { { needsConfirmation -> createAndAddSdk(module, if (needsConfirmation) Source.CONFIGURATOR else Source.INSPECTION) } }
|
||||
|
||||
override fun asPyProjectTomlSdkConfigurationExtension(): PyProjectTomlConfigurationExtension? = null
|
||||
|
||||
private fun checkManageableEnv(module: Module): EnvCheckerResult {
|
||||
val configFile = getRequirementsTxtOrSetupPy(module) ?: return EnvCheckerResult.CannotConfigure
|
||||
return EnvCheckerResult.EnvNotFound(PyCharmCommunityCustomizationBundle.message("sdk.create.venv.suggestion", configFile.name))
|
||||
}
|
||||
|
||||
private suspend fun createAndAddSdk(module: Module, source: Source): PyResult<Sdk?> {
|
||||
val existingSdks = PythonSdkUtil.getAllSdks()
|
||||
|
||||
+84
-38
@@ -1,28 +1,29 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.pycharm.community.ide.impl.configuration
|
||||
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.UserDataHolderBase
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.openapi.vfs.readText
|
||||
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
||||
import com.intellij.python.pyproject.PyProjectToml
|
||||
import com.intellij.python.pyproject.model.api.SuggestedSdk
|
||||
import com.intellij.python.pyproject.model.api.suggestSdk
|
||||
import com.intellij.python.sdk.ui.icons.PythonSdkUIIcons
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.ToolId
|
||||
import com.jetbrains.python.errorProcessing.MessageError
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.getOrLogException
|
||||
import com.jetbrains.python.onSuccess
|
||||
import com.jetbrains.python.projectModel.uv.UV_TOOL_ID
|
||||
import com.jetbrains.python.sdk.*
|
||||
import com.jetbrains.python.sdk.configuration.*
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.persist
|
||||
import com.jetbrains.python.sdk.setAssociationToModule
|
||||
import com.jetbrains.python.sdk.uv.impl.getUvExecutable
|
||||
import com.jetbrains.python.sdk.uv.setupExistingEnvAndSdk
|
||||
import com.jetbrains.python.sdk.uv.setupNewUvSdkAndEnv
|
||||
import com.jetbrains.python.venvReader.tryResolvePath
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -34,51 +35,96 @@ import java.nio.file.Path
|
||||
private val logger = fileLogger()
|
||||
|
||||
@ApiStatus.Internal
|
||||
class PyUvSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
class PyUvSdkConfiguration : PyProjectTomlConfigurationExtension {
|
||||
private val existingSdks by lazy { PythonSdkUtil.getAllSdks() }
|
||||
private val context = UserDataHolderBase()
|
||||
|
||||
override val toolInfo: PyToolUIInfo = PyToolUIInfo("uv", PythonSdkUIIcons.Tools.UV)
|
||||
override val toolId: ToolId = UV_TOOL_ID
|
||||
|
||||
override suspend fun getIntention(module: Module): @IntentionName String? {
|
||||
val tomlFile = PyProjectToml.findFile(module) ?: return null
|
||||
getUvExecutable() ?: return null
|
||||
override suspend fun checkEnvironmentAndPrepareSdkCreator(module: Module): CreateSdkInfo? = prepareSdkCreator(
|
||||
toolInfo, { checkExistence -> checkManageableEnv(module, checkExistence, true) }
|
||||
) { envExists -> { createUv(module, envExists) } }
|
||||
|
||||
val tomlFileContent = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
tomlFile.readText()
|
||||
override suspend fun createSdkWithoutPyProjectTomlChecks(module: Module): CreateSdkInfo? = prepareSdkCreator(
|
||||
toolInfo, { checkExistence -> checkManageableEnv(module, checkExistence, false) }
|
||||
) { envExists -> { createUv(module, envExists) } }
|
||||
|
||||
override fun asPyProjectTomlSdkConfigurationExtension(): PyProjectTomlConfigurationExtension = this
|
||||
|
||||
/**
|
||||
* This method checks whether uv environment exists and whether uv can manage the environment using the following logic:
|
||||
* - If uv is not found on the system, the sdk cannot be configured with uv
|
||||
* - If pyproject.toml check is required
|
||||
* - If pyproject.toml file is found, we check whether we can manage this project
|
||||
* - If there's no pyproject.toml, we assume that we cannot configure the project however,
|
||||
* if we found existing uv environment, we will use it
|
||||
* - If pyproject.toml check shouldn't be performed, then we just check whether the environment exists
|
||||
*/
|
||||
private suspend fun checkManageableEnv(module: Module, checkExistence: CheckExistence, checkToml: CheckToml): EnvCheckerResult {
|
||||
getUvExecutable() ?: return EnvCheckerResult.CannotConfigure
|
||||
|
||||
val (canManage, projectName) = if (checkToml) {
|
||||
val tomlFile = PyProjectToml.findFile(module)
|
||||
|
||||
val projectName = tomlFile?.let {
|
||||
val tomlFileContent = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
tomlFile.readText()
|
||||
}
|
||||
catch (e: IOException) {
|
||||
logger.debug("Can't read ${tomlFile}", e)
|
||||
null
|
||||
}
|
||||
} ?: return EnvCheckerResult.CannotConfigure
|
||||
val tomlContentResult = withContext(Dispatchers.Default) { PyProjectToml.parse(tomlFileContent) }
|
||||
val tomlContent = tomlContentResult.getOrLogException(logger) ?: return EnvCheckerResult.CannotConfigure
|
||||
val project = tomlContent.project ?: return EnvCheckerResult.CannotConfigure
|
||||
project.name ?: module.name
|
||||
}
|
||||
catch (e: IOException) {
|
||||
logger.debug("Can't read ${tomlFile}", e)
|
||||
null
|
||||
}
|
||||
} ?: return null
|
||||
val tomlContentResult = withContext(Dispatchers.Default) { PyProjectToml.parse(tomlFileContent) }
|
||||
val tomlContent = tomlContentResult.getOrLogException(logger) ?: return null
|
||||
val project = tomlContent.project ?: return null
|
||||
|
||||
projectName?.let { true to it } ?: (false to module.name)
|
||||
}
|
||||
else true to module.name
|
||||
|
||||
return PyCharmCommunityCustomizationBundle.message("sdk.set.up.uv.environment", project.name ?: tomlFile.inputStream)
|
||||
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.set.up.uv.environment", projectName)
|
||||
|
||||
return when {
|
||||
checkExistence && getUvEnv(if (checkToml) module else module.getSdkAssociatedModule()) != null -> EnvCheckerResult.EnvFound("", intentionName)
|
||||
canManage -> EnvCheckerResult.EnvNotFound(intentionName)
|
||||
else -> EnvCheckerResult.CannotConfigure
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): PyResult<Sdk> = createUv(module)
|
||||
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): PyResult<Sdk> = createUv(module)
|
||||
|
||||
override fun supportsHeadlessModel(): Boolean = true
|
||||
|
||||
private suspend fun createUv(module: Module): PyResult<Sdk> {
|
||||
val sdkAssociatedModule =
|
||||
when (val r = module.suggestSdk()) {
|
||||
// Workspace suggested by uv
|
||||
is SuggestedSdk.SameAs -> if (r.accordingTo == toolId) r.parentModule else null
|
||||
null, is SuggestedSdk.PyProjectIndependent -> null
|
||||
} ?: module
|
||||
private fun getUvEnv(module: Module): PyDetectedSdk? = detectAssociatedEnvironments(module, existingSdks, context).firstOrNull {
|
||||
it.pyvenvContains("uv = ")
|
||||
}
|
||||
|
||||
private suspend fun Module.getSdkAssociatedModule() =
|
||||
when (val r = suggestSdk()) {
|
||||
// Workspace suggested by uv
|
||||
is SuggestedSdk.SameAs -> if (r.accordingTo == toolId) r.parentModule else null
|
||||
null, is SuggestedSdk.PyProjectIndependent -> null
|
||||
} ?: this
|
||||
|
||||
private suspend fun createUv(module: Module, envExists: Boolean): PyResult<Sdk> {
|
||||
val sdkAssociatedModule = module.getSdkAssociatedModule()
|
||||
val workingDir: Path? = tryResolvePath(sdkAssociatedModule.basePath)
|
||||
if (workingDir == null) {
|
||||
return PyResult.failure(MessageError("Can't determine working dir for the module"))
|
||||
throw IllegalStateException("Can't determine working dir for the module")
|
||||
}
|
||||
|
||||
val sdkSetupResult = setupNewUvSdkAndEnv(workingDir, PythonSdkUtil.getAllSdks(), null)
|
||||
val sdkSetupResult = if (envExists) {
|
||||
getUvEnv(sdkAssociatedModule)?.homePath?.toNioPathOrNull()?.let {
|
||||
setupExistingEnvAndSdk(it, workingDir, false, workingDir, existingSdks)
|
||||
} ?: run {
|
||||
logger.error("Can't find existing uv environment in project, but it was expected. " +
|
||||
"Probably it was deleted. New environment will be created")
|
||||
setupNewUvSdkAndEnv(workingDir, existingSdks, null)
|
||||
}
|
||||
}
|
||||
else setupNewUvSdkAndEnv(workingDir, existingSdks, null)
|
||||
|
||||
sdkSetupResult.onSuccess {
|
||||
withContext(Dispatchers.EDT) {
|
||||
it.persist()
|
||||
@@ -87,4 +133,4 @@ class PyUvSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
}
|
||||
return sdkSetupResult
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
-13
@@ -1,15 +1,20 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.pycharm.community.ide.impl.configuration
|
||||
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.UserDataHolderBase
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.errorProcessing.MessageError
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.sdk.*
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.configuration.*
|
||||
import com.jetbrains.python.sdk.flavors.PyFlavorAndData
|
||||
import com.jetbrains.python.sdk.flavors.PyFlavorData
|
||||
import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -17,28 +22,43 @@ import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
class PyVenvSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
private val existingSdks = PythonSdkUtil.getAllSdks()
|
||||
private val existingSdks by lazy { PythonSdkUtil.getAllSdks() }
|
||||
private val context = UserDataHolderBase()
|
||||
|
||||
override suspend fun getIntention(module: Module): @IntentionName String? =
|
||||
override val toolInfo: PyToolUIInfo = PyToolUIInfo("Virtualenv", null)
|
||||
|
||||
override suspend fun checkEnvironmentAndPrepareSdkCreator(module: Module): CreateSdkInfo? = prepareSdkCreator(
|
||||
toolInfo, { checkManageableEnv(module) }
|
||||
) { { setupVenv(module) } }
|
||||
|
||||
override fun asPyProjectTomlSdkConfigurationExtension(): PyProjectTomlConfigurationExtension? = null
|
||||
|
||||
private suspend fun checkManageableEnv(
|
||||
module: Module,
|
||||
): EnvCheckerResult = withBackgroundProgress(module.project, PyBundle.message("python.sdk.validating.environment")) {
|
||||
withContext(Dispatchers.IO) {
|
||||
detectAssociatedEnvironments(module, existingSdks, context).firstOrNull()
|
||||
}?.let {
|
||||
PyCharmCommunityCustomizationBundle.message("sdk.create.venv.suggestion", it.name)
|
||||
getVirtualEnv(module)?.let {
|
||||
EnvCheckerResult.EnvFound("", PyCharmCommunityCustomizationBundle.message("sdk.use.existing.venv", it.name))
|
||||
} ?: EnvCheckerResult.CannotConfigure
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): PyResult<Sdk> = setupVenv(module)
|
||||
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): PyResult<Sdk> = setupVenv(module)
|
||||
private fun getVirtualEnv(module: Module): PyDetectedSdk? = detectAssociatedEnvironments(module, existingSdks, context)
|
||||
.firstOrNull { it.pyvenvContains("virtualenv = ") }
|
||||
|
||||
private suspend fun setupVenv(module: Module): PyResult<Sdk> {
|
||||
val env = withContext(Dispatchers.IO) {
|
||||
detectAssociatedEnvironments(module, existingSdks, context).firstOrNull()
|
||||
getVirtualEnv(module)
|
||||
} ?: return PyResult.failure(MessageError("Can't find venv for the module"))
|
||||
|
||||
val sdk = env.setupAssociated(existingSdks, module.basePath, true).getOr { return it }
|
||||
val sdk = env.setupAssociated(
|
||||
existingSdks,
|
||||
module.basePath,
|
||||
true,
|
||||
PyFlavorAndData(PyFlavorData.Empty, VirtualEnvSdkFlavor.getInstance())
|
||||
).getOr { return it }
|
||||
sdk.persist()
|
||||
|
||||
return PyResult.success(sdk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user