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
This commit is contained in:
Ilya.Kazakevich
2025-05-04 05:12:37 +00:00
committed by intellij-monorepo-bot
parent 6e1fc41f1f
commit a3c2ae48b2
6 changed files with 96 additions and 29 deletions
@@ -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<String> {
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<String> =
ExecService().execGetStdout(WhatToExec.Binary(executable), args.toList(), ExecOptions(workingDirectory = projectPath))
suspend fun runExecutable(executable: Path, projectPath: Path?, vararg args: String): Result<String> {
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<String>,
@NlsContexts.DialogMessage errorMessage: String = "",
): Result<String> {
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())
}
}
}
@@ -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<Path> {
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<Path> {
val result = runCommandLine(GeneralCommandLine(pythonExecutable.pathString, scriptPath.absolutePathString(), *args)).getOrElse { return Result.failure(it) }
return Result.success(Path.of(result.split("\n").last()))
}
@@ -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<String> = 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 {
@@ -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<String> {
val executable = getPipEnvExecutable().getOrElse { return Result.failure(it) }
return runExecutable(executable, dirPath, *args).asKotlinResult()
return runExecutable(executable, dirPath, *args)
}
/**
@@ -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<String> {
val executable = getPoetryExecutable().getOrElse { return Result.failure(it) }
return runExecutable(executable, projectPath, *args).asKotlinResult()
return runExecutable(executable, projectPath, *args)
}
@@ -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<String> {
return runExecutable(uv, workingDir, *args).asKotlinResult()
return runExecutable(uv, workingDir, *args)
}
private class UvCliImpl(val dispatcher: CoroutineDispatcher, uvPath: Path?) : UvCli {