std/process/lib
std/process/src/lib.trb
The process itself, and the child processes it starts: Process for arguments and exiting, Process.run for a
program run to its end, Child for a running program's pipes, and ProcessOutput for what a finished one left behind.
A child process's three pipes are the same Source and Sink a file or a socket has, so a pipeline between two
programs is first.output().into(second.input()).
type ProcessOutput
type ProcessOutput
What a program run with Process.run or Process.runBlocking left behind.
field exitCode
exitCode: Int
0 for success, anything else for the code the program exited with.
field standardOutput
standardOutput: String
Everything the program wrote to its standard output.
field standardError
standardError: String
Everything the program wrote to its standard error, kept apart from ProcessOutput.standardOutput.
fn isSuccess
fn isSuccess(): Bool
The usual question about a child process: did it do what it was asked.
type Child
shared type Child with Close
A child process that is still running, started with Process.start. Its three pipes are the same Source and
Sink a file or a socket has, so a pipeline between two programs is first.output().into(second.input()).
Examples
fn sortTwo(): Task<Result<Void, IoError>> {
using child = Process.start("sort", ["-u"])?
var input = child.input()
input.addLine("banana").await()?
input.addLine("apple").await()?
input.end().await()?
const chunks = child.output().toList().await()?
const code = child.wait().await()?
print "{chunks.length()} chunks, exit code {code}"
Ok void
}
Every line is .await()?: the ? is the failure of the pipe, and a cancellation needs nothing - it stops the task
at the await() it meets, and the using closes the child's pipes on the way out. A program whose whole input is
known up front needs no pipe at all: Process.run feeds it.
Pitfalls
Child.close releases the pipes and stops waiting for the child; it does not kill it, because ending somebody
else's program is a decision and not a cleanup.
fn of
static fn of(handle: Int, program: String): Child
A child over a handle the runtime answered.
fn input
fn input(): Sink<Bytes, IoError>
What the child reads. end() closes its standard input, which is how most filters learn that they are done.
fn output
fn output(): Source<Bytes, IoError>
What the child writes to standard output.
fn errors
fn errors(): Source<Bytes, IoError>
What the child writes to standard error, kept apart from Child.output.
fn wait
fn wait(): Task<Result<Int, IoError>>
The exit code, once the program has ended; the child is waited for on a thread of the blocking pool.
fn close
var fn close()
Releases the three pipes and stops waiting for the child; see the type's own pitfall about killing it.
fn read
fn read(which: Int, maximum: Int): Task<Result<Bytes?, IoError>>
At most maximum bytes of one of its two outputs: None at its end.
fn write
fn write(bytes: Bytes): Task<Result<Void, IoError>>
All of bytes into its input.
fn endInput
fn endInput(): Result<Void, IoError>
Closes its input: the child reads the end of it.
type Process
native type Process
The running program: its command line, how it exits, and the child processes it starts.
fn arguments
native static fn arguments(): List<String>
The command line arguments, without the program itself.
fn exit
native static fn exit(code: Int): Never
Ends the program. Open resources are not closed: prefer to return from the entry file.
fn executablePath
native static fn executablePath(): String?
The absolute path of the running program's own executable, with / between its parts, or None where the
operating system does not say. It is native because only the operating system knows it, and it is how a program
finds the files it was installed beside - torb finds its std/ and runtime/ this way.
Examples
if const Some(path) = Process.executablePath() {
print path
}
Pitfalls
Windows and Linux answer; an operating system without a way to ask (macOS, a BSD without /proc) answers None
rather than a guess from the command line, which names whatever the caller typed.
fn run
static fn run(program: String, arguments: List<String>, input: String = ""): Task<Result<ProcessOutput, IoError>>
Runs a program to its end as a task: feeds it input as the whole of its standard input, and collects its exit
code and the two streams it wrote into a ProcessOutput. The arguments are passed as they are: nothing about them
is interpreted and nothing has to be quoted.
fn sorted(): Task<Result<String, IoError>> {
const output = Process.run("sort", [], input: "banana\napple\n").await()?
Ok output.standardOutput
}
A program that could not be started at all is an IoError; a program that ran and failed is an exit code, which
is how a caller tells "there is no such program" from "the program said no".
Pitfalls
- The input ends after
input, and an empty one ends at once: the program never reads the terminal this program was started from, so a program that would ask a question gets the end of its input instead of hanging. - The two streams arrive apart, so the order in which the program interleaved them is lost: a line of
ProcessOutput.standardErrorcannot be placed between two lines ofProcessOutput.standardOutput. A caller that needs the output while it arrives reaches forProcess.startinstead, which hands out real pipes. - The wait for the program runs on the blocking pool where the arguments may move there (
offload), and blocks the worker that asked where they may not. Cancelling the task does not stop the program: it runs to its end, and what it wrote is thrown away.
Related
Process.runBlocking- the same run for code that is not a task, without an input.Process.start- a program whose output is read while it runs.
fn runBlocking
static fn runBlocking(program: String, arguments: List<String>): Result<ProcessOutput, IoError>
Runs a program to its end and collects everything about it, blocking the thread that asks - the form for code that
is not a task, such as a driver that runs the C compiler. The child reads this program's own standard input. The
arguments are passed as they are, like Process.run's.
A program that could not be started at all is an IoError; a program that ran and failed is an exit code, which
is why torb build can tell "there is no C compiler" from "the C compiler said no".
Pitfalls
- Inside a task it stalls every other task of the worker that runs it until the program ends: a task writes
Process.run(program, arguments).await()?. - The two streams arrive apart, as they do for
Process.run.
fn runFeeding
native static fn runFeeding(command: String, arguments: ArrayList<String>, input: String, var output: String, var failure: String): Int
Process.runCollecting with input as all the child reads: the one shape a runtime function can have for
Process.run, for the same reasons.
fn runCollecting
native static fn runCollecting(command: String, arguments: ArrayList<String>, var output: String, var failure: String): Int
The exit code of the program, with what it wrote to standard output in output and what it wrote to standard
error in failure - or -1 where it could not be started at all, and then failure says why. The one shape a runtime function can have for this: a Result and a
ProcessOutput are types of the program, and starting a process is the operating system.
fn runPassingThrough
static fn runPassingThrough(program: String, arguments: List<String>): Result<Int, IoError>
Runs a program to its end with this program's own three streams, and answers its exit code. The arguments are
passed as they are, like Process.run's.
This is what a driver runs a program with: nothing is collected, so what the child writes appears where this
program's output appears while it writes it, its two streams stay apart and in their order, and a program that
reads standard input reads the one the user is typing into. Process.run answers what a program wrote, which is
the other question and needs a pipe for it.
A program that could not be started at all is an IoError; a program that ran and failed is an exit code.
Examples
const code = Process.runPassingThrough("git", ["status", "--short"])?
Process.exit code
Errors
- The program is not on
PATH, or it is not a program at all.
Related
Process.run- the same run with everything it wrote collected into aProcessOutput.
fn runInheriting
native static fn runInheriting(command: String, arguments: ArrayList<String>, var failure: String): Int
The exit code of the program, or -1 where it could not be started at all, and then failure says why. The
same shape Process.runCollecting has, without the output: there is none to answer with.
fn start
static fn start(program: String, arguments: List<String>): Result<Child, IoError>
Starts a program and hands back its pipes, for output that is too big to collect or that has to be read while it
arrives. Process.run is the short form for everything else. The program is started through no shell, and a
program that cannot be started at all is the failure.