ExecService: all high-level sugar extensions to simplify usages

GitOrigin-RevId: ffc01ccc4c1c8f95820f3eb2b1b7c232cde81fee
This commit is contained in:
Ilya.Kazakevich
2025-06-04 21:08:29 +02:00
committed by intellij-monorepo-bot
parent 354eade715
commit 6953f5f6c0
7 changed files with 103 additions and 27 deletions

View File

@@ -4,4 +4,4 @@ py.exec.defaultName.helper=Helper
py.exec.start.error={0} Failed to Start: {1} (Code {2})
py.exec.timeout.error={0} Timed out (Run More Than {1})
py.exec.exitCode.error={0} Exited with {1}
py.exec.fileNotFound=File {0} not found on {1}

View File

@@ -3,14 +3,17 @@ package com.intellij.python.community.execService
import com.intellij.openapi.util.NlsSafe
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.getShell
import com.intellij.platform.eel.provider.asNioPath
import com.intellij.platform.eel.provider.utils.EelProcessExecutionResult
import com.intellij.platform.eel.provider.utils.stdoutString
import com.intellij.python.community.execService.impl.ExecServiceImpl
import com.intellij.python.community.execService.impl.PyExecBundle
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.ExecError
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.CheckReturnValue
import org.jetbrains.annotations.Nls
@@ -69,22 +72,6 @@ interface ExecService {
processOutputTransformer: ProcessOutputTransformer<T>,
): PyExecResult<T>
/**
* See [execute]
*/
@CheckReturnValue
suspend fun execGetStdout(
whatToExec: WhatToExec,
args: List<String> = emptyList(),
options: ExecOptions = ExecOptions(),
procListener: PyProcessListener? = null,
): PyExecResult<String> = execute(
whatToExec = whatToExec,
args = args,
options = options,
processOutputTransformer = ZeroCodeStdoutTransformer,
procListener = procListener
)
}
/**
@@ -124,4 +111,65 @@ sealed interface WhatToExec {
/**
* Default server implementation
*/
fun ExecService(): ExecService = ExecServiceImpl
fun ExecService(): ExecService = ExecServiceImpl
/**
* See [ExecService.execute]
*/
@CheckReturnValue
suspend fun ExecService.execGetStdout(
whatToExec: WhatToExec,
args: List<String> = emptyList(),
options: ExecOptions = ExecOptions(),
procListener: PyProcessListener? = null,
): PyExecResult<String> = execute(
whatToExec = whatToExec,
args = args,
options = options,
processOutputTransformer = ZeroCodeStdoutTransformer,
procListener = procListener
)
/**
* Execute [binaryName] on [eelApi].
* This [binaryName] will be searched in `PATH`
*/
@CheckReturnValue
suspend fun ExecService.execGetStdout(
eelApi: EelApi,
binaryName: String,
args: List<String> = emptyList(),
options: ExecOptions = ExecOptions(),
procListener: PyProcessListener? = null,
): PyResult<String> {
val whatToExec = WhatToExec.Binary.fromRelativeName(eelApi, binaryName)
?: return PyResult.localizedError(PyExecBundle.message("py.exec.fileNotFound", binaryName, eelApi.descriptor.userReadableDescription))
return execGetStdout(whatToExec, args, options, procListener)
}
/**
* Execute [binary] right directly on the eel it resides on.
*/
@CheckReturnValue
suspend fun ExecService.execGetStdout(
binary: Path,
args: List<String> = emptyList(),
options: ExecOptions = ExecOptions(),
procListener: PyProcessListener? = null,
): PyExecResult<String> = execGetStdout(WhatToExec.Binary(binary), args, options, procListener)
/**
* Execute [commandForShell] on [eelApi].
* Shell is `cmd` for Windows and Bourne Shell for POSIX.
*/
@CheckReturnValue
suspend fun ExecService.execGetStdoutInShell(
eelApi: EelApi,
commandForShell: String,
args: List<String> = emptyList(),
options: ExecOptions = ExecOptions(),
procListener: PyProcessListener? = null,
): PyExecResult<String> {
val (shell, arg) = eelApi.exec.getShell()
return execGetStdout(WhatToExec.Binary(shell.asNioPath()), listOf(arg, commandForShell) + args, options, procListener)
}

