Skip to content

Commit

Permalink
zig build: many enhancements related to parallel building
Browse files Browse the repository at this point in the history
Rework std.Build.Step to have an `owner: *Build` field. This
simplified the implementation of installation steps, as well as provided
some much-needed common API for the new parallelized build system.

--verbose is now defined very concretely: it prints to stderr just
before spawning a child process.

Child process execution is updated to conform to the new
parallel-friendly make() function semantics.

DRY up the failWithCacheError handling code. It now integrates properly
with the step graph instead of incorrectly dumping to stderr and calling
process exit.

In the main CLI, fix `zig fmt` crash when there are no errors and stdin
is used.

Deleted steps:
 * EmulatableRunStep - this entire thing can be removed in favor of a
   flag added to std.Build.RunStep called `skip_foreign_checks`.
 * LogStep - this doesn't really fit with a multi-threaded build runner
   and is effectively superseded by the new build summary output.

build runner:
 * add -fsummary and -fno-summary to override the default behavior,
   which is to print a summary if any of the build steps fail.
 * print the dep prefix when emitting error messages for steps.

std.Build.FmtStep:
 * This step now supports exclude paths as well as a check flag.
 * The check flag decides between two modes, modify mode, and check
   mode. These can be used to update source files in place, or to fail
   the build, respectively.

Zig's own build.zig:
 * The `test-fmt` step will do all the `zig fmt` checking that we expect
   to be done. Since the `test` step depends on this one, we can simply
   remove the explicit call to `zig fmt` in the CI.
 * The new `fmt` step will actually perform `zig fmt` and update source
   files in place.

std.Build.RunStep:
 * expose max_stdio_size is a field (previously an unchangeable
   hard-coded value).
 * rework the API. Instead of configuring each stream independently,
   there is a `stdio` field where you can choose between
   `infer_from_args`, `inherit`, or `check`. These determine whether the
   RunStep is considered to have side-effects or not. The previous
   field, `condition` is gone.
 * when stdio mode is set to `check` there is a slice of any number of
   checks to make, which include things like exit code, stderr matching,
   or stdout matching.
 * remove the ill-defined `print` field.
 * when adding an output arg, it takes the opportunity to give itself a
   better name.
 * The flag `skip_foreign_checks` is added. If this is true, a RunStep
   which is configured to check the output of the executed binary will
   not fail the build if the binary cannot be executed due to being for
   a foreign binary to the host system which is running the build graph.
   Command-line arguments such as -fqemu and -fwasmtime may affect
   whether a binary is detected as foreign, as well as system
   configuration such as Rosetta (macOS) and binfmt_misc (Linux).
   - This makes EmulatableRunStep no longer needed.
 * Fix the child process handling to properly integrate with the new
   bulid API and to avoid deadlocks in stdout/stderr streams by polling
   if necessary.

std.Build.RemoveDirStep now uses the open build_root directory handle
instead of an absolute path.
  • Loading branch information
andrewrk committed Mar 15, 2023
1 parent d0f6758 commit 58edefc
Show file tree
Hide file tree
Showing 23 changed files with 1,113 additions and 1,167 deletions.
24 changes: 18 additions & 6 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ pub fn build(b: *std.Build) !void {
test_cases.stack_size = stack_size;
test_cases.single_threaded = single_threaded;

const fmt_build_zig = b.addFmt(&[_][]const u8{"build.zig"});

const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false;
const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false;
const skip_release_small = b.option(bool, "skip-release-small", "Main test suite skips release-small builds") orelse skip_release;
Expand Down Expand Up @@ -386,10 +384,24 @@ pub fn build(b: *std.Build) !void {
}
const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index];

// run stage1 `zig fmt` on this build.zig file just to make sure it works
test_step.dependOn(&fmt_build_zig.step);
const fmt_step = b.step("test-fmt", "Run zig fmt against build.zig to make sure it works");
fmt_step.dependOn(&fmt_build_zig.step);
const fmt_include_paths = &.{ "doc", "lib", "src", "test", "tools", "build.zig" };
const fmt_exclude_paths = &.{ "test/cases" };
const check_fmt = b.addFmt(.{
.paths = fmt_include_paths,
.exclude_paths = fmt_exclude_paths,
.check = true,
});
const do_fmt = b.addFmt(.{
.paths = fmt_include_paths,
.exclude_paths = fmt_exclude_paths,
});

const test_fmt_step = b.step("test-fmt", "Check whether source files have conforming formatting");
test_fmt_step.dependOn(&check_fmt.step);

const do_fmt_step = b.step("fmt", "Modify source files in place to have conforming formatting");
do_fmt_step.dependOn(&do_fmt.step);


