diff --git a/python/python-exec-service/src/com/intellij/python/community/execService/impl/processLaunchers/targets.kt b/python/python-exec-service/src/com/intellij/python/community/execService/impl/processLaunchers/targets.kt index 791834635cad..2b8ec7d805c1 100644 --- a/python/python-exec-service/src/com/intellij/python/community/execService/impl/processLaunchers/targets.kt +++ b/python/python-exec-service/src/com/intellij/python/community/execService/impl/processLaunchers/targets.kt @@ -10,12 +10,12 @@ import com.intellij.execution.target.local.LocalTargetEnvironmentRequest import com.intellij.execution.target.local.LocalTargetPtyOptions import com.intellij.openapi.diagnostic.fileLogger import com.intellij.openapi.project.ProjectManager -import com.intellij.openapi.util.io.toNioPathOrNull import com.intellij.platform.eel.provider.utils.ProcessFunctions import com.intellij.platform.eel.provider.utils.bindProcessToScopeImpl import com.intellij.python.community.execService.BinOnTarget import com.intellij.python.community.execService.ExecuteGetProcessError import com.intellij.python.community.execService.spi.TargetEnvironmentRequestHandler +import com.intellij.remoteServer.util.ServerRuntimeException import com.jetbrains.python.Result import com.jetbrains.python.errorProcessing.Exe import com.jetbrains.python.errorProcessing.ExecErrorReason @@ -24,8 +24,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext -import com.intellij.remoteServer.util.ServerRuntimeException -import java.nio.file.Path import kotlin.io.path.pathString import kotlin.time.Duration.Companion.milliseconds @@ -54,6 +52,10 @@ internal suspend fun createProcessLauncherOnTarget(binOnTarget: BinOnTarget, lau val targetEnv = try { request.prepareEnvironment(TargetProgressIndicator.EMPTY) } + catch (e: RuntimeException) { // some types like DockerRemoteRequest throw base RuntimeException instead of anything meaningful, need to change platform code first + fileLogger().warn("Failed to start $target", e) // TODO: i18n + return@withContext Result.failure(ExecuteGetProcessError.EnvironmentError(MessageError("Failed to start environment due to ${e.localizedMessage}"))) + } catch (e: ExecutionException) { fileLogger().warn("Failed to start $target", e) // TODO: i18n return@withContext Result.failure(ExecuteGetProcessError.EnvironmentError(MessageError("Failed to start environment due to ${e.localizedMessage}"))) @@ -104,7 +106,7 @@ private class TargetProcessCommands( } targetEnv.shutdown() }, killProcess = { - process?.destroyForcibly(); + process?.destroyForcibly() targetEnv.shutdown() }) diff --git a/python/src/com/jetbrains/python/sdk/add/v2/FileSystem.kt b/python/src/com/jetbrains/python/sdk/add/v2/FileSystem.kt index cacb1d191df2..444cf9a15064 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/FileSystem.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/FileSystem.kt @@ -1,18 +1,24 @@ // 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.add.v2 +import com.intellij.execution.target.BrowsableTargetEnvironmentType import com.intellij.execution.target.TargetEnvironmentConfiguration +import com.intellij.execution.target.getTargetType import com.intellij.execution.target.joinTargetPaths import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.fileLogger import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.util.io.FileUtil import com.intellij.platform.eel.EelApi +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.execService.Args import com.intellij.python.community.execService.BinOnEel import com.intellij.python.community.execService.BinOnTarget import com.intellij.python.community.execService.BinaryToExec import com.intellij.python.community.execService.ExecService +import com.intellij.python.community.execService.execGetStdout import com.intellij.python.community.execService.python.validatePythonAndGetInfo import com.intellij.python.community.services.internal.impl.VanillaPythonWithPythonInfoImpl import com.intellij.python.community.services.shared.VanillaPythonWithPythonInfo @@ -55,6 +61,7 @@ internal class VenvAlreadyExistsError
( sealed interface FileSystem
{ val isReadOnly: Boolean + val isBrowseable: Boolean fun parsePath(raw: String): PyResult
@@ -68,11 +75,13 @@ sealed interface FileSystem
{
suspend fun resolvePythonBinary(pythonHome: P): P?
fun getBinaryToExec(path: P): BinaryToExec
+ suspend fun which(cmd: String): P?
data class Eel(
val eelApi: EelApi,
- override val isReadOnly: Boolean = false,
) : FileSystem {
override suspend fun resolvePythonBinary(pythonHome: PathHolder.Eel): PathHolder.Eel? {
return pythonHome.path.resolvePythonBinary()?.let { PathHolder.Eel(it) }
}
+
+ override suspend fun which(cmd: String): PathHolder.Eel? {
+ return eelApi.exec.where(cmd)?.asNioPath()?.let { PathHolder.Eel(it) }
+ }
}
data class Target(
@@ -180,6 +193,8 @@ sealed interface FileSystem {
) : FileSystem {
return pythonBinaryPath
}
+
+ override suspend fun which(cmd: String): PathHolder.Target? {
+ val which = getBinaryToExec(PathHolder.Target("which"))
+ val condaPathString = ExecService().execGetStdout(which, Args(cmd)).getOr { return null }
+ val condaPathOnFS = parsePath(condaPathString).getOr { return null }
+ return condaPathOnFS
+ }
}
}
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 8eed1fe8dbd2..dfb14aa198eb 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt
@@ -59,7 +59,7 @@ internal class PythonAddCustomInterpreter (
private val newInterpreterCreators = if (model.fileSystem.isReadOnly) emptyMap()
else mapOf(
VIRTUALENV to { EnvironmentCreatorVenv(model) },
- CONDA to { CondaNewEnvironmentCreator(model, errorSink) },
+ CONDA to { CondaNewEnvironmentCreator(model) },
PIPENV to { EnvironmentCreatorPip(model, errorSink) },
POETRY to { EnvironmentCreatorPoetry(model, module, errorSink) },
UV to { EnvironmentCreatorUv(model, module, errorSink) },
@@ -68,7 +68,7 @@ internal class PythonAddCustomInterpreter (
private val existingInterpreterSelectors = buildMap {
put(PYTHON) { PythonExistingEnvironmentSelector(model, module) }
- put(CONDA) { CondaExistingEnvironmentSelector(model, errorSink) }
+ put(CONDA) { CondaExistingEnvironmentSelector(model) }
if (!limitExistingEnvironments) {
put(POETRY) { PoetryExistingEnvironmentSelector(model, module) }
put(UV) { UvExistingEnvironmentSelector(model, module) }
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonSdkPanelBuilderAndSdkCreator.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonSdkPanelBuilderAndSdkCreator.kt
index d8d25964bfa2..138792baab11 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonSdkPanelBuilderAndSdkCreator.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonSdkPanelBuilderAndSdkCreator.kt
@@ -133,7 +133,7 @@ internal class PythonSdkPanelBuilderAndSdkCreator(
validationRequestor = validationRequestor,
labelText = message("sdk.create.custom.venv.executable.path", "conda"),
missingExecutableText = message("sdk.create.custom.venv.missing.text", "conda"),
- installAction = createInstallCondaFix(model, errorSink),
+ installAction = createInstallCondaFix(model),
)
}.visibleIf(_baseConda)
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/ValidatedPathField.kt b/python/src/com/jetbrains/python/sdk/add/v2/ValidatedPathField.kt
index 18ca44ab84f6..a2d649f24854 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/ValidatedPathField.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/ValidatedPathField.kt
@@ -338,23 +338,18 @@ internal class ValidatedPathField (model: PythonAddInterpreterModel , private val errorSink: ErrorSink) : PythonExistingEnvironmentConfigurator (model) {
+internal class CondaExistingEnvironmentSelector (model: PythonAddInterpreterModel ) : PythonExistingEnvironmentConfigurator (model) {
private lateinit var envComboBox: ComboBox (model: PythonAdd
validationRequestor = validationRequestor,
labelText = message("sdk.create.custom.venv.executable.path", "conda"),
missingExecutableText = message("sdk.create.custom.venv.missing.text", "conda"),
- installAction = createInstallCondaFix(model, errorSink)
+ installAction = createInstallCondaFix(model)
)
rowsRange {
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/conda/CondaNewEnvironmentCreator.kt b/python/src/com/jetbrains/python/sdk/add/v2/conda/CondaNewEnvironmentCreator.kt
index f250c3855da9..90e2ac4188d4 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/conda/CondaNewEnvironmentCreator.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/conda/CondaNewEnvironmentCreator.kt
@@ -23,7 +23,7 @@ import com.jetbrains.python.statistics.InterpreterType
import com.jetbrains.python.ui.flow.bindText
import kotlinx.coroutines.CoroutineScope
-internal class CondaNewEnvironmentCreator , private val errorSink: ErrorSink) : PythonNewEnvironmentCreator (model) {
+internal class CondaNewEnvironmentCreator ) : PythonNewEnvironmentCreator (model) {
private lateinit var pythonVersion: ObservableMutableProperty (
val fileSystem: FileSystem ,
propertyGraph: PropertyGraph,
@@ -39,16 +32,7 @@ class CondaViewModel (
toolVersionPrefix = "conda",
backProperty = condaExecutable,
propertyGraph = propertyGraph,
- defaultPathSupplier = {
- val targetEnvironmentConfiguration = (fileSystem as? FileSystem.Target)?.targetEnvironmentConfiguration
- val executor = targetEnvironmentConfiguration.toExecutor()
- val suggestedCondaPath = runCatching {
- suggestCondaPath(targetCommandExecutor = executor)
- }.getOrLogException(LOG)
-
- val condaPathOnFS = suggestedCondaPath?.let { fileSystem.parsePath(suggestedCondaPath).getOrLogException(LOG) }
- condaPathOnFS
- }
+ defaultPathSupplier = { fileSystem.which("conda") }
)
override fun initialize(scope: CoroutineScope) {
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/conda/condaUtils.kt b/python/src/com/jetbrains/python/sdk/add/v2/conda/condaUtils.kt
index d5cbc4e96a73..baee771836d0 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/conda/condaUtils.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/conda/condaUtils.kt
@@ -1,8 +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.add.v2.conda
-import com.intellij.execution.target.TargetEnvironmentConfiguration
-import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
import com.intellij.openapi.application.EDT
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
@@ -52,11 +50,6 @@ internal suspend fun PythonAddInterpreterModel<*>.createCondaEnvironment(moduleO
return result
}
-internal fun TargetEnvironmentConfiguration?.toExecutor(): TargetCommandExecutor {
- return TargetEnvironmentRequestCommandExecutor(this?.createEnvironmentRequest(project = null) ?: LocalTargetEnvironmentRequest())
-}
-
-
internal fun PythonAddInterpreterModel<*>.getBaseCondaOrError(): PyResult Panel.pythonInterpreterComboBox(
preferredSize = JBUI.size(preferredSize)
isEditable = true
}
- .validationRequestor(validationRequestor and WHEN_PROPERTY_CHANGED(selectedSdkProperty))
+ .validationRequestor(
+ validationRequestor
+ and WHEN_PROPERTY_CHANGED(selectedSdkProperty)
+ and WHEN_PROPERTY_CHANGED(comboBox.isLoading)
+ )
.validationInfo {
when {
- !comboBox.isVisible -> null
- selectedSdkProperty.get() == null -> {
- if (comboBox.isBusy) {
- ValidationInfo(message("python.add.sdk.panel.wait"))
- }
- else {
- ValidationInfo(message("sdk.create.custom.existing.error.no.interpreters.to.select"))
- }
- }
+ !it.isVisible -> null
+ it.isLoading.get() -> ValidationInfo(message("python.add.sdk.panel.wait"))
+ selectedSdkProperty.get() == null -> ValidationInfo(message("sdk.create.custom.existing.error.no.interpreters.to.select"))
else -> null
}
}
- .validationOnApply {
- if (!comboBox.isVisible) return@validationOnApply null
- // This component must set sdk: clients expect it not to be null (PY-77463)
- if (comboBox.isBusy || selectedSdkProperty.get() == null) {
- ValidationInfo(message("python.add.sdk.panel.wait"))
- }
- else null
- }
.align(Align.FILL)
}
}.also { customizer(it) }
@@ -294,9 +290,10 @@ internal class PythonInterpreterComboBox (
val fileSystem: FileSystem ,
private val errorSink: ErrorSink,
) : ComboBox (
selectedItemReminder?.let { selectedItem = it }
setBusy(false)
+ isLoading.set(false)
}.launchIn(scope + Dispatchers.EDT)
}
@@ -420,7 +418,9 @@ private fun ExtendableTextComponent.removeLoadingExtension() {
removeExtension(loaderExtension)
}
-internal fun createInstallCondaFix(model: PythonAddInterpreterModel , errorSink: ErrorSink): ActionLink {
+internal fun createInstallCondaFix(model: PythonAddInterpreterModel ): ActionLink? {
+ if ((model.fileSystem as? FileSystem.Eel)?.eelApi != localEel) return null
+
return ActionLink(message("sdk.create.custom.venv.install.fix.title", "Miniconda", "")) {
PythonSdkFlavor.clearExecutablesCache()
CondaInstallManager.installLatest(null)