View File

@@ -2,7 +2,6 @@
package com.intellij.python.junit5Tests.unit.alsoWin
import com.intellij.platform.eel.EelPlatform
import com.intellij.platform.eel.getOrThrow
import com.intellij.platform.eel.getShell
import com.intellij.platform.eel.provider.asNioPath
import com.intellij.platform.eel.provider.utils.readWholeText
@@ -34,6 +33,39 @@ import kotlin.time.Duration.Companion.minutes
@TestApplicationWithEel(osesMayNotHaveRemoteEels = [OS.WINDOWS, OS.LINUX, OS.MAC])
class ExecServiceShowCaseTest {
@CartesianTest
fun testExecSimpleApi(
@EelSource eelHolder: EelHolder,
@CartesianTest.Values(booleans = [true, false]) rainyDay: Boolean,
@CartesianTest.Values(booleans = [true, false]) useShell: Boolean,
): Unit = timeoutRunBlocking(5.minutes) {
val eel = eelHolder.eel
val sut = ExecService()
val hello = "hello"
val r = if (useShell) {
sut.execGetStdoutInShell(eel, if (rainyDay) "abc123" else "echo $hello")
}
else {
val (binary, args) = when (eel.platform) {
is EelPlatform.Windows -> Pair("cmd.exe", arrayOf("/C", "echo $hello\r\nexit\r\n"))
is EelPlatform.Posix -> Pair("sh", arrayOf("-c", "echo $hello && exit"))
}
sut.execGetStdout(eel, if (rainyDay) "abc123" else binary, args.toList())
}
when (r) {
is Result.Failure -> {
assertTrue(rainyDay, "unexpected error ${r.error}")
}
is Result.Success -> {
assertFalse(rainyDay)
assertThat("No expected stdout", r.result, CoreMatchers.containsString(hello))
}
}
}
@ParameterizedTest
@EelSource
fun testDataTransformer(eelHolder: EelHolder): Unit = timeoutRunBlocking {

View File

@@ -3,10 +3,7 @@ package com.intellij.python.community.impl.venv
import com.intellij.openapi.application.EDT
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.python.community.execService.ExecOptions
import com.intellij.python.community.execService.ExecService
import com.intellij.python.community.execService.HelperName
import com.intellij.python.community.execService.WhatToExec
import com.intellij.python.community.execService.*
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult

View File

@@ -4,10 +4,7 @@ package com.jetbrains.python.sdk
import com.intellij.execution.process.AnsiEscapeDecoder
import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.platform.util.progress.reportRawProgress
import com.intellij.python.community.execService.ExecOptions
import com.intellij.python.community.execService.ExecService
import com.intellij.python.community.execService.ProcessEvent
import com.intellij.python.community.execService.WhatToExec
import com.intellij.python.community.execService.*
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyExecResult
import org.jetbrains.annotations.ApiStatus.Internal

View File

@@ -4,6 +4,7 @@ package com.jetbrains.python.sdk
import com.intellij.openapi.util.SystemInfo
import com.intellij.python.community.execService.ExecService
import com.intellij.python.community.execService.WhatToExec
import com.intellij.python.community.execService.execGetStdout
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult

View File

@@ -12,6 +12,7 @@ import com.intellij.openapi.util.registry.Registry
import com.intellij.python.community.execService.ExecOptions
import com.intellij.python.community.execService.ExecService
import com.intellij.python.community.execService.WhatToExec
import com.intellij.python.community.execService.execGetStdout
import com.intellij.python.community.impl.poetry.poetryPath
import com.intellij.util.SystemProperties
import com.jetbrains.python.PyBundle