From 9033ee55671ddf42c57fe4510ffc029016aca303 Mon Sep 17 00:00:00 2001 From: Vladislav Ertel Date: Mon, 29 Sep 2025 14:25:45 +0000 Subject: [PATCH] [intellij.build.io] Support custom output and error redirection in `runJava` function GitOrigin-RevId: fdedd1b69edd173ecfb6410c47c6962b2fbe6ff4 --- .../jetbrains/intellij/build/io/process.kt | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/build/tasks/src/org/jetbrains/intellij/build/io/process.kt b/build/tasks/src/org/jetbrains/intellij/build/io/process.kt index cfe5f39ae473..6d34e9d514a2 100644 --- a/build/tasks/src/org/jetbrains/intellij/build/io/process.kt +++ b/build/tasks/src/org/jetbrains/intellij/build/io/process.kt @@ -10,6 +10,7 @@ import org.jetbrains.annotations.ApiStatus.Obsolete import org.jetbrains.intellij.build.telemetry.TraceManager.spanBuilder import org.jetbrains.intellij.build.telemetry.use import java.io.InputStream +import java.lang.ProcessBuilder.Redirect import java.nio.charset.MalformedInputException import java.nio.file.Files import java.nio.file.Path @@ -30,7 +31,8 @@ suspend fun runJava(mainClass: String, javaExe: Path, timeout: Duration = DEFAULT_TIMEOUT, workingDir: Path? = null, - customOutputFile: Path? = null, + customOutput: Redirect? = null, + customError: Redirect? = null, onError: (() -> Unit)? = null) { val workingDir = workingDir ?: Path.of(System.getProperty("user.dir")) val useJsonOutput = jvmArgs.any { it == "-Dintellij.log.to.json.stdout=true" } @@ -47,19 +49,35 @@ suspend fun runJava(mainClass: String, .use(Dispatchers.IO) { span -> val toDelete = ArrayList(3) var process: Process? = null + fun processRedirect(customRedirect: Redirect?, prefix: String): Pair { + var outputFile: Path? = null + val outputRedirect = if (customRedirect != null) { + if (customRedirect != Redirect.DISCARD && (customRedirect.type() == Redirect.Type.WRITE || customRedirect.type() == Redirect.Type.APPEND)) { + customRedirect.file()?.parentFile?.let { Files.createDirectories(it.toPath()) } + outputFile = customRedirect.file()?.toPath() + } + customRedirect + } + else { + val file = Files.createTempFile(prefix, ".txt").also(toDelete::add).toFile() + outputFile = file.toPath() + Redirect.to(file) + } + return Pair(outputFile, outputRedirect) + } + try { val classpathFile = Files.createTempFile("classpath-", ".txt").also(toDelete::add) val classPathStringBuilder = createClassPathFile(classPath, classpathFile) val processArgs = createProcessArgs(javaExe, jvmArgs, classpathFile, mainClass, args) span.setAttribute(AttributeKey.stringArrayKey("processArgs"), processArgs) - val errorOutputFile = Files.createTempFile("error-out-", ".txt").also(toDelete::add) - val outputFile = customOutputFile?.also { customOutputFile.parent?.let { Files.createDirectories(it) } } - ?: Files.createTempFile("out-", ".txt").also(toDelete::add) + val (outputFile, outputRedirect) = processRedirect(customOutput, "out-") + val (errorOutputFile, errorRedirect) = processRedirect(customError, "error-out-") logFreeDiskSpace(workingDir, "before $commandLine") process = ProcessBuilder(processArgs) .directory(workingDir.toFile()) - .redirectError(errorOutputFile.toFile()) - .redirectOutput(outputFile.toFile()) + .redirectError(errorRedirect) + .redirectOutput(outputRedirect) .start() span.setAttribute("pid", process.pid()) @@ -67,9 +85,9 @@ suspend fun runJava(mainClass: String, fun javaRunFailed(reason: String) { span.setAttribute("classPath", classPathStringBuilder.substring("-classpath".length)) span.setAttribute("processArgs", processArgs.joinToString(separator = " ")) - span.setAttribute("output", runCatching { Files.readString(outputFile) }.getOrNull() ?: "output file doesn't exist") - val errorOutput = runCatching { Files.readString(errorOutputFile) }.getOrNull() - val output = runCatching { Files.readString(outputFile) }.getOrNull() + val output = runCatching { outputFile?.let(Files::readString) }.getOrNull() + span.setAttribute("output", output ?: "output file doesn't exist") + val errorOutput = runCatching { errorOutputFile?.let(Files::readString) }.getOrNull() val errorMessage = StringBuilder( "Cannot execute $mainClass: $reason\n${processArgs.joinToString(separator = " ")}" + "\n--- error output ---\n" + @@ -119,7 +137,11 @@ suspend fun runJava(mainClass: String, } } -private fun checkOutput(outputFile: Path, span: Span, errorConsumer: (String) -> Unit) { +private fun checkOutput(outputFile: Path?, span: Span, errorConsumer: (String) -> Unit) { + if (outputFile == null) { + span.setAttribute("output", "output file is null") + return + } val out = try { try { Files.readString(outputFile)