test_step.dependOn(tests.addPkgTests(
b,
Expand Down
71 changes: 46 additions & 25 deletions lib/build_runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ pub fn main() !void {

var install_prefix: ?[]const u8 = null;
var dir_list = std.Build.DirList{};
var enable_summary: ?bool = null;

const Color = enum { auto, off, on };
var color: Color = .auto;
Expand Down Expand Up @@ -217,6 +218,10 @@ pub fn main() !void {
builder.enable_darling = true;
} else if (mem.eql(u8, arg, "-fno-darling")) {
builder.enable_darling = false;
} else if (mem.eql(u8, arg, "-fsummary")) {
enable_summary = true;
} else if (mem.eql(u8, arg, "-fno-summary")) {
enable_summary = false;
} else if (mem.eql(u8, arg, "-freference-trace")) {
builder.reference_trace = 256;
} else if (mem.startsWith(u8, arg, "-freference-trace=")) {
Expand Down Expand Up @@ -252,8 +257,9 @@ pub fn main() !void {
}
}

const stderr = std.io.getStdErr();
const ttyconf: std.debug.TTY.Config = switch (color) {
.auto => std.debug.detectTTYConfig(std.io.getStdErr()),
.auto => std.debug.detectTTYConfig(stderr),
.on => .escape_codes,
.off => .no_color,
};
Expand All @@ -279,6 +285,8 @@ pub fn main() !void {
main_progress_node,
thread_pool_options,
ttyconf,
stderr,
enable_summary,
) catch |err| switch (err) {
error.UncleanExit => process.exit(1),
else => return err,
Expand All @@ -292,6 +300,8 @@ fn runStepNames(
parent_prog_node: *std.Progress.Node,
thread_pool_options: std.Thread.Pool.Options,
ttyconf: std.debug.TTY.Config,
stderr: std.fs.File,
enable_summary: ?bool,
) !void {
const gpa = b.allocator;
var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{};
Expand Down Expand Up @@ -382,28 +392,35 @@ fn runStepNames(

// A proper command line application defaults to silently succeeding.
// The user may request verbose mode if they have a different preference.
if (failure_count == 0 and !b.verbose) return cleanExit();

const stderr = std.io.getStdErr();

const total_count = success_count + failure_count + pending_count;
ttyconf.setColor(stderr, .Cyan) catch {};
stderr.writeAll("Build Summary: ") catch {};
ttyconf.setColor(stderr, .Reset) catch {};
stderr.writer().print("{d}/{d} steps succeeded; {d} failed; {d} total compile errors\n", .{
success_count, total_count, failure_count, total_compile_errors,
}) catch {};
if (failure_count == 0 and enable_summary != true) return cleanExit();

if (enable_summary != false) {
const total_count = success_count + failure_count + pending_count;
ttyconf.setColor(stderr, .Cyan) catch {};
stderr.writeAll("Build Summary:") catch {};
ttyconf.setColor(stderr, .Reset) catch {};
stderr.writer().print(" {d}/{d} steps succeeded; {d} failed", .{
success_count, total_count, failure_count,
}) catch {};

if (enable_summary == null) {
ttyconf.setColor(stderr, .Dim) catch {};
stderr.writeAll(" (disable with -fno-summary)") catch {};
ttyconf.setColor(stderr, .Reset) catch {};
}
stderr.writeAll("\n") catch {};

// Print a fancy tree with build results.
var print_node: PrintNode = .{ .parent = null };
if (step_names.len == 0) {
print_node.last = true;
printTreeStep(b, b.default_step, stderr, ttyconf, &print_node, &step_stack) catch {};
} else {
for (step_names, 0..) |step_name, i| {
const tls = b.top_level_steps.get(step_name).?;
print_node.last = i + 1 == b.top_level_steps.count();
printTreeStep(b, &tls.step, stderr, ttyconf, &print_node, &step_stack) catch {};
// Print a fancy tree with build results.
var print_node: PrintNode = .{ .parent = null };
if (step_names.len == 0) {
print_node.last = true;
printTreeStep(b, b.default_step, stderr, ttyconf, &print_node, &step_stack) catch {};
} else {
for (step_names, 0..) |step_name, i| {
const tls = b.top_level_steps.get(step_name).?;
print_node.last = i + 1 == b.top_level_steps.count();
printTreeStep(b, &tls.step, stderr, ttyconf, &print_node, &step_stack) catch {};
}
}
}

Expand Down Expand Up @@ -453,9 +470,9 @@ fn printTreeStep(
step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
) !void {
const first = step_stack.swapRemove(s);
if (!first) try ttyconf.setColor(stderr, .Dim);
try printPrefix(parent_node, stderr);

if (!first) try ttyconf.setColor(stderr, .Dim);
if (parent_node.parent != null) {
if (parent_node.last) {
try stderr.writeAll("└─ ");
Expand All @@ -464,7 +481,7 @@ fn printTreeStep(
}
}

// TODO print the dep prefix too?
// dep_prefix omitted here because it is redundant with the tree.
try stderr.writeAll(s.name);

if (first) {
Expand Down Expand Up @@ -608,8 +625,10 @@ fn workerMakeOneStep(
const stderr = std.io.getStdErr();

for (s.result_error_msgs.items) |msg| {
// TODO print the dep prefix too
// Sometimes it feels like you just can't catch a break. Finally,
// with Zig, you can.
ttyconf.setColor(stderr, .Bold) catch break;
stderr.writeAll(s.owner.dep_prefix) catch break;
stderr.writeAll(s.name) catch break;
stderr.writeAll(": ") catch break;
ttyconf.setColor(stderr, .Red) catch break;
Expand Down Expand Up @@ -735,6 +754,8 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
\\Advanced Options:
\\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
\\ -fno-reference-trace Disable reference trace
\\ -fsummary Print the build summary, even on success
\\ -fno-summary Omit the build summary, even on failure
\\ --build-file [file] Override path to build.zig
\\ --cache-dir [path] Override path to local Zig cache directory
\\ --global-cache-dir [path] Override path to global Zig cache directory
Expand Down

0 comments on commit 58edefc

Please sign in to comment.