[python] (PY-84946) catch RuntimeException in target execution

* too-wide catch block because some types like DockerRemoteRequest throw base RuntimeException instead of anything meaningful, need to change platform code first

GitOrigin-RevId: 869bd4892b82be39d42a082a83bf1191c8d404f2
This commit is contained in:
Vitaly Legchilkin
2025-10-22 08:32:38 +00:00
committed by intellij-monorepo-bot
parent 441ca725f9
commit c983a10dc4
10 changed files with 64 additions and 68 deletions
@@ -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()
})
@@ -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<P : PathHolder>(
sealed interface FileSystem<P : PathHolder> {
val isReadOnly: Boolean
val isBrowseable: Boolean
fun parsePath(raw: String): PyResult<P>
@@ -68,11 +75,13 @@ sealed interface FileSystem<P : PathHolder> {
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<PathHolder.Eel> {
override val isBrowseable: Boolean = true
override val isReadOnly: Boolean = false
override fun getBinaryToExec(path: PathHolder.Eel): BinaryToExec {
return BinOnEel(path.path)
}
@@ -172,6 +181,10 @@ sealed interface FileSystem<P : PathHolder> {
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<P : PathHolder> {
) : FileSystem<PathHolder.Target> {
override val isReadOnly: Boolean
get() = !PythonInterpreterTargetEnvironmentFactory.isMutable(targetEnvironmentConfiguration)
override val isBrowseable: Boolean
get() = targetEnvironmentConfiguration.getTargetType() is BrowsableTargetEnvironmentType
private val systemPythonCache = ArrayList<DetectedSelectableInterpreter<PathHolder.Target>>()
@@ -270,6 +285,13 @@ sealed interface FileSystem<P : PathHolder> {
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
}
}
}
@@ -59,7 +59,7 @@ internal class PythonAddCustomInterpreter<P : PathHolder>(
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<P : PathHolder>(
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) }
@@ -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)
@@ -338,23 +338,18 @@ internal class ValidatedPathField<T, P : PathHolder, VP : ValidatedPath<T, P>>(
private fun <T, P : PathHolder, V : ValidatedPath<T, P>> Panel.installToolRow(
fileSystem: FileSystem<*>,
missingExecutableText: @Nls String,
installAction: ActionLink? = null,
installAction: ActionLink,
validatedPathField: ValidatedPathField<T, P, V>,
): Row {
val selectExecutableLink = ActionLink(message("sdk.create.custom.select.executable.link")) {
val selectExecutableLink = if (fileSystem.isBrowseable) ActionLink(message("sdk.create.custom.select.executable.link")) {
validatedPathField.button.doClick()
}
val (firstFix, secondFix) = if (installAction == null || fileSystem.isReadOnly) {
Pair(selectExecutableLink, null)
}
else {
Pair(installAction, selectExecutableLink)
}
else null
return row("") {
validationTooltip(missingExecutableText,
firstFix,
secondFix,
installAction,
selectExecutableLink,
validationType = ValidationType.WARNING,
inline = true)
.align(Align.FILL)
@@ -379,7 +374,7 @@ internal fun <T, P : PathHolder, VP : ValidatedPath<T, P>> Panel.validatablePath
isFileSelectionMode = isFileSelectionMode,
)
missingExecutableText?.let {
if (missingExecutableText != null && installAction != null && !fileSystem.isReadOnly) {
installToolRow(
fileSystem = fileSystem,
missingExecutableText = missingExecutableText,
@@ -38,7 +38,7 @@ import java.awt.event.ActionEvent
import javax.swing.AbstractAction
internal class CondaExistingEnvironmentSelector<P : PathHolder>(model: PythonAddInterpreterModel<P>, private val errorSink: ErrorSink) : PythonExistingEnvironmentConfigurator<P>(model) {
internal class CondaExistingEnvironmentSelector<P : PathHolder>(model: PythonAddInterpreterModel<P>) : PythonExistingEnvironmentConfigurator<P>(model) {
private lateinit var envComboBox: ComboBox<PyCondaEnv?>
private lateinit var condaExecutable: ValidatedPathField<Version, P, ValidatedPath.Executable<P>>
private lateinit var reloadLink: ActionLink
@@ -53,7 +53,7 @@ internal class CondaExistingEnvironmentSelector<P : PathHolder>(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 {
@@ -23,7 +23,7 @@ import com.jetbrains.python.statistics.InterpreterType
import com.jetbrains.python.ui.flow.bindText
import kotlinx.coroutines.CoroutineScope
internal class CondaNewEnvironmentCreator<P: PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>, private val errorSink: ErrorSink) : PythonNewEnvironmentCreator<P>(model) {
internal class CondaNewEnvironmentCreator<P: PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>) : PythonNewEnvironmentCreator<P>(model) {
private lateinit var pythonVersion: ObservableMutableProperty<LanguageLevel>
private lateinit var versionComboBox: ComboBox<LanguageLevel>
@@ -49,7 +49,7 @@ internal class CondaNewEnvironmentCreator<P: PathHolder>(model: PythonMutableTar
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)
)
}
}
@@ -2,16 +2,11 @@
package com.jetbrains.python.sdk.add.v2.conda
import com.intellij.openapi.application.UI
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.openapi.diagnostic.getOrLogException
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.observable.properties.PropertyGraph
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.getOrLogException
import com.jetbrains.python.sdk.add.v2.*
import com.jetbrains.python.sdk.conda.suggestCondaPath
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
import kotlinx.coroutines.CoroutineScope
@@ -20,8 +15,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
private val LOG: Logger = fileLogger()
class CondaViewModel<P : PathHolder>(
val fileSystem: FileSystem<P>,
propertyGraph: PropertyGraph,
@@ -39,16 +32,7 @@ class CondaViewModel<P : PathHolder>(
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) {
@@ -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<PyCondaEnv> {
val baseConda = condaViewModel.baseCondaEnv.get()
return if (baseConda != null) PyResult.success(baseConda) else PyResult.localizedError(message("python.sdk.conda.no.base.env.error"))
@@ -4,6 +4,7 @@ package com.jetbrains.python.sdk.add.v2
import com.intellij.icons.AllIcons
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.application.EDT
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.observable.properties.PropertyGraph
@@ -14,6 +15,7 @@ import com.intellij.openapi.ui.validation.WHEN_PROPERTY_CHANGED
import com.intellij.openapi.ui.validation.and
import com.intellij.openapi.util.IconLoader
import com.intellij.openapi.util.NlsSafe
import com.intellij.platform.eel.provider.localEel
import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.python.community.impl.installer.CondaInstallManager
@@ -43,7 +45,11 @@ import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
import com.jetbrains.python.util.ShowingMessageErrorSync
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.NonNls
@@ -259,29 +265,19 @@ internal fun <P : PathHolder> 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<P : PathHolder>(
val fileSystem: FileSystem<P>,
private val errorSink: ErrorSink,
) : ComboBox<PythonSelectableInterpreter<P>?>() {
val isLoading: ObservableMutableProperty<Boolean> = AtomicBooleanProperty(true)
init {
renderer = PythonSdkComboBoxListCellRenderer { isBusy }
renderer = PythonSdkComboBoxListCellRenderer { isLoading.get() }
val newOnPathSelected: (String) -> Unit = { rawPath ->
runWithModalProgressBlocking(ModalTaskOwner.guess(), message("python.sdk.validating.environment")) {
val pathOnFileSystem = fileSystem.parsePath(rawPath).onFailure { error ->
@@ -331,6 +328,7 @@ internal class PythonInterpreterComboBox<P : PathHolder>(
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 <P : PathHolder> createInstallCondaFix(model: PythonAddInterpreterModel<P>, errorSink: ErrorSink): ActionLink {
internal fun <P : PathHolder> createInstallCondaFix(model: PythonAddInterpreterModel<P>): 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)