Running Programs
● Intermediate5 minBefore this: Capabilities, catchSpec 14.23
std.process runs other programs: a compiler, a simulator, git, any tool with no library. It needs the capability Process, never goes through a shell, and ends everything a program started when it stops.
use std.process
fn main() ! ProcessError uses Process, Console {
const done = try process.run("git", ["--version"])
print(done.code)
print(done.out.starts_with("git version"))
print(done.cpu_ms >= 0 and done.peak_bytes > 0)
// One odd argument, never a second command.
const odd = try process.run("git", ["--version; echo injected"])
print(odd.code)
print(odd.err.lines()[0])
print(odd.out.is_empty())
}The program ran and printed:
kavach run run.kav
0truetrue129unknown option: --version; echo injectedtrue
process.runruns a program to its end and gives aFinished: its exit code, what it printed (out,err) and what it cost (cpu_ms,peak_bytes).argsis a list, passed as it is, never through a shell."--version; echo injected"is one odd argument, so git refuses it: nothing else runs.- A program is a name looked up on the system's
PATH, or a path such as./tools/gen. folder:sets the folder it runs in, andenv:adds or changes its environment variables. It reads nothing on standard input.
It needs Process
Running a program reaches outside your own, so the function says so in its uses, or it doesn't compile:
use std.process
fn status() -> Int ! ProcessError uses Console {
const done = try process.run("git", ["status"])
done.code
}
fn main() uses Console {
print(status() catch _ => -1)
}The compiler refused the program:
kavach run needs.kav
error: `status` uses Process (through `run`), but its `uses` clause doesn't list it: add `Process` to `uses` (spec 8.1)Which programs
Process can be granted for one program, as Fs is for one folder. Inside a function with uses Process("git"), every program it runs, and everything it calls runs, must be git. Any other fails with Denied; a program that isn't there fails with NotFound:
use std.process
fn version(program: String) -> String ! ProcessError uses Process("git") {
const done = try process.run(program, ["--version"])
"exit code {done.code}"
}
fn anything(program: String) -> String ! ProcessError uses Process {
const done = try process.run(program, [])
"exit code {done.code}"
}
fn main() uses Process, Console {
print(version("git") catch e => "{e}")
print(version("cargo") catch e => "{e}")
print(anything("no-such-program-here") catch e => "{e}")
}The program ran and printed:
kavach run names.kav
exit code 0Denied(program: "cargo")NotFound(program: "no-such-program-here")
| Written | Allows |
|---|---|
uses Process | Running any program |
uses Process("verilator") | Only verilator |
- The name is checked when the program runs, since it is a value.
- A name without a
/matches only that name onPATH; a name with one matches only that path. - Names nest, as folders do: inside two, a program must match both. So a function that runs two tools has
uses Process, or calls one function per tool. - A task keeps the names of the function that spawned it.
Output as it comes
process.start gives the program back running, as a Child. Its lines come one by one, each an Output.Out(line) or an Output.Err(line), without its line end. each calls a function with each one:
use std.process
fn main() ! ProcessError uses Process, Console {
const git = try process.start("git", ["--version"])
git.each(out => match out {
Output.Out(line) => {
print("out: starts with git version? {line.starts_with("git version")}")
true
},
Output.Err(line) => {
print("err: {line}")
true
},
})
print(git.wait().code)
}The program ran and printed:
kavach run lines.kav
out: starts with git version? true0
next() reads one line, and wait() waits for the end, skipping what is still printed:
use std.process
fn main() ! ProcessError uses Process, Console {
const git = try process.start("git", ["--no-such-flag"])
if let first = git.next() {
print(match first {
Output.Out(line) => "out: {line}",
Output.Err(line) => "err: {line}",
})
}
const end = git.wait()
print(end.code)
}The program ran and printed:
kavach run next.kav
err: unknown option: --no-such-flag129
- An exit code of 0 most often means it went well. Git gives 129 for an option it doesn't know.
- On Linux and macOS, a program ended by a signal gives 128 plus the signal's number, as shells say it.
- Waiting for a line or for the end lets other tasks take their turns, so a program can run several tools at once, each in its own task.
A longer one, from the spec: a simulator whose errors are picked out of its output.
use std.process
fn simulate(top: String) -> Exit ! ProcessError uses Process("vsim"), Console {
const sim = try process.start("vsim", ["-c", top], folder: "build")
sim.each(out => match out {
Output.Out(line) => {
if line.contains("Error") {
print(line)
}
true
},
Output.Err(line) => {
print("! {line}")
true
},
})
sim.wait()
}A program is a tree
What a program starts is part of it. kill(), a Child nothing holds any more, a task cancelled while it waits, and a time limit (.timeout) all end the whole tree: through a job object on Windows, and a process group on Linux and macOS. So a timeout never leaves a simulator running behind it.
What it cost
cpu_ms and peak_bytes say how long a program used the processor and the most memory it held, so a tool that runs too long or grows too big can be seen. process.peak_bytes() says the same of your own program, and needs no capability:
use std.process
fn main() uses Console {
let numbers: [Int] = []
for i in 0..1_000_000 {
numbers.push(i)
}
print(numbers.len())
print(process.peak_bytes() > 8_000_000)
}The program ran and printed:
kavach run peak.kav
1000000true
Every function, method and type
| Function or method | Gives |
|---|---|
process.run(program, args, folder: "", env: [:]) -> Finished ! ProcessError | Runs program to its end and gives its exit code, what it printed and what it cost |
process.start(program, args, folder: "", env: [:]) -> Child ! ProcessError | Starts program and gives it back running, to read its output as it comes |
child.next() -> Output? | The next line the program printed, waiting for it; none once it has ended and every line was read |
child.each(f) | Calls f(output) with each line as it comes, until f gives false or the program has ended and every line was read |
child.wait() -> Exit | Waits for the program's end, skipping what it still prints |
child.kill() | Ends the program, and every program it started, at once |
process.peak_bytes() -> Int | The most memory this program has held so far, in bytes. Needs no capability |
| Type | Holds |
|---|---|
Finished | code: Int, out: String, err: String, cpu_ms: Int, peak_bytes: Int; out and err are the lines printed, each ending in a line break |
Output | Out(line: String) or Err(line: String): a line of standard output or of standard error, without its line end |
Exit | code: Int, cpu_ms: Int, peak_bytes: Int |
ProcessError | NotFound(program: String), Denied(program: String) or Failed(reason: String) |
Not yet: text sent to a program's standard input, output read as Bytes, and a fake Process for kavach test. Until there is one, generated tests skip functions that use Process.