Skip to content

Commit

Permalink
build system: add --watch flag and report source file in InstallFile
Browse files Browse the repository at this point in the history
This direction is not quite right because it mutates shared state in a
threaded context, so the next commit will need to fix this.
  • Loading branch information
andrewrk committed Jul 9, 2024
1 parent 626597b commit 0be7680
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 21 deletions.
66 changes: 45 additions & 21 deletions lib/compiler/build_runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub fn main() !void {
.query = .{},
.result = try std.zig.system.resolveTargetQuery(.{}),
},
.watch = null,
};

graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
Expand All @@ -97,12 +98,12 @@ pub fn main() !void {
var dir_list = std.Build.DirList{};
var summary: ?Summary = null;
var max_rss: u64 = 0;
var skip_oom_steps: bool = false;
var skip_oom_steps = false;
var color: Color = .auto;
var seed: u32 = 0;
var prominent_compile_errors: bool = false;
var help_menu: bool = false;
var steps_menu: bool = false;
var prominent_compile_errors = false;
var help_menu = false;
var steps_menu = false;
var output_tmp_nonce: ?[16]u8 = null;

while (nextArg(args, &arg_idx)) |arg| {
Expand Down Expand Up @@ -227,6 +228,10 @@ pub fn main() !void {
builder.verbose_llvm_cpu_features = true;
} else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
prominent_compile_errors = true;
} else if (mem.eql(u8, arg, "--watch")) {
const watch = try arena.create(std.Build.Watch);
watch.* = std.Build.Watch.init;
graph.watch = watch;
} else if (mem.eql(u8, arg, "-fwine")) {
builder.enable_wine = true;
} else if (mem.eql(u8, arg, "-fno-wine")) {
Expand Down Expand Up @@ -344,7 +349,7 @@ pub fn main() !void {
.prominent_compile_errors = prominent_compile_errors,

.claimed_rss = 0,
.summary = summary,
.summary = summary orelse if (graph.watch != null) .new else .failures,
.ttyconf = ttyconf,
.stderr = stderr,
};
Expand All @@ -363,7 +368,10 @@ pub fn main() !void {
&run,
seed,
) catch |err| switch (err) {
error.UncleanExit => process.exit(1),
error.UncleanExit => {
if (graph.watch == null)
process.exit(1);
},
else => return err,
};
}
Expand All @@ -377,7 +385,7 @@ const Run = struct {
prominent_compile_errors: bool,

claimed_rss: usize,
summary: ?Summary,
summary: Summary,
ttyconf: std.io.tty.Config,
stderr: File,
};
Expand Down Expand Up @@ -417,7 +425,7 @@ fn runStepNames(

for (starting_steps) |s| {
constructGraphAndCheckForDependencyLoop(b, s, &step_stack, rand) catch |err| switch (err) {
error.DependencyLoopDetected => return error.UncleanExit,
error.DependencyLoopDetected => return uncleanExit(),
else => |e| return e,
};
}
Expand All @@ -442,7 +450,7 @@ fn runStepNames(
if (run.max_rss_is_default) {
std.debug.print("note: use --maxrss to override the default", .{});
}
return error.UncleanExit;
return uncleanExit();
}
}

Expand Down Expand Up @@ -524,13 +532,19 @@ fn runStepNames(

// A proper command line application defaults to silently succeeding.
// The user may request verbose mode if they have a different preference.
const failures_only = run.summary != .all and run.summary != .new;
if (failure_count == 0 and failures_only) return cleanExit();
const failures_only = switch (run.summary) {
.failures, .none => true,
else => false,
};
if (failure_count == 0 and failures_only) {
if (b.graph.watch != null) return;
return cleanExit();
}

const ttyconf = run.ttyconf;
const stderr = run.stderr;

if (run.summary != Summary.none) {
if (run.summary != .none) {
const total_count = success_count + failure_count + pending_count + skipped_count;
ttyconf.setColor(stderr, .cyan) catch {};
stderr.writeAll("Build Summary:") catch {};
Expand All @@ -544,11 +558,6 @@ fn runStepNames(
if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};

if (run.summary == null) {
ttyconf.setColor(stderr, .dim) catch {};
stderr.writeAll(" (disable with --summary none)") catch {};
ttyconf.setColor(stderr, .reset) catch {};
}
stderr.writeAll("\n") catch {};

// Print a fancy tree with build results.
Expand All @@ -562,7 +571,7 @@ fn runStepNames(
while (i > 0) {
i -= 1;
const step = b.top_level_steps.get(step_names[i]).?.step;
const found = switch (run.summary orelse .failures) {
const found = switch (run.summary) {
.all, .none => unreachable,
.failures => step.state != .success,
.new => !step.result_cached,
Expand All @@ -579,7 +588,10 @@ fn runStepNames(
}
}

if (failure_count == 0) return cleanExit();
if (failure_count == 0) {
if (b.graph.watch != null) return;
return cleanExit();
}

// Finally, render compile errors at the bottom of the terminal.
// We use a separate compile_error_steps array list because step_stack is destructively
Expand All @@ -591,13 +603,24 @@ fn runStepNames(
}
}

if (b.graph.watch != null) return uncleanExit();

// Signal to parent process that we have printed compile errors. The
// parent process may choose to omit the "following command failed"
// line in this case.
process.exit(2);
}

process.exit(1);
return uncleanExit();
}

fn uncleanExit() error{UncleanExit}!void {
if (builtin.mode == .Debug) {
return error.UncleanExit;
} else {
std.debug.lockStdErr();
process.exit(1);
}
}

const PrintNode = struct {
Expand Down Expand Up @@ -768,7 +791,7 @@ fn printTreeStep(
step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
) !void {
const first = step_stack.swapRemove(s);
const summary = run.summary orelse .failures;
const summary = run.summary;
const skip = switch (summary) {
.none => unreachable,
.all => false,
Expand Down Expand Up @@ -1124,6 +1147,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void {
\\ --maxrss <bytes> Limit memory usage (default is to use available memory)
\\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
\\ --fetch Exit after fetching dependency tree
\\ --watch Continuously rebuild when source files are modified
\\
\\Project-Specific Options:
\\
Expand Down
55 changes: 55 additions & 0 deletions lib/std/Build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,61 @@ pub const Graph = struct {
needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .{},
/// Information about the native target. Computed before build() is invoked.
host: ResolvedTarget,
/// When `--watch` is provided, collects the set of files that should be
/// watched and the state to required to poll the system for changes.
watch: ?*Watch,
};

pub const Watch = struct {
table: Table,

pub const init: Watch = .{
.table = .{},
};

/// Key is the directory to watch which contains one or more files we are
/// interested in noticing changes to.
pub const Table = std.ArrayHashMapUnmanaged(Cache.Path, ReactionSet, TableContext, false);

const Hash = std.hash.Wyhash;

pub const TableContext = struct {
pub fn hash(self: TableContext, a: Cache.Path) u32 {
_ = self;
const seed: u32 = @bitCast(a.root_dir.handle.fd);
return @truncate(Hash.hash(seed, a.sub_path));
}
pub fn eql(self: TableContext, a: Cache.Path, b: Cache.Path, b_index: usize) bool {
_ = self;
_ = b_index;
return a.eql(b);
}
};

pub const ReactionSet = std.ArrayHashMapUnmanaged(Match, void, Match.Context, false);

pub const Match = struct {
/// Relative to the watched directory, the file path that triggers this
/// match.
basename: []const u8,
/// The step to re-run when file corresponding to `basename` is changed.
step: *Step,

pub const Context = struct {
pub fn hash(self: Context, a: Match) u32 {
_ = self;
var hasher = Hash.init(0);
std.hash.autoHash(&hasher, a.step);
hasher.update(a.basename);
return @truncate(hasher.final());
}
pub fn eql(self: Context, a: Match, b: Match, b_index: usize) bool {
_ = self;
_ = b_index;
return a.step == b.step and mem.eql(u8, a.basename, b.basename);
}
};
};
};

const AvailableDeps = []const struct { []const u8, []const u8 };
Expand Down
46 changes: 46 additions & 0 deletions lib/std/Build/Step.zig
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,52 @@ pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void {
}
}

fn oom(err: anytype) noreturn {
switch (err) {
error.OutOfMemory => @panic("out of memory"),
}
}

pub fn addWatchInput(step: *Step, lazy_path: std.Build.LazyPath) void {
errdefer |err| oom(err);
const w = step.owner.graph.watch orelse return;
switch (lazy_path) {
.src_path => |src_path| try addWatchInputFromBuilder(step, w, src_path.owner, src_path.sub_path),
.dependency => |d| try addWatchInputFromBuilder(step, w, d.dependency.builder, d.sub_path),
.cwd_relative => |path_string| {
try addWatchInputFromPath(w, .{
.root_dir = .{
.path = null,
.handle = std.fs.cwd(),
},
.sub_path = std.fs.path.dirname(path_string) orelse "",
}, .{
.step = step,
.basename = std.fs.path.basename(path_string),
});
},
// Nothing to watch because this dependency edge is modeled instead via `dependants`.
.generated => {},
}
}

fn addWatchInputFromBuilder(step: *Step, w: *std.Build.Watch, builder: *std.Build, sub_path: []const u8) !void {
return addWatchInputFromPath(w, .{
.root_dir = builder.build_root,
.sub_path = std.fs.path.dirname(sub_path) orelse "",
}, .{
.step = step,
.basename = std.fs.path.basename(sub_path),
});
}

fn addWatchInputFromPath(w: *std.Build.Watch, path: std.Build.Cache.Path, match: std.Build.Watch.Match) !void {
const gpa = match.step.owner.allocator;
const gop = try w.table.getOrPut(gpa, path);
if (!gop.found_existing) gop.value_ptr.* = .{};
try gop.value_ptr.put(gpa, match, {});
}

test {
_ = CheckFile;
_ = CheckObject;
Expand Down
1 change: 1 addition & 0 deletions lib/std/Build/Step/InstallFile.zig
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
_ = prog_node;
const b = step.owner;
const install_file: *InstallFile = @fieldParentPtr("step", step);
step.addWatchInput(install_file.source);
const full_src_path = install_file.source.getPath2(b, step);
const full_dest_path = b.getInstallPath(install_file.dir, install_file.dest_rel_path);
const cwd = std.fs.cwd();
Expand Down

0 comments on commit 0be7680

Please sign in to comment.