|
| 1 | +package scala.cli.mill |
| 2 | + |
| 3 | +import mill.main.client.InputPumper |
| 4 | +import os.SubProcess |
| 5 | +import java.io.PipedInputStream |
| 6 | + |
| 7 | +// Adapted from Mill Jvm.scala https://github.com/com-lihaoyi/mill/blob/f96162ecb41a9dfbac0bc524b77e09093fd61029/main/src/mill/modules/Jvm.scala#L37 |
| 8 | +// Changes: |
| 9 | +// - return stdout instead of printing it |
| 10 | +// - return Either[Unit, os.SubProcess.OutputStream] in runSubprocess instead of Unit and to receive os.Shellable* instead of Seq[String] |
| 11 | +// - receive os.Shellable* instead of Seq[String] |
| 12 | +// - avoid receiving env and cwd since we don't pass them |
| 13 | +object ProcessUtils { |
| 14 | + /** |
| 15 | + * Runs a generic subprocess and waits for it to terminate. |
| 16 | + */ |
| 17 | + def runSubprocess(command: os.Shellable*): Either[Unit, os.SubProcess.OutputStream] = { |
| 18 | + val process = spawnSubprocess(command) |
| 19 | + val shutdownHook = new Thread("subprocess-shutdown") { |
| 20 | + override def run(): Unit = { |
| 21 | + System.err.println("Host JVM shutdown. Forcefully destroying subprocess ...") |
| 22 | + process.destroy() |
| 23 | + } |
| 24 | + } |
| 25 | + Runtime.getRuntime().addShutdownHook(shutdownHook) |
| 26 | + try { |
| 27 | + process.waitFor() |
| 28 | + } catch { |
| 29 | + case e: InterruptedException => |
| 30 | + System.err.println("Interrupted. Forcefully destroying subprocess ...") |
| 31 | + process.destroy() |
| 32 | + // rethrow |
| 33 | + throw e |
| 34 | + } finally { |
| 35 | + Runtime.getRuntime().removeShutdownHook(shutdownHook) |
| 36 | + } |
| 37 | + if (process.exitCode() == 0) Right(process.stdout) |
| 38 | + else Left(()) |
| 39 | + } |
| 40 | + |
| 41 | + /** |
| 42 | + * Spawns a generic subprocess, streaming the stdout and stderr to the |
| 43 | + * console. If the System.out/System.err have been substituted, makes sure |
| 44 | + * that the subprocess's stdout and stderr streams go to the subtituted |
| 45 | + * streams |
| 46 | + */ |
| 47 | + def spawnSubprocess( |
| 48 | + command: os.Shellable* |
| 49 | + ): SubProcess = { |
| 50 | + // If System.in is fake, then we pump output manually rather than relying |
| 51 | + // on `os.Inherit`. That is because `os.Inherit` does not follow changes |
| 52 | + // to System.in/System.out/System.err, so the subprocess's streams get sent |
| 53 | + // to the parent process's origin outputs even if we want to direct them |
| 54 | + // elsewhere |
| 55 | + if (System.in.isInstanceOf[PipedInputStream]) { |
| 56 | + val process = os.proc(command).spawn( |
| 57 | + stdin = os.Pipe, |
| 58 | + stdout = os.Pipe, |
| 59 | + stderr = os.Pipe |
| 60 | + ) |
| 61 | + |
| 62 | + val sources = Seq( |
| 63 | + (process.stderr, System.err, "spawnSubprocess.stderr", false, () => true), |
| 64 | + (System.in, process.stdin, "spawnSubprocess.stdin", true, () => process.isAlive()) |
| 65 | + ) |
| 66 | + |
| 67 | + for ((std, dest, name, checkAvailable, runningCheck) <- sources) { |
| 68 | + val t = new Thread( |
| 69 | + new InputPumper(std, dest, checkAvailable, () => runningCheck()), |
| 70 | + name |
| 71 | + ) |
| 72 | + t.setDaemon(true) |
| 73 | + t.start() |
| 74 | + } |
| 75 | + |
| 76 | + process |
| 77 | + } else { |
| 78 | + os.proc(command).spawn( |
| 79 | + stdin = os.Inherit, |
| 80 | + stdout = os.Pipe, |
| 81 | + stderr = os.Inherit |
| 82 | + ) |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | +} |
0 commit comments