mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[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:
committed by
intellij-monorepo-bot
parent
441ca725f9
commit
c983a10dc4
+6
-4
@@ -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,
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user