From a3c2ae48b22c724919827bd9283dddb68e45c2d6 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Sun, 4 May 2025 05:19:16 +0200 Subject: [PATCH] PY-80873: Revert some parts of `4e76993685f02f90cb2e4d0d8e8384aa47102eb9` until further discussion. As part of `4e76993685f02f90cb2e4d0d8e8384aa47102eb9` we migrated execution to a new API which doesn't report progress. This progress is a crucial part of PY-79958, so we revert it for now. GitOrigin-RevId: 529b5b701af49936084e06b81835d315442c2684 --- .../python/sdk/PySdkCommandRunner.kt | 92 +++++++++++++++++-- .../sdk/PySdkPackageInstallationUtils.kt | 21 ++--- .../sdk/add/v2/CustomNewEnvironmentCreator.kt | 3 +- .../sdk/pipenv/PipenvCommandExecutor.kt | 3 +- .../sdk/poetry/PoetryCommandExecutor.kt | 3 +- .../com/jetbrains/python/sdk/uv/impl/UvCli.kt | 3 +- 6 files changed, 96 insertions(+), 29 deletions(-) diff --git a/python/src/com/jetbrains/python/sdk/PySdkCommandRunner.kt b/python/src/com/jetbrains/python/sdk/PySdkCommandRunner.kt index c41b00731777..913f21cef115 100644 --- a/python/src/com/jetbrains/python/sdk/PySdkCommandRunner.kt +++ b/python/src/com/jetbrains/python/sdk/PySdkCommandRunner.kt @@ -1,12 +1,63 @@ // 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 -import com.intellij.python.community.execService.ExecOptions -import com.intellij.python.community.execService.ExecService -import com.intellij.python.community.execService.WhatToExec -import com.jetbrains.python.errorProcessing.PyExecResult +import com.intellij.execution.ExecutionException +import com.intellij.execution.RunCanceledByUserException +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.process.ProcessOutput +import com.intellij.openapi.diagnostic.fileLogger +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.coroutineToIndicator +import com.intellij.openapi.util.NlsContexts +import com.jetbrains.python.packaging.PyExecutionException +import com.jetbrains.python.packaging.conda.PyPackageProcessHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.jetbrains.annotations.ApiStatus.Internal import java.nio.file.Path +import kotlin.io.path.absolutePathString + +private val logger = fileLogger() + +/** + * Runs a command line operation in a background thread. + * + * @param [commandLine] The command line to execute. + * @return A [Result] object containing the output of the command execution. + */ +internal suspend fun runCommandLine(commandLine: GeneralCommandLine): Result { + logger.info("Running command: ${commandLine.commandLineString}") + try { + val capturingProcessHandler = PyPackageProcessHandler(commandLine) + + val commandOutput = with(capturingProcessHandler) { + withContext(Dispatchers.IO) { + coroutineToIndicator { + val progressIndicator = ProgressManager.getInstance().progressIndicator + capturingProcessHandler.lastLineNotifier = { line -> + @Suppress("HardCodedStringLiteral") + progressIndicator.text = line + } + runProcessWithProgressIndicator(progressIndicator) + } + } + } + + return processOutput( + commandOutput, + commandLine.exePath, + commandLine.parametersList.list, + ) + } + catch (e: ExecutionException) { + return Result.failure(PyExecutionException( + startException = e.toIOException(), + additionalMessage = null, + command = commandLine.exePath, + args = commandLine.parametersList.list + )) + } +} /** * Executes a given executable with specified arguments within an optional project directory. @@ -17,5 +68,34 @@ import java.nio.file.Path * @return A [Result] object containing the output of the command execution. */ @Internal -suspend fun runExecutable(executable: Path, projectPath: Path?, vararg args: String): PyExecResult = - ExecService().execGetStdout(WhatToExec.Binary(executable), args.toList(), ExecOptions(workingDirectory = projectPath)) \ No newline at end of file +suspend fun runExecutable(executable: Path, projectPath: Path?, vararg args: String): Result { + val commandLine = GeneralCommandLine(listOf(executable.absolutePathString()) + args).withWorkingDirectory(projectPath) + return runCommandLine(commandLine) +} + + +/** + * Processes the output of a command execution. + * + * @param[output] the output of the executed command. + * @param[commandString] the command string that was executed. + * @param[args] the arguments passed to the command. + * @param[errorMessage] the error message to be used if the command fails. + * @return A [Result] object containing the processed output. + */ +internal fun processOutput( + output: ProcessOutput, + commandString: String, + args: List, + @NlsContexts.DialogMessage errorMessage: String = "", +): Result { + return with(output) { + when { + isCancelled -> + Result.failure(RunCanceledByUserException()) + exitCode != 0 -> + Result.failure(PyExecutionException(errorMessage, commandString, args, stdout, stderr, exitCode, emptyList())) + else -> Result.success(output.stdout.trim()) + } + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/PySdkPackageInstallationUtils.kt b/python/src/com/jetbrains/python/sdk/PySdkPackageInstallationUtils.kt index fa998b3592ad..cb0bb8236990 100644 --- a/python/src/com/jetbrains/python/sdk/PySdkPackageInstallationUtils.kt +++ b/python/src/com/jetbrains/python/sdk/PySdkPackageInstallationUtils.kt @@ -1,19 +1,12 @@ // 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 +import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.openapi.util.SystemInfo -import com.intellij.platform.eel.path.EelPath -import com.intellij.platform.eel.provider.asEelPath -import com.intellij.platform.eel.provider.asNioPath -import com.intellij.platform.eel.provider.getEelDescriptor -import com.intellij.platform.eel.provider.utils.EelPathUtils -import com.intellij.python.community.execService.ExecService -import com.intellij.python.community.execService.WhatToExec -import com.jetbrains.python.PythonBinary -import com.jetbrains.python.Result -import com.jetbrains.python.errorProcessing.PyResult import org.jetbrains.annotations.ApiStatus.Internal import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.pathString /** * Returns the string representation of the Python executable ("py" on Windows or "python" on Unix-like OS) based on the current system. @@ -32,9 +25,7 @@ fun getPythonExecutableString(): String = if (SystemInfo.isWindows) "py" else "p * @return executable [Path] */ @Internal -suspend fun installExecutableViaPythonScript(scriptPath: Path, pythonExecutable: PythonBinary, vararg args: String): PyResult { - val eel = pythonExecutable.getEelDescriptor().upgrade() - val scriptPath = EelPathUtils.transferLocalContentToRemote(scriptPath, EelPathUtils.TransferTarget.Temporary(eel.descriptor)).asEelPath() - val result = ExecService().execGetStdout(WhatToExec.Binary(pythonExecutable), listOf(scriptPath.toString()) + args.toList()).getOr { return it } - return Result.success(EelPath.parse(result.trim().split("\n").last(), eel.descriptor).asNioPath()) +suspend fun installExecutableViaPythonScript(scriptPath: Path, pythonExecutable: Path, vararg args: String): Result { + val result = runCommandLine(GeneralCommandLine(pythonExecutable.pathString, scriptPath.absolutePathString(), *args)).getOrElse { return Result.failure(it) } + return Result.success(Path.of(result.split("\n").last())) } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/add/v2/CustomNewEnvironmentCreator.kt b/python/src/com/jetbrains/python/sdk/add/v2/CustomNewEnvironmentCreator.kt index 7c9417a411ce..3707491bc6b8 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/CustomNewEnvironmentCreator.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/CustomNewEnvironmentCreator.kt @@ -20,7 +20,6 @@ import com.jetbrains.python.PythonHelpersLocator import com.jetbrains.python.Result import com.jetbrains.python.errorProcessing.ErrorSink import com.jetbrains.python.errorProcessing.PyResult -import com.jetbrains.python.errorProcessing.asKotlinResult import com.jetbrains.python.errorProcessing.emit import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo import com.jetbrains.python.sdk.* @@ -172,7 +171,7 @@ internal abstract class CustomNewEnvironmentCreator( runWithModalProgressBlocking(ModalTaskOwner.guess(), message("sdk.create.custom.venv.install.fix.title", name, "via pip")) { if (installationScript != null) { val versionArgs: List = installationVersion?.let { listOf("-v", it) } ?: emptyList() - val executablePath = installExecutableViaPythonScript(installationScript, pythonExecutable, "-n", name, *versionArgs.toTypedArray()).asKotlinResult() + val executablePath = installExecutableViaPythonScript(installationScript, pythonExecutable, "-n", name, *versionArgs.toTypedArray()) executablePath.onSuccess { savePathToExecutableToProperties(it) }.onFailure { diff --git a/python/src/com/jetbrains/python/sdk/pipenv/PipenvCommandExecutor.kt b/python/src/com/jetbrains/python/sdk/pipenv/PipenvCommandExecutor.kt index 4289a7fab684..fbd3b53e37c4 100644 --- a/python/src/com/jetbrains/python/sdk/pipenv/PipenvCommandExecutor.kt +++ b/python/src/com/jetbrains/python/sdk/pipenv/PipenvCommandExecutor.kt @@ -10,7 +10,6 @@ import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.util.SystemInfo import com.intellij.platform.ide.progress.withBackgroundProgress import com.jetbrains.python.PyBundle -import com.jetbrains.python.errorProcessing.asKotlinResult import com.jetbrains.python.sdk.basePath import com.jetbrains.python.sdk.createSdk import com.jetbrains.python.sdk.runExecutable @@ -25,7 +24,7 @@ import java.nio.file.Path @Internal suspend fun runPipEnv(dirPath: Path?, vararg args: String): Result { val executable = getPipEnvExecutable().getOrElse { return Result.failure(it) } - return runExecutable(executable, dirPath, *args).asKotlinResult() + return runExecutable(executable, dirPath, *args) } /** diff --git a/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt b/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt index 7ac4bea280f2..b31be6ad779f 100644 --- a/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt +++ b/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt @@ -21,7 +21,6 @@ import com.intellij.python.community.execService.WhatToExec import com.intellij.python.community.impl.poetry.poetryPath import com.intellij.util.SystemProperties import com.jetbrains.python.PyBundle -import com.jetbrains.python.errorProcessing.asKotlinResult import com.jetbrains.python.onFailure import com.jetbrains.python.packaging.PyPackage import com.jetbrains.python.packaging.PyPackageManager @@ -58,7 +57,7 @@ private val VERSION_2 = "2.0.0".toVersion() @Internal suspend fun runPoetry(projectPath: Path?, vararg args: String): Result { val executable = getPoetryExecutable().getOrElse { return Result.failure(it) } - return runExecutable(executable, projectPath, *args).asKotlinResult() + return runExecutable(executable, projectPath, *args) } diff --git a/python/src/com/jetbrains/python/sdk/uv/impl/UvCli.kt b/python/src/com/jetbrains/python/sdk/uv/impl/UvCli.kt index fe51ddc183ae..95f200fd2525 100644 --- a/python/src/com/jetbrains/python/sdk/uv/impl/UvCli.kt +++ b/python/src/com/jetbrains/python/sdk/uv/impl/UvCli.kt @@ -7,7 +7,6 @@ import com.intellij.openapi.ui.ValidationInfo import com.intellij.openapi.util.SystemInfo import com.intellij.util.SystemProperties import com.jetbrains.python.PyBundle -import com.jetbrains.python.errorProcessing.asKotlinResult import com.jetbrains.python.pathValidation.PlatformAndRoot import com.jetbrains.python.pathValidation.ValidationRequest import com.jetbrains.python.pathValidation.validateExecutableFile @@ -40,7 +39,7 @@ private fun validateUvExecutable(uvPath: Path?): ValidationInfo? { } private suspend fun runUv(uv: Path, workingDir: Path, vararg args: String): Result { - return runExecutable(uv, workingDir, *args).asKotlinResult() + return runExecutable(uv, workingDir, *args) } private class UvCliImpl(val dispatcher: CoroutineDispatcher, uvPath: Path?) : UvCli {