feature. See also
. The project being documented here (as the example) is the Zig library itself.
File
Code
const Run = @This();
const builtin = @import("builtin");
const std = @import("std");
const Io = std.Io;
const Build = std.Build;
const Step = std.Build.Step;
const Dir = std.Io.Dir;
const mem = std.mem;
const process = std.process;
const EnvMap = std.process.Environ.Map;
const assert = std.debug.assert;
const Path = std.Build.Cache.Path;
const Configuration = std.Build.Configuration;
pub const base_tag: Step.Tag = .run;
step: Step,
argv: std.ArrayList(Arg),
cwd: ?Build.LazyPath,
environ_map: ?*EnvMap,
preopens: std.array_hash_map.String(Build.LazyPath),
color: Color = .auto,
disable_zig_progress: bool,
stdio: StdIo,
stdin: StdIn,
file_inputs: std.ArrayList(std.Build.LazyPath),
rename_step_with_output_arg: bool,
skip_foreign_checks: bool,
failing_to_execute_foreign_is_an_error: bool,
stdio_limit: std.Io.Limit,
captured_stdout: ?*CapturedStdIo,
captured_stderr: ?*CapturedStdIo,
has_side_effects: bool,
test_runner_mode: bool = false,
producer: ?*Step.Compile,
pub const Color = std.Build.Configuration.Step.Run.Color;
pub const StdIn = union(enum) {
none,
bytes: []const u8,
lazy_path: std.Build.LazyPath,
};
pub const StdIo = union(enum) {
infer_from_args,
inherit,
check: std.ArrayList(Check),
zig_test,
pub const Check = union(enum) {
expect_stderr_exact: []const u8,
expect_stderr_match: []const u8,
expect_stdout_exact: []const u8,
expect_stdout_match: []const u8,
expect_term: process.Child.Term,
expect_stderr_snapshot: std.Build.LazyPath,
expect_stdout_snapshot: std.Build.LazyPath,
};
};
pub const Arg = union(enum) {
artifact: DecoratedArtifact,
lazy_path: DecoratedLazyPath,
decorated_directory: DecoratedLazyPath,
file_content: DecoratedFileContent,
bytes: []const u8,
output_file: *Output,
output_file_dep: *Output,
output_directory: *Output,
passthru,
};
pub const DecoratedArtifact = struct {
prefix: []const u8,
suffix: []const u8,
artifact: *Step.Compile,
make_absolute: bool,
};
pub const DecoratedLazyPath = struct {
prefix: []const u8,
lazy_path: std.Build.LazyPath,
suffix: []const u8,
make_absolute: bool,
};
pub const DecoratedFileContent = struct {
prefix: []const u8,
lazy_path: std.Build.LazyPath,
suffix: []const u8,
};
pub const Output = struct {
generated_file: Configuration.GeneratedFileIndex,
prefix: []const u8,
basename: []const u8,
suffix: []const u8,
make_absolute: bool,
};
pub const CapturedStdIo = struct {
generated_file: Configuration.GeneratedFileIndex,
prefix: []const u8,
basename: []const u8,
trim_whitespace: TrimWhitespace,
pub const Options = struct {
basename: ?[]const u8 = null,
trim_whitespace: TrimWhitespace = .none,
};
pub const TrimWhitespace = std.Build.Configuration.Step.Run.TrimWhitespace;
};
pub fn create(owner: *std.Build, name: []const u8) *Run {
const run = owner.allocator.create(Run) catch @panic("OOM");
run.* = .{
.step = .init(.{
.tag = base_tag,
.name = name,
.owner = owner,
}),
.argv = .empty,
.cwd = null,
.environ_map = null,
.preopens = .empty,
.disable_zig_progress = false,
.stdio = .infer_from_args,
.stdin = .none,
.file_inputs = .empty,
.rename_step_with_output_arg = true,
.skip_foreign_checks = false,
.failing_to_execute_foreign_is_an_error = true,
.stdio_limit = .unlimited,
.captured_stdout = null,
.captured_stderr = null,
.has_side_effects = false,
.producer = null,
};
return run;
}
pub fn setName(run: *Run, name: []const u8) void {
run.step.name = name;
run.rename_step_with_output_arg = false;
}
pub fn enableTestRunnerMode(run: *Run) void {
if (run.test_runner_mode) return;
run.stdio = .zig_test;
run.test_runner_mode = true;
}
pub const ArgOptions = struct {
prefix: []const u8 = "",
suffix: []const u8 = "",
};
pub const PathArgOptions = struct {
prefix: []const u8 = "",
suffix: []const u8 = "",
make_absolute: bool = false,
};
pub fn addArtifactArg(run: *Run, artifact: *Step.Compile) void {
run.addArtifactArg2(artifact, .{});
}
pub fn addPrefixedArtifactArg(run: *Run, prefix: []const u8, artifact: *Step.Compile) void {
run.addArtifactArg2(artifact, .{ .prefix = prefix });
}
pub fn addArtifactArg2(run: *Run, artifact: *Step.Compile, options: PathArgOptions) void {
const graph = run.step.owner.graph;
const arena = graph.arena;
const prefixed_artifact: DecoratedArtifact = .{
.prefix = graph.dupeString(options.prefix),
.artifact = artifact,
.suffix = graph.dupeString(options.suffix),
.make_absolute = options.make_absolute,
};
run.argv.append(arena, .{ .artifact = prefixed_artifact }) catch @panic("OOM");
const bin_file = artifact.getEmittedBin();
bin_file.addStepDependencies(&run.step);
}
pub fn addOutputFileArg(run: *Run, sub_path: []const u8) std.Build.LazyPath {
return run.addOutputFileArg2(sub_path, .{});
}
pub fn addPrefixedOutputFileArg(
run: *Run,
prefix: []const u8,
sub_path: []const u8,
) std.Build.LazyPath {
return run.addOutputFileArg2(sub_path, .{ .prefix = prefix });
}
pub fn addOutputFileArg2(
run: *Run,
sub_path: []const u8,
options: PathArgOptions,
) std.Build.LazyPath {
const b = run.step.owner;
const graph = b.graph;
const arena = graph.arena;
assert(sub_path.len != 0);
const output = graph.create(Output);
output.* = .{
.prefix = graph.dupeString(options.prefix),
.basename = graph.dupeString(sub_path),
.suffix = graph.dupeString(options.suffix),
.generated_file = graph.addGeneratedFile(&run.step),
.make_absolute = options.make_absolute,
};
run.argv.append(arena, .{ .output_file = output }) catch @panic("OOM");
if (run.rename_step_with_output_arg) {
run.setName(b.fmt("{s} ({s})", .{ run.step.name, sub_path }));
}
return .{ .generated = .{ .index = output.generated_file } };
}
pub fn addFileArg(run: *Run, lp: std.Build.LazyPath) void {
run.addFileArg2(lp, .{});
}
pub fn addPrefixedFileArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath) void {
run.addFileArg2(lp, .{ .prefix = prefix });
}
pub fn addFileArg2(run: *Run, lp: std.Build.LazyPath, options: PathArgOptions) void {
const graph = run.step.owner.graph;
const arena = graph.arena;
const prefixed_file_source: DecoratedLazyPath = .{
.prefix = graph.dupeString(options.prefix),
.lazy_path = lp.dupe(graph),
.suffix = graph.dupeString(options.suffix),
.make_absolute = options.make_absolute,
};
run.argv.append(arena, .{ .lazy_path = prefixed_file_source }) catch @panic("OOM");
lp.addStepDependencies(&run.step);
}
pub fn addFileContentArg(run: *Run, lp: std.Build.LazyPath) void {
return run.addFileContentArg2(lp, .{});
}
pub fn addPrefixedFileContentArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath) void {
return run.addFileContentArg2(lp, .{ .prefix = prefix });
}
pub fn addFileContentArg2(run: *Run, lp: std.Build.LazyPath, options: ArgOptions) void {
const graph = run.step.owner.graph;
const arena = graph.arena;
// transparent/readable, but the content of the file specified by `lp` remains completely
// opaque until its path can be resolved during the make phase.
if (run.argv.items.len == 0) {
@panic("'addFileContentArg'/'addPrefixedFileContentArg' cannot be first argument");
}
const file_content: DecoratedFileContent = .{
.prefix = graph.dupeString(options.prefix),
.lazy_path = lp.dupe(graph),
.suffix = graph.dupeString(options.suffix),
};
run.argv.append(arena, .{ .file_content = file_content }) catch @panic("OOM");
lp.addStepDependencies(&run.step);
}
pub fn addOutputDirectoryArg(run: *Run, basename: []const u8) std.Build.LazyPath {
return run.addOutputDirectoryArg2(basename, .{});
}
pub fn addPrefixedOutputDirectoryArg(
run: *Run,
prefix: []const u8,
basename: []const u8,
) std.Build.LazyPath {
return run.addOutputDirectoryArg2(basename, .{ .prefix = prefix });
}
pub fn addOutputDirectoryArg2(
run: *Run,
basename: []const u8,
options: PathArgOptions,
) std.Build.LazyPath {
if (basename.len == 0) @panic("basename must not be empty");
const graph = run.step.owner.graph;
const arena = graph.arena;
const output = arena.create(Output) catch @panic("OOM");
output.* = .{
.prefix = graph.dupeString(options.prefix),
.basename = graph.dupeString(basename),
.suffix = graph.dupeString(options.suffix),
.generated_file = graph.addGeneratedFile(&run.step),
.make_absolute = options.make_absolute,
};
run.argv.append(arena, .{ .output_directory = output }) catch @panic("OOM");
if (run.rename_step_with_output_arg) {
run.setName(std.fmt.allocPrint(arena, "{s} ({s})", .{ run.step.name, basename }) catch @panic("OOM"));
}
return .{ .generated = .{ .index = output.generated_file } };
}
pub fn addDirectoryArg(run: *Run, lazy_directory: std.Build.LazyPath) void {
run.addDirectoryArg2(lazy_directory, .{});
}
pub fn addPrefixedDirectoryArg(run: *Run, prefix: []const u8, lazy_directory: std.Build.LazyPath) void {
run.addDirectoryArg2(lazy_directory, .{ .prefix = prefix });
}
pub fn addDecoratedDirectoryArg(
run: *Run,
prefix: []const u8,
lazy_directory: std.Build.LazyPath,
suffix: []const u8,
) void {
run.addDirectoryArg2(lazy_directory, .{ .prefix = prefix, .suffix = suffix });
}
pub fn addDirectoryArg2(
run: *Run,
lazy_directory: std.Build.LazyPath,
options: PathArgOptions,
) void {
const graph = run.step.owner.graph;
const arena = graph.arena;
run.argv.append(arena, .{ .decorated_directory = .{
.prefix = graph.dupeString(options.prefix),
.lazy_path = lazy_directory.dupe(graph),
.suffix = graph.dupeString(options.suffix),
.make_absolute = options.make_absolute,
} }) catch @panic("OOM");
lazy_directory.addStepDependencies(&run.step);
}
pub fn addDepFileOutputArg(run: *Run, basename: []const u8) std.Build.LazyPath {
return run.addDepFileOutputArg2(basename, .{});
}
pub fn addPrefixedDepFileOutputArg(run: *Run, prefix: []const u8, basename: []const u8) std.Build.LazyPath {
return run.addDepFileOutputArg2(basename, .{ .prefix = prefix });
}
pub fn addDepFileOutputArg2(run: *Run, basename: []const u8, options: PathArgOptions) std.Build.LazyPath {
const b = run.step.owner;
const graph = b.graph;
const arena = graph.arena;
const dep_file = arena.create(Output) catch @panic("OOM");
dep_file.* = .{
.prefix = graph.dupeString(options.prefix),
.basename = graph.dupeString(basename),
.suffix = graph.dupeString(options.suffix),
.generated_file = graph.addGeneratedFile(&run.step),
.make_absolute = options.make_absolute,
};
run.argv.append(arena, .{ .output_file_dep = dep_file }) catch @panic("OOM");
return .{ .generated = .{ .index = dep_file.generated_file } };
}
pub fn addArg(run: *Run, arg: []const u8) void {
const graph = run.step.owner.graph;
const arena = graph.arena;
run.argv.append(arena, .{ .bytes = graph.dupeString(arg) }) catch @panic("OOM");
}
pub fn addArgs(run: *Run, args: []const []const u8) void {
for (args) |arg| run.addArg(arg);
}
pub fn addPassthruArgs(run: *Run) void {
const graph = run.step.owner.graph;
const arena = graph.arena;
run.argv.append(arena, .passthru) catch @panic("OOM");
}
pub fn setStdIn(run: *Run, stdin: StdIn) void {
switch (stdin) {
.lazy_path => |lazy_path| lazy_path.addStepDependencies(&run.step),
.bytes, .none => {},
}
run.stdin = stdin;
}
pub fn setCwd(run: *Run, cwd: Build.LazyPath) void {
const graph = run.step.owner.graph;
cwd.addStepDependencies(&run.step);
run.cwd = cwd.dupe(graph);
}
pub fn clearEnvironment(run: *Run) void {
const b = run.step.owner;
const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM");
new_env_map.* = .init(b.allocator);
run.environ_map = new_env_map;
}
pub fn getEnvMap(run: *Run) *EnvMap {
return getEnvMapInternal(run);
}
fn getEnvMapInternal(run: *Run) *EnvMap {
const graph = run.step.owner.graph;
const arena = graph.arena;
return run.environ_map orelse {
const cloned_map = arena.create(EnvMap) catch @panic("OOM");
cloned_map.* = graph.environ_map.clone(arena) catch @panic("OOM");
run.environ_map = cloned_map;
return cloned_map;
};
}
pub fn setEnvironmentVariable(run: *Run, key: []const u8, value: []const u8) void {
const environ_map = run.getEnvMap();
environ_map.put(key, value) catch @panic("OOM");
}
pub fn removeEnvironmentVariable(run: *Run, key: []const u8) void {
_ = run.getEnvMap().swapRemove(key);
}
pub fn setPreopen(run: *Run, name: []const u8, resource: Build.LazyPath) void {
const graph = run.step.owner.graph;
const arena = graph.arena;
resource.addStepDependencies(&run.step);
run.preopens.put(
arena,
graph.dupeString(name),
resource.dupe(graph),
) catch @panic("OOM");
}
pub fn expectStdErrEqual(run: *Run, bytes: []const u8) void {
const graph = run.step.owner.graph;
run.addCheck(.{ .expect_stderr_exact = graph.dupeString(bytes) });
}
pub fn expectStdErrMatch(run: *Run, bytes: []const u8) void {
const graph = run.step.owner.graph;
run.addCheck(.{ .expect_stderr_match = graph.dupeString(bytes) });
}
pub fn expectStdOutEqual(run: *Run, bytes: []const u8) void {
const graph = run.step.owner.graph;
run.addCheck(.{ .expect_stdout_exact = graph.dupeString(bytes) });
if (!run.hasTermCheck()) run.expectExitCode(0);
}
pub fn expectStdOutMatch(run: *Run, bytes: []const u8) void {
const graph = run.step.owner.graph;
run.addCheck(.{ .expect_stdout_match = graph.dupeString(bytes) });
if (!run.hasTermCheck()) run.expectExitCode(0);
}
pub fn expectExitCode(run: *Run, code: u8) void {
const new_check: StdIo.Check = .{ .expect_term = .{ .exited = code } };
run.addCheck(new_check);
}
pub fn hasTermCheck(run: Run) bool {
for (run.stdio.check.items) |check| switch (check) {
.expect_term => return true,
else => continue,
};
return false;
}
pub fn addCheck(run: *Run, new_check: StdIo.Check) void {
const b = run.step.owner;
switch (run.stdio) {
.infer_from_args => {
run.stdio = .{ .check = .empty };
run.stdio.check.append(b.allocator, new_check) catch @panic("OOM");
},
.check => |*checks| checks.append(b.allocator, new_check) catch @panic("OOM"),
else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of Run instead"),
}
switch (new_check) {
.expect_stderr_snapshot,
.expect_stdout_snapshot,
=> |file| run.addFileInput(file),
else => {},
}
}
pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
assert(run.stdio != .inherit);
assert(run.stdio != .zig_test);
const b = run.step.owner;
const graph = b.graph;
const arena = graph.arena;
if (run.captured_stderr) |captured| return .{ .generated = .{ .index = captured.generated_file } };
const captured = arena.create(CapturedStdIo) catch @panic("OOM");
captured.* = .{
.prefix = "",
.basename = if (options.basename) |basename| graph.dupeString(basename) else "stderr",
.generated_file = graph.addGeneratedFile(&run.step),
.trim_whitespace = options.trim_whitespace,
};
run.captured_stderr = captured;
return .{ .generated = .{ .index = captured.generated_file } };
}
pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
assert(run.stdio != .inherit);
assert(run.stdio != .zig_test);
const b = run.step.owner;
const graph = b.graph;
const arena = graph.arena;
if (run.captured_stdout) |captured| return .{ .generated = .{ .index = captured.generated_file } };
const captured = arena.create(CapturedStdIo) catch @panic("OOM");
captured.* = .{
.prefix = "",
.basename = if (options.basename) |basename| graph.dupeString(basename) else "stdout",
.generated_file = graph.addGeneratedFile(&run.step),
.trim_whitespace = options.trim_whitespace,
};
run.captured_stdout = captured;
return .{ .generated = .{ .index = captured.generated_file } };
}
pub fn addFileInput(run: *Run, file_input: std.Build.LazyPath) void {
const graph = run.step.owner.graph;
const arena = graph.arena;
file_input.addStepDependencies(&run.step);
run.file_inputs.append(arena, file_input.dupe(graph)) catch @panic("OOM");
}