From 1c33aab97d15d85351f298d27556e2d5216b4654 Mon Sep 17 00:00:00 2001 From: "Egor.Eliseev" Date: Fri, 20 Dec 2024 15:19:05 +0200 Subject: [PATCH] PY-77381 Unable to select an existing Poetry environment as a project interpreter Add PoetryExistingEnvironmentSelector (cherry picked from commit 4fc75a24669d8a2fc12b5ff963cc5715ebc6ab3a) GitOrigin-RevId: b5ed7b45a3b4e27786bf899ab42f9ffe757d7f9d --- .../messages/PyBundle.properties | 1 + .../v2/CustomExistingEnvironmentSelector.kt | 85 +++++++++++++++++++ .../v2/PoetryExistingEnvironmentSelector.kt | 40 +++++++++ .../sdk/add/v2/PythonAddCustomInterpreter.kt | 9 +- .../com/jetbrains/python/sdk/add/v2/models.kt | 2 - .../sdk/poetry/PoetryCommandExecutor.kt | 4 +- 6 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 python/src/com/jetbrains/python/sdk/add/v2/CustomExistingEnvironmentSelector.kt create mode 100644 python/src/com/jetbrains/python/sdk/add/v2/PoetryExistingEnvironmentSelector.kt diff --git a/python/pluginResources/messages/PyBundle.properties b/python/pluginResources/messages/PyBundle.properties index d45fe0d4098d..b7af2fe72d9a 100644 --- a/python/pluginResources/messages/PyBundle.properties +++ b/python/pluginResources/messages/PyBundle.properties @@ -551,6 +551,7 @@ sdk.create.tooltip.browse=Browse\u2026 sdk.create.custom.venv.install.fix.title=Install {0} {1} sdk.create.custom.venv.run.error.message= Error Running {0} sdk.create.custom.venv.progress.title.detect.executable=Detect executable +sdk.create.custom.existing.env.title ={0} env use sdk.create.targets.local=Local Machine sdk.create.custom.virtualenv=Virtualenv diff --git a/python/src/com/jetbrains/python/sdk/add/v2/CustomExistingEnvironmentSelector.kt b/python/src/com/jetbrains/python/sdk/add/v2/CustomExistingEnvironmentSelector.kt new file mode 100644 index 000000000000..e381b67f7e54 --- /dev/null +++ b/python/src/com/jetbrains/python/sdk/add/v2/CustomExistingEnvironmentSelector.kt @@ -0,0 +1,85 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.python.sdk.add.v2 + +import com.intellij.openapi.observable.properties.ObservableMutableProperty +import com.intellij.openapi.observable.util.notEqualsTo +import com.intellij.openapi.ui.validation.DialogValidationRequestor +import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.dsl.builder.Panel +import com.jetbrains.python.PyBundle.message +import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo +import com.jetbrains.python.sdk.ModuleOrProject +import com.jetbrains.python.sdk.PySdkUtil +import com.jetbrains.python.sdk.PythonSdkUtil +import com.jetbrains.python.sdk.poetry.pyProjectToml +import com.jetbrains.python.statistics.InterpreterCreationMode +import com.jetbrains.python.statistics.InterpreterType +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import org.jetbrains.annotations.ApiStatus.Internal +import java.nio.file.Path +import java.util.* + + +@Internal +abstract class CustomExistingEnvironmentSelector(private val name: String, model: PythonMutableTargetAddInterpreterModel, protected val moduleOrProject: ModuleOrProject) : PythonExistingEnvironmentConfigurator(model) { + private lateinit var comboBox: PythonInterpreterComboBox + protected val existingEnvironments: MutableStateFlow> = MutableStateFlow(emptyList()) + protected val selectedEnv: ObservableMutableProperty = propertyGraph.property(null) + + init { + model.scope.launch { + val modulePath = when (moduleOrProject) { + is ModuleOrProject.ProjectOnly -> moduleOrProject.project.basePath?.let { Path.of(it) } + is ModuleOrProject.ModuleAndProject -> pyProjectToml(moduleOrProject.module)?.let { Path.of(it.parent.path) } + } + + if (modulePath != null) { + detectEnvironments(modulePath) + } + } + } + + override fun buildOptions(panel: Panel, validationRequestor: DialogValidationRequestor) { + with(panel) { + executableSelector( + executable, + validationRequestor, + message("sdk.create.custom.venv.executable.path", name), + message("sdk.create.custom.venv.missing.text", name), + ).component + + row(message("sdk.create.custom.existing.env.title", name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() })) { + comboBox = pythonInterpreterComboBox(selectedEnv, model, { path -> addEnvByPath(path) }, model.interpreterLoading) + .align(Align.FILL) + .component + }.visibleIf(executable.notEqualsTo("")) + } + } + + override fun onShown() { + comboBox.setItems(existingEnvironments) + } + + override fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo { + val statisticsTarget = target.toStatisticsField() + + return InterpreterStatisticsInfo(interpreterType, + statisticsTarget, + false, + false, + true, + false, + InterpreterCreationMode.CUSTOM) + } + + private fun addEnvByPath(path: String) { + val languageLevel = PySdkUtil.getLanguageLevelForSdk(PythonSdkUtil.findSdkByKey(path)) + val interpreter = ManuallyAddedSelectableInterpreter(path, languageLevel) + existingEnvironments.value += interpreter + } + + internal abstract val executable: ObservableMutableProperty + internal abstract val interpreterType: InterpreterType + internal abstract suspend fun detectEnvironments(modulePath: Path) +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PoetryExistingEnvironmentSelector.kt b/python/src/com/jetbrains/python/sdk/add/v2/PoetryExistingEnvironmentSelector.kt new file mode 100644 index 000000000000..37a1b523e514 --- /dev/null +++ b/python/src/com/jetbrains/python/sdk/add/v2/PoetryExistingEnvironmentSelector.kt @@ -0,0 +1,40 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.python.sdk.add.v2 + +import com.intellij.openapi.observable.properties.ObservableMutableProperty +import com.intellij.openapi.projectRoots.ProjectJdkTable +import com.intellij.openapi.projectRoots.Sdk +import com.jetbrains.python.sdk.ModuleOrProject +import com.jetbrains.python.sdk.poetry.detectPoetryEnvs +import com.jetbrains.python.sdk.poetry.isPoetry +import com.jetbrains.python.sdk.poetry.setupPoetrySdkUnderProgress +import com.jetbrains.python.statistics.InterpreterType +import com.jetbrains.python.statistics.version +import java.nio.file.Path +import kotlin.io.path.pathString + +class PoetryExistingEnvironmentSelector(model: PythonMutableTargetAddInterpreterModel, moduleOrProject: ModuleOrProject) : CustomExistingEnvironmentSelector("poetry", model, moduleOrProject) { + override val executable: ObservableMutableProperty = model.state.poetryExecutable + override val interpreterType: InterpreterType = InterpreterType.POETRY + + override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): Result { + val selectedInterpreter = selectedEnv.get() + ProjectJdkTable.getInstance().allJdks.find { sdk -> sdk.isPoetry && sdk.homePath == selectedInterpreter?.homePath }?.let { return Result.success(it) } + val module = when (moduleOrProject) { + is ModuleOrProject.ModuleAndProject -> { + moduleOrProject.module + } + else -> null + } + + return setupPoetrySdkUnderProgress(moduleOrProject.project, module, ProjectJdkTable.getInstance().allJdks.toList(), null, selectedInterpreter?.homePath, true) + } + + override suspend fun detectEnvironments(modulePath: Path) { + val existingEnvs = detectPoetryEnvs(null, null, modulePath.pathString).mapNotNull { + env -> env.homePath?.let { path -> DetectedSelectableInterpreter(path, env.version) } + } + + existingEnvironments.value = existingEnvs + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt index d687a00663b4..a3ae61f24237 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt @@ -34,10 +34,11 @@ class PythonAddCustomInterpreter(val model: PythonMutableTargetAddInterpreterMod UV to EnvironmentCreatorUv(model, moduleOrProject), ) - private val existingInterpreterSelectors = mapOf( - PYTHON to PythonExistingEnvironmentSelector(model), - CONDA to CondaExistingEnvironmentSelector(model, errorSink), - ) + private val existingInterpreterSelectors = buildMap { + put(PYTHON, PythonExistingEnvironmentSelector(model)) + put(CONDA, CondaExistingEnvironmentSelector(model, errorSink)) + if (moduleOrProject != null) put(POETRY, PoetryExistingEnvironmentSelector(model, moduleOrProject)) + } val currentSdkManager: PythonAddEnvironment get() { diff --git a/python/src/com/jetbrains/python/sdk/add/v2/models.kt b/python/src/com/jetbrains/python/sdk/add/v2/models.kt index 47b5f1669341..ddebc5809a14 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/models.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/models.kt @@ -274,8 +274,6 @@ open class AddInterpreterState(propertyGraph: PropertyGraph) { * Use [PythonAddInterpreterModel.getBaseCondaOrError] */ val baseCondaEnv: ObservableMutableProperty = propertyGraph.property(null) - - } class MutableTargetState(propertyGraph: PropertyGraph) : AddInterpreterState(propertyGraph) { diff --git a/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt b/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt index 304446edeaac..5cea4b1e61b0 100644 --- a/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt +++ b/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt @@ -154,9 +154,9 @@ internal fun runPoetryInBackground(module: Module, args: List, @NlsSafe } } -internal suspend fun detectPoetryEnvs(module: Module?, existingSdkPaths: Set, projectPath: @SystemIndependent @NonNls String?): List { +internal suspend fun detectPoetryEnvs(module: Module?, existingSdkPaths: Set?, projectPath: @SystemIndependent @NonNls String?): List { val path = module?.basePath?.let { Path.of(it) } ?: projectPath?.let { Path.of(it) } ?: return emptyList() - return getPoetryEnvs(path).filter { existingSdkPaths.contains(getPythonExecutable(it)) }.map { PyDetectedSdk(getPythonExecutable(it)) } + return getPoetryEnvs(path).filter { existingSdkPaths?.contains(getPythonExecutable(it)) != false }.map { PyDetectedSdk(getPythonExecutable(it)) } } internal suspend fun getPoetryVersion(): String? = runPoetry(null, "--version").getOrNull()?.split(' ')?.lastOrNull()