mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-22 06:21:25 +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
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@@ -1369,6 +1369,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/python/intellij.python.community.impl.iml" filepath="$PROJECT_DIR$/python/intellij.python.community.impl.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/huggingFace/intellij.python.community.impl.huggingFace.iml" filepath="$PROJECT_DIR$/python/huggingFace/intellij.python.community.impl.huggingFace.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/installer/intellij.python.community.impl.installer.iml" filepath="$PROJECT_DIR$/python/installer/intellij.python.community.impl.installer.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/pipenv/intellij.python.community.impl.pipenv.iml" filepath="$PROJECT_DIR$/python/pipenv/intellij.python.community.impl.pipenv.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/poetry/intellij.python.community.impl.poetry.iml" filepath="$PROJECT_DIR$/python/poetry/intellij.python.community.impl.poetry.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/python-venv/intellij.python.community.impl.venv.iml" filepath="$PROJECT_DIR$/python/python-venv/intellij.python.community.impl.venv.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/interpreters/intellij.python.community.interpreters.iml" filepath="$PROJECT_DIR$/python/interpreters/intellij.python.community.interpreters.iml" />
|
||||
|
||||
@@ -1425,6 +1425,7 @@ python/interpreters
|
||||
python/junit5Tests-framework
|
||||
python/junit5Tests-framework/conda
|
||||
python/openapi
|
||||
python/pipenv
|
||||
python/pluginCore
|
||||
python/pluginCore/impl
|
||||
python/pluginJava
|
||||
|
||||
@@ -333,6 +333,7 @@ jvm_library(
|
||||
"//platform/platform-impl/ui",
|
||||
"//libraries/jackson/module-kotlin:libraries-jackson-module-kotlin",
|
||||
"//python/poetry",
|
||||
"//python/pipenv",
|
||||
"//python/installer",
|
||||
"//platform/eel",
|
||||
"//platform/eel-provider",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<module name="intellij.python.sdk.ui"/>
|
||||
<module name="intellij.python.pyproject"/>
|
||||
<module name="intellij.python.community.impl.poetry"/>
|
||||
<module name="intellij.python.community.impl.pipenv"/>
|
||||
<module name="intellij.python.community.core.impl"/>
|
||||
<module name="intellij.python.community.helpersLocator"/>
|
||||
<module name="intellij.python.community"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,7 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.ui" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.jackson.module.kotlin" />
|
||||
<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.installer" />
|
||||
<orderEntry type="module" module-name="intellij.platform.eel" />
|
||||
<orderEntry type="module" module-name="intellij.platform.eel.provider" />
|
||||
|
||||
@@ -56,6 +56,7 @@ jvm_library(
|
||||
"//python/openapi:community",
|
||||
"//python/openapi:community_test_lib",
|
||||
"//python/poetry",
|
||||
"//python/pipenv",
|
||||
"//python/setup-test-environment:community-testFramework-testEnv",
|
||||
"//python/python-sdk:sdk",
|
||||
"//python/python-sdk:sdk_test_lib",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.util.coroutines" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.community" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.impl.poetry" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.impl.pipenv" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.plugin" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.testFramework.testEnv" exported="" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.sdk" scope="TEST" />
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.process.CapturingProcessHandler
|
||||
import com.intellij.execution.process.ProcessNotCreatedException
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.python.community.impl.pipenv.pipenvPath
|
||||
import com.intellij.python.community.impl.poetry.poetryPath
|
||||
import com.intellij.python.community.testFramework.testEnv.PythonType
|
||||
import com.intellij.python.community.testFramework.testEnv.TypeVanillaPython3
|
||||
@@ -27,32 +28,13 @@ internal class VanillaPythonEnvExtension : PythonEnvExtensionBase<PythonBinary,
|
||||
additionalTags = arrayOf("poetry")
|
||||
) {
|
||||
private companion object {
|
||||
val checkedPoetries = mutableMapOf<Path, Unit>()
|
||||
val checkedTools = mutableMapOf<String, MutableSet<Path>>()
|
||||
}
|
||||
|
||||
override fun onEnvFound(env: PythonBinary) {
|
||||
val poetry = env.resolvePythonHome().resolvePythonTool("poetry")
|
||||
if (poetry !in checkedPoetries) {
|
||||
val output = try {
|
||||
CapturingProcessHandler(GeneralCommandLine(poetry.toString(), "--version")).runProcess(60_000, true)
|
||||
}
|
||||
catch (e: ProcessNotCreatedException) {
|
||||
val customPythonMessage = buildString {
|
||||
PythonType.customPythonMessage?.let {
|
||||
append(it)
|
||||
append(" install poetry there, i.e: 'python -m pip install poetry' ")
|
||||
}
|
||||
append(" or run/rerun ")
|
||||
append(PythonType.BUILD_KTS_MESSAGE)
|
||||
}
|
||||
throw AssertionError(customPythonMessage, e)
|
||||
}
|
||||
assert(output.exitCode == 0) { "$poetry seems to be broken, output: $output. For Windows check `fix_path.cmd`" }
|
||||
LOG.info("Poetry found at $poetry")
|
||||
checkedPoetries[poetry] = Unit
|
||||
}
|
||||
// There is no API that accepts path to poetry: only this global object is used
|
||||
PropertiesComponent.getInstance().poetryPath = poetry.toString()
|
||||
// There is no API that accepts path to poetry or pipenv: only this global object is used
|
||||
PropertiesComponent.getInstance().poetryPath = checkAndGetToolPath(env, "poetry", true)
|
||||
PropertiesComponent.getInstance().pipenvPath = checkAndGetToolPath(env, "pipenv", false)
|
||||
|
||||
val uv = env.resolvePythonHome().resolvePythonTool("uv")
|
||||
PropertiesComponent.getInstance().setValue(
|
||||
@@ -60,4 +42,35 @@ internal class VanillaPythonEnvExtension : PythonEnvExtensionBase<PythonBinary,
|
||||
uv.toString()
|
||||
)
|
||||
}
|
||||
|
||||
private fun checkAndGetToolPath(env: PythonBinary, toolName: String, toThrow: Boolean): String? {
|
||||
val tool = env.resolvePythonHome().resolvePythonTool(toolName)
|
||||
if (checkedTools[toolName]?.contains(tool) != true) {
|
||||
val output = try {
|
||||
CapturingProcessHandler(GeneralCommandLine(tool.toString(), "--version")).runProcess(60_000, true)
|
||||
}
|
||||
catch (e: ProcessNotCreatedException) {
|
||||
val customPythonMessage = buildString {
|
||||
PythonType.customPythonMessage?.let {
|
||||
append(it)
|
||||
append(" install ${toolName} there, i.e: 'python -m pip install ${toolName}' ")
|
||||
}
|
||||
append(" or run/rerun ")
|
||||
append(PythonType.BUILD_KTS_MESSAGE)
|
||||
}
|
||||
if (toThrow) {
|
||||
throw AssertionError(customPythonMessage, e)
|
||||
}
|
||||
else {
|
||||
LOG.error(customPythonMessage)
|
||||
return null
|
||||
}
|
||||
}
|
||||
assert(output.exitCode == 0) { "$tool seems to be broken, output: $output. For Windows check `fix_path.cmd`" }
|
||||
LOG.info("${toolName} found at $tool")
|
||||
checkedTools.compute(toolName) { _, v -> (v ?: mutableSetOf()).also { it.add(tool) } }
|
||||
}
|
||||
|
||||
return tool.toString()
|
||||
}
|
||||
}
|
||||
23
python/pipenv/BUILD.bazel
Normal file
23
python/pipenv/BUILD.bazel
Normal file
@@ -0,0 +1,23 @@
|
||||
### auto-generated section `build intellij.python.community.impl.pipenv` start
|
||||
load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup")
|
||||
|
||||
resourcegroup(
|
||||
name = "pipenv_resources",
|
||||
srcs = glob(["resources/**/*"]),
|
||||
strip_prefix = "resources"
|
||||
)
|
||||
|
||||
jvm_library(
|
||||
name = "pipenv",
|
||||
module_name = "intellij.python.community.impl.pipenv",
|
||||
visibility = ["//visibility:public"],
|
||||
srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True),
|
||||
resources = [":pipenv_resources"],
|
||||
deps = [
|
||||
"@lib//:kotlin-stdlib",
|
||||
"@lib//:jetbrains-annotations",
|
||||
"//platform/core-api:core",
|
||||
"//python/python-sdk:sdk",
|
||||
]
|
||||
)
|
||||
### auto-generated section `build intellij.python.community.impl.pipenv` end
|
||||
16
python/pipenv/intellij.python.community.impl.pipenv.iml
Normal file
16
python/pipenv/intellij.python.community.impl.pipenv.iml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="kotlin-stdlib" level="project" />
|
||||
<orderEntry type="library" name="jetbrains-annotations" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" />
|
||||
<orderEntry type="module" module-name="intellij.python.sdk" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,5 @@
|
||||
<idea-plugin>
|
||||
<dependencies>
|
||||
<module name="intellij.python.sdk"/>
|
||||
</dependencies>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@ApiStatus.Internal
|
||||
package com.intellij.python.community.impl.pipenv;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -0,0 +1,17 @@
|
||||
// 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.python.community.impl.pipenv
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import org.jetbrains.annotations.SystemDependent
|
||||
|
||||
private const val PIPENV_PATH_SETTING: String = "PyCharm.Pipenv.Path"
|
||||
|
||||
/**
|
||||
* Tells if the SDK was added as pipenv.
|
||||
* The user-set persisted a path to the pipenv executable.
|
||||
*/
|
||||
var PropertiesComponent.pipenvPath: @SystemDependent String?
|
||||
get() = getValue(PIPENV_PATH_SETTING)
|
||||
set(value) {
|
||||
setValue(PIPENV_PATH_SETTING, value)
|
||||
}
|
||||
@@ -60,6 +60,9 @@
|
||||
- name: $MAVEN_REPOSITORY$/completion/ml/python/features/ml-completion-prev-exprs-models/1/ml-completion-prev-exprs-models-1.jar
|
||||
completion-ranking-python-with-full-line:
|
||||
- name: $MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/completion/completion-ranking-python-with-full-line/0/completion-ranking-python-with-full-line-0.jar
|
||||
- name: lib/modules/intellij.python.community.impl.pipenv.jar
|
||||
contentModules:
|
||||
- name: intellij.python.community.impl.pipenv
|
||||
- name: lib/modules/intellij.python.community.impl.poetry.jar
|
||||
contentModules:
|
||||
- name: intellij.python.community.impl.poetry
|
||||
|
||||
@@ -37,6 +37,7 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
|
||||
<module name="intellij.python.sdkConfigurator.backend"/>
|
||||
<module name="intellij.python.sdkConfigurator.frontend"/>
|
||||
<module name="intellij.python.community.impl.poetry" loading="required"/>
|
||||
<module name="intellij.python.community.impl.pipenv" loading="required"/>
|
||||
<module name="intellij.python.community.core.impl" loading="required"/>
|
||||
<module name="intellij.python.community.helpersLocator" loading="required"/>
|
||||
<module name="intellij.python.community" loading="required"/>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<module name="intellij.python.community.execService.python"/>
|
||||
<module name="intellij.python.community.interpreters"/>
|
||||
<module name="intellij.python.community.impl.poetry"/>
|
||||
<module name="intellij.python.community.impl.pipenv"/>
|
||||
<module name="intellij.python.hatch"/>
|
||||
<module name="intellij.python.pydev"/>
|
||||
<module name="intellij.python.sdk.ui"/>
|
||||
@@ -85,6 +86,7 @@
|
||||
<extensions defaultExtensionNs="com.intellij.python.pyproject.model">
|
||||
<tool implementation="com.jetbrains.python.projectModel.poetry.PoetryTool"/>
|
||||
<tool implementation="com.jetbrains.python.projectModel.uv.UvTool"/>
|
||||
<tool implementation="com.jetbrains.python.projectModel.hatch.HatchTool"/>
|
||||
</extensions>
|
||||
|
||||
<projectListeners>
|
||||
|
||||
@@ -373,6 +373,7 @@ python.sdk.next=Next
|
||||
python.sdk.previous=Previous
|
||||
python.sdk.finish=Finish
|
||||
python.sdk.setting.up.pipenv.sentence=Setting up pipenv environment
|
||||
python.sdk.using.pipenv.sentence=Using pipenv environment
|
||||
python.sdk.setting.up.pipenv.title=Setting up Pipenv Environment
|
||||
python.sdk.install.requirements.from.pipenv.lock=Install requirements from Pipfile.lock
|
||||
python.sdk.pipenv.executable=Pipenv executable:
|
||||
|
||||
@@ -254,11 +254,11 @@ data class HatchEnvironments(
|
||||
data class HatchEnvironment(
|
||||
val name: @NlsSafe String,
|
||||
val type: @NlsSafe String,
|
||||
val features: String? = null,
|
||||
val dependencies: String? = null,
|
||||
val environmentVariables: String? = null,
|
||||
val scripts: String? = null,
|
||||
val description: String? = null,
|
||||
val features: String = "",
|
||||
val dependencies: String = "",
|
||||
val environmentVariables: String = "",
|
||||
val scripts: String = "",
|
||||
val description: String = "",
|
||||
) {
|
||||
companion object {
|
||||
val DEFAULT: HatchEnvironment = HatchEnvironment(name = DEFAULT_ENV_NAME, type = ENV_TYPE_VIRTUAL)
|
||||
@@ -286,11 +286,11 @@ private fun AsciiTable.parseHatchEnvironments(): List<Pair<HatchEnvironment, Lis
|
||||
HatchEnvironment(
|
||||
name = row[nameIdx],
|
||||
type = row[typeIdx],
|
||||
features = row.cell(featuresIdx),
|
||||
dependencies = row.cell(dependenciesIdx),
|
||||
environmentVariables = row.cell(environmentVariablesIdx),
|
||||
scripts = row.cell(scriptsIdx),
|
||||
description = row.cell(descriptionIdx),
|
||||
features = row.cell(featuresIdx) ?: "",
|
||||
dependencies = row.cell(dependenciesIdx) ?: "",
|
||||
environmentVariables = row.cell(environmentVariablesIdx) ?: "",
|
||||
scripts = row.cell(scriptsIdx) ?: "",
|
||||
description = row.cell(descriptionIdx) ?: "",
|
||||
) to matrixEnvironments
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,12 +95,18 @@ interface HatchService {
|
||||
suspend fun createVirtualEnvironment(basePythonBinaryPath: PythonBinary? = null, envName: String? = null): PyResult<PythonVirtualEnvironment.Existing>
|
||||
|
||||
suspend fun findVirtualEnvironments(): PyResult<List<HatchVirtualEnvironment>>
|
||||
|
||||
/**
|
||||
* This function detects all Hatch virtual environments and returns the 'default' one if it exists. If such an environment
|
||||
* doesn't exist, `null` is returned. In case of errors `PyError` is returned.
|
||||
*/
|
||||
suspend fun findDefaultVirtualEnvironmentOrNull(): PyResult<HatchVirtualEnvironment?>
|
||||
}
|
||||
|
||||
/**
|
||||
* Hatch Service for working directory (where hatch.toml / pyproject.toml is usually placed)
|
||||
*/
|
||||
suspend fun Path?.getHatchService(hatchExecutablePath: Path? = null, hatchEnvironmentName: String? = null): PyResult<HatchService> {
|
||||
suspend fun Path?.getHatchService(hatchExecutablePath: Path? = null, hatchEnvironmentName: String? = null): PyResult<HatchService> {
|
||||
return CliBasedHatchService(hatchExecutablePath = hatchExecutablePath, workingDirectoryPath = this, hatchEnvironmentName = hatchEnvironmentName)
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,9 @@ internal class CliBasedHatchService private constructor(
|
||||
return Result.success(available)
|
||||
}
|
||||
|
||||
override suspend fun findDefaultVirtualEnvironmentOrNull(): PyResult<HatchVirtualEnvironment?> =
|
||||
findVirtualEnvironments().mapSuccess { envs -> envs.singleOrNull { it.hatchEnvironment == HatchEnvironment.DEFAULT } }
|
||||
|
||||
|
||||
override suspend fun createNewProject(projectName: String): PyResult<ProjectStructure> {
|
||||
val eelApi = workingDirectoryPath.getEelDescriptor().toEelApi()
|
||||
@@ -138,6 +141,7 @@ private fun HatchEnvironments.getAvailableVirtualHatchEnvironments(): List<Hatch
|
||||
HatchEnvironment(
|
||||
name = envName,
|
||||
type = type,
|
||||
features = features,
|
||||
dependencies = dependencies,
|
||||
environmentVariables = environmentVariables,
|
||||
scripts = scripts,
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.intellij.python.sdkConfigurator.common.impl.ModuleName
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModulesDTO
|
||||
import com.intellij.python.sdkConfigurator.common.impl.SHOW_SDK_CONFIG_UI_TOPIC
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.sdk.configuration.CreateSdkInfo
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.getOrCreateAdditionalData
|
||||
import com.jetbrains.python.sdk.setAssociationToPath
|
||||
@@ -57,13 +58,13 @@ internal suspend fun configureSdkAutomatically(project: Project, modulesOnly: Se
|
||||
}
|
||||
val configurators = PyProjectSdkConfigurationExtension.EP_NAME.extensionList
|
||||
val configuratorsByTool = configurators
|
||||
.mapNotNull { extension -> extension.toolId?.let { Pair(it, extension) } }
|
||||
.mapNotNull { extension -> extension.asPyProjectTomlSdkConfigurationExtension()?.toolId?.let { Pair(it, extension) } }
|
||||
.toMap()
|
||||
|
||||
assert(configurators.isNotEmpty()) { "PyCharm can't work without any SDK configurator" }
|
||||
|
||||
val tomlBasedConfigurators = configurators.filter { it.toolId != null }
|
||||
val legacyConfigurators = configurators.filter { it.toolId == null }
|
||||
val tomlBasedConfigurators = configuratorsByTool.values
|
||||
val legacyConfigurators = configurators.filter { it.asPyProjectTomlSdkConfigurationExtension() == null }
|
||||
val allSortedConfigurators = tomlBasedConfigurators + legacyConfigurators
|
||||
|
||||
val modulesWithSameSdk = mutableMapOf<Module, Module>()
|
||||
@@ -120,14 +121,20 @@ private suspend fun getModulesWithoutSDK(project: Project): ModulesDTO =
|
||||
})
|
||||
|
||||
private suspend fun configureSdkForModule(module: Module, configurators: List<PyProjectSdkConfigurationExtension>, checkForIntention: Boolean): Boolean {
|
||||
for (extension in configurators) {
|
||||
if (checkForIntention && extension.getIntention(module) == null) {
|
||||
logger.info("${extension.javaClass} skipped for ${module.name}")
|
||||
continue
|
||||
}
|
||||
val created = when (val r = extension.createAndAddSdkForInspection(module)) {
|
||||
// TODO: Parallelize call to checkEnvironmentAndPrepareSdkCreator
|
||||
val createSdkInfos = configurators.mapNotNull {
|
||||
if (checkForIntention) it.checkEnvironmentAndPrepareSdkCreator(module)
|
||||
else it.asPyProjectTomlSdkConfigurationExtension()?.createSdkWithoutPyProjectTomlChecks(module)
|
||||
}.sorted()
|
||||
|
||||
for (createSdkInfo in createSdkInfos) {
|
||||
val created = when (val r = createSdkInfo.sdkCreator(false)) {
|
||||
is Result.Failure -> {
|
||||
logger.warn("can't create SDK for ${module.name}: ${r.error.message}")
|
||||
val msgExtraInfo = when (createSdkInfo) {
|
||||
is CreateSdkInfo.ExistingEnv -> " using existing environment "
|
||||
is CreateSdkInfo.WillCreateEnv -> " "
|
||||
}
|
||||
logger.warn("can't create SDK${msgExtraInfo}for ${module.name}: ${r.error.message}")
|
||||
false
|
||||
}
|
||||
is Result.Success -> r.result?.also { sdk ->
|
||||
|
||||
@@ -36,14 +36,14 @@ private class AutoconfigSelectSdkProvider() : EvoSelectSdkProvider {
|
||||
text = PySdkUiBundle.message("evo.sdk.status.bar.popup.shortcuts.best.options"),
|
||||
icon = AllIcons.General.Layout
|
||||
) {
|
||||
val extensions = PyProjectSdkConfigurationExtension.EP_NAME.extensionsIfPointIsRegistered.mapNotNull {
|
||||
it.getIntention(evoModuleSdk.module)?.let { intention -> it to intention }
|
||||
val createSdkInfos = PyProjectSdkConfigurationExtension.EP_NAME.extensionsIfPointIsRegistered.mapNotNull {
|
||||
it.checkEnvironmentAndPrepareSdkCreator(evoModuleSdk.module)
|
||||
}
|
||||
|
||||
val section = EvoTreeSection(
|
||||
label = null,
|
||||
elements = extensions.mapIndexed { idx, (extension, intention) ->
|
||||
EvoTreeLeafElement(RunConfiguratorAction(intention, idx))
|
||||
elements = createSdkInfos.mapIndexed { idx, createSdkInfo ->
|
||||
EvoTreeLeafElement(RunConfiguratorAction(createSdkInfo.intentionName, idx))
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.jetbrains.python.sdk.configuration
|
||||
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
typealias NeedsConfirmation = Boolean
|
||||
typealias CheckExistence = Boolean
|
||||
typealias CheckToml = Boolean
|
||||
typealias EnvExists = Boolean
|
||||
|
||||
@ApiStatus.Internal
|
||||
sealed interface CreateSdkInfo : Comparable<CreateSdkInfo> {
|
||||
@get:IntentionName
|
||||
val intentionName: String
|
||||
val toolInfo: PyToolUIInfo
|
||||
val sdkCreator: suspend (NeedsConfirmation) -> PyResult<Sdk?>
|
||||
|
||||
/**
|
||||
* We want to preserve the initial order, but at the same time existing environment should have a higher priority by default
|
||||
*/
|
||||
override fun compareTo(other: CreateSdkInfo): Int {
|
||||
val thisExists = if (this is ExistingEnv) 0 else 1
|
||||
val otherExists = if (other is ExistingEnv) 0 else 1
|
||||
return thisExists.compareTo(otherExists)
|
||||
}
|
||||
|
||||
data class ExistingEnv(
|
||||
val version: String,
|
||||
override val intentionName: String,
|
||||
override val toolInfo: PyToolUIInfo,
|
||||
override val sdkCreator: suspend (NeedsConfirmation) -> PyResult<Sdk?>,
|
||||
) : CreateSdkInfo
|
||||
|
||||
data class WillCreateEnv(
|
||||
override val intentionName: String,
|
||||
override val toolInfo: PyToolUIInfo,
|
||||
override val sdkCreator: suspend (NeedsConfirmation) -> PyResult<Sdk?>,
|
||||
) : CreateSdkInfo
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
sealed interface EnvCheckerResult {
|
||||
data class EnvFound(val version: String, val intentionName: @IntentionName String) : EnvCheckerResult
|
||||
data class EnvNotFound(val intentionName: @IntentionName String) : EnvCheckerResult
|
||||
object CannotConfigure : EnvCheckerResult
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
// TODO: Make internal after we drop WSL sdk configurator
|
||||
suspend fun prepareSdkCreator(
|
||||
toolInfo: PyToolUIInfo,
|
||||
envChecker: suspend (CheckExistence) -> EnvCheckerResult,
|
||||
sdkCreator: (EnvExists) -> (suspend (NeedsConfirmation) -> PyResult<Sdk?>),
|
||||
): CreateSdkInfo? {
|
||||
var res = envChecker(true)
|
||||
return when (res) {
|
||||
is EnvCheckerResult.EnvFound -> CreateSdkInfo.ExistingEnv(
|
||||
res.version,
|
||||
res.intentionName,
|
||||
toolInfo,
|
||||
sdkCreator(true)
|
||||
)
|
||||
is EnvCheckerResult.EnvNotFound -> {
|
||||
res = envChecker(false)
|
||||
when (res) {
|
||||
is EnvCheckerResult.EnvNotFound -> CreateSdkInfo.WillCreateEnv(res.intentionName, toolInfo, sdkCreator(false))
|
||||
is EnvCheckerResult.EnvFound -> throw AssertionError("Env shouldn't exist if we didn't check for it")
|
||||
is EnvCheckerResult.CannotConfigure -> null
|
||||
}
|
||||
}
|
||||
is EnvCheckerResult.CannotConfigure -> null
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.sdk.configuration
|
||||
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.progress.runBlockingMaybeCancellable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.ToolId
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.CheckReturnValue
|
||||
|
||||
@@ -21,60 +18,51 @@ import org.jetbrains.annotations.CheckReturnValue
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
interface PyProjectSdkConfigurationExtension {
|
||||
/**
|
||||
* Every tool (poetry, uv) has id
|
||||
*/
|
||||
val toolId: ToolId? get() = null
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val EP_NAME: ExtensionPointName<PyProjectSdkConfigurationExtension> = ExtensionPointName.create<PyProjectSdkConfigurationExtension>("Pythonid.projectSdkConfigurationExtension")
|
||||
val EP_NAME: ExtensionPointName<PyProjectSdkConfigurationExtension> = ExtensionPointName.create("Pythonid.projectSdkConfigurationExtension")
|
||||
|
||||
@JvmStatic
|
||||
@RequiresBackgroundThread
|
||||
fun findForModule(module: Module): Pair<@IntentionName String, PyProjectSdkConfigurationExtension>? = runBlockingMaybeCancellable {
|
||||
EP_NAME.extensionsIfPointIsRegistered.firstNotNullOfOrNull { ext -> ext.getIntention(module)?.let { Pair(it, ext) } }
|
||||
fun findForModule(module: Module): CreateSdkInfo? = runBlockingMaybeCancellable {
|
||||
EP_NAME.extensionsIfPointIsRegistered.firstNotNullOfOrNull { ext -> ext.checkEnvironmentAndPrepareSdkCreator(module) }
|
||||
}
|
||||
}
|
||||
|
||||
val toolInfo: PyToolUIInfo
|
||||
|
||||
/**
|
||||
* An implementation is responsible for interpreter setup and registration in IDE.
|
||||
* In case of failures `null` should be returned, the implementation is responsible for errors displaying.
|
||||
* Discovers whether this extension can provide a Python SDK for the given module and prepares a creator for it.
|
||||
*
|
||||
* Rule of thumb is to explicitly ask a user if sdk creation is desired and allowed.
|
||||
* This function is executed on a background thread and may perform I/O-intensive checks such as
|
||||
* reading project files (for example, pyproject.toml, Pipfile, requirements.txt, environment.yml), probing the
|
||||
* file system, or invoking external tools (poetry/hatch/pipenv/uv/etc.). No SDK must be created or registered here.
|
||||
* Instead, the method returns a [CreateSdkInfo] descriptor that encapsulates:
|
||||
* - user-facing labels (intentionName) and tool metadata (toolInfo), and
|
||||
* - a suspendable sdkCreator that will create and register the SDK when executed by the caller
|
||||
* (see [PyProjectSdkConfiguration.setSdkUsingCreateSdkInfo]).
|
||||
*
|
||||
* Return value semantics:
|
||||
* - Existing environment found: return a CreateSdkInfo.ExistingEnv whose creator simply registers the discovered SDK.
|
||||
* - No environment yet, but can be created: return a CreateSdkInfo.WillCreateEnv whose creator performs the creation
|
||||
* (and optional user confirmation) and registers the SDK.
|
||||
* - Tool is not applicable, or configuration cannot proceed (missing binaries, incompatible project, errors): return null.
|
||||
* Implementations are responsible for showing any user-facing error notifications when they decide to return null.
|
||||
*
|
||||
* The default ordering prefers existing environments over newly created ones; see CreateSdkInfo.compareTo.
|
||||
*
|
||||
* @param module module to inspect and derive configuration from
|
||||
* @return descriptor to create/register a suitable SDK, or null if this extension cannot configure the project
|
||||
*/
|
||||
@CheckReturnValue
|
||||
suspend fun createAndAddSdkForConfigurator(module: Module): PyResult<Sdk?>
|
||||
suspend fun checkEnvironmentAndPrepareSdkCreator(module: Module): CreateSdkInfo?
|
||||
|
||||
/**
|
||||
* An implementation is responsible for interpreter setup and registration in IDE.
|
||||
* In case of failures `null` should be returned, the implementation is responsible for errors displaying.
|
||||
* Returns this extension as a [PyProjectTomlConfigurationExtension] when a tool supports configuring with
|
||||
* pyproject.toml, or null otherwise.
|
||||
*
|
||||
* You're free here to create sdk immediately, without any user permission since quick fix is explicitly clicked.
|
||||
* Callers that need to skip pyproject.toml validation should do it using
|
||||
* [PyProjectTomlConfigurationExtension.createSdkWithoutPyProjectTomlChecks].
|
||||
*/
|
||||
@CheckReturnValue
|
||||
suspend fun createAndAddSdkForInspection(module: Module): PyResult<Sdk?>
|
||||
|
||||
/**
|
||||
* Called by sdk configurator and interpreter inspection
|
||||
* to determine if an extension could configure or suggest an interpreter for the passed [module].
|
||||
*
|
||||
* First applicable extension is processed, others are ignored.
|
||||
* If there is no applicable extension, configurator and inspection guess a suitable interpreter.
|
||||
*
|
||||
* Could be called from AWT hence should be as fast as possible.
|
||||
*
|
||||
* If returned value is `null`, then the extension can't be used to configure an interpreter (not applicable).
|
||||
* Otherwise returned string is used as a quick fix name.
|
||||
*
|
||||
* Example: `Create a virtual environment using requirements.txt`.
|
||||
*/
|
||||
@IntentionName
|
||||
suspend fun getIntention(module: Module): String?
|
||||
|
||||
/**
|
||||
* If headless supported implementation is responsible for interpreter setup and registration
|
||||
* for [createAndAddSdkForConfigurator] method in IDE without an additional user input.
|
||||
*/
|
||||
fun supportsHeadlessModel(): Boolean = false
|
||||
fun asPyProjectTomlSdkConfigurationExtension(): PyProjectTomlConfigurationExtension?
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.jetbrains.python.sdk.configuration
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.jetbrains.python.ToolId
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
interface PyProjectTomlConfigurationExtension : PyProjectSdkConfigurationExtension {
|
||||
val toolId: ToolId
|
||||
|
||||
suspend fun createSdkWithoutPyProjectTomlChecks(module: Module): CreateSdkInfo?
|
||||
}
|
||||
@@ -112,7 +112,7 @@ public abstract class PythonSdkFlavor<D extends PyFlavorData> {
|
||||
return suggestLocalHomePathsImpl(module, context).stream().filter(path -> {
|
||||
var flavor = tryDetectFlavorByLocalPath(path.toString());
|
||||
boolean correctFlavor = flavor != null && flavor.getClass().equals(getClass());
|
||||
// Some flavors might report foreign pythons: i.e Windows might find conda on PATH.
|
||||
// Some flavors might report foreign pythons: e.g. Windows might find conda on PATH.
|
||||
if (!correctFlavor) {
|
||||
LOG.info(String.format("Path %s has a wrong flavor, not %s, skipping", path, this));
|
||||
return false;
|
||||
@@ -470,7 +470,8 @@ public abstract class PythonSdkFlavor<D extends PyFlavorData> {
|
||||
@ApiStatus.Internal
|
||||
public void dropCaches() {
|
||||
}
|
||||
@ApiStatus.Internal
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static final class UnknownFlavor extends PythonSdkFlavor<PyFlavorData.Empty> {
|
||||
|
||||
public static final UnknownFlavor INSTANCE = new UnknownFlavor();
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.intellij.openapi.ui.DialogPanel;
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.python.community.impl.pipenv.PathKt;
|
||||
import com.intellij.ui.CollectionComboBoxModel;
|
||||
import com.intellij.ui.IdeBorderFactory;
|
||||
import com.intellij.ui.SimpleListCellRenderer;
|
||||
@@ -258,7 +259,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable {
|
||||
return true;
|
||||
}
|
||||
if (!myPipEnvPathField.getText()
|
||||
.equals(StringUtil.notNullize(PipenvCommandExecutorKt.getPipEnvPath(PropertiesComponent.getInstance())))) {
|
||||
.equals(StringUtil.notNullize(PathKt.getPipenvPath(PropertiesComponent.getInstance())))) {
|
||||
return true;
|
||||
}
|
||||
return ContainerUtil.exists(myCustomizePanels, panel -> panel.isModified());
|
||||
@@ -292,7 +293,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable {
|
||||
setRequirementsPath(myRequirementsPathField.getText());
|
||||
|
||||
DaemonCodeAnalyzer.getInstance(myProject).restart(this);
|
||||
PipenvCommandExecutorKt.setPipEnvPath(PropertiesComponent.getInstance(), StringUtil.nullize(myPipEnvPathField.getText()));
|
||||
PathKt.setPipenvPath(PropertiesComponent.getInstance(), StringUtil.nullize(myPipEnvPathField.getText()));
|
||||
|
||||
for (@NotNull DialogPanel panel : myCustomizePanels) {
|
||||
panel.apply();
|
||||
@@ -328,7 +329,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable {
|
||||
// TODO: Move pipenv settings into a separate configurable
|
||||
final JBTextField pipEnvText = ObjectUtils.tryCast(myPipEnvPathField.getTextField(), JBTextField.class);
|
||||
if (pipEnvText != null) {
|
||||
final String savedPath = PipenvCommandExecutorKt.getPipEnvPath(PropertiesComponent.getInstance());
|
||||
final String savedPath = PathKt.getPipenvPath(PropertiesComponent.getInstance());
|
||||
if (savedPath != null) {
|
||||
pipEnvText.setText(savedPath);
|
||||
}
|
||||
|
||||
@@ -50,11 +50,11 @@ import com.jetbrains.python.sdk.PySdkExtKt;
|
||||
import com.jetbrains.python.sdk.PySdkPopupFactory;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import com.jetbrains.python.sdk.conda.PyCondaSdkCustomizer;
|
||||
import com.jetbrains.python.sdk.configuration.CreateSdkInfo;
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration;
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension;
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil;
|
||||
import com.jetbrains.python.ui.PyUiUtil;
|
||||
import kotlin.Pair;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -93,17 +93,17 @@ public final class PyInterpreterInspection extends PyInspection {
|
||||
private static final AsyncLoadingCache<@NotNull Module, @NotNull List<PyDetectedSdk>> DETECTED_ASSOCIATED_ENVS_CACHE =
|
||||
Caffeine.newBuilder().executor(AppExecutorUtil.getAppExecutorService())
|
||||
|
||||
// Even though various listeners invalidate the cache on many actions, it's unfeasible to track for venv/conda interpreters
|
||||
// creation performed outside the IDE.
|
||||
// 20 seconds timeout is taken at random.
|
||||
.expireAfterWrite(Duration.ofSeconds(20))
|
||||
// Even though various listeners invalidate the cache on many actions, it's unfeasible to track for venv/conda interpreters
|
||||
// creation performed outside the IDE.
|
||||
// 20 seconds timeout is taken at random.
|
||||
.expireAfterWrite(Duration.ofSeconds(20))
|
||||
|
||||
.weakKeys()
|
||||
.buildAsync(module -> {
|
||||
final List<Sdk> existingSdks = getExistingSdks();
|
||||
final UserDataHolderBase context = new UserDataHolderBase();
|
||||
return PySdkExtKt.detectAssociatedEnvironments(module, existingSdks, context);
|
||||
});
|
||||
.weakKeys()
|
||||
.buildAsync(module -> {
|
||||
final List<Sdk> existingSdks = getExistingSdks();
|
||||
final UserDataHolderBase context = new UserDataHolderBase();
|
||||
return PySdkExtKt.detectAssociatedEnvironments(module, existingSdks, context);
|
||||
});
|
||||
|
||||
public Visitor(@Nullable ProblemsHolder holder,
|
||||
@NotNull TypeEvalContext context) {
|
||||
@@ -182,10 +182,9 @@ public final class PyInterpreterInspection extends PyInspection {
|
||||
return new UseDetectedInterpreterFix(detectedAssociatedSdk, existingSdks, true, module);
|
||||
}
|
||||
|
||||
final Pair<@IntentionName String, PyProjectSdkConfigurationExtension> textAndExtension =
|
||||
PyProjectSdkConfigurationExtension.findForModule(module);
|
||||
if (textAndExtension != null) {
|
||||
return new UseProvidedInterpreterFix(module, textAndExtension.getSecond(), textAndExtension.getFirst());
|
||||
final CreateSdkInfo createSdkInfo = PyProjectSdkConfigurationExtension.findForModule(module);
|
||||
if (createSdkInfo != null) {
|
||||
return new UseProvidedInterpreterFix(module, createSdkInfo);
|
||||
}
|
||||
|
||||
if (name != null) {
|
||||
@@ -221,9 +220,10 @@ public final class PyInterpreterInspection extends PyInspection {
|
||||
|
||||
PyProjectSdkConfigurationExtension configurator = PyCondaSdkCustomizer.Companion.getInstance().getFallbackConfigurator();
|
||||
if (configurator != null) {
|
||||
String intentionName = PyCondaSdkCustomizer.Companion.getIntentionBlocking(configurator, module);
|
||||
if (intentionName != null) {
|
||||
return new UseProvidedInterpreterFix(module, configurator, intentionName);
|
||||
final CreateSdkInfo fallbackCreateSdkInfo =
|
||||
PyCondaSdkCustomizer.Companion.checkEnvironmentAndPrepareSdkCreatorBlocking(configurator, module);
|
||||
if (fallbackCreateSdkInfo != null) {
|
||||
return new UseProvidedInterpreterFix(module, fallbackCreateSdkInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,16 +411,12 @@ public final class PyInterpreterInspection extends PyInspection {
|
||||
|
||||
private final @NotNull Module myModule;
|
||||
|
||||
private final @NotNull PyProjectSdkConfigurationExtension myExtension;
|
||||
|
||||
private final @NotNull @IntentionName String myName;
|
||||
private final @NotNull CreateSdkInfo myCreateSdkInfo;
|
||||
|
||||
private UseProvidedInterpreterFix(@NotNull Module module,
|
||||
@NotNull PyProjectSdkConfigurationExtension extension,
|
||||
@NotNull @IntentionName String name) {
|
||||
@NotNull CreateSdkInfo createSdkInfo) {
|
||||
myModule = module;
|
||||
myExtension = extension;
|
||||
myName = name;
|
||||
myCreateSdkInfo = createSdkInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -430,13 +426,13 @@ public final class PyInterpreterInspection extends PyInspection {
|
||||
|
||||
@Override
|
||||
public @IntentionName @NotNull String getName() {
|
||||
return myName;
|
||||
return myCreateSdkInfo.getIntentionName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
if (! detectSdkForModulesIn(project)) {
|
||||
PyProjectSdkConfiguration.INSTANCE.configureSdkUsingExtension(myModule, myExtension);
|
||||
if (!detectSdkForModulesIn(project)) {
|
||||
PyProjectSdkConfiguration.INSTANCE.configureSdkUsingCreateSdkInfo(myModule, myCreateSdkInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
package com.jetbrains.python.packaging.conda.environmentYml.format
|
||||
|
||||
import com.charleskorn.kaml.*
|
||||
import com.intellij.openapi.application.runReadAction
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.readText
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.packaging.PyRequirement
|
||||
import com.jetbrains.python.packaging.PyRequirementParser
|
||||
import com.jetbrains.python.packaging.parser.RequirementsParserHelper
|
||||
@@ -15,12 +17,16 @@ import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
object CondaEnvironmentYmlParser {
|
||||
fun readNameFromFile(file: VirtualFile): String? {
|
||||
val text = FileDocumentManager.getInstance().getDocument(file)?.text ?: return null
|
||||
fun readNameFromFile(file: VirtualFile): String? = readFieldFromFile(file, "name")
|
||||
fun readPrefixFromFile(file: VirtualFile): String? = readFieldFromFile(file, "prefix")
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun readFieldFromFile(file: VirtualFile, field: String): String? = runReadAction {
|
||||
val text = FileDocumentManager.getInstance().getDocument(file)?.text ?: return@runReadAction null
|
||||
val yaml = Yaml(configuration = YamlConfiguration(strictMode = false))
|
||||
val environment: YamlMap = yaml.parseToYamlNode(text).yamlMap
|
||||
|
||||
return environment.get<YamlScalar>("name")?.yamlScalar?.content
|
||||
environment.get<YamlScalar>(field)?.yamlScalar?.content
|
||||
}
|
||||
|
||||
fun fromFile(file: VirtualFile): List<PyRequirement>? {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.projectModel.common
|
||||
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.python.pyproject.PyProjectToml
|
||||
import com.intellij.python.pyproject.model.spi.ProjectName
|
||||
import com.intellij.python.pyproject.model.spi.ProjectStructureInfo
|
||||
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.venvReader.Directory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.toPath
|
||||
|
||||
@RequiresBackgroundThread
|
||||
internal fun getDependenciesFromToml(projectToml: PyProjectToml): Set<Directory> {
|
||||
val depsFromFile = projectToml.project?.dependencies?.project ?: emptyList()
|
||||
val moduleDependencies = depsFromFile
|
||||
.mapNotNull { depSpec ->
|
||||
val match = PEP_621_PATH_DEPENDENCY.matchEntire(depSpec) ?: return@mapNotNull null
|
||||
val (_, depUri) = match.destructured
|
||||
return@mapNotNull parseDepUri(depUri)
|
||||
}
|
||||
return moduleDependencies.toSet()
|
||||
}
|
||||
|
||||
internal suspend fun getProjectStructure(
|
||||
entries: Map<ProjectName, PyProjectTomlProject>,
|
||||
rootIndex: Map<Directory, ProjectName>,
|
||||
dependenciesGetter: (PyProjectTomlProject) -> Set<Directory>,
|
||||
): ProjectStructureInfo = withContext(Dispatchers.Default) {
|
||||
val deps = entries.asSequence().map { (name, entry) ->
|
||||
val deps = dependenciesGetter(entry).mapNotNull { dir ->
|
||||
rootIndex[dir] ?: run {
|
||||
logger.warn("Can't find project for dir $dir")
|
||||
null
|
||||
}
|
||||
}.toSet()
|
||||
Pair(name, deps)
|
||||
}.toMap()
|
||||
ProjectStructureInfo(dependencies = deps, membersToWorkspace = emptyMap()) // No workspace info (yet)
|
||||
}
|
||||
|
||||
// e.g. "lib @ file:///home/user/projects/main/lib"
|
||||
private val PEP_621_PATH_DEPENDENCY = """([\w-]+) @ (file:.*)""".toRegex()
|
||||
|
||||
private val logger = fileLogger()
|
||||
internal fun parseDepUri(depUri: String): Path? =
|
||||
try {
|
||||
URI(depUri).toPath()
|
||||
}
|
||||
catch (e: InvalidPathException) {
|
||||
logger.info("Dep $depUri points to wrong path", e)
|
||||
null
|
||||
}
|
||||
catch (e: URISyntaxException) {
|
||||
logger.info("Dep $depUri can't be parsed", e)
|
||||
null
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@ApiStatus.Internal
|
||||
package com.jetbrains.python.projectModel.common;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.projectModel.hatch
|
||||
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.python.pyproject.model.spi.ProjectName
|
||||
import com.intellij.python.pyproject.model.spi.ProjectStructureInfo
|
||||
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
|
||||
import com.intellij.python.pyproject.model.spi.Tool
|
||||
import com.intellij.python.sdk.ui.icons.PythonSdkUIIcons
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.ToolId
|
||||
import com.jetbrains.python.projectModel.common.getDependenciesFromToml
|
||||
import com.jetbrains.python.projectModel.common.getProjectStructure
|
||||
import com.jetbrains.python.venvReader.Directory
|
||||
import org.apache.tuweni.toml.TomlTable
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
val HATCH_TOOL_ID: ToolId = ToolId("hatch")
|
||||
internal class HatchTool : Tool {
|
||||
|
||||
override val id: ToolId = HATCH_TOOL_ID
|
||||
override val ui: PyToolUIInfo = PyToolUIInfo("Hatch", PythonSdkUIIcons.Tools.Hatch)
|
||||
|
||||
override suspend fun getSrcRoots(toml: TomlTable, projectRoot: Directory): Set<Directory> = emptySet()
|
||||
|
||||
override suspend fun getProjectName(projectToml: TomlTable): @NlsSafe String? = null
|
||||
|
||||
override suspend fun getProjectStructure(entries: Map<ProjectName, PyProjectTomlProject>, rootIndex: Map<Directory, ProjectName>): ProjectStructureInfo =
|
||||
getProjectStructure(entries, rootIndex) { getDependenciesFromToml(it.pyProjectToml) }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@ApiStatus.Internal
|
||||
package com.jetbrains.python.projectModel.hatch;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -1,24 +1,21 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.projectModel.poetry
|
||||
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.python.pyproject.PyProjectToml
|
||||
import com.intellij.python.pyproject.model.spi.*
|
||||
import com.intellij.python.pyproject.model.spi.ProjectName
|
||||
import com.intellij.python.pyproject.model.spi.ProjectStructureInfo
|
||||
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
|
||||
import com.intellij.python.pyproject.model.spi.Tool
|
||||
import com.intellij.python.sdk.ui.icons.PythonSdkUIIcons
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.ToolId
|
||||
import com.jetbrains.python.projectModel.common.getDependenciesFromToml
|
||||
import com.jetbrains.python.projectModel.common.getProjectStructure
|
||||
import com.jetbrains.python.venvReader.Directory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.apache.tuweni.toml.TomlTable
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.toPath
|
||||
|
||||
@ApiStatus.Internal
|
||||
val POETRY_TOOL_ID: ToolId = ToolId("poetry")
|
||||
@@ -32,54 +29,18 @@ internal class PoetryTool : Tool {
|
||||
override suspend fun getProjectName(projectToml: TomlTable): @NlsSafe String? =
|
||||
projectToml.getString("tool.poetry.name")
|
||||
|
||||
override suspend fun getProjectStructure(entries: Map<ProjectName, PyProjectTomlProject>, rootIndex: Map<Directory, ProjectName>): ProjectStructureInfo = withContext(Dispatchers.Default) {
|
||||
val deps = entries.asSequence().map { (name, entry) ->
|
||||
val deps = getDependencies(entry.root, entry.pyProjectToml).mapNotNull { dir ->
|
||||
rootIndex[dir] ?: run {
|
||||
logger.warn("Can't find project for dir $dir")
|
||||
null
|
||||
}
|
||||
}.toSet()
|
||||
Pair(name, deps)
|
||||
}.toMap()
|
||||
return@withContext ProjectStructureInfo(dependencies = deps, membersToWorkspace = emptyMap()) // No workspace info (yet)
|
||||
}
|
||||
override suspend fun getProjectStructure(entries: Map<ProjectName, PyProjectTomlProject>, rootIndex: Map<Directory, ProjectName>): ProjectStructureInfo =
|
||||
getProjectStructure(entries, rootIndex) { getDependencies(it.root, it.pyProjectToml) }
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun getDependencies(rootDir: Directory, projectToml: PyProjectToml): Set<Directory> {
|
||||
val depsFromFile = projectToml.project?.dependencies?.project ?: emptyList()
|
||||
val moduleDependencies = depsFromFile
|
||||
.mapNotNull { depSpec ->
|
||||
val match = PEP_621_PATH_DEPENDENCY.matchEntire(depSpec) ?: return@mapNotNull null
|
||||
val (_, depUri) = match.destructured
|
||||
return@mapNotNull parseDepUri(depUri)
|
||||
}
|
||||
|
||||
val moduleDependenciesSet = getDependenciesFromToml(projectToml)
|
||||
val oldStyleModuleDependencies = projectToml.toml.getTableOrEmpty("tool.poetry.dependencies")
|
||||
.toMap().entries
|
||||
.mapNotNull { (_, depSpec) ->
|
||||
if (depSpec !is TomlTable || depSpec.getBoolean("develop") != true) return@mapNotNull null
|
||||
depSpec.getString("path")?.let { rootDir.resolve(it).normalize() }
|
||||
}
|
||||
return moduleDependencies.toSet() + oldStyleModuleDependencies.toSet()
|
||||
return moduleDependenciesSet + oldStyleModuleDependencies.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// e.g. "lib @ file:///home/user/projects/main/lib"
|
||||
private val PEP_621_PATH_DEPENDENCY = """([\w-]+) @ (file:.*)""".toRegex()
|
||||
|
||||
private val logger = fileLogger()
|
||||
private fun parseDepUri(depUri: String): Path? =
|
||||
try {
|
||||
URI(depUri).toPath()
|
||||
}
|
||||
catch (e: InvalidPathException) {
|
||||
logger.info("Dep $depUri points to wrong path", e)
|
||||
null
|
||||
}
|
||||
catch (e: URISyntaxException) {
|
||||
logger.info("Dep $depUri can't be parsed", e)
|
||||
null
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,9 @@ import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.execution.target.*
|
||||
import com.intellij.ide.projectView.actions.MarkRootsManager
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.application.*
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleUtil
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
@@ -43,13 +41,13 @@ import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.remote.PyRemoteSdkAdditionalData
|
||||
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil.isPythonSdk
|
||||
import com.jetbrains.python.sdk.add.v2.PathHolder
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.setReadyToUseSdk
|
||||
import com.jetbrains.python.sdk.flavors.PyFlavorAndData
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
|
||||
import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil.isPythonSdk
|
||||
import com.jetbrains.python.sdk.readOnly.PythonSdkReadOnlyProvider
|
||||
import com.jetbrains.python.sdk.skeleton.PySkeletonUtil
|
||||
import com.jetbrains.python.target.PyTargetAwareAdditionalData
|
||||
@@ -348,6 +346,7 @@ suspend fun PyDetectedSdk.setupAssociated(
|
||||
existingSdks: List<Sdk>,
|
||||
associatedModulePath: String?,
|
||||
doAssociate: Boolean,
|
||||
flavorAndData: PyFlavorAndData<*, *> = PyFlavorAndData.UNKNOWN_FLAVOR_DATA,
|
||||
): PyResult<Sdk> = withContext(Dispatchers.IO) {
|
||||
if (!sdkSeemsValid) {
|
||||
return@withContext PyResult.localizedError(PyBundle.message("python.sdk.error.invalid.interpreter.selected", homePath))
|
||||
@@ -370,10 +369,10 @@ suspend fun PyDetectedSdk.setupAssociated(
|
||||
else null
|
||||
|
||||
val data = targetEnvConfiguration?.let { targetConfig ->
|
||||
PyTargetAwareAdditionalData(PyFlavorAndData.UNKNOWN_FLAVOR_DATA).also {
|
||||
PyTargetAwareAdditionalData(flavorAndData).also {
|
||||
it.targetEnvironmentConfiguration = targetConfig
|
||||
}
|
||||
} ?: PythonSdkAdditionalData()
|
||||
} ?: PythonSdkAdditionalData(flavorAndData)
|
||||
|
||||
if (doAssociate && associatedModulePath != null) {
|
||||
data.associatedModulePath = associatedModulePath
|
||||
@@ -503,6 +502,20 @@ private fun Sdk.isLocatedInsideBaseDir(baseDir: Path?): Boolean {
|
||||
return FileUtil.isAncestor(basePath, homePath, true)
|
||||
}
|
||||
|
||||
@Internal
|
||||
@RequiresBackgroundThread
|
||||
fun PyDetectedSdk.pyvenvContains(pattern: String): Boolean = runReadAction {
|
||||
// TODO: Support for remote targets as well
|
||||
// (probably the best way is to prepare a helper python script to check config file and run using exec service)
|
||||
if (isTargetBased()) {
|
||||
return@runReadAction false
|
||||
}
|
||||
homeDirectory?.toNioPathOrNull()?.parent?.parent?.resolve("pyvenv.cfg")
|
||||
val pyvenvFile = homeDirectory?.parent?.parent?.findFile("pyvenv.cfg") ?: return@runReadAction false
|
||||
val text = FileDocumentManager.getInstance().getDocument(pyvenvFile)?.text ?: return@runReadAction false
|
||||
pattern in text
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
val PyDetectedSdk.guessedLanguageLevel: LanguageLevel?
|
||||
get() {
|
||||
@@ -612,4 +625,4 @@ val Sdk.sdkSeemsValid: Boolean
|
||||
val pythonSdkAdditionalData = getOrCreateAdditionalData()
|
||||
if (pythonSdkAdditionalData is PyRemoteSdkAdditionalData) return true
|
||||
return pythonSdkAdditionalData.flavorAndData.sdkSeemsValid(this, targetEnvConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,7 @@ import com.intellij.python.pyproject.PyProjectToml
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.jetbrains.python.*
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.Result.Companion.success
|
||||
import com.jetbrains.python.TraceContext
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.jetbrains.python.sdk.add.v2.pipenv
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.platform.eel.LocalEelApi
|
||||
import com.intellij.python.community.impl.pipenv.pipenvPath
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
@@ -12,7 +13,6 @@ import com.jetbrains.python.sdk.add.v2.FileSystem
|
||||
import com.jetbrains.python.sdk.add.v2.PathHolder
|
||||
import com.jetbrains.python.sdk.add.v2.PythonMutableTargetAddInterpreterModel
|
||||
import com.jetbrains.python.sdk.add.v2.ToolValidator
|
||||
import com.jetbrains.python.sdk.pipenv.pipEnvPath
|
||||
import com.jetbrains.python.sdk.pipenv.setupPipEnvSdkWithProgressReport
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import java.nio.file.Path
|
||||
@@ -27,7 +27,7 @@ internal class EnvironmentCreatorPip<P : PathHolder>(model: PythonMutableTargetA
|
||||
val savingPath = (pathHolder as? PathHolder.Eel)?.path
|
||||
?: (toolValidator.backProperty.get()?.pathHolder as? PathHolder.Eel)?.path
|
||||
savingPath?.let {
|
||||
PropertiesComponent.getInstance().pipEnvPath = it.toString()
|
||||
PropertiesComponent.getInstance().pipenvPath = it.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,4 +37,4 @@ internal class EnvironmentCreatorPip<P : PathHolder>(model: PythonMutableTargetA
|
||||
else -> PyResult.localizedError(PyBundle.message("target.is.not.supported", basePythonBinaryPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.sdk.conda
|
||||
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.progress.runBlockingMaybeCancellable
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.sdk.configuration.CreateSdkInfo
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@@ -35,11 +35,9 @@ interface PyCondaSdkCustomizer {
|
||||
get() = EP_NAME.extensionList.first()
|
||||
|
||||
@RequiresBackgroundThread
|
||||
@IntentionName
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
fun getIntentionBlocking(extension: PyProjectSdkConfigurationExtension, module: Module): String? =
|
||||
fun checkEnvironmentAndPrepareSdkCreatorBlocking(extension: PyProjectSdkConfigurationExtension, module: Module): CreateSdkInfo? =
|
||||
runBlockingMaybeCancellable {
|
||||
extension.getIntention(module)
|
||||
extension.checkEnvironmentAndPrepareSdkCreator(module)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,50 +17,43 @@ import com.intellij.openapi.util.use
|
||||
import com.intellij.openapi.wm.ex.WelcomeScreenProjectProvider
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.sdk.impl.PySdkBundle
|
||||
import com.jetbrains.python.PythonPluginDisposable
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||
import com.jetbrains.python.sdk.PySdkPopupFactory
|
||||
import com.jetbrains.python.sdk.configuration.suppressors.PyInterpreterInspectionSuppressor
|
||||
import com.jetbrains.python.sdk.configuration.suppressors.PyPackageRequirementsInspectionSuppressor
|
||||
import com.jetbrains.python.sdk.configuration.suppressors.TipOfTheDaySuppressor
|
||||
import com.jetbrains.python.sdk.configurePythonSdk
|
||||
import com.jetbrains.python.sdk.impl.PySdkBundle
|
||||
import com.jetbrains.python.statistics.ConfiguredPythonInterpreterIdsHolder.Companion.SDK_HAS_BEEN_CONFIGURED_AS_THE_PROJECT_INTERPRETER
|
||||
import com.jetbrains.python.util.ShowingMessageErrorSync
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
object PyProjectSdkConfiguration {
|
||||
fun configureSdkUsingExtension(module: Module, extension: PyProjectSdkConfigurationExtension) {
|
||||
val lifetime = suppressTipAndInspectionsFor(module, extension)
|
||||
fun configureSdkUsingCreateSdkInfo(module: Module, createSdkInfo: CreateSdkInfo) {
|
||||
val lifetime = suppressTipAndInspectionsFor(module, createSdkInfo.toolInfo.toolName)
|
||||
|
||||
val project = module.project
|
||||
PyPackageCoroutine.launch(project) {
|
||||
val title = extension.getIntention(module) ?: PySdkBundle.message("python.configuring.interpreter.progress")
|
||||
withBackgroundProgress(project, title, false) {
|
||||
lifetime.use {
|
||||
setSdkUsingExtension(module, extension) {
|
||||
withContext(Dispatchers.Default) {
|
||||
extension.createAndAddSdkForInspection(module)
|
||||
}
|
||||
}
|
||||
}
|
||||
withBackgroundProgress(project, createSdkInfo.intentionName, false) {
|
||||
lifetime.use { setSdkUsingCreateSdkInfo(module, createSdkInfo, false) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setSdkUsingExtension(module: Module, extension: PyProjectSdkConfigurationExtension, supplier: suspend () -> PyResult<Sdk?>): Boolean {
|
||||
thisLogger().debug("Configuring sdk with ${extension.javaClass.canonicalName} extension")
|
||||
suspend fun setSdkUsingCreateSdkInfo(
|
||||
module: Module, createSdkInfo: CreateSdkInfo, needsConfirmation: NeedsConfirmation,
|
||||
): Boolean = withContext(Dispatchers.Default) {
|
||||
thisLogger().debug("Configuring sdk using ${createSdkInfo.toolInfo.toolName}")
|
||||
|
||||
val sdk = supplier().getOr {
|
||||
val sdk = createSdkInfo.sdkCreator(needsConfirmation).getOr {
|
||||
ShowingMessageErrorSync.emit(it.error)
|
||||
return true
|
||||
} ?: return false
|
||||
return@withContext true
|
||||
} ?: return@withContext false
|
||||
|
||||
// TODO Move this to PyUvSdkConfiguration, show better notification
|
||||
setReadyToUseSdk(module.project, module, sdk)
|
||||
return true
|
||||
true
|
||||
}
|
||||
|
||||
fun setReadyToUseSdkSync(project: Project, module: Module, sdk: Sdk) {
|
||||
@@ -80,12 +73,12 @@ object PyProjectSdkConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
fun suppressTipAndInspectionsFor(module: Module, extension: PyProjectSdkConfigurationExtension): Disposable {
|
||||
fun suppressTipAndInspectionsFor(module: Module, toolName: String): Disposable {
|
||||
val project = module.project
|
||||
|
||||
val lifetime = Disposer.newDisposable(
|
||||
PythonPluginDisposable.getInstance(project),
|
||||
"Configuring sdk using ${extension.javaClass.name} extension"
|
||||
"Configuring sdk using $toolName"
|
||||
)
|
||||
|
||||
TipOfTheDaySuppressor.suppress()?.let { Disposer.register(lifetime, it) }
|
||||
|
||||
@@ -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.jetbrains.python.sdk.flavors.conda
|
||||
|
||||
import com.intellij.openapi.application.runReadAction
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.python.community.execService.BinaryToExec
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
@@ -72,9 +71,9 @@ sealed class NewCondaEnvRequest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun readEnvName(): String = runReadAction {
|
||||
val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(environmentYaml) ?: return@runReadAction DEFAULT_ENV_NAME
|
||||
CondaEnvironmentYmlParser.readNameFromFile(virtualFile) ?: DEFAULT_ENV_NAME
|
||||
private fun readEnvName(): String {
|
||||
val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(environmentYaml) ?: return DEFAULT_ENV_NAME
|
||||
return CondaEnvironmentYmlParser.readNameFromFile(virtualFile) ?: DEFAULT_ENV_NAME
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import org.jetbrains.annotations.ApiStatus.Internal
|
||||
object PipEnvFileHelper {
|
||||
const val PIP_FILE: String = "Pipfile"
|
||||
const val PIP_FILE_LOCK: String = "Pipfile.lock"
|
||||
const val PIPENV_PATH_SETTING: String = "PyCharm.Pipenv.Path"
|
||||
|
||||
fun getPipFileLock(sdk: Sdk): VirtualFile? =
|
||||
sdk.associatedModulePath?.let { StandardFileSystems.local().findFileByPath(it)?.findChild(PIP_FILE_LOCK) }
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.platform.eel.provider.asNioPath
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.intellij.platform.eel.where
|
||||
import com.intellij.python.community.impl.pipenv.pipenvPath
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
@@ -30,15 +31,6 @@ suspend fun runPipEnv(dirPath: Path?, vararg args: String): PyResult<String> {
|
||||
return runExecutableWithProgress(executable, dirPath, 10.minutes, args = args)
|
||||
}
|
||||
|
||||
/**
|
||||
* The user-set persisted a path to the pipenv executable.
|
||||
*/
|
||||
var PropertiesComponent.pipEnvPath: @SystemDependent String?
|
||||
get() = getValue(PipEnvFileHelper.PIPENV_PATH_SETTING)
|
||||
set(value) {
|
||||
setValue(PipEnvFileHelper.PIPENV_PATH_SETTING, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the pipenv executable in `$PATH`.
|
||||
*/
|
||||
@@ -66,7 +58,7 @@ fun detectPipEnvExecutableOrNull(): Path? {
|
||||
*/
|
||||
@Internal
|
||||
suspend fun getPipEnvExecutable(): PyResult<Path> =
|
||||
PropertiesComponent.getInstance().pipEnvPath?.let { PyResult.success(Path.of(it)) } ?: detectPipEnvExecutable()
|
||||
PropertiesComponent.getInstance().pipenvPath?.let { PyResult.success(Path.of(it)) } ?: detectPipEnvExecutable()
|
||||
|
||||
/**
|
||||
* Sets up the pipenv environment under the modal progress window.
|
||||
|
||||
Reference in New Issue
Block a user