PY-82326: Preparation for it (WIP): support ExecError for targets.

SSH is now target-based and we can't use `EelPath` with it. So we now have two types of `ExecError.exe`.

Test is going to be created as next commit

GitOrigin-RevId: 6bc56eb9770caeb7ba32c8f43e6f374fcf5a3325
This commit is contained in:
Ilya.Kazakevich
2025-07-01 08:05:21 +00:00
committed by intellij-monorepo-bot
parent 4dc8c380e3
commit 4964cc3a7d
7 changed files with 47 additions and 20 deletions
@@ -4,18 +4,45 @@ package com.jetbrains.python.errorProcessing
import com.intellij.execution.process.ProcessOutput
import com.intellij.openapi.util.NlsContexts
import com.intellij.platform.eel.path.EelPath
import com.intellij.platform.eel.path.EelPathException
import com.intellij.platform.eel.provider.asEelPath
import com.intellij.platform.eel.provider.utils.EelProcessExecutionResult
import com.intellij.platform.eel.provider.utils.EelProcessExecutionResultInfo
import com.intellij.platform.eel.provider.utils.stderrString
import com.intellij.platform.eel.provider.utils.stdoutString
import com.jetbrains.python.PyCommunityBundle
import org.jetbrains.annotations.Nls
import kotlin.io.path.Path
/**
* Exe might sit on eel (new one) or on target (legacy)
*/
interface Exe {
companion object {
fun fromString(path: String): Exe {
try {
return OnEel(Path(path).asEelPath())
}
catch (_: EelPathException) {
return OnTarget(path)
}
}
}
data class OnEel(val eelPath: EelPath) : Exe {
override fun toString(): String = eelPath.toString()
}
data class OnTarget(val path: String) : Exe {
override fun toString(): String = path
}
}
/**
* External process error.
*/
class ExecError(
val exe: EelPath,
val exe: Exe,
/**
* I.e ['-v']
*/
@@ -4,11 +4,9 @@ package com.jetbrains.python.packaging
import com.intellij.execution.ExecutionException
import com.intellij.execution.process.ProcessOutput
import com.intellij.openapi.util.NlsContexts
import com.intellij.platform.eel.provider.asEelPath
import com.jetbrains.python.errorProcessing.*
import org.jetbrains.annotations.ApiStatus
import java.io.IOException
import kotlin.io.path.Path
/**
* Wraps [PyError] for cases where [ExecutionException] is used.
@@ -31,7 +29,7 @@ class PyExecutionException private constructor(
additionalMessageToUser: @NlsContexts.DialogMessage String?,
command: String,
args: List<String>,
): PyExecutionException = PyExecutionException(ExecError(Path(command).asEelPath(), args.toTypedArray(), ExecErrorReason.Timeout, additionalMessageToUser))
): PyExecutionException = PyExecutionException(ExecError(Exe.fromString(command), args.toTypedArray(), ExecErrorReason.Timeout, additionalMessageToUser))
}
@@ -65,7 +63,7 @@ class PyExecutionException private constructor(
args: List<String>,
fixes: List<PyExecutionFix> = listOf<PyExecutionFix>(),
) : this(
pyError = ExecError(Path(command).asEelPath(), args.toTypedArray(), ExecErrorReason.CantStart(null, startException.localizedMessage), additionalMessage),
pyError = ExecError(Exe.fromString(command), args.toTypedArray(), ExecErrorReason.CantStart(null, startException.localizedMessage), additionalMessage),
fixes = fixes,
ioException = startException)
@@ -84,7 +82,7 @@ class PyExecutionException private constructor(
output: ProcessOutput,
fixes: List<PyExecutionFix> = listOf<PyExecutionFix>(),
) : this(
pyError = ExecError(Path(command).asEelPath(), args.toTypedArray(), output.asExecutionFailed()),
pyError = ExecError(Exe.fromString(command), args.toTypedArray(), output.asExecutionFailed()),
fixes = fixes)
/**
@@ -85,7 +85,7 @@ private suspend fun EelExecutableProcess.run(): PyExecResult<EelProcess> {
private fun EelExecutableProcess.failAsCantStart(executeProcessError: ExecuteProcessException): Result.Failure<ExecError> {
return ExecError(
exe = exe,
exe = Exe.OnEel(exe),
args = args.toTypedArray(),
additionalMessageToUser = PyExecBundle.message("py.exec.start.error", description, executeProcessError.message, executeProcessError.errno),
errorReason = ExecErrorReason.CantStart(executeProcessError.errno, executeProcessError.message)
@@ -96,7 +96,7 @@ private suspend fun EelExecutableProcess.killProcessAndFailAsTimeout(eelProcess:
eelProcess.kill()
return ExecError(
exe = exe,
exe = Exe.OnEel( exe),
args = args.toTypedArray(),
additionalMessageToUser = PyExecBundle.message("py.exec.timeout.error", description, timeout),
errorReason = ExecErrorReason.Timeout
@@ -109,7 +109,7 @@ private fun EelExecutableProcess.failAsExecutionFailed(processOutput: ExecErrorR
}
return ExecError(
exe = exe,
exe = Exe.OnEel(exe),
args = args.toTypedArray(),
additionalMessageToUser = additionalMessage,
errorReason = processOutput
@@ -13,6 +13,7 @@ import com.intellij.platform.testFramework.junit5.eel.params.api.TestApplication
import com.intellij.python.community.execService.*
import com.intellij.testFramework.common.timeoutRunBlocking
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.Exe
import com.jetbrains.python.errorProcessing.ExecError
import com.jetbrains.python.getOrThrow
import kotlinx.coroutines.*
@@ -195,7 +196,7 @@ class ExecServiceShowCaseTest {
is Result.Failure -> {
assertFalse(sunny, "Unexpected failure ${result.error}")
assertThat("Wrong message to user", result.error.message, CoreMatchers.containsString(messageToUser))
assertEquals(shell, (result.error as ExecError).exe.asNioPath(), "Wrong exe")
assertEquals(shell, (result.error.exe as Exe.OnEel).eelPath.asNioPath(), "Wrong exe")
}
is Result.Success -> {
assertTrue(sunny, "Unexpected success")
@@ -213,7 +214,7 @@ class ExecServiceShowCaseTest {
is Result.Success -> fail("Execution of bad command should lead to an error")
is Result.Failure -> {
val err = (output.error as ExecError)
assertEquals(binary, err.exe.asNioPath(), "Wrong command reported")
assertEquals(binary, (err.exe as Exe.OnEel).eelPath.asNioPath(), "Wrong command reported")
assertEquals("foo", err.args[0], "Wrong args reported")
}
}
@@ -2,14 +2,13 @@
package com.jetbrains.python.packaging
import com.intellij.execution.process.ProcessOutput
import com.intellij.platform.eel.provider.asEelPath
import com.intellij.platform.eel.provider.utils.stderrString
import com.intellij.platform.eel.provider.utils.stdoutString
import com.jetbrains.python.errorProcessing.Exe
import com.jetbrains.python.errorProcessing.ExecError
import com.jetbrains.python.errorProcessing.ExecErrorReason
import com.jetbrains.python.errorProcessing.MessageError
import java.io.IOException
import kotlin.io.path.Path
/**
* A temporary hack for some outdated code (see usages), do not use in a new code. Stay away from [PyExecutionException]
@@ -22,7 +21,7 @@ internal fun PyExecutionException.copyWith(newCommand: String, newArgs: List<Str
PyExecutionException(IOException(reason.cantExecProcessError), err.message, newCommand, newArgs, fixes)
}
ExecErrorReason.Timeout -> {
PyExecutionException(ExecError(Path(newCommand).asEelPath(), newArgs.toTypedArray(), ExecErrorReason.Timeout, err.message))
PyExecutionException(ExecError(Exe.fromString(newCommand), newArgs.toTypedArray(), ExecErrorReason.Timeout, err.message))
}
is ExecErrorReason.UnexpectedProcessTermination -> {
val output = ProcessOutput(reason.stdoutString, reason.stderrString, reason.exitCode, false, false)
@@ -36,13 +36,15 @@ import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus
import kotlin.math.min
internal class PipPackageManagerEngine(
@ApiStatus.Internal
class PipPackageManagerEngine(
private val project: Project,
private val sdk: Sdk,
) : PythonPackageManagerEngine {
override suspend fun installPackageCommand(installRequest: PythonPackageInstallRequest, options: List<String>): PyResult<Unit> {
val manager = PythonPackageManager.forSdk(project, sdk)
PipManagementInstaller(sdk, manager).installManagementIfNeeded()
PipManagementInstaller(sdk, manager).installManagementIfNeeded()
val result = runPackagingTool(
operation = "install",
arguments = installRequest.indexUrlIfApplicable() + options
@@ -19,6 +19,7 @@ import com.intellij.webcore.packaging.PackageManagementServiceEx;
import com.intellij.webcore.packaging.RepoPackage;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PySdkBundle;
import com.jetbrains.python.errorProcessing.Exe;
import com.jetbrains.python.errorProcessing.ExecError;
import com.jetbrains.python.errorProcessing.ExecErrorReason;
import com.jetbrains.python.packaging.*;
@@ -35,7 +36,6 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.regex.Matcher;
@@ -195,7 +195,7 @@ public class PyPackageManagementService extends PackageManagementServiceEx {
extraArgs.add("-U");
}
final PyRequirement req = version == null
? PyRequirementsKt.pyRequirement(packageName,null)
? PyRequirementsKt.pyRequirement(packageName, null)
: PyRequirementsKt.pyRequirement(packageName, PyRequirementRelation.EQ, version);
final PyPackageManagerUI ui = new PyPackageManagerUI(myProject, mySdk, new PyPackageManagerUI.Listener() {
@@ -358,8 +358,8 @@ public class PyPackageManagementService extends PackageManagementServiceEx {
}
}
var fileName = e.getExe().getFileName();
if (fileName.startsWith("pip") && sdk != null) {
var fileName = e.getExe();
if (fileName instanceof Exe.OnEel exeOnEel && exeOnEel.getEelPath().getFileName().startsWith("pip") && sdk != null) {
return PySdkBundle.message("python.sdk.try.to.run.command.from.system.terminal", sdk.getHomePath());
}
}