authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-12 16:56:17-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-07-12 16:56:17-07:00
log1d20ff11d7efc1f07de9a300abc15ad6ae9d6d6f
treee72a2989fddfa91571999ade7ef68c87c7c2610f
parent0d79aa01768d600a19f7a7493afce417da7e3810
parent5efcc2e9e7c84893b9e418ca82d8d2d4366dde7c
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #20580 from ziglang/watch

introduce file system watching features to the zig build system

27 files changed, 1552 insertions(+), 417 deletions(-)

build.zig+5-6
...@@ -595,7 +595,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {...@@ -595,7 +595,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
595 run_opt.addArg("-o");595 run_opt.addArg("-o");
596 run_opt.addFileArg(b.path("stage1/zig1.wasm"));596 run_opt.addFileArg(b.path("stage1/zig1.wasm"));
597597
598 const copy_zig_h = b.addWriteFiles();598 const copy_zig_h = b.addUpdateSourceFiles();
599 copy_zig_h.addCopyFileToSource(b.path("lib/zig.h"), "stage1/zig.h");599 copy_zig_h.addCopyFileToSource(b.path("lib/zig.h"), "stage1/zig.h");
600600
601 const update_zig1_step = b.step("update-zig1", "Update stage1/zig1.wasm");601 const update_zig1_step = b.step("update-zig1", "Update stage1/zig1.wasm");
...@@ -1261,7 +1261,9 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {...@@ -1261,7 +1261,9 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
1261 });1261 });
12621262
1263 var dir = b.build_root.handle.openDir("doc/langref", .{ .iterate = true }) catch |err| {1263 var dir = b.build_root.handle.openDir("doc/langref", .{ .iterate = true }) catch |err| {
1264 std.debug.panic("unable to open 'doc/langref' directory: {s}", .{@errorName(err)});1264 std.debug.panic("unable to open '{}doc/langref' directory: {s}", .{
1265 b.build_root, @errorName(err),
1266 });
1265 };1267 };
1266 defer dir.close();1268 defer dir.close();
12671269
...@@ -1280,10 +1282,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {...@@ -1280,10 +1282,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
1280 // in a temporary directory1282 // in a temporary directory
1281 "--cache-root", b.cache_root.path orelse ".",1283 "--cache-root", b.cache_root.path orelse ".",
1282 });1284 });
1283 if (b.zig_lib_dir) |p| {1285 cmd.addArgs(&.{ "--zig-lib-dir", b.fmt("{}", .{b.graph.zig_lib_directory}) });
1284 cmd.addArg("--zig-lib-dir");
1285 cmd.addDirectoryArg(p);
1286 }
1287 cmd.addArgs(&.{"-i"});1286 cmd.addArgs(&.{"-i"});
1288 cmd.addFileArg(b.path(b.fmt("doc/langref/{s}", .{entry.name})));1287 cmd.addFileArg(b.path(b.fmt("doc/langref/{s}", .{entry.name})));
12891288
lib/compiler/build_runner.zig+177-85
...@@ -8,6 +8,9 @@ const process = std.process;...@@ -8,6 +8,9 @@ const process = std.process;
8const ArrayList = std.ArrayList;8const ArrayList = std.ArrayList;
9const File = std.fs.File;9const File = std.fs.File;
10const Step = std.Build.Step;10const Step = std.Build.Step;
11const Watch = std.Build.Watch;
12const Allocator = std.mem.Allocator;
13const fatal = std.zig.fatal;
1114
12pub const root = @import("@build");15pub const root = @import("@build");
13pub const dependencies = @import("@dependencies");16pub const dependencies = @import("@dependencies");
...@@ -29,21 +32,15 @@ pub fn main() !void {...@@ -29,21 +32,15 @@ pub fn main() !void {
29 // skip my own exe name32 // skip my own exe name
30 var arg_idx: usize = 1;33 var arg_idx: usize = 1;
3134
32 const zig_exe = nextArg(args, &arg_idx) orelse {35 const zig_exe = nextArg(args, &arg_idx) orelse fatal("missing zig compiler path", .{});
33 std.debug.print("Expected path to zig compiler\n", .{});36 const zig_lib_dir = nextArg(args, &arg_idx) orelse fatal("missing zig lib directory path", .{});
34 return error.InvalidArgs;37 const build_root = nextArg(args, &arg_idx) orelse fatal("missing build root directory path", .{});
35 };38 const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{});
36 const build_root = nextArg(args, &arg_idx) orelse {39 const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{});
37 std.debug.print("Expected build root directory path\n", .{});40
38 return error.InvalidArgs;41 const zig_lib_directory: std.Build.Cache.Directory = .{
39 };42 .path = zig_lib_dir,
40 const cache_root = nextArg(args, &arg_idx) orelse {43 .handle = try std.fs.cwd().openDir(zig_lib_dir, .{}),
41 std.debug.print("Expected cache root directory path\n", .{});
42 return error.InvalidArgs;
43 };
44 const global_cache_root = nextArg(args, &arg_idx) orelse {
45 std.debug.print("Expected global cache root directory path\n", .{});
46 return error.InvalidArgs;
47 };44 };
4845
49 const build_root_directory: std.Build.Cache.Directory = .{46 const build_root_directory: std.Build.Cache.Directory = .{
...@@ -70,6 +67,7 @@ pub fn main() !void {...@@ -70,6 +67,7 @@ pub fn main() !void {
70 .zig_exe = zig_exe,67 .zig_exe = zig_exe,
71 .env_map = try process.getEnvMap(arena),68 .env_map = try process.getEnvMap(arena),
72 .global_cache_root = global_cache_directory,69 .global_cache_root = global_cache_directory,
70 .zig_lib_directory = zig_lib_directory,
73 .host = .{71 .host = .{
74 .query = .{},72 .query = .{},
75 .result = try std.zig.system.resolveTargetQuery(.{}),73 .result = try std.zig.system.resolveTargetQuery(.{}),
...@@ -97,13 +95,15 @@ pub fn main() !void {...@@ -97,13 +95,15 @@ pub fn main() !void {
97 var dir_list = std.Build.DirList{};95 var dir_list = std.Build.DirList{};
98 var summary: ?Summary = null;96 var summary: ?Summary = null;
99 var max_rss: u64 = 0;97 var max_rss: u64 = 0;
100 var skip_oom_steps: bool = false;98 var skip_oom_steps = false;
101 var color: Color = .auto;99 var color: Color = .auto;
102 var seed: u32 = 0;100 var seed: u32 = 0;
103 var prominent_compile_errors: bool = false;101 var prominent_compile_errors = false;
104 var help_menu: bool = false;102 var help_menu = false;
105 var steps_menu: bool = false;103 var steps_menu = false;
106 var output_tmp_nonce: ?[16]u8 = null;104 var output_tmp_nonce: ?[16]u8 = null;
105 var watch = false;
106 var debounce_interval_ms: u16 = 50;
107107
108 while (nextArg(args, &arg_idx)) |arg| {108 while (nextArg(args, &arg_idx)) |arg| {
109 if (mem.startsWith(u8, arg, "-Z")) {109 if (mem.startsWith(u8, arg, "-Z")) {
...@@ -185,13 +185,19 @@ pub fn main() !void {...@@ -185,13 +185,19 @@ pub fn main() !void {
185 arg, next_arg,185 arg, next_arg,
186 });186 });
187 };187 };
188 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
189 builder.zig_lib_dir = .{ .cwd_relative = nextArgOrFatal(args, &arg_idx) };
190 } else if (mem.eql(u8, arg, "--seed")) {188 } else if (mem.eql(u8, arg, "--seed")) {
191 const next_arg = nextArg(args, &arg_idx) orelse189 const next_arg = nextArg(args, &arg_idx) orelse
192 fatalWithHint("expected u32 after '{s}'", .{arg});190 fatalWithHint("expected u32 after '{s}'", .{arg});
193 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {191 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
194 fatal("unable to parse seed '{s}' as 32-bit integer: {s}\n", .{192 fatal("unable to parse seed '{s}' as unsigned 32-bit integer: {s}\n", .{
193 next_arg, @errorName(err),
194 });
195 };
196 } else if (mem.eql(u8, arg, "--debounce")) {
197 const next_arg = nextArg(args, &arg_idx) orelse
198 fatalWithHint("expected u16 after '{s}'", .{arg});
199 debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| {
200 fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {s}\n", .{
195 next_arg, @errorName(err),201 next_arg, @errorName(err),
196 });202 });
197 };203 };
...@@ -227,6 +233,8 @@ pub fn main() !void {...@@ -227,6 +233,8 @@ pub fn main() !void {
227 builder.verbose_llvm_cpu_features = true;233 builder.verbose_llvm_cpu_features = true;
228 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {234 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
229 prominent_compile_errors = true;235 prominent_compile_errors = true;
236 } else if (mem.eql(u8, arg, "--watch")) {
237 watch = true;
230 } else if (mem.eql(u8, arg, "-fwine")) {238 } else if (mem.eql(u8, arg, "-fwine")) {
231 builder.enable_wine = true;239 builder.enable_wine = true;
232 } else if (mem.eql(u8, arg, "-fno-wine")) {240 } else if (mem.eql(u8, arg, "-fno-wine")) {
...@@ -292,6 +300,7 @@ pub fn main() !void {...@@ -292,6 +300,7 @@ pub fn main() !void {
292 const main_progress_node = std.Progress.start(.{300 const main_progress_node = std.Progress.start(.{
293 .disable_printing = (color == .off),301 .disable_printing = (color == .off),
294 });302 });
303 defer main_progress_node.end();
295304
296 builder.debug_log_scopes = debug_log_scopes.items;305 builder.debug_log_scopes = debug_log_scopes.items;
297 builder.resolveInstallPrefix(install_prefix, dir_list);306 builder.resolveInstallPrefix(install_prefix, dir_list);
...@@ -340,13 +349,16 @@ pub fn main() !void {...@@ -340,13 +349,16 @@ pub fn main() !void {
340 .max_rss_is_default = false,349 .max_rss_is_default = false,
341 .max_rss_mutex = .{},350 .max_rss_mutex = .{},
342 .skip_oom_steps = skip_oom_steps,351 .skip_oom_steps = skip_oom_steps,
352 .watch = watch,
343 .memory_blocked_steps = std.ArrayList(*Step).init(arena),353 .memory_blocked_steps = std.ArrayList(*Step).init(arena),
354 .step_stack = .{},
344 .prominent_compile_errors = prominent_compile_errors,355 .prominent_compile_errors = prominent_compile_errors,
345356
346 .claimed_rss = 0,357 .claimed_rss = 0,
347 .summary = summary,358 .summary = summary orelse if (watch) .new else .failures,
348 .ttyconf = ttyconf,359 .ttyconf = ttyconf,
349 .stderr = stderr,360 .stderr = stderr,
361 .thread_pool = undefined,
350 };362 };
351363
352 if (run.max_rss == 0) {364 if (run.max_rss == 0) {
...@@ -354,18 +366,78 @@ pub fn main() !void {...@@ -354,18 +366,78 @@ pub fn main() !void {
354 run.max_rss_is_default = true;366 run.max_rss_is_default = true;
355 }367 }
356368
357 runStepNames(369 const gpa = arena;
358 arena,370 prepare(gpa, arena, builder, targets.items, &run, seed) catch |err| switch (err) {
359 builder,
360 targets.items,
361 main_progress_node,
362 thread_pool_options,
363 &run,
364 seed,
365 ) catch |err| switch (err) {
366 error.UncleanExit => process.exit(1),371 error.UncleanExit => process.exit(1),
367 else => return err,372 else => return err,
368 };373 };
374
375 var w = if (watch) try Watch.init() else undefined;
376
377 try run.thread_pool.init(thread_pool_options);
378 defer run.thread_pool.deinit();
379
380 rebuild: while (true) {
381 runStepNames(
382 gpa,
383 builder,
384 targets.items,
385 main_progress_node,
386 &run,
387 ) catch |err| switch (err) {
388 error.UncleanExit => {
389 assert(!run.watch);
390 process.exit(1);
391 },
392 else => return err,
393 };
394 if (!watch) return cleanExit();
395
396 switch (builtin.os.tag) {
397 .linux => {},
398 else => fatal("--watch not yet implemented for {s}", .{@tagName(builtin.os.tag)}),
399 }
400
401 try w.update(gpa, run.step_stack.keys());
402
403 // Wait until a file system notification arrives. Read all such events
404 // until the buffer is empty. Then wait for a debounce interval, resetting
405 // if any more events come in. After the debounce interval has passed,
406 // trigger a rebuild on all steps with modified inputs, as well as their
407 // recursive dependants.
408 var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
409 const caption = std.fmt.bufPrint(&caption_buf, "Watching {d} Directories", .{
410 w.dir_table.entries.len,
411 }) catch &caption_buf;
412 var debouncing_node = main_progress_node.start(caption, 0);
413 var debounce_timeout: Watch.Timeout = .none;
414 while (true) switch (try w.wait(gpa, debounce_timeout)) {
415 .timeout => {
416 debouncing_node.end();
417 markFailedStepsDirty(gpa, run.step_stack.keys());
418 continue :rebuild;
419 },
420 .dirty => if (debounce_timeout == .none) {
421 debounce_timeout = .{ .ms = debounce_interval_ms };
422 debouncing_node.end();
423 debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
424 },
425 .clean => {},
426 };
427 }
428}
429
430fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void {
431 for (all_steps) |step| switch (step.state) {
432 .dependency_failure, .failure, .skipped => step.recursiveReset(gpa),
433 else => continue,
434 };
435 // Now that all dirty steps have been found, the remaining steps that
436 // succeeded from last run shall be marked "cached".
437 for (all_steps) |step| switch (step.state) {
438 .success => step.result_cached = true,
439 else => continue,
440 };
369}441}
370442
371const Run = struct {443const Run = struct {
...@@ -373,27 +445,27 @@ const Run = struct {...@@ -373,27 +445,27 @@ const Run = struct {
373 max_rss_is_default: bool,445 max_rss_is_default: bool,
374 max_rss_mutex: std.Thread.Mutex,446 max_rss_mutex: std.Thread.Mutex,
375 skip_oom_steps: bool,447 skip_oom_steps: bool,
448 watch: bool,
376 memory_blocked_steps: std.ArrayList(*Step),449 memory_blocked_steps: std.ArrayList(*Step),
450 step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),
377 prominent_compile_errors: bool,451 prominent_compile_errors: bool,
452 thread_pool: std.Thread.Pool,
378453
379 claimed_rss: usize,454 claimed_rss: usize,
380 summary: ?Summary,455 summary: Summary,
381 ttyconf: std.io.tty.Config,456 ttyconf: std.io.tty.Config,
382 stderr: File,457 stderr: File,
383};458};
384459
385fn runStepNames(460fn prepare(
386 arena: std.mem.Allocator,461 gpa: Allocator,
462 arena: Allocator,
387 b: *std.Build,463 b: *std.Build,
388 step_names: []const []const u8,464 step_names: []const []const u8,
389 parent_prog_node: std.Progress.Node,
390 thread_pool_options: std.Thread.Pool.Options,
391 run: *Run,465 run: *Run,
392 seed: u32,466 seed: u32,
393) !void {467) !void {
394 const gpa = b.allocator;468 const step_stack = &run.step_stack;
395 var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{};
396 defer step_stack.deinit(gpa);
397469
398 if (step_names.len == 0) {470 if (step_names.len == 0) {
399 try step_stack.put(gpa, b.default_step, {});471 try step_stack.put(gpa, b.default_step, {});
...@@ -416,8 +488,8 @@ fn runStepNames(...@@ -416,8 +488,8 @@ fn runStepNames(
416 rand.shuffle(*Step, starting_steps);488 rand.shuffle(*Step, starting_steps);
417489
418 for (starting_steps) |s| {490 for (starting_steps) |s| {
419 constructGraphAndCheckForDependencyLoop(b, s, &step_stack, rand) catch |err| switch (err) {491 constructGraphAndCheckForDependencyLoop(b, s, &run.step_stack, rand) catch |err| switch (err) {
420 error.DependencyLoopDetected => return error.UncleanExit,492 error.DependencyLoopDetected => return uncleanExit(),
421 else => |e| return e,493 else => |e| return e,
422 };494 };
423 }495 }
...@@ -442,17 +514,22 @@ fn runStepNames(...@@ -442,17 +514,22 @@ fn runStepNames(
442 if (run.max_rss_is_default) {514 if (run.max_rss_is_default) {
443 std.debug.print("note: use --maxrss to override the default", .{});515 std.debug.print("note: use --maxrss to override the default", .{});
444 }516 }
445 return error.UncleanExit;517 return uncleanExit();
446 }518 }
447 }519 }
520}
448521
449 var thread_pool: std.Thread.Pool = undefined;522fn runStepNames(
450 try thread_pool.init(thread_pool_options);523 gpa: Allocator,
451 defer thread_pool.deinit();524 b: *std.Build,
525 step_names: []const []const u8,
526 parent_prog_node: std.Progress.Node,
527 run: *Run,
528) !void {
529 const step_stack = &run.step_stack;
530 const thread_pool = &run.thread_pool;
452531
453 {532 {
454 defer parent_prog_node.end();
455
456 const step_prog = parent_prog_node.start("steps", step_stack.count());533 const step_prog = parent_prog_node.start("steps", step_stack.count());
457 defer step_prog.end();534 defer step_prog.end();
458535
...@@ -468,7 +545,7 @@ fn runStepNames(...@@ -468,7 +545,7 @@ fn runStepNames(
468 if (step.state == .skipped_oom) continue;545 if (step.state == .skipped_oom) continue;
469546
470 thread_pool.spawnWg(&wait_group, workerMakeOneStep, .{547 thread_pool.spawnWg(&wait_group, workerMakeOneStep, .{
471 &wait_group, &thread_pool, b, step, step_prog, run,548 &wait_group, b, step, step_prog, run,
472 });549 });
473 }550 }
474 }551 }
...@@ -485,8 +562,6 @@ fn runStepNames(...@@ -485,8 +562,6 @@ fn runStepNames(
485 var failure_count: usize = 0;562 var failure_count: usize = 0;
486 var pending_count: usize = 0;563 var pending_count: usize = 0;
487 var total_compile_errors: usize = 0;564 var total_compile_errors: usize = 0;
488 var compile_error_steps: std.ArrayListUnmanaged(*Step) = .{};
489 defer compile_error_steps.deinit(gpa);
490565
491 for (step_stack.keys()) |s| {566 for (step_stack.keys()) |s| {
492 test_fail_count += s.test_results.fail_count;567 test_fail_count += s.test_results.fail_count;
...@@ -516,7 +591,6 @@ fn runStepNames(...@@ -516,7 +591,6 @@ fn runStepNames(
516 const compile_errors_len = s.result_error_bundle.errorMessageCount();591 const compile_errors_len = s.result_error_bundle.errorMessageCount();
517 if (compile_errors_len > 0) {592 if (compile_errors_len > 0) {
518 total_compile_errors += compile_errors_len;593 total_compile_errors += compile_errors_len;
519 try compile_error_steps.append(gpa, s);
520 }594 }
521 },595 },
522 }596 }
...@@ -524,13 +598,22 @@ fn runStepNames(...@@ -524,13 +598,22 @@ fn runStepNames(
524598
525 // A proper command line application defaults to silently succeeding.599 // A proper command line application defaults to silently succeeding.
526 // The user may request verbose mode if they have a different preference.600 // The user may request verbose mode if they have a different preference.
527 const failures_only = run.summary != .all and run.summary != .new;601 const failures_only = switch (run.summary) {
528 if (failure_count == 0 and failures_only) return cleanExit();602 .failures, .none => true,
603 else => false,
604 };
605 if (failure_count == 0 and failures_only) {
606 if (!run.watch) cleanExit();
607 return;
608 }
529609
530 const ttyconf = run.ttyconf;610 const ttyconf = run.ttyconf;
531 const stderr = run.stderr;
532611
533 if (run.summary != Summary.none) {612 if (run.summary != .none) {
613 std.debug.lockStdErr();
614 defer std.debug.unlockStdErr();
615 const stderr = run.stderr;
616
534 const total_count = success_count + failure_count + pending_count + skipped_count;617 const total_count = success_count + failure_count + pending_count + skipped_count;
535 ttyconf.setColor(stderr, .cyan) catch {};618 ttyconf.setColor(stderr, .cyan) catch {};
536 stderr.writeAll("Build Summary:") catch {};619 stderr.writeAll("Build Summary:") catch {};
...@@ -544,25 +627,23 @@ fn runStepNames(...@@ -544,25 +627,23 @@ fn runStepNames(
544 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};627 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
545 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};628 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
546629
547 if (run.summary == null) {
548 ttyconf.setColor(stderr, .dim) catch {};
549 stderr.writeAll(" (disable with --summary none)") catch {};
550 ttyconf.setColor(stderr, .reset) catch {};
551 }
552 stderr.writeAll("\n") catch {};630 stderr.writeAll("\n") catch {};
553631
554 // Print a fancy tree with build results.632 // Print a fancy tree with build results.
633 var step_stack_copy = try step_stack.clone(gpa);
634 defer step_stack_copy.deinit(gpa);
635
555 var print_node: PrintNode = .{ .parent = null };636 var print_node: PrintNode = .{ .parent = null };
556 if (step_names.len == 0) {637 if (step_names.len == 0) {
557 print_node.last = true;638 print_node.last = true;
558 printTreeStep(b, b.default_step, run, stderr, ttyconf, &print_node, &step_stack) catch {};639 printTreeStep(b, b.default_step, run, stderr, ttyconf, &print_node, &step_stack_copy) catch {};
559 } else {640 } else {
560 const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: {641 const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: {
561 var i: usize = step_names.len;642 var i: usize = step_names.len;
562 while (i > 0) {643 while (i > 0) {
563 i -= 1;644 i -= 1;
564 const step = b.top_level_steps.get(step_names[i]).?.step;645 const step = b.top_level_steps.get(step_names[i]).?.step;
565 const found = switch (run.summary orelse .failures) {646 const found = switch (run.summary) {
566 .all, .none => unreachable,647 .all, .none => unreachable,
567 .failures => step.state != .success,648 .failures => step.state != .success,
568 .new => !step.result_cached,649 .new => !step.result_cached,
...@@ -574,30 +655,34 @@ fn runStepNames(...@@ -574,30 +655,34 @@ fn runStepNames(
574 for (step_names, 0..) |step_name, i| {655 for (step_names, 0..) |step_name, i| {
575 const tls = b.top_level_steps.get(step_name).?;656 const tls = b.top_level_steps.get(step_name).?;
576 print_node.last = i + 1 == last_index;657 print_node.last = i + 1 == last_index;
577 printTreeStep(b, &tls.step, run, stderr, ttyconf, &print_node, &step_stack) catch {};658 printTreeStep(b, &tls.step, run, stderr, ttyconf, &print_node, &step_stack_copy) catch {};
578 }659 }
579 }660 }
580 }661 }
581662
582 if (failure_count == 0) return cleanExit();663 if (failure_count == 0) {
664 if (!run.watch) cleanExit();
665 return;
666 }
583667
584 // Finally, render compile errors at the bottom of the terminal.668 // Finally, render compile errors at the bottom of the terminal.
585 // We use a separate compile_error_steps array list because step_stack is destructively
586 // mutated in printTreeStep above.
587 if (run.prominent_compile_errors and total_compile_errors > 0) {669 if (run.prominent_compile_errors and total_compile_errors > 0) {
588 for (compile_error_steps.items) |s| {670 for (step_stack.keys()) |s| {
589 if (s.result_error_bundle.errorMessageCount() > 0) {671 if (s.result_error_bundle.errorMessageCount() > 0) {
590 s.result_error_bundle.renderToStdErr(renderOptions(ttyconf));672 s.result_error_bundle.renderToStdErr(renderOptions(ttyconf));
591 }673 }
592 }674 }
593675
594 // Signal to parent process that we have printed compile errors. The676 if (!run.watch) {
595 // parent process may choose to omit the "following command failed"677 // Signal to parent process that we have printed compile errors. The
596 // line in this case.678 // parent process may choose to omit the "following command failed"
597 process.exit(2);679 // line in this case.
680 std.debug.lockStdErr();
681 process.exit(2);
682 }
598 }683 }
599684
600 process.exit(1);685 if (!run.watch) return uncleanExit();
601}686}
602687
603const PrintNode = struct {688const PrintNode = struct {
...@@ -768,7 +853,7 @@ fn printTreeStep(...@@ -768,7 +853,7 @@ fn printTreeStep(
768 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),853 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
769) !void {854) !void {
770 const first = step_stack.swapRemove(s);855 const first = step_stack.swapRemove(s);
771 const summary = run.summary orelse .failures;856 const summary = run.summary;
772 const skip = switch (summary) {857 const skip = switch (summary) {
773 .none => unreachable,858 .none => unreachable,
774 .all => false,859 .all => false,
...@@ -889,12 +974,13 @@ fn constructGraphAndCheckForDependencyLoop(...@@ -889,12 +974,13 @@ fn constructGraphAndCheckForDependencyLoop(
889974
890fn workerMakeOneStep(975fn workerMakeOneStep(
891 wg: *std.Thread.WaitGroup,976 wg: *std.Thread.WaitGroup,
892 thread_pool: *std.Thread.Pool,
893 b: *std.Build,977 b: *std.Build,
894 s: *Step,978 s: *Step,
895 prog_node: std.Progress.Node,979 prog_node: std.Progress.Node,
896 run: *Run,980 run: *Run,
897) void {981) void {
982 const thread_pool = &run.thread_pool;
983
898 // First, check the conditions for running this step. If they are not met,984 // First, check the conditions for running this step. If they are not met,
899 // then we return without doing the step, relying on another worker to985 // then we return without doing the step, relying on another worker to
900 // queue this step up again when dependencies are met.986 // queue this step up again when dependencies are met.
...@@ -974,7 +1060,7 @@ fn workerMakeOneStep(...@@ -974,7 +1060,7 @@ fn workerMakeOneStep(
974 // Successful completion of a step, so we queue up its dependants as well.1060 // Successful completion of a step, so we queue up its dependants as well.
975 for (s.dependants.items) |dep| {1061 for (s.dependants.items) |dep| {
976 thread_pool.spawnWg(wg, workerMakeOneStep, .{1062 thread_pool.spawnWg(wg, workerMakeOneStep, .{
977 wg, thread_pool, b, dep, prog_node, run,1063 wg, b, dep, prog_node, run,
978 });1064 });
979 }1065 }
980 }1066 }
...@@ -999,7 +1085,7 @@ fn workerMakeOneStep(...@@ -999,7 +1085,7 @@ fn workerMakeOneStep(
999 remaining -= dep.max_rss;1085 remaining -= dep.max_rss;
10001086
1001 thread_pool.spawnWg(wg, workerMakeOneStep, .{1087 thread_pool.spawnWg(wg, workerMakeOneStep, .{
1002 wg, thread_pool, b, dep, prog_node, run,1088 wg, b, dep, prog_node, run,
1003 });1089 });
1004 } else {1090 } else {
1005 run.memory_blocked_steps.items[i] = dep;1091 run.memory_blocked_steps.items[i] = dep;
...@@ -1124,6 +1210,8 @@ fn usage(b: *std.Build, out_stream: anytype) !void {...@@ -1124,6 +1210,8 @@ fn usage(b: *std.Build, out_stream: anytype) !void {
1124 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)1210 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
1125 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss1211 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
1126 \\ --fetch Exit after fetching dependency tree1212 \\ --fetch Exit after fetching dependency tree
1213 \\ --watch Continuously rebuild when source files are modified
1214 \\ --debounce <ms> Delay before rebuilding after changed file detected
1127 \\1215 \\
1128 \\Project-Specific Options:1216 \\Project-Specific Options:
1129 \\1217 \\
...@@ -1218,13 +1306,22 @@ fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 {...@@ -1218,13 +1306,22 @@ fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 {
1218 return args[idx..];1306 return args[idx..];
1219}1307}
12201308
1309/// Perhaps in the future there could be an Advanced Options flag such as
1310/// --debug-build-runner-leaks which would make this function return instead of
1311/// calling exit.
1221fn cleanExit() void {1312fn cleanExit() void {
1222 // Perhaps in the future there could be an Advanced Options flag such as1313 std.debug.lockStdErr();
1223 // --debug-build-runner-leaks which would make this function return instead
1224 // of calling exit.
1225 process.exit(0);1314 process.exit(0);
1226}1315}
12271316
1317/// Perhaps in the future there could be an Advanced Options flag such as
1318/// --debug-build-runner-leaks which would make this function return instead of
1319/// calling exit.
1320fn uncleanExit() error{UncleanExit} {
1321 std.debug.lockStdErr();
1322 process.exit(1);
1323}
1324
1228const Color = std.zig.Color;1325const Color = std.zig.Color;
1229const Summary = enum { all, new, failures, none };1326const Summary = enum { all, new, failures, none };
12301327
...@@ -1249,11 +1346,6 @@ fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {...@@ -1249,11 +1346,6 @@ fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
1249 process.exit(1);1346 process.exit(1);
1250}1347}
12511348
1252fn fatal(comptime f: []const u8, args: anytype) noreturn {
1253 std.debug.print(f ++ "\n", args);
1254 process.exit(1);
1255}
1256
1257fn validateSystemLibraryOptions(b: *std.Build) void {1349fn validateSystemLibraryOptions(b: *std.Build) void {
1258 var bad = false;1350 var bad = false;
1259 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {1351 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
lib/compiler/objcopy.zig+2-8
...@@ -198,20 +198,14 @@ fn cmdObjCopy(...@@ -198,20 +198,14 @@ fn cmdObjCopy(
198 return std.process.cleanExit();198 return std.process.cleanExit();
199 },199 },
200 .update => {200 .update => {
201 if (seen_update) {201 if (seen_update) fatal("zig objcopy only supports 1 update for now", .{});
202 std.debug.print("zig objcopy only supports 1 update for now\n", .{});
203 std.process.exit(1);
204 }
205 seen_update = true;202 seen_update = true;
206203
207 try server.serveEmitBinPath(output, .{204 try server.serveEmitBinPath(output, .{
208 .flags = .{ .cache_hit = false },205 .flags = .{ .cache_hit = false },
209 });206 });
210 },207 },
211 else => {208 else => fatal("unsupported message: {s}", .{@tagName(hdr.tag)}),
212 std.debug.print("unsupported message: {s}", .{@tagName(hdr.tag)});
213 std.process.exit(1);
214 },
215 }209 }
216 }210 }
217 }211 }
lib/std/Build.zig+51-61
...@@ -20,6 +20,7 @@ const Build = @This();...@@ -20,6 +20,7 @@ const Build = @This();
20pub const Cache = @import("Build/Cache.zig");20pub const Cache = @import("Build/Cache.zig");
21pub const Step = @import("Build/Step.zig");21pub const Step = @import("Build/Step.zig");
22pub const Module = @import("Build/Module.zig");22pub const Module = @import("Build/Module.zig");
23pub const Watch = @import("Build/Watch.zig");
2324
24/// Shared state among all Build instances.25/// Shared state among all Build instances.
25graph: *Graph,26graph: *Graph,
...@@ -50,11 +51,9 @@ install_path: []const u8,...@@ -50,11 +51,9 @@ install_path: []const u8,
50sysroot: ?[]const u8 = null,51sysroot: ?[]const u8 = null,
51search_prefixes: std.ArrayListUnmanaged([]const u8),52search_prefixes: std.ArrayListUnmanaged([]const u8),
52libc_file: ?[]const u8 = null,53libc_file: ?[]const u8 = null,
53installed_files: ArrayList(InstalledFile),
54/// Path to the directory containing build.zig.54/// Path to the directory containing build.zig.
55build_root: Cache.Directory,55build_root: Cache.Directory,
56cache_root: Cache.Directory,56cache_root: Cache.Directory,
57zig_lib_dir: ?LazyPath,
58pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,57pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
59args: ?[]const []const u8 = null,58args: ?[]const []const u8 = null,
60debug_log_scopes: []const []const u8 = &.{},59debug_log_scopes: []const []const u8 = &.{},
...@@ -117,6 +116,7 @@ pub const Graph = struct {...@@ -117,6 +116,7 @@ pub const Graph = struct {
117 zig_exe: [:0]const u8,116 zig_exe: [:0]const u8,
118 env_map: EnvMap,117 env_map: EnvMap,
119 global_cache_root: Cache.Directory,118 global_cache_root: Cache.Directory,
119 zig_lib_directory: Cache.Directory,
120 needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .{},120 needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .{},
121 /// Information about the native target. Computed before build() is invoked.121 /// Information about the native target. Computed before build() is invoked.
122 host: ResolvedTarget,122 host: ResolvedTarget,
...@@ -276,7 +276,6 @@ pub fn create(...@@ -276,7 +276,6 @@ pub fn create(
276 .exe_dir = undefined,276 .exe_dir = undefined,
277 .h_dir = undefined,277 .h_dir = undefined,
278 .dest_dir = graph.env_map.get("DESTDIR"),278 .dest_dir = graph.env_map.get("DESTDIR"),
279 .installed_files = ArrayList(InstalledFile).init(arena),
280 .install_tls = .{279 .install_tls = .{
281 .step = Step.init(.{280 .step = Step.init(.{
282 .id = TopLevelStep.base_id,281 .id = TopLevelStep.base_id,
...@@ -294,7 +293,6 @@ pub fn create(...@@ -294,7 +293,6 @@ pub fn create(
294 }),293 }),
295 .description = "Remove build artifacts from prefix path",294 .description = "Remove build artifacts from prefix path",
296 },295 },
297 .zig_lib_dir = null,
298 .install_path = undefined,296 .install_path = undefined,
299 .args = null,297 .args = null,
300 .host = graph.host,298 .host = graph.host,
...@@ -378,10 +376,8 @@ fn createChildOnly(...@@ -378,10 +376,8 @@ fn createChildOnly(
378 .sysroot = parent.sysroot,376 .sysroot = parent.sysroot,
379 .search_prefixes = parent.search_prefixes,377 .search_prefixes = parent.search_prefixes,
380 .libc_file = parent.libc_file,378 .libc_file = parent.libc_file,
381 .installed_files = ArrayList(InstalledFile).init(allocator),
382 .build_root = build_root,379 .build_root = build_root,
383 .cache_root = parent.cache_root,380 .cache_root = parent.cache_root,
384 .zig_lib_dir = parent.zig_lib_dir,
385 .debug_log_scopes = parent.debug_log_scopes,381 .debug_log_scopes = parent.debug_log_scopes,
386 .debug_compile_errors = parent.debug_compile_errors,382 .debug_compile_errors = parent.debug_compile_errors,
387 .debug_pkg_config = parent.debug_pkg_config,383 .debug_pkg_config = parent.debug_pkg_config,
...@@ -689,7 +685,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile {...@@ -689,7 +685,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile {
689 .max_rss = options.max_rss,685 .max_rss = options.max_rss,
690 .use_llvm = options.use_llvm,686 .use_llvm = options.use_llvm,
691 .use_lld = options.use_lld,687 .use_lld = options.use_lld,
692 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,688 .zig_lib_dir = options.zig_lib_dir,
693 .win32_manifest = options.win32_manifest,689 .win32_manifest = options.win32_manifest,
694 });690 });
695}691}
...@@ -737,7 +733,7 @@ pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile {...@@ -737,7 +733,7 @@ pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile {
737 .max_rss = options.max_rss,733 .max_rss = options.max_rss,
738 .use_llvm = options.use_llvm,734 .use_llvm = options.use_llvm,
739 .use_lld = options.use_lld,735 .use_lld = options.use_lld,
740 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,736 .zig_lib_dir = options.zig_lib_dir,
741 });737 });
742}738}
743739
...@@ -793,7 +789,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *Step.Compile...@@ -793,7 +789,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *Step.Compile
793 .max_rss = options.max_rss,789 .max_rss = options.max_rss,
794 .use_llvm = options.use_llvm,790 .use_llvm = options.use_llvm,
795 .use_lld = options.use_lld,791 .use_lld = options.use_lld,
796 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,792 .zig_lib_dir = options.zig_lib_dir,
797 .win32_manifest = options.win32_manifest,793 .win32_manifest = options.win32_manifest,
798 });794 });
799}795}
...@@ -844,7 +840,7 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *Step.Compile...@@ -844,7 +840,7 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *Step.Compile
844 .max_rss = options.max_rss,840 .max_rss = options.max_rss,
845 .use_llvm = options.use_llvm,841 .use_llvm = options.use_llvm,
846 .use_lld = options.use_lld,842 .use_lld = options.use_lld,
847 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,843 .zig_lib_dir = options.zig_lib_dir,
848 });844 });
849}845}
850846
...@@ -907,7 +903,7 @@ pub fn addTest(b: *Build, options: TestOptions) *Step.Compile {...@@ -907,7 +903,7 @@ pub fn addTest(b: *Build, options: TestOptions) *Step.Compile {
907 .test_runner = options.test_runner,903 .test_runner = options.test_runner,
908 .use_llvm = options.use_llvm,904 .use_llvm = options.use_llvm,
909 .use_lld = options.use_lld,905 .use_lld = options.use_lld,
910 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,906 .zig_lib_dir = options.zig_lib_dir,
911 });907 });
912}908}
913909
...@@ -931,7 +927,7 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *Step.Compile {...@@ -931,7 +927,7 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *Step.Compile {
931 .optimize = options.optimize,927 .optimize = options.optimize,
932 },928 },
933 .max_rss = options.max_rss,929 .max_rss = options.max_rss,
934 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,930 .zig_lib_dir = options.zig_lib_dir,
935 });931 });
936 obj_step.addAssemblyFile(options.source_file);932 obj_step.addAssemblyFile(options.source_file);
937 return obj_step;933 return obj_step;
...@@ -1054,7 +1050,11 @@ pub fn addWriteFiles(b: *Build) *Step.WriteFile {...@@ -1054,7 +1050,11 @@ pub fn addWriteFiles(b: *Build) *Step.WriteFile {
1054 return Step.WriteFile.create(b);1050 return Step.WriteFile.create(b);
1055}1051}
10561052
1057pub fn addRemoveDirTree(b: *Build, dir_path: []const u8) *Step.RemoveDir {1053pub fn addUpdateSourceFiles(b: *Build) *Step.UpdateSourceFiles {
1054 return Step.UpdateSourceFiles.create(b);
1055}
1056
1057pub fn addRemoveDirTree(b: *Build, dir_path: LazyPath) *Step.RemoveDir {
1058 return Step.RemoveDir.create(b, dir_path);1058 return Step.RemoveDir.create(b, dir_path);
1059}1059}
10601060
...@@ -1083,15 +1083,8 @@ fn makeUninstall(uninstall_step: *Step, prog_node: std.Progress.Node) anyerror!v...@@ -1083,15 +1083,8 @@ fn makeUninstall(uninstall_step: *Step, prog_node: std.Progress.Node) anyerror!v
1083 const uninstall_tls: *TopLevelStep = @fieldParentPtr("step", uninstall_step);1083 const uninstall_tls: *TopLevelStep = @fieldParentPtr("step", uninstall_step);
1084 const b: *Build = @fieldParentPtr("uninstall_tls", uninstall_tls);1084 const b: *Build = @fieldParentPtr("uninstall_tls", uninstall_tls);
10851085
1086 for (b.installed_files.items) |installed_file| {1086 _ = b;
1087 const full_path = b.getInstallPath(installed_file.dir, installed_file.path);1087 @panic("TODO implement https://github.com/ziglang/zig/issues/14943");
1088 if (b.verbose) {
1089 log.info("rm {s}", .{full_path});
1090 }
1091 fs.cwd().deleteTree(full_path) catch {};
1092 }
1093
1094 // TODO remove empty directories
1095}1088}
10961089
1097/// Creates a configuration option to be passed to the build.zig script.1090/// Creates a configuration option to be passed to the build.zig script.
...@@ -1664,15 +1657,6 @@ pub fn addCheckFile(...@@ -1664,15 +1657,6 @@ pub fn addCheckFile(
1664 return Step.CheckFile.create(b, file_source, options);1657 return Step.CheckFile.create(b, file_source, options);
1665}1658}
16661659
1667/// deprecated: https://github.com/ziglang/zig/issues/14943
1668pub fn pushInstalledFile(b: *Build, dir: InstallDir, dest_rel_path: []const u8) void {
1669 const file = InstalledFile{
1670 .dir = dir,
1671 .path = dest_rel_path,
1672 };
1673 b.installed_files.append(file.dupe(b)) catch @panic("OOM");
1674}
1675
1676pub fn truncateFile(b: *Build, dest_path: []const u8) !void {1660pub fn truncateFile(b: *Build, dest_path: []const u8) !void {
1677 if (b.verbose) {1661 if (b.verbose) {
1678 log.info("truncate {s}", .{dest_path});1662 log.info("truncate {s}", .{dest_path});
...@@ -2341,36 +2325,52 @@ pub const LazyPath = union(enum) {...@@ -2341,36 +2325,52 @@ pub const LazyPath = union(enum) {
2341 }2325 }
2342 }2326 }
23432327
2344 /// Returns an absolute path.2328 /// Deprecated, see `getPath3`.
2345 /// Intended to be used during the make phase only.
2346 pub fn getPath(lazy_path: LazyPath, src_builder: *Build) []const u8 {2329 pub fn getPath(lazy_path: LazyPath, src_builder: *Build) []const u8 {
2347 return getPath2(lazy_path, src_builder, null);2330 return getPath2(lazy_path, src_builder, null);
2348 }2331 }
23492332
2350 /// Returns an absolute path.2333 /// Deprecated, see `getPath3`.
2334 pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {
2335 const p = getPath3(lazy_path, src_builder, asking_step);
2336 return src_builder.pathResolve(&.{ p.root_dir.path orelse ".", p.sub_path });
2337 }
2338
2351 /// Intended to be used during the make phase only.2339 /// Intended to be used during the make phase only.
2352 ///2340 ///
2353 /// `asking_step` is only used for debugging purposes; it's the step being2341 /// `asking_step` is only used for debugging purposes; it's the step being
2354 /// run that is asking for the path.2342 /// run that is asking for the path.
2355 pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {2343 pub fn getPath3(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Cache.Path {
2356 switch (lazy_path) {2344 switch (lazy_path) {
2357 .src_path => |sp| return sp.owner.pathFromRoot(sp.sub_path),2345 .src_path => |sp| return .{
2358 .cwd_relative => |p| return src_builder.pathFromCwd(p),2346 .root_dir = sp.owner.build_root,
2347 .sub_path = sp.sub_path,
2348 },
2349 .cwd_relative => |sub_path| return .{
2350 .root_dir = Cache.Directory.cwd(),
2351 .sub_path = sub_path,
2352 },
2359 .generated => |gen| {2353 .generated => |gen| {
2360 var file_path: []const u8 = gen.file.step.owner.pathFromRoot(gen.file.path orelse {2354 // TODO make gen.file.path not be absolute and use that as the
2361 std.debug.lockStdErr();2355 // basis for not traversing up too many directories.
2362 const stderr = std.io.getStdErr();2356
2363 dumpBadGetPathHelp(gen.file.step, stderr, src_builder, asking_step) catch {};2357 var file_path: Cache.Path = .{
2364 std.debug.unlockStdErr();2358 .root_dir = gen.file.step.owner.build_root,
2365 @panic("misconfigured build script");2359 .sub_path = gen.file.path orelse {
2366 });2360 std.debug.lockStdErr();
2361 const stderr = std.io.getStdErr();
2362 dumpBadGetPathHelp(gen.file.step, stderr, src_builder, asking_step) catch {};
2363 std.debug.unlockStdErr();
2364 @panic("misconfigured build script");
2365 },
2366 };
23672367
2368 if (gen.up > 0) {2368 if (gen.up > 0) {
2369 const cache_root_path = src_builder.cache_root.path orelse2369 const cache_root_path = src_builder.cache_root.path orelse
2370 (src_builder.cache_root.join(src_builder.allocator, &.{"."}) catch @panic("OOM"));2370 (src_builder.cache_root.join(src_builder.allocator, &.{"."}) catch @panic("OOM"));
23712371
2372 for (0..gen.up) |_| {2372 for (0..gen.up) |_| {
2373 if (mem.eql(u8, file_path, cache_root_path)) {2373 if (mem.eql(u8, file_path.sub_path, cache_root_path)) {
2374 // If we hit the cache root and there's still more to go,2374 // If we hit the cache root and there's still more to go,
2375 // the script attempted to go too far.2375 // the script attempted to go too far.
2376 dumpBadDirnameHelp(gen.file.step, asking_step,2376 dumpBadDirnameHelp(gen.file.step, asking_step,
...@@ -2384,7 +2384,7 @@ pub const LazyPath = union(enum) {...@@ -2384,7 +2384,7 @@ pub const LazyPath = union(enum) {
2384 // path is absolute.2384 // path is absolute.
2385 // dirname will return null only if we're at root.2385 // dirname will return null only if we're at root.
2386 // Typically, we'll stop well before that at the cache root.2386 // Typically, we'll stop well before that at the cache root.
2387 file_path = fs.path.dirname(file_path) orelse {2387 file_path.sub_path = fs.path.dirname(file_path.sub_path) orelse {
2388 dumpBadDirnameHelp(gen.file.step, asking_step,2388 dumpBadDirnameHelp(gen.file.step, asking_step,
2389 \\dirname() reached root.2389 \\dirname() reached root.
2390 \\No more directories left to go up.2390 \\No more directories left to go up.
...@@ -2395,9 +2395,12 @@ pub const LazyPath = union(enum) {...@@ -2395,9 +2395,12 @@ pub const LazyPath = union(enum) {
2395 }2395 }
2396 }2396 }
23972397
2398 return src_builder.pathResolve(&.{ file_path, gen.sub_path });2398 return file_path.join(src_builder.allocator, gen.sub_path) catch @panic("OOM");
2399 },
2400 .dependency => |dep| return .{
2401 .root_dir = dep.dependency.builder.build_root,
2402 .sub_path = dep.sub_path,
2399 },2403 },
2400 .dependency => |dep| return dep.dependency.builder.pathFromRoot(dep.sub_path),
2401 }2404 }
2402 }2405 }
24032406
...@@ -2512,19 +2515,6 @@ pub const InstallDir = union(enum) {...@@ -2512,19 +2515,6 @@ pub const InstallDir = union(enum) {
2512 }2515 }
2513};2516};
25142517
2515pub const InstalledFile = struct {
2516 dir: InstallDir,
2517 path: []const u8,
2518
2519 /// Duplicates the installed file path and directory.
2520 pub fn dupe(file: InstalledFile, builder: *Build) InstalledFile {
2521 return .{
2522 .dir = file.dir.dupe(builder),
2523 .path = builder.dupe(file.path),
2524 };
2525 }
2526};
2527
2528/// This function is intended to be called in the `configure` phase only.2518/// This function is intended to be called in the `configure` phase only.
2529/// It returns an absolute directory path, which is potentially going to be a2519/// It returns an absolute directory path, which is potentially going to be a
2530/// source of API breakage in the future, so keep that in mind when using this2520/// source of API breakage in the future, so keep that in mind when using this
lib/std/Build/Cache.zig+34
...@@ -354,6 +354,19 @@ pub const Manifest = struct {...@@ -354,6 +354,19 @@ pub const Manifest = struct {
354 /// ```354 /// ```
355 /// var file_contents = cache_hash.files.keys()[file_index].contents.?;355 /// var file_contents = cache_hash.files.keys()[file_index].contents.?;
356 /// ```356 /// ```
357 pub fn addFilePath(m: *Manifest, file_path: Path, max_file_size: ?usize) !usize {
358 const gpa = m.cache.gpa;
359 try m.files.ensureUnusedCapacity(gpa, 1);
360 const resolved_path = try fs.path.resolve(gpa, &.{
361 file_path.root_dir.path orelse ".",
362 file_path.subPathOrDot(),
363 });
364 errdefer gpa.free(resolved_path);
365 const prefixed_path = try m.cache.findPrefixResolved(resolved_path);
366 return addFileInner(m, prefixed_path, max_file_size);
367 }
368
369 /// Deprecated; use `addFilePath`.
357 pub fn addFile(self: *Manifest, file_path: []const u8, max_file_size: ?usize) !usize {370 pub fn addFile(self: *Manifest, file_path: []const u8, max_file_size: ?usize) !usize {
358 assert(self.manifest_file == null);371 assert(self.manifest_file == null);
359372
...@@ -362,6 +375,10 @@ pub const Manifest = struct {...@@ -362,6 +375,10 @@ pub const Manifest = struct {
362 const prefixed_path = try self.cache.findPrefix(file_path);375 const prefixed_path = try self.cache.findPrefix(file_path);
363 errdefer gpa.free(prefixed_path.sub_path);376 errdefer gpa.free(prefixed_path.sub_path);
364377
378 return addFileInner(self, prefixed_path, max_file_size);
379 }
380
381 fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, max_file_size: ?usize) !usize {
365 const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{});382 const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{});
366 if (gop.found_existing) {383 if (gop.found_existing) {
367 gop.key_ptr.updateMaxSize(max_file_size);384 gop.key_ptr.updateMaxSize(max_file_size);
...@@ -990,6 +1007,23 @@ pub const Manifest = struct {...@@ -990,6 +1007,23 @@ pub const Manifest = struct {
990 }1007 }
991 self.files.deinit(self.cache.gpa);1008 self.files.deinit(self.cache.gpa);
992 }1009 }
1010
1011 pub fn populateFileSystemInputs(man: *Manifest, buf: *std.ArrayListUnmanaged(u8)) Allocator.Error!void {
1012 assert(@typeInfo(std.zig.Server.Message.PathPrefix).Enum.fields.len == man.cache.prefixes_len);
1013 buf.clearRetainingCapacity();
1014 const gpa = man.cache.gpa;
1015 const files = man.files.keys();
1016 if (files.len > 0) {
1017 for (files) |file| {
1018 try buf.ensureUnusedCapacity(gpa, file.prefixed_path.sub_path.len + 2);
1019 buf.appendAssumeCapacity(file.prefixed_path.prefix + 1);
1020 buf.appendSliceAssumeCapacity(file.prefixed_path.sub_path);
1021 buf.appendAssumeCapacity(0);
1022 }
1023 // The null byte is a separator, not a terminator.
1024 buf.items.len -= 1;
1025 }
1026 }
993};1027};
9941028
995/// On operating systems that support symlinks, does a readlink. On other operating systems,1029/// On operating systems that support symlinks, does a readlink. On other operating systems,
lib/std/Build/Cache/Path.zig+57-2
...@@ -58,6 +58,20 @@ pub fn openFile(...@@ -58,6 +58,20 @@ pub fn openFile(
58 return p.root_dir.handle.openFile(joined_path, flags);58 return p.root_dir.handle.openFile(joined_path, flags);
59}59}
6060
61pub fn openDir(
62 p: Path,
63 sub_path: []const u8,
64 args: fs.Dir.OpenOptions,
65) fs.Dir.OpenError!fs.Dir {
66 var buf: [fs.max_path_bytes]u8 = undefined;
67 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
68 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
69 p.sub_path, sub_path,
70 }) catch return error.NameTooLong;
71 };
72 return p.root_dir.handle.openDir(joined_path, args);
73}
74
61pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs.Dir {75pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs.Dir {
62 var buf: [fs.max_path_bytes]u8 = undefined;76 var buf: [fs.max_path_bytes]u8 = undefined;
63 const joined_path = if (p.sub_path.len == 0) sub_path else p: {77 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
...@@ -137,16 +151,57 @@ pub fn format(...@@ -137,16 +151,57 @@ pub fn format(
137 }151 }
138 if (fmt_string.len > 0)152 if (fmt_string.len > 0)
139 std.fmt.invalidFmtError(fmt_string, self);153 std.fmt.invalidFmtError(fmt_string, self);
154 if (std.fs.path.isAbsolute(self.sub_path)) {
155 try writer.writeAll(self.sub_path);
156 return;
157 }
140 if (self.root_dir.path) |p| {158 if (self.root_dir.path) |p| {
141 try writer.writeAll(p);159 try writer.writeAll(p);
142 try writer.writeAll(fs.path.sep_str);160 if (self.sub_path.len > 0) {
161 try writer.writeAll(fs.path.sep_str);
162 try writer.writeAll(self.sub_path);
163 }
164 return;
143 }165 }
144 if (self.sub_path.len > 0) {166 if (self.sub_path.len > 0) {
145 try writer.writeAll(self.sub_path);167 try writer.writeAll(self.sub_path);
146 try writer.writeAll(fs.path.sep_str);168 return;
147 }169 }
170 try writer.writeByte('.');
171}
172
173pub fn eql(self: Path, other: Path) bool {
174 return self.root_dir.eql(other.root_dir) and std.mem.eql(u8, self.sub_path, other.sub_path);
175}
176
177pub fn subPathOpt(self: Path) ?[]const u8 {
178 return if (self.sub_path.len == 0) null else self.sub_path;
148}179}
149180
181pub fn subPathOrDot(self: Path) []const u8 {
182 return if (self.sub_path.len == 0) "." else self.sub_path;
183}
184
185/// Useful to make `Path` a key in `std.ArrayHashMap`.
186pub const TableAdapter = struct {
187 pub const Hash = std.hash.Wyhash;
188
189 pub fn hash(self: TableAdapter, a: Cache.Path) u32 {
190 _ = self;
191 const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.fd))) {
192 .Pointer => @intFromPtr(a.root_dir.handle.fd),
193 .Int => @as(u32, @bitCast(a.root_dir.handle.fd)),
194 else => @compileError("unimplemented hash function"),
195 };
196 return @truncate(Hash.hash(seed, a.sub_path));
197 }
198 pub fn eql(self: TableAdapter, a: Cache.Path, b: Cache.Path, b_index: usize) bool {
199 _ = self;
200 _ = b_index;
201 return a.eql(b);
202 }
203};
204
150const Path = @This();205const Path = @This();
151const std = @import("../../std.zig");206const std = @import("../../std.zig");
152const fs = std.fs;207const fs = std.fs;
lib/std/Build/Step.zig+235-3
...@@ -7,6 +7,16 @@ dependencies: std.ArrayList(*Step),...@@ -7,6 +7,16 @@ dependencies: std.ArrayList(*Step),
7/// This field is empty during execution of the user's build script, and7/// This field is empty during execution of the user's build script, and
8/// then populated during dependency loop checking in the build runner.8/// then populated during dependency loop checking in the build runner.
9dependants: std.ArrayListUnmanaged(*Step),9dependants: std.ArrayListUnmanaged(*Step),
10/// Collects the set of files that retrigger this step to run.
11///
12/// This is used by the build system's implementation of `--watch` but it can
13/// also be potentially useful for IDEs to know what effects editing a
14/// particular file has.
15///
16/// Populated within `make`. Implementation may choose to clear and repopulate,
17/// retain previous value, or update.
18inputs: Inputs,
19
10state: State,20state: State,
11/// Set this field to declare an upper bound on the amount of bytes of memory it will21/// Set this field to declare an upper bound on the amount of bytes of memory it will
12/// take to run the step. Zero means no limit.22/// take to run the step. Zero means no limit.
...@@ -63,6 +73,11 @@ pub const MakeFn = *const fn (step: *Step, prog_node: std.Progress.Node) anyerro...@@ -63,6 +73,11 @@ pub const MakeFn = *const fn (step: *Step, prog_node: std.Progress.Node) anyerro
63pub const State = enum {73pub const State = enum {
64 precheck_unstarted,74 precheck_unstarted,
65 precheck_started,75 precheck_started,
76 /// This is also used to indicate "dirty" steps that have been modified
77 /// after a previous build completed, in which case, the step may or may
78 /// not have been completed before. Either way, one or more of its direct
79 /// file system inputs have been modified, meaning that the step needs to
80 /// be re-evaluated.
66 precheck_done,81 precheck_done,
67 running,82 running,
68 dependency_failure,83 dependency_failure,
...@@ -87,6 +102,7 @@ pub const Id = enum {...@@ -87,6 +102,7 @@ pub const Id = enum {
87 fmt,102 fmt,
88 translate_c,103 translate_c,
89 write_file,104 write_file,
105 update_source_files,
90 run,106 run,
91 check_file,107 check_file,
92 check_object,108 check_object,
...@@ -107,6 +123,7 @@ pub const Id = enum {...@@ -107,6 +123,7 @@ pub const Id = enum {
107 .fmt => Fmt,123 .fmt => Fmt,
108 .translate_c => TranslateC,124 .translate_c => TranslateC,
109 .write_file => WriteFile,125 .write_file => WriteFile,
126 .update_source_files => UpdateSourceFiles,
110 .run => Run,127 .run => Run,
111 .check_file => CheckFile,128 .check_file => CheckFile,
112 .check_object => CheckObject,129 .check_object => CheckObject,
...@@ -133,6 +150,28 @@ pub const RemoveDir = @import("Step/RemoveDir.zig");...@@ -133,6 +150,28 @@ pub const RemoveDir = @import("Step/RemoveDir.zig");
133pub const Run = @import("Step/Run.zig");150pub const Run = @import("Step/Run.zig");
134pub const TranslateC = @import("Step/TranslateC.zig");151pub const TranslateC = @import("Step/TranslateC.zig");
135pub const WriteFile = @import("Step/WriteFile.zig");152pub const WriteFile = @import("Step/WriteFile.zig");
153pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig");
154
155pub const Inputs = struct {
156 table: Table,
157
158 pub const init: Inputs = .{
159 .table = .{},
160 };
161
162 pub const Table = std.ArrayHashMapUnmanaged(Build.Cache.Path, Files, Build.Cache.Path.TableAdapter, false);
163 /// The special file name "." means any changes inside the directory.
164 pub const Files = std.ArrayListUnmanaged([]const u8);
165
166 pub fn populated(inputs: *Inputs) bool {
167 return inputs.table.count() != 0;
168 }
169
170 pub fn clear(inputs: *Inputs, gpa: Allocator) void {
171 for (inputs.table.values()) |*files| files.deinit(gpa);
172 inputs.table.clearRetainingCapacity();
173 }
174};
136175
137pub const StepOptions = struct {176pub const StepOptions = struct {
138 id: Id,177 id: Id,
...@@ -153,6 +192,7 @@ pub fn init(options: StepOptions) Step {...@@ -153,6 +192,7 @@ pub fn init(options: StepOptions) Step {
153 .makeFn = options.makeFn,192 .makeFn = options.makeFn,
154 .dependencies = std.ArrayList(*Step).init(arena),193 .dependencies = std.ArrayList(*Step).init(arena),
155 .dependants = .{},194 .dependants = .{},
195 .inputs = Inputs.init,
156 .state = .precheck_unstarted,196 .state = .precheck_unstarted,
157 .max_rss = options.max_rss,197 .max_rss = options.max_rss,
158 .debug_stack_trace = blk: {198 .debug_stack_trace = blk: {
...@@ -395,6 +435,44 @@ pub fn evalZigProcess(...@@ -395,6 +435,44 @@ pub fn evalZigProcess(
395 s.result_cached = ebp_hdr.flags.cache_hit;435 s.result_cached = ebp_hdr.flags.cache_hit;
396 result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);436 result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
397 },437 },
438 .file_system_inputs => {
439 s.clearWatchInputs();
440 var it = std.mem.splitScalar(u8, body, 0);
441 while (it.next()) |prefixed_path| {
442 const prefix_index: std.zig.Server.Message.PathPrefix = @enumFromInt(prefixed_path[0] - 1);
443 const sub_path = try arena.dupe(u8, prefixed_path[1..]);
444 const sub_path_dirname = std.fs.path.dirname(sub_path) orelse "";
445 switch (prefix_index) {
446 .cwd => {
447 const path: Build.Cache.Path = .{
448 .root_dir = Build.Cache.Directory.cwd(),
449 .sub_path = sub_path_dirname,
450 };
451 try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path));
452 },
453 .zig_lib => zl: {
454 if (s.cast(Step.Compile)) |compile| {
455 if (compile.zig_lib_dir) |lp| {
456 try addWatchInput(s, lp);
457 break :zl;
458 }
459 }
460 const path: Build.Cache.Path = .{
461 .root_dir = s.owner.graph.zig_lib_directory,
462 .sub_path = sub_path_dirname,
463 };
464 try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path));
465 },
466 .local_cache => {
467 const path: Build.Cache.Path = .{
468 .root_dir = b.cache_root,
469 .sub_path = sub_path_dirname,
470 };
471 try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path));
472 },
473 }
474 }
475 },
398 else => {}, // ignore other messages476 else => {}, // ignore other messages
399 }477 }
400478
...@@ -542,19 +620,36 @@ pub fn allocPrintCmd2(...@@ -542,19 +620,36 @@ pub fn allocPrintCmd2(
542 return buf.toOwnedSlice(arena);620 return buf.toOwnedSlice(arena);
543}621}
544622
545pub fn cacheHit(s: *Step, man: *std.Build.Cache.Manifest) !bool {623/// Prefer `cacheHitAndWatch` unless you already added watch inputs
624/// separately from using the cache system.
625pub fn cacheHit(s: *Step, man: *Build.Cache.Manifest) !bool {
546 s.result_cached = man.hit() catch |err| return failWithCacheError(s, man, err);626 s.result_cached = man.hit() catch |err| return failWithCacheError(s, man, err);
547 return s.result_cached;627 return s.result_cached;
548}628}
549629
550fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyerror) anyerror {630/// Clears previous watch inputs, if any, and then populates watch inputs from
631/// the full set of files picked up by the cache manifest.
632///
633/// Must be accompanied with `writeManifestAndWatch`.
634pub fn cacheHitAndWatch(s: *Step, man: *Build.Cache.Manifest) !bool {
635 const is_hit = man.hit() catch |err| return failWithCacheError(s, man, err);
636 s.result_cached = is_hit;
637 // The above call to hit() populates the manifest with files, so in case of
638 // a hit, we need to populate watch inputs.
639 if (is_hit) try setWatchInputsFromManifest(s, man);
640 return is_hit;
641}
642
643fn failWithCacheError(s: *Step, man: *const Build.Cache.Manifest, err: anyerror) anyerror {
551 const i = man.failed_file_index orelse return err;644 const i = man.failed_file_index orelse return err;
552 const pp = man.files.keys()[i].prefixed_path;645 const pp = man.files.keys()[i].prefixed_path;
553 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";646 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
554 return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path });647 return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path });
555}648}
556649
557pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void {650/// Prefer `writeManifestAndWatch` unless you already added watch inputs
651/// separately from using the cache system.
652pub fn writeManifest(s: *Step, man: *Build.Cache.Manifest) !void {
558 if (s.test_results.isSuccess()) {653 if (s.test_results.isSuccess()) {
559 man.writeManifest() catch |err| {654 man.writeManifest() catch |err| {
560 try s.addError("unable to write cache manifest: {s}", .{@errorName(err)});655 try s.addError("unable to write cache manifest: {s}", .{@errorName(err)});
...@@ -562,6 +657,142 @@ pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void {...@@ -562,6 +657,142 @@ pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void {
562 }657 }
563}658}
564659
660/// Clears previous watch inputs, if any, and then populates watch inputs from
661/// the full set of files picked up by the cache manifest.
662///
663/// Must be accompanied with `cacheHitAndWatch`.
664pub fn writeManifestAndWatch(s: *Step, man: *Build.Cache.Manifest) !void {
665 try writeManifest(s, man);
666 try setWatchInputsFromManifest(s, man);
667}
668
669fn setWatchInputsFromManifest(s: *Step, man: *Build.Cache.Manifest) !void {
670 const arena = s.owner.allocator;
671 const prefixes = man.cache.prefixes();
672 clearWatchInputs(s);
673 for (man.files.keys()) |file| {
674 // The file path data is freed when the cache manifest is cleaned up at the end of `make`.
675 const sub_path = try arena.dupe(u8, file.prefixed_path.sub_path);
676 try addWatchInputFromPath(s, .{
677 .root_dir = prefixes[file.prefixed_path.prefix],
678 .sub_path = std.fs.path.dirname(sub_path) orelse "",
679 }, std.fs.path.basename(sub_path));
680 }
681}
682
683/// For steps that have a single input that never changes when re-running `make`.
684pub fn singleUnchangingWatchInput(step: *Step, lazy_path: Build.LazyPath) Allocator.Error!void {
685 if (!step.inputs.populated()) try step.addWatchInput(lazy_path);
686}
687
688pub fn clearWatchInputs(step: *Step) void {
689 const gpa = step.owner.allocator;
690 step.inputs.clear(gpa);
691}
692
693/// Places a *file* dependency on the path.
694pub fn addWatchInput(step: *Step, lazy_file: Build.LazyPath) Allocator.Error!void {
695 switch (lazy_file) {
696 .src_path => |src_path| try addWatchInputFromBuilder(step, src_path.owner, src_path.sub_path),
697 .dependency => |d| try addWatchInputFromBuilder(step, d.dependency.builder, d.sub_path),
698 .cwd_relative => |path_string| {
699 try addWatchInputFromPath(step, .{
700 .root_dir = .{
701 .path = null,
702 .handle = std.fs.cwd(),
703 },
704 .sub_path = std.fs.path.dirname(path_string) orelse "",
705 }, std.fs.path.basename(path_string));
706 },
707 // Nothing to watch because this dependency edge is modeled instead via `dependants`.
708 .generated => {},
709 }
710}
711
712/// Any changes inside the directory will trigger invalidation.
713///
714/// See also `addDirectoryWatchInputFromPath` which takes a `Build.Cache.Path` instead.
715///
716/// Paths derived from this directory should also be manually added via
717/// `addDirectoryWatchInputFromPath` if and only if this function returns
718/// `true`.
719pub fn addDirectoryWatchInput(step: *Step, lazy_directory: Build.LazyPath) Allocator.Error!bool {
720 switch (lazy_directory) {
721 .src_path => |src_path| try addDirectoryWatchInputFromBuilder(step, src_path.owner, src_path.sub_path),
722 .dependency => |d| try addDirectoryWatchInputFromBuilder(step, d.dependency.builder, d.sub_path),
723 .cwd_relative => |path_string| {
724 try addDirectoryWatchInputFromPath(step, .{
725 .root_dir = .{
726 .path = null,
727 .handle = std.fs.cwd(),
728 },
729 .sub_path = path_string,
730 });
731 },
732 // Nothing to watch because this dependency edge is modeled instead via `dependants`.
733 .generated => return false,
734 }
735 return true;
736}
737
738/// Any changes inside the directory will trigger invalidation.
739///
740/// See also `addDirectoryWatchInput` which takes a `Build.LazyPath` instead.
741///
742/// This function should only be called when it has been verified that the
743/// dependency on `path` is not already accounted for by a `Step` dependency.
744/// In other words, before calling this function, first check that the
745/// `Build.LazyPath` which this `path` is derived from is not `generated`.
746pub fn addDirectoryWatchInputFromPath(step: *Step, path: Build.Cache.Path) !void {
747 return addWatchInputFromPath(step, path, ".");
748}
749
750fn addWatchInputFromBuilder(step: *Step, builder: *Build, sub_path: []const u8) !void {
751 return addWatchInputFromPath(step, .{
752 .root_dir = builder.build_root,
753 .sub_path = std.fs.path.dirname(sub_path) orelse "",
754 }, std.fs.path.basename(sub_path));
755}
756
757fn addDirectoryWatchInputFromBuilder(step: *Step, builder: *Build, sub_path: []const u8) !void {
758 return addDirectoryWatchInputFromPath(step, .{
759 .root_dir = builder.build_root,
760 .sub_path = sub_path,
761 });
762}
763
764fn addWatchInputFromPath(step: *Step, path: Build.Cache.Path, basename: []const u8) !void {
765 const gpa = step.owner.allocator;
766 const gop = try step.inputs.table.getOrPut(gpa, path);
767 if (!gop.found_existing) gop.value_ptr.* = .{};
768 try gop.value_ptr.append(gpa, basename);
769}
770
771fn reset(step: *Step, gpa: Allocator) void {
772 assert(step.state == .precheck_done);
773
774 step.result_error_msgs.clearRetainingCapacity();
775 step.result_stderr = "";
776 step.result_cached = false;
777 step.result_duration_ns = null;
778 step.result_peak_rss = 0;
779 step.test_results = .{};
780
781 step.result_error_bundle.deinit(gpa);
782 step.result_error_bundle = std.zig.ErrorBundle.empty;
783}
784
785/// Implementation detail of file watching. Prepares the step for being re-evaluated.
786pub fn recursiveReset(step: *Step, gpa: Allocator) void {
787 assert(step.state != .precheck_done);
788 step.state = .precheck_done;
789 step.reset(gpa);
790 for (step.dependants.items) |dep| {
791 if (dep.state == .precheck_done) continue;
792 dep.recursiveReset(gpa);
793 }
794}
795
565test {796test {
566 _ = CheckFile;797 _ = CheckFile;
567 _ = CheckObject;798 _ = CheckObject;
...@@ -577,4 +808,5 @@ test {...@@ -577,4 +808,5 @@ test {
577 _ = Run;808 _ = Run;
578 _ = TranslateC;809 _ = TranslateC;
579 _ = WriteFile;810 _ = WriteFile;
811 _ = UpdateSourceFiles;
580}812}
lib/std/Build/Step/CheckFile.zig+1
...@@ -50,6 +50,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -50,6 +50,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
50 _ = prog_node;50 _ = prog_node;
51 const b = step.owner;51 const b = step.owner;
52 const check_file: *CheckFile = @fieldParentPtr("step", step);52 const check_file: *CheckFile = @fieldParentPtr("step", step);
53 try step.singleUnchangingWatchInput(check_file.source);
5354
54 const src_path = check_file.source.getPath2(b, step);55 const src_path = check_file.source.getPath2(b, step);
55 const contents = fs.cwd().readFileAlloc(b.allocator, src_path, check_file.max_bytes) catch |err| {56 const contents = fs.cwd().readFileAlloc(b.allocator, src_path, check_file.max_bytes) catch |err| {
lib/std/Build/Step/CheckObject.zig+1
...@@ -555,6 +555,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -555,6 +555,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
555 const b = step.owner;555 const b = step.owner;
556 const gpa = b.allocator;556 const gpa = b.allocator;
557 const check_object: *CheckObject = @fieldParentPtr("step", step);557 const check_object: *CheckObject = @fieldParentPtr("step", step);
558 try step.singleUnchangingWatchInput(check_object.source);
558559
559 const src_path = check_object.source.getPath2(b, step);560 const src_path = check_object.source.getPath2(b, step);
560 const contents = fs.cwd().readFileAllocOptions(561 const contents = fs.cwd().readFileAllocOptions(
lib/std/Build/Step/ConfigHeader.zig+2
...@@ -168,6 +168,8 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -168,6 +168,8 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
168 _ = prog_node;168 _ = prog_node;
169 const b = step.owner;169 const b = step.owner;
170 const config_header: *ConfigHeader = @fieldParentPtr("step", step);170 const config_header: *ConfigHeader = @fieldParentPtr("step", step);
171 if (config_header.style.getPath()) |lp| try step.singleUnchangingWatchInput(lp);
172
171 const gpa = b.allocator;173 const gpa = b.allocator;
172 const arena = b.allocator;174 const arena = b.allocator;
173175
lib/std/Build/Step/InstallDir.zig+16-11
...@@ -41,7 +41,6 @@ pub const Options = struct {...@@ -41,7 +41,6 @@ pub const Options = struct {
41};41};
4242
43pub fn create(owner: *std.Build, options: Options) *InstallDir {43pub fn create(owner: *std.Build, options: Options) *InstallDir {
44 owner.pushInstalledFile(options.install_dir, options.install_subdir);
45 const install_dir = owner.allocator.create(InstallDir) catch @panic("OOM");44 const install_dir = owner.allocator.create(InstallDir) catch @panic("OOM");
46 install_dir.* = .{45 install_dir.* = .{
47 .step = Step.init(.{46 .step = Step.init(.{
...@@ -60,12 +59,14 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -60,12 +59,14 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
60 _ = prog_node;59 _ = prog_node;
61 const b = step.owner;60 const b = step.owner;
62 const install_dir: *InstallDir = @fieldParentPtr("step", step);61 const install_dir: *InstallDir = @fieldParentPtr("step", step);
62 step.clearWatchInputs();
63 const arena = b.allocator;63 const arena = b.allocator;
64 const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir);64 const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir);
65 const src_dir_path = install_dir.options.source_dir.getPath2(b, step);65 const src_dir_path = install_dir.options.source_dir.getPath3(b, step);
66 var src_dir = b.build_root.handle.openDir(src_dir_path, .{ .iterate = true }) catch |err| {66 const need_derived_inputs = try step.addDirectoryWatchInput(install_dir.options.source_dir);
67 return step.fail("unable to open source directory '{}{s}': {s}", .{67 var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
68 b.build_root, src_dir_path, @errorName(err),68 return step.fail("unable to open source directory '{}': {s}", .{
69 src_dir_path, @errorName(err),
69 });70 });
70 };71 };
71 defer src_dir.close();72 defer src_dir.close();
...@@ -89,12 +90,16 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -89,12 +90,16 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
89 }90 }
9091
91 // relative to src build root92 // relative to src build root
92 const src_sub_path = b.pathJoin(&.{ src_dir_path, entry.path });93 const src_sub_path = try src_dir_path.join(arena, entry.path);
93 const dest_path = b.pathJoin(&.{ dest_prefix, entry.path });94 const dest_path = b.pathJoin(&.{ dest_prefix, entry.path });
94 const cwd = fs.cwd();95 const cwd = fs.cwd();
9596
96 switch (entry.kind) {97 switch (entry.kind) {
97 .directory => try cwd.makePath(dest_path),98 .directory => {
99 if (need_derived_inputs) try step.addDirectoryWatchInputFromPath(src_sub_path);
100 try cwd.makePath(dest_path);
101 // TODO: set result_cached=false if the directory did not already exist.
102 },
98 .file => {103 .file => {
99 for (install_dir.options.blank_extensions) |ext| {104 for (install_dir.options.blank_extensions) |ext| {
100 if (mem.endsWith(u8, entry.path, ext)) {105 if (mem.endsWith(u8, entry.path, ext)) {
...@@ -104,14 +109,14 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -104,14 +109,14 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
104 }109 }
105110
106 const prev_status = fs.Dir.updateFile(111 const prev_status = fs.Dir.updateFile(
107 b.build_root.handle,112 src_sub_path.root_dir.handle,
108 src_sub_path,113 src_sub_path.sub_path,
109 cwd,114 cwd,
110 dest_path,115 dest_path,
111 .{},116 .{},
112 ) catch |err| {117 ) catch |err| {
113 return step.fail("unable to update file from '{}{s}' to '{s}': {s}", .{118 return step.fail("unable to update file from '{}' to '{s}': {s}", .{
114 b.build_root, src_sub_path, dest_path, @errorName(err),119 src_sub_path, dest_path, @errorName(err),
115 });120 });
116 };121 };
117 all_cached = all_cached and prev_status == .fresh;122 all_cached = all_cached and prev_status == .fresh;
lib/std/Build/Step/InstallFile.zig+2-1
...@@ -19,7 +19,6 @@ pub fn create(...@@ -19,7 +19,6 @@ pub fn create(
19 dest_rel_path: []const u8,19 dest_rel_path: []const u8,
20) *InstallFile {20) *InstallFile {
21 assert(dest_rel_path.len != 0);21 assert(dest_rel_path.len != 0);
22 owner.pushInstalledFile(dir, dest_rel_path);
23 const install_file = owner.allocator.create(InstallFile) catch @panic("OOM");22 const install_file = owner.allocator.create(InstallFile) catch @panic("OOM");
24 install_file.* = .{23 install_file.* = .{
25 .step = Step.init(.{24 .step = Step.init(.{
...@@ -40,6 +39,8 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -40,6 +39,8 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
40 _ = prog_node;39 _ = prog_node;
41 const b = step.owner;40 const b = step.owner;
42 const install_file: *InstallFile = @fieldParentPtr("step", step);41 const install_file: *InstallFile = @fieldParentPtr("step", step);
42 try step.singleUnchangingWatchInput(install_file.source);
43
43 const full_src_path = install_file.source.getPath2(b, step);44 const full_src_path = install_file.source.getPath2(b, step);
44 const full_dest_path = b.getInstallPath(install_file.dir, install_file.dest_rel_path);45 const full_dest_path = b.getInstallPath(install_file.dir, install_file.dest_rel_path);
45 const cwd = std.fs.cwd();46 const cwd = std.fs.cwd();
lib/std/Build/Step/ObjCopy.zig+1-4
...@@ -93,14 +93,11 @@ pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath {...@@ -93,14 +93,11 @@ pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath {
93fn make(step: *Step, prog_node: std.Progress.Node) !void {93fn make(step: *Step, prog_node: std.Progress.Node) !void {
94 const b = step.owner;94 const b = step.owner;
95 const objcopy: *ObjCopy = @fieldParentPtr("step", step);95 const objcopy: *ObjCopy = @fieldParentPtr("step", step);
96 try step.singleUnchangingWatchInput(objcopy.input_file);
9697
97 var man = b.graph.cache.obtain();98 var man = b.graph.cache.obtain();
98 defer man.deinit();99 defer man.deinit();
99100
100 // Random bytes to make ObjCopy unique. Refresh this with new random
101 // bytes when ObjCopy implementation is modified incompatibly.
102 man.hash.add(@as(u32, 0xe18b7baf));
103
104 const full_src_path = objcopy.input_file.getPath2(b, step);101 const full_src_path = objcopy.input_file.getPath2(b, step);
105 _ = try man.addFile(full_src_path, null);102 _ = try man.addFile(full_src_path, null);
106 man.hash.addOptionalBytes(objcopy.only_section);103 man.hash.addOptionalBytes(objcopy.only_section);
lib/std/Build/Step/Options.zig+4
...@@ -424,6 +424,9 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -424,6 +424,9 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
424 item.path.getPath2(b, step),424 item.path.getPath2(b, step),
425 );425 );
426 }426 }
427 if (!step.inputs.populated()) for (options.args.items) |item| {
428 try step.addWatchInput(item.path);
429 };
427430
428 const basename = "options.zig";431 const basename = "options.zig";
429432
...@@ -520,6 +523,7 @@ test Options {...@@ -520,6 +523,7 @@ test Options {
520 .query = .{},523 .query = .{},
521 .result = try std.zig.system.resolveTargetQuery(.{}),524 .result = try std.zig.system.resolveTargetQuery(.{}),
522 },525 },
526 .zig_lib_directory = std.Build.Cache.Directory.cwd(),
523 };527 };
524528
525 var builder = try std.Build.create(529 var builder = try std.Build.create(
lib/std/Build/Step/RemoveDir.zig+13-7
...@@ -2,22 +2,23 @@ const std = @import("std");...@@ -2,22 +2,23 @@ const std = @import("std");
2const fs = std.fs;2const fs = std.fs;
3const Step = std.Build.Step;3const Step = std.Build.Step;
4const RemoveDir = @This();4const RemoveDir = @This();
5const LazyPath = std.Build.LazyPath;
56
6pub const base_id: Step.Id = .remove_dir;7pub const base_id: Step.Id = .remove_dir;
78
8step: Step,9step: Step,
9dir_path: []const u8,10doomed_path: LazyPath,
1011
11pub fn create(owner: *std.Build, dir_path: []const u8) *RemoveDir {12pub fn create(owner: *std.Build, doomed_path: LazyPath) *RemoveDir {
12 const remove_dir = owner.allocator.create(RemoveDir) catch @panic("OOM");13 const remove_dir = owner.allocator.create(RemoveDir) catch @panic("OOM");
13 remove_dir.* = .{14 remove_dir.* = .{
14 .step = Step.init(.{15 .step = Step.init(.{
15 .id = base_id,16 .id = base_id,
16 .name = owner.fmt("RemoveDir {s}", .{dir_path}),17 .name = owner.fmt("RemoveDir {s}", .{doomed_path.getDisplayName()}),
17 .owner = owner,18 .owner = owner,
18 .makeFn = make,19 .makeFn = make,
19 }),20 }),
20 .dir_path = owner.dupePath(dir_path),21 .doomed_path = doomed_path.dupe(owner),
21 };22 };
22 return remove_dir;23 return remove_dir;
23}24}
...@@ -30,14 +31,19 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -30,14 +31,19 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
30 const b = step.owner;31 const b = step.owner;
31 const remove_dir: *RemoveDir = @fieldParentPtr("step", step);32 const remove_dir: *RemoveDir = @fieldParentPtr("step", step);
3233
33 b.build_root.handle.deleteTree(remove_dir.dir_path) catch |err| {34 step.clearWatchInputs();
35 try step.addWatchInput(remove_dir.doomed_path);
36
37 const full_doomed_path = remove_dir.doomed_path.getPath2(b, step);
38
39 b.build_root.handle.deleteTree(full_doomed_path) catch |err| {
34 if (b.build_root.path) |base| {40 if (b.build_root.path) |base| {
35 return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{41 return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{
36 base, remove_dir.dir_path, @errorName(err),42 base, full_doomed_path, @errorName(err),
37 });43 });
38 } else {44 } else {
39 return step.fail("unable to recursively delete path '{s}': {s}", .{45 return step.fail("unable to recursively delete path '{s}': {s}", .{
40 remove_dir.dir_path, @errorName(err),46 full_doomed_path, @errorName(err),
41 });47 });
42 }48 }
43 };49 };
lib/std/Build/Step/Run.zig+4-4
...@@ -632,7 +632,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -632,7 +632,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
632 // On Windows we don't have rpaths so we have to add .dll search paths to PATH632 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
633 run.addPathForDynLibs(artifact);633 run.addPathForDynLibs(artifact);
634 }634 }
635 const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?; // the path is guaranteed to be set635 const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?;
636636
637 try argv_list.append(b.fmt("{s}{s}", .{ pa.prefix, file_path }));637 try argv_list.append(b.fmt("{s}{s}", .{ pa.prefix, file_path }));
638638
...@@ -682,7 +682,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -682,7 +682,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
682 _ = try man.addFile(lazy_path.getPath2(b, step), null);682 _ = try man.addFile(lazy_path.getPath2(b, step), null);
683 }683 }
684684
685 if (!has_side_effects and try step.cacheHit(&man)) {685 if (!has_side_effects and try step.cacheHitAndWatch(&man)) {
686 // cache hit, skip running command686 // cache hit, skip running command
687 const digest = man.final();687 const digest = man.final();
688688
...@@ -736,7 +736,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -736,7 +736,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
736 }736 }
737737
738 try runCommand(run, argv_list.items, has_side_effects, output_dir_path, prog_node);738 try runCommand(run, argv_list.items, has_side_effects, output_dir_path, prog_node);
739 if (!has_side_effects) try step.writeManifest(&man);739 if (!has_side_effects) try step.writeManifestAndWatch(&man);
740 return;740 return;
741 };741 };
742742
...@@ -812,7 +812,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -812,7 +812,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
812 };812 };
813 }813 }
814814
815 if (!has_side_effects) try step.writeManifest(&man);815 if (!has_side_effects) try step.writeManifestAndWatch(&man);
816816
817 try populateGeneratedPaths(817 try populateGeneratedPaths(
818 arena,818 arena,
lib/std/Build/Step/UpdateSourceFiles.zig created+114
...@@ -0,0 +1,114 @@
1//! Writes data to paths relative to the package root, effectively mutating the
2//! package's source files. Be careful with the latter functionality; it should
3//! not be used during the normal build process, but as a utility run by a
4//! developer with intention to update source files, which will then be
5//! committed to version control.
6const std = @import("std");
7const Step = std.Build.Step;
8const fs = std.fs;
9const ArrayList = std.ArrayList;
10const UpdateSourceFiles = @This();
11
12step: Step,
13output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
14
15pub const base_id: Step.Id = .update_source_files;
16
17pub const OutputSourceFile = struct {
18 contents: Contents,
19 sub_path: []const u8,
20};
21
22pub const Contents = union(enum) {
23 bytes: []const u8,
24 copy: std.Build.LazyPath,
25};
26
27pub fn create(owner: *std.Build) *UpdateSourceFiles {
28 const usf = owner.allocator.create(UpdateSourceFiles) catch @panic("OOM");
29 usf.* = .{
30 .step = Step.init(.{
31 .id = base_id,
32 .name = "UpdateSourceFiles",
33 .owner = owner,
34 .makeFn = make,
35 }),
36 .output_source_files = .{},
37 };
38 return usf;
39}
40
41/// A path relative to the package root.
42///
43/// Be careful with this because it updates source files. This should not be
44/// used as part of the normal build process, but as a utility occasionally
45/// run by a developer with intent to modify source files and then commit
46/// those changes to version control.
47pub fn addCopyFileToSource(usf: *UpdateSourceFiles, source: std.Build.LazyPath, sub_path: []const u8) void {
48 const b = usf.step.owner;
49 usf.output_source_files.append(b.allocator, .{
50 .contents = .{ .copy = source },
51 .sub_path = sub_path,
52 }) catch @panic("OOM");
53 source.addStepDependencies(&usf.step);
54}
55
56/// A path relative to the package root.
57///
58/// Be careful with this because it updates source files. This should not be
59/// used as part of the normal build process, but as a utility occasionally
60/// run by a developer with intent to modify source files and then commit
61/// those changes to version control.
62pub fn addBytesToSource(usf: *UpdateSourceFiles, bytes: []const u8, sub_path: []const u8) void {
63 const b = usf.step.owner;
64 usf.output_source_files.append(b.allocator, .{
65 .contents = .{ .bytes = bytes },
66 .sub_path = sub_path,
67 }) catch @panic("OOM");
68}
69
70fn make(step: *Step, prog_node: std.Progress.Node) !void {
71 _ = prog_node;
72 const b = step.owner;
73 const usf: *UpdateSourceFiles = @fieldParentPtr("step", step);
74
75 var any_miss = false;
76 for (usf.output_source_files.items) |output_source_file| {
77 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
78 b.build_root.handle.makePath(dirname) catch |err| {
79 return step.fail("unable to make path '{}{s}': {s}", .{
80 b.build_root, dirname, @errorName(err),
81 });
82 };
83 }
84 switch (output_source_file.contents) {
85 .bytes => |bytes| {
86 b.build_root.handle.writeFile(.{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| {
87 return step.fail("unable to write file '{}{s}': {s}", .{
88 b.build_root, output_source_file.sub_path, @errorName(err),
89 });
90 };
91 any_miss = true;
92 },
93 .copy => |file_source| {
94 if (!step.inputs.populated()) try step.addWatchInput(file_source);
95
96 const source_path = file_source.getPath2(b, step);
97 const prev_status = fs.Dir.updateFile(
98 fs.cwd(),
99 source_path,
100 b.build_root.handle,
101 output_source_file.sub_path,
102 .{},
103 ) catch |err| {
104 return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{
105 source_path, b.build_root, output_source_file.sub_path, @errorName(err),
106 });
107 };
108 any_miss = any_miss or prev_status == .stale;
109 },
110 }
111 }
112
113 step.result_cached = !any_miss;
114}
lib/std/Build/Step/WriteFile.zig+83-126
...@@ -1,13 +1,6 @@...@@ -1,13 +1,6 @@
1//! WriteFile is primarily used to create a directory in an appropriate1//! WriteFile is used to create a directory in an appropriate location inside
2//! location inside the local cache which has a set of files that have either2//! the local cache which has a set of files that have either been generated
3//! been generated during the build, or are copied from the source package.3//! during the build, or are copied from the source package.
4//!
5//! However, this step has an additional capability of writing data to paths
6//! relative to the package root, effectively mutating the package's source
7//! files. Be careful with the latter functionality; it should not be used
8//! during the normal build process, but as a utility run by a developer with
9//! intention to update source files, which will then be committed to version
10//! control.
11const std = @import("std");4const std = @import("std");
12const Step = std.Build.Step;5const Step = std.Build.Step;
13const fs = std.fs;6const fs = std.fs;
...@@ -19,8 +12,6 @@ step: Step,...@@ -19,8 +12,6 @@ step: Step,
19// The elements here are pointers because we need stable pointers for the GeneratedFile field.12// The elements here are pointers because we need stable pointers for the GeneratedFile field.
20files: std.ArrayListUnmanaged(File),13files: std.ArrayListUnmanaged(File),
21directories: std.ArrayListUnmanaged(Directory),14directories: std.ArrayListUnmanaged(Directory),
22
23output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
24generated_directory: std.Build.GeneratedFile,15generated_directory: std.Build.GeneratedFile,
2516
26pub const base_id: Step.Id = .write_file;17pub const base_id: Step.Id = .write_file;
...@@ -49,12 +40,23 @@ pub const Directory = struct {...@@ -49,12 +40,23 @@ pub const Directory = struct {
49 .include_extensions = if (opts.include_extensions) |incs| b.dupeStrings(incs) else null,40 .include_extensions = if (opts.include_extensions) |incs| b.dupeStrings(incs) else null,
50 };41 };
51 }42 }
52 };
53};
5443
55pub const OutputSourceFile = struct {44 pub fn pathIncluded(opts: Options, path: []const u8) bool {
56 contents: Contents,45 for (opts.exclude_extensions) |ext| {
57 sub_path: []const u8,46 if (std.mem.endsWith(u8, path, ext))
47 return false;
48 }
49 if (opts.include_extensions) |incs| {
50 for (incs) |inc| {
51 if (std.mem.endsWith(u8, path, inc))
52 return true;
53 } else {
54 return false;
55 }
56 }
57 return true;
58 }
59 };
58};60};
5961
60pub const Contents = union(enum) {62pub const Contents = union(enum) {
...@@ -73,7 +75,6 @@ pub fn create(owner: *std.Build) *WriteFile {...@@ -73,7 +75,6 @@ pub fn create(owner: *std.Build) *WriteFile {
73 }),75 }),
74 .files = .{},76 .files = .{},
75 .directories = .{},77 .directories = .{},
76 .output_source_files = .{},
77 .generated_directory = .{ .step = &write_file.step },78 .generated_directory = .{ .step = &write_file.step },
78 };79 };
79 return write_file;80 return write_file;
...@@ -150,33 +151,6 @@ pub fn addCopyDirectory(...@@ -150,33 +151,6 @@ pub fn addCopyDirectory(
150 };151 };
151}152}
152153
153/// A path relative to the package root.
154/// Be careful with this because it updates source files. This should not be
155/// used as part of the normal build process, but as a utility occasionally
156/// run by a developer with intent to modify source files and then commit
157/// those changes to version control.
158pub fn addCopyFileToSource(write_file: *WriteFile, source: std.Build.LazyPath, sub_path: []const u8) void {
159 const b = write_file.step.owner;
160 write_file.output_source_files.append(b.allocator, .{
161 .contents = .{ .copy = source },
162 .sub_path = sub_path,
163 }) catch @panic("OOM");
164 source.addStepDependencies(&write_file.step);
165}
166
167/// A path relative to the package root.
168/// Be careful with this because it updates source files. This should not be
169/// used as part of the normal build process, but as a utility occasionally
170/// run by a developer with intent to modify source files and then commit
171/// those changes to version control.
172pub fn addBytesToSource(write_file: *WriteFile, bytes: []const u8, sub_path: []const u8) void {
173 const b = write_file.step.owner;
174 write_file.output_source_files.append(b.allocator, .{
175 .contents = .{ .bytes = bytes },
176 .sub_path = sub_path,
177 }) catch @panic("OOM");
178}
179
180/// Returns a `LazyPath` representing the base directory that contains all the154/// Returns a `LazyPath` representing the base directory that contains all the
181/// files from this `WriteFile`.155/// files from this `WriteFile`.
182pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath {156pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath {
...@@ -200,47 +174,10 @@ fn maybeUpdateName(write_file: *WriteFile) void {...@@ -200,47 +174,10 @@ fn maybeUpdateName(write_file: *WriteFile) void {
200fn make(step: *Step, prog_node: std.Progress.Node) !void {174fn make(step: *Step, prog_node: std.Progress.Node) !void {
201 _ = prog_node;175 _ = prog_node;
202 const b = step.owner;176 const b = step.owner;
177 const arena = b.allocator;
178 const gpa = arena;
203 const write_file: *WriteFile = @fieldParentPtr("step", step);179 const write_file: *WriteFile = @fieldParentPtr("step", step);
204180 step.clearWatchInputs();
205 // Writing to source files is kind of an extra capability of this
206 // WriteFile - arguably it should be a different step. But anyway here
207 // it is, it happens unconditionally and does not interact with the other
208 // files here.
209 var any_miss = false;
210 for (write_file.output_source_files.items) |output_source_file| {
211 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
212 b.build_root.handle.makePath(dirname) catch |err| {
213 return step.fail("unable to make path '{}{s}': {s}", .{
214 b.build_root, dirname, @errorName(err),
215 });
216 };
217 }
218 switch (output_source_file.contents) {
219 .bytes => |bytes| {
220 b.build_root.handle.writeFile(.{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| {
221 return step.fail("unable to write file '{}{s}': {s}", .{
222 b.build_root, output_source_file.sub_path, @errorName(err),
223 });
224 };
225 any_miss = true;
226 },
227 .copy => |file_source| {
228 const source_path = file_source.getPath2(b, step);
229 const prev_status = fs.Dir.updateFile(
230 fs.cwd(),
231 source_path,
232 b.build_root.handle,
233 output_source_file.sub_path,
234 .{},
235 ) catch |err| {
236 return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{
237 source_path, b.build_root, output_source_file.sub_path, @errorName(err),
238 });
239 };
240 any_miss = any_miss or prev_status == .stale;
241 },
242 }
243 }
244181
245 // The cache is used here not really as a way to speed things up - because writing182 // The cache is used here not really as a way to speed things up - because writing
246 // the data to a file would probably be very fast - but as a way to find a canonical183 // the data to a file would probably be very fast - but as a way to find a canonical
...@@ -252,39 +189,73 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -252,39 +189,73 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
252 var man = b.graph.cache.obtain();189 var man = b.graph.cache.obtain();
253 defer man.deinit();190 defer man.deinit();
254191
255 // Random bytes to make WriteFile unique. Refresh this with
256 // new random bytes when WriteFile implementation is modified
257 // in a non-backwards-compatible way.
258 man.hash.add(@as(u32, 0xd767ee59));
259
260 for (write_file.files.items) |file| {192 for (write_file.files.items) |file| {
261 man.hash.addBytes(file.sub_path);193 man.hash.addBytes(file.sub_path);
194
262 switch (file.contents) {195 switch (file.contents) {
263 .bytes => |bytes| {196 .bytes => |bytes| {
264 man.hash.addBytes(bytes);197 man.hash.addBytes(bytes);
265 },198 },
266 .copy => |file_source| {199 .copy => |lazy_path| {
267 _ = try man.addFile(file_source.getPath2(b, step), null);200 const path = lazy_path.getPath3(b, step);
201 _ = try man.addFilePath(path, null);
202 try step.addWatchInput(lazy_path);
268 },203 },
269 }204 }
270 }205 }
271 for (write_file.directories.items) |dir| {206
272 man.hash.addBytes(dir.source.getPath2(b, step));207 const open_dir_cache = try arena.alloc(fs.Dir, write_file.directories.items.len);
208 var open_dirs_count: usize = 0;
209 defer closeDirs(open_dir_cache[0..open_dirs_count]);
210
211 for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| {
273 man.hash.addBytes(dir.sub_path);212 man.hash.addBytes(dir.sub_path);
274 for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext);213 for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext);
275 if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc);214 if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc);
215
216 const need_derived_inputs = try step.addDirectoryWatchInput(dir.source);
217 const src_dir_path = dir.source.getPath3(b, step);
218
219 var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
220 return step.fail("unable to open source directory '{}': {s}", .{
221 src_dir_path, @errorName(err),
222 });
223 };
224 open_dir_cache_elem.* = src_dir;
225 open_dirs_count += 1;
226
227 var it = try src_dir.walk(gpa);
228 defer it.deinit();
229 while (try it.next()) |entry| {
230 if (!dir.options.pathIncluded(entry.path)) continue;
231
232 switch (entry.kind) {
233 .directory => {
234 if (need_derived_inputs) {
235 const entry_path = try src_dir_path.join(arena, entry.path);
236 try step.addDirectoryWatchInputFromPath(entry_path);
237 }
238 },
239 .file => {
240 const entry_path = try src_dir_path.join(arena, entry.path);
241 _ = try man.addFilePath(entry_path, null);
242 },
243 else => continue,
244 }
245 }
276 }246 }
277247
278 if (try step.cacheHit(&man)) {248 if (try step.cacheHit(&man)) {
279 const digest = man.final();249 const digest = man.final();
280 write_file.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });250 write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest });
251 step.result_cached = true;
281 return;252 return;
282 }253 }
283254
284 const digest = man.final();255 const digest = man.final();
285 const cache_path = "o" ++ fs.path.sep_str ++ digest;256 const cache_path = "o" ++ fs.path.sep_str ++ digest;
286257
287 write_file.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });258 write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest });
288259
289 var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {260 var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {
290 return step.fail("unable to make path '{}{s}': {s}", .{261 return step.fail("unable to make path '{}{s}': {s}", .{
...@@ -337,8 +308,9 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -337,8 +308,9 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
337 },308 },
338 }309 }
339 }310 }
340 for (write_file.directories.items) |dir| {311
341 const full_src_dir_path = dir.source.getPath2(b, step);312 for (write_file.directories.items, open_dir_cache) |dir, already_open_dir| {
313 const src_dir_path = dir.source.getPath3(b, step);
342 const dest_dirname = dir.sub_path;314 const dest_dirname = dir.sub_path;
343315
344 if (dest_dirname.len != 0) {316 if (dest_dirname.len != 0) {
...@@ -349,44 +321,25 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -349,44 +321,25 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
349 };321 };
350 }322 }
351323
352 var src_dir = b.build_root.handle.openDir(full_src_dir_path, .{ .iterate = true }) catch |err| {324 var it = try already_open_dir.walk(gpa);
353 return step.fail("unable to open source directory '{s}': {s}", .{325 defer it.deinit();
354 full_src_dir_path, @errorName(err),326 while (try it.next()) |entry| {
355 });327 if (!dir.options.pathIncluded(entry.path)) continue;
356 };
357 defer src_dir.close();
358328
359 var it = try src_dir.walk(b.allocator);329 const src_entry_path = try src_dir_path.join(arena, entry.path);
360 next_entry: while (try it.next()) |entry| {
361 for (dir.options.exclude_extensions) |ext| {
362 if (std.mem.endsWith(u8, entry.path, ext)) continue :next_entry;
363 }
364 if (dir.options.include_extensions) |incs| {
365 for (incs) |inc| {
366 if (std.mem.endsWith(u8, entry.path, inc)) break;
367 } else {
368 continue :next_entry;
369 }
370 }
371 const full_src_entry_path = b.pathJoin(&.{ full_src_dir_path, entry.path });
372 const dest_path = b.pathJoin(&.{ dest_dirname, entry.path });330 const dest_path = b.pathJoin(&.{ dest_dirname, entry.path });
373 switch (entry.kind) {331 switch (entry.kind) {
374 .directory => try cache_dir.makePath(dest_path),332 .directory => try cache_dir.makePath(dest_path),
375 .file => {333 .file => {
376 const prev_status = fs.Dir.updateFile(334 const prev_status = fs.Dir.updateFile(
377 cwd,335 src_entry_path.root_dir.handle,
378 full_src_entry_path,336 src_entry_path.sub_path,
379 cache_dir,337 cache_dir,
380 dest_path,338 dest_path,
381 .{},339 .{},
382 ) catch |err| {340 ) catch |err| {
383 return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{341 return step.fail("unable to update file from '{}' to '{}{s}{c}{s}': {s}", .{
384 full_src_entry_path,342 src_entry_path, b.cache_root, cache_path, fs.path.sep, dest_path, @errorName(err),
385 b.cache_root,
386 cache_path,
387 fs.path.sep,
388 dest_path,
389 @errorName(err),
390 });343 });
391 };344 };
392 _ = prev_status;345 _ = prev_status;
...@@ -398,3 +351,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -398,3 +351,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
398351
399 try step.writeManifest(&man);352 try step.writeManifest(&man);
400}353}
354
355fn closeDirs(dirs: []fs.Dir) void {
356 for (dirs) |*d| d.close();
357}
lib/std/Build/Watch.zig created+363
...@@ -0,0 +1,363 @@
1const builtin = @import("builtin");
2const std = @import("../std.zig");
3const Watch = @This();
4const Step = std.Build.Step;
5const Allocator = std.mem.Allocator;
6const assert = std.debug.assert;
7const fatal = std.zig.fatal;
8
9dir_table: DirTable,
10os: Os,
11generation: Generation,
12
13/// Key is the directory to watch which contains one or more files we are
14/// interested in noticing changes to.
15///
16/// Value is generation.
17const DirTable = std.ArrayHashMapUnmanaged(Cache.Path, void, Cache.Path.TableAdapter, false);
18
19/// Special key of "." means any changes in this directory trigger the steps.
20const ReactionSet = std.StringArrayHashMapUnmanaged(StepSet);
21const StepSet = std.AutoArrayHashMapUnmanaged(*Step, Generation);
22
23const Generation = u8;
24
25const Hash = std.hash.Wyhash;
26const Cache = std.Build.Cache;
27
28const Os = switch (builtin.os.tag) {
29 .linux => struct {
30 const posix = std.posix;
31
32 /// Keyed differently but indexes correspond 1:1 with `dir_table`.
33 handle_table: HandleTable,
34 poll_fds: [1]posix.pollfd,
35
36 const HandleTable = std.ArrayHashMapUnmanaged(FileHandle, ReactionSet, FileHandle.Adapter, false);
37
38 const fan_mask: std.os.linux.fanotify.MarkMask = .{
39 .CLOSE_WRITE = true,
40 .CREATE = true,
41 .DELETE = true,
42 .DELETE_SELF = true,
43 .EVENT_ON_CHILD = true,
44 .MOVED_FROM = true,
45 .MOVED_TO = true,
46 .MOVE_SELF = true,
47 .ONDIR = true,
48 };
49
50 const FileHandle = struct {
51 handle: *align(1) std.os.linux.file_handle,
52
53 fn clone(lfh: FileHandle, gpa: Allocator) Allocator.Error!FileHandle {
54 const bytes = lfh.slice();
55 const new_ptr = try gpa.alignedAlloc(
56 u8,
57 @alignOf(std.os.linux.file_handle),
58 @sizeOf(std.os.linux.file_handle) + bytes.len,
59 );
60 const new_header: *std.os.linux.file_handle = @ptrCast(new_ptr);
61 new_header.* = lfh.handle.*;
62 const new: FileHandle = .{ .handle = new_header };
63 @memcpy(new.slice(), lfh.slice());
64 return new;
65 }
66
67 fn destroy(lfh: FileHandle, gpa: Allocator) void {
68 const ptr: [*]u8 = @ptrCast(lfh.handle);
69 const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes];
70 return gpa.free(allocated_slice);
71 }
72
73 fn slice(lfh: FileHandle) []u8 {
74 const ptr: [*]u8 = &lfh.handle.f_handle;
75 return ptr[0..lfh.handle.handle_bytes];
76 }
77
78 const Adapter = struct {
79 pub fn hash(self: Adapter, a: FileHandle) u32 {
80 _ = self;
81 const unsigned_type: u32 = @bitCast(a.handle.handle_type);
82 return @truncate(Hash.hash(unsigned_type, a.slice()));
83 }
84 pub fn eql(self: Adapter, a: FileHandle, b: FileHandle, b_index: usize) bool {
85 _ = self;
86 _ = b_index;
87 return a.handle.handle_type == b.handle.handle_type and std.mem.eql(u8, a.slice(), b.slice());
88 }
89 };
90 };
91
92 fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path) !FileHandle {
93 var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined;
94 var mount_id: i32 = undefined;
95 var buf: [std.fs.max_path_bytes]u8 = undefined;
96 const adjusted_path = if (path.sub_path.len == 0) "./" else std.fmt.bufPrint(&buf, "{s}/", .{
97 path.sub_path,
98 }) catch return error.NameTooLong;
99 const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer);
100 stack_ptr.handle_bytes = file_handle_buffer.len - @sizeOf(std.os.linux.file_handle);
101 try posix.name_to_handle_at(path.root_dir.handle.fd, adjusted_path, stack_ptr, &mount_id, std.os.linux.AT.HANDLE_FID);
102 const stack_lfh: FileHandle = .{ .handle = stack_ptr };
103 return stack_lfh.clone(gpa);
104 }
105
106 fn markDirtySteps(w: *Watch, gpa: Allocator) !bool {
107 const fan_fd = w.os.getFanFd();
108 const fanotify = std.os.linux.fanotify;
109 const M = fanotify.event_metadata;
110 var events_buf: [256 + 4096]u8 = undefined;
111 var any_dirty = false;
112 while (true) {
113 var len = posix.read(fan_fd, &events_buf) catch |err| switch (err) {
114 error.WouldBlock => return any_dirty,
115 else => |e| return e,
116 };
117 var meta: [*]align(1) M = @ptrCast(&events_buf);
118 while (len >= @sizeOf(M) and meta[0].event_len >= @sizeOf(M) and meta[0].event_len <= len) : ({
119 len -= meta[0].event_len;
120 meta = @ptrCast(@as([*]u8, @ptrCast(meta)) + meta[0].event_len);
121 }) {
122 assert(meta[0].vers == M.VERSION);
123 if (meta[0].mask.Q_OVERFLOW) {
124 any_dirty = true;
125 std.log.warn("file system watch queue overflowed; falling back to fstat", .{});
126 markAllFilesDirty(w, gpa);
127 return true;
128 }
129 const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1);
130 switch (fid.hdr.info_type) {
131 .DFID_NAME => {
132 const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle);
133 const file_name_z: [*:0]u8 = @ptrCast((&file_handle.f_handle).ptr + file_handle.handle_bytes);
134 const file_name = std.mem.span(file_name_z);
135 const lfh: FileHandle = .{ .handle = file_handle };
136 if (w.os.handle_table.getPtr(lfh)) |reaction_set| {
137 if (reaction_set.getPtr(".")) |glob_set|
138 any_dirty = markStepSetDirty(gpa, glob_set, any_dirty);
139 if (reaction_set.getPtr(file_name)) |step_set|
140 any_dirty = markStepSetDirty(gpa, step_set, any_dirty);
141 }
142 },
143 else => |t| std.log.warn("unexpected fanotify event '{s}'", .{@tagName(t)}),
144 }
145 }
146 }
147 }
148
149 fn getFanFd(os: *const @This()) posix.fd_t {
150 return os.poll_fds[0].fd;
151 }
152
153 fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void {
154 const fan_fd = w.os.getFanFd();
155 // Add missing marks and note persisted ones.
156 for (steps) |step| {
157 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
158 const reaction_set = rs: {
159 const gop = try w.dir_table.getOrPut(gpa, path);
160 if (!gop.found_existing) {
161 const dir_handle = try Os.getDirHandle(gpa, path);
162 // `dir_handle` may already be present in the table in
163 // the case that we have multiple Cache.Path instances
164 // that compare inequal but ultimately point to the same
165 // directory on the file system.
166 // In such case, we must revert adding this directory, but keep
167 // the additions to the step set.
168 const dh_gop = try w.os.handle_table.getOrPut(gpa, dir_handle);
169 if (dh_gop.found_existing) {
170 _ = w.dir_table.pop();
171 } else {
172 assert(dh_gop.index == gop.index);
173 dh_gop.value_ptr.* = .{};
174 posix.fanotify_mark(fan_fd, .{
175 .ADD = true,
176 .ONLYDIR = true,
177 }, fan_mask, path.root_dir.handle.fd, path.subPathOrDot()) catch |err| {
178 fatal("unable to watch {}: {s}", .{ path, @errorName(err) });
179 };
180 }
181 break :rs dh_gop.value_ptr;
182 }
183 break :rs &w.os.handle_table.values()[gop.index];
184 };
185 for (files.items) |basename| {
186 const gop = try reaction_set.getOrPut(gpa, basename);
187 if (!gop.found_existing) gop.value_ptr.* = .{};
188 try gop.value_ptr.put(gpa, step, w.generation);
189 }
190 }
191 }
192
193 {
194 // Remove marks for files that are no longer inputs.
195 var i: usize = 0;
196 while (i < w.os.handle_table.entries.len) {
197 {
198 const reaction_set = &w.os.handle_table.values()[i];
199 var step_set_i: usize = 0;
200 while (step_set_i < reaction_set.entries.len) {
201 const step_set = &reaction_set.values()[step_set_i];
202 var dirent_i: usize = 0;
203 while (dirent_i < step_set.entries.len) {
204 const generations = step_set.values();
205 if (generations[dirent_i] == w.generation) {
206 dirent_i += 1;
207 continue;
208 }
209 step_set.swapRemoveAt(dirent_i);
210 }
211 if (step_set.entries.len > 0) {
212 step_set_i += 1;
213 continue;
214 }
215 reaction_set.swapRemoveAt(step_set_i);
216 }
217 if (reaction_set.entries.len > 0) {
218 i += 1;
219 continue;
220 }
221 }
222
223 const path = w.dir_table.keys()[i];
224
225 posix.fanotify_mark(fan_fd, .{
226 .REMOVE = true,
227 .ONLYDIR = true,
228 }, fan_mask, path.root_dir.handle.fd, path.subPathOrDot()) catch |err| switch (err) {
229 error.FileNotFound => {}, // Expected, harmless.
230 else => |e| std.log.warn("unable to unwatch '{}': {s}", .{ path, @errorName(e) }),
231 };
232
233 w.dir_table.swapRemoveAt(i);
234 w.os.handle_table.swapRemoveAt(i);
235 }
236 w.generation +%= 1;
237 }
238 }
239 },
240 else => void,
241};
242
243pub fn init() !Watch {
244 switch (builtin.os.tag) {
245 .linux => {
246 const fan_fd = try std.posix.fanotify_init(.{
247 .CLASS = .NOTIF,
248 .CLOEXEC = true,
249 .NONBLOCK = true,
250 .REPORT_NAME = true,
251 .REPORT_DIR_FID = true,
252 .REPORT_FID = true,
253 .REPORT_TARGET_FID = true,
254 }, 0);
255 return .{
256 .dir_table = .{},
257 .os = switch (builtin.os.tag) {
258 .linux => .{
259 .handle_table = .{},
260 .poll_fds = .{
261 .{
262 .fd = fan_fd,
263 .events = std.posix.POLL.IN,
264 .revents = undefined,
265 },
266 },
267 },
268 else => {},
269 },
270 .generation = 0,
271 };
272 },
273 else => @panic("unimplemented"),
274 }
275}
276
277pub const Match = struct {
278 /// Relative to the watched directory, the file path that triggers this
279 /// match.
280 basename: []const u8,
281 /// The step to re-run when file corresponding to `basename` is changed.
282 step: *Step,
283
284 pub const Context = struct {
285 pub fn hash(self: Context, a: Match) u32 {
286 _ = self;
287 var hasher = Hash.init(0);
288 std.hash.autoHash(&hasher, a.step);
289 hasher.update(a.basename);
290 return @truncate(hasher.final());
291 }
292 pub fn eql(self: Context, a: Match, b: Match, b_index: usize) bool {
293 _ = self;
294 _ = b_index;
295 return a.step == b.step and std.mem.eql(u8, a.basename, b.basename);
296 }
297 };
298};
299
300fn markAllFilesDirty(w: *Watch, gpa: Allocator) void {
301 for (w.os.handle_table.values()) |reaction_set| {
302 for (reaction_set.values()) |step_set| {
303 for (step_set.keys()) |step| {
304 step.recursiveReset(gpa);
305 }
306 }
307 }
308}
309
310fn markStepSetDirty(gpa: Allocator, step_set: *StepSet, any_dirty: bool) bool {
311 var this_any_dirty = false;
312 for (step_set.keys()) |step| {
313 if (step.state != .precheck_done) {
314 step.recursiveReset(gpa);
315 this_any_dirty = true;
316 }
317 }
318 return any_dirty or this_any_dirty;
319}
320
321pub fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void {
322 switch (builtin.os.tag) {
323 .linux => return Os.update(w, gpa, steps),
324 else => @compileError("unimplemented"),
325 }
326}
327
328pub const Timeout = union(enum) {
329 none,
330 ms: u16,
331
332 pub fn to_i32_ms(t: Timeout) i32 {
333 return switch (t) {
334 .none => -1,
335 .ms => |ms| ms,
336 };
337 }
338};
339
340pub const WaitResult = enum {
341 timeout,
342 /// File system watching triggered on files that were marked as inputs to at least one Step.
343 /// Relevant steps have been marked dirty.
344 dirty,
345 /// File system watching triggered but none of the events were relevant to
346 /// what we are listening to. There is nothing to do.
347 clean,
348};
349
350pub fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult {
351 switch (builtin.os.tag) {
352 .linux => {
353 const events_len = try std.posix.poll(&w.os.poll_fds, timeout.to_i32_ms());
354 return if (events_len == 0)
355 .timeout
356 else if (try Os.markDirtySteps(w, gpa))
357 .dirty
358 else
359 .clean;
360 },
361 else => @compileError("unimplemented"),
362 }
363}
lib/std/os/linux.zig+182-52
...@@ -698,12 +698,42 @@ pub fn inotify_rm_watch(fd: i32, wd: i32) usize {...@@ -698,12 +698,42 @@ pub fn inotify_rm_watch(fd: i32, wd: i32) usize {
698 return syscall2(.inotify_rm_watch, @as(usize, @bitCast(@as(isize, fd))), @as(usize, @bitCast(@as(isize, wd))));698 return syscall2(.inotify_rm_watch, @as(usize, @bitCast(@as(isize, fd))), @as(usize, @bitCast(@as(isize, wd))));
699}699}
700700
701pub fn fanotify_init(flags: u32, event_f_flags: u32) usize {701pub fn fanotify_init(flags: fanotify.InitFlags, event_f_flags: u32) usize {
702 return syscall2(.fanotify_init, flags, event_f_flags);702 return syscall2(.fanotify_init, @as(u32, @bitCast(flags)), event_f_flags);
703}703}
704704
705pub fn fanotify_mark(fd: i32, flags: u32, mask: u64, dirfd: i32, pathname: ?[*:0]const u8) usize {705pub fn fanotify_mark(
706 return syscall5(.fanotify_mark, @as(usize, @bitCast(@as(isize, fd))), flags, mask, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(pathname));706 fd: fd_t,
707 flags: fanotify.MarkFlags,
708 mask: fanotify.MarkMask,
709 dirfd: fd_t,
710 pathname: ?[*:0]const u8,
711) usize {
712 return syscall5(
713 .fanotify_mark,
714 @bitCast(@as(isize, fd)),
715 @as(u32, @bitCast(flags)),
716 @bitCast(mask),
717 @bitCast(@as(isize, dirfd)),
718 @intFromPtr(pathname),
719 );
720}
721
722pub fn name_to_handle_at(
723 dirfd: fd_t,
724 pathname: [*:0]const u8,
725 handle: *std.os.linux.file_handle,
726 mount_id: *i32,
727 flags: u32,
728) usize {
729 return syscall5(
730 .name_to_handle_at,
731 @as(u32, @bitCast(dirfd)),
732 @intFromPtr(pathname),
733 @intFromPtr(handle),
734 @intFromPtr(mount_id),
735 flags,
736 );
707}737}
708738
709pub fn readlink(noalias path: [*:0]const u8, noalias buf_ptr: [*]u8, buf_len: usize) usize {739pub fn readlink(noalias path: [*:0]const u8, noalias buf_ptr: [*]u8, buf_len: usize) usize {
...@@ -2916,6 +2946,8 @@ pub const AT = struct {...@@ -2916,6 +2946,8 @@ pub const AT = struct {
29162946
2917 /// Apply to the entire subtree2947 /// Apply to the entire subtree
2918 pub const RECURSIVE = 0x8000;2948 pub const RECURSIVE = 0x8000;
2949
2950 pub const HANDLE_FID = REMOVEDIR;
2919};2951};
29202952
2921pub const FALLOC = struct {2953pub const FALLOC = struct {
...@@ -4135,58 +4167,156 @@ pub const IN = struct {...@@ -4135,58 +4167,156 @@ pub const IN = struct {
4135 pub const ONESHOT = 0x80000000;4167 pub const ONESHOT = 0x80000000;
4136};4168};
41374169
4138pub const FAN = struct {4170pub const fanotify = struct {
4139 pub const ACCESS = 0x00000001;4171 pub const InitFlags = packed struct(u32) {
4140 pub const MODIFY = 0x00000002;4172 CLOEXEC: bool = false,
4141 pub const CLOSE_WRITE = 0x00000008;4173 NONBLOCK: bool = false,
4142 pub const CLOSE_NOWRITE = 0x00000010;4174 CLASS: enum(u2) {
4143 pub const OPEN = 0x00000020;4175 NOTIF = 0,
4144 pub const Q_OVERFLOW = 0x00004000;4176 CONTENT = 1,
4145 pub const OPEN_PERM = 0x00010000;4177 PRE_CONTENT = 2,
4146 pub const ACCESS_PERM = 0x00020000;4178 } = .NOTIF,
4147 pub const ONDIR = 0x40000000;4179 UNLIMITED_QUEUE: bool = false,
4148 pub const EVENT_ON_CHILD = 0x08000000;4180 UNLIMITED_MARKS: bool = false,
4149 pub const CLOSE = CLOSE_WRITE | CLOSE_NOWRITE;4181 ENABLE_AUDIT: bool = false,
4150 pub const CLOEXEC = 0x00000001;4182 REPORT_PIDFD: bool = false,
4151 pub const NONBLOCK = 0x00000002;4183 REPORT_TID: bool = false,
4152 pub const CLASS_NOTIF = 0x00000000;4184 REPORT_FID: bool = false,
4153 pub const CLASS_CONTENT = 0x00000004;4185 REPORT_DIR_FID: bool = false,
4154 pub const CLASS_PRE_CONTENT = 0x00000008;4186 REPORT_NAME: bool = false,
4155 pub const ALL_CLASS_BITS = CLASS_NOTIF | CLASS_CONTENT | CLASS_PRE_CONTENT;4187 REPORT_TARGET_FID: bool = false,
4156 pub const UNLIMITED_QUEUE = 0x00000010;4188 _: u19 = 0,
4157 pub const UNLIMITED_MARKS = 0x00000020;4189 };
4158 pub const ALL_INIT_FLAGS = CLOEXEC | NONBLOCK | ALL_CLASS_BITS | UNLIMITED_QUEUE | UNLIMITED_MARKS;4190
4159 pub const MARK_ADD = 0x00000001;4191 pub const MarkFlags = packed struct(u32) {
4160 pub const MARK_REMOVE = 0x00000002;4192 ADD: bool = false,
4161 pub const MARK_DONT_FOLLOW = 0x00000004;4193 REMOVE: bool = false,
4162 pub const MARK_ONLYDIR = 0x00000008;4194 DONT_FOLLOW: bool = false,
4163 pub const MARK_MOUNT = 0x00000010;4195 ONLYDIR: bool = false,
4164 pub const MARK_IGNORED_MASK = 0x00000020;4196 MOUNT: bool = false,
4165 pub const MARK_IGNORED_SURV_MODIFY = 0x00000040;4197 /// Mutually exclusive with `IGNORE`
4166 pub const MARK_FLUSH = 0x00000080;4198 IGNORED_MASK: bool = false,
4167 pub const ALL_MARK_FLAGS = MARK_ADD | MARK_REMOVE | MARK_DONT_FOLLOW | MARK_ONLYDIR | MARK_MOUNT | MARK_IGNORED_MASK | MARK_IGNORED_SURV_MODIFY | MARK_FLUSH;4199 IGNORED_SURV_MODIFY: bool = false,
4168 pub const ALL_EVENTS = ACCESS | MODIFY | CLOSE | OPEN;4200 FLUSH: bool = false,
4169 pub const ALL_PERM_EVENTS = OPEN_PERM | ACCESS_PERM;4201 FILESYSTEM: bool = false,
4170 pub const ALL_OUTGOING_EVENTS = ALL_EVENTS | ALL_PERM_EVENTS | Q_OVERFLOW;4202 EVICTABLE: bool = false,
4171 pub const ALLOW = 0x01;4203 /// Mutually exclusive with `IGNORED_MASK`
4172 pub const DENY = 0x02;4204 IGNORE: bool = false,
4173};4205 _: u21 = 0,
41744206 };
4175pub const fanotify_event_metadata = extern struct {4207
4176 event_len: u32,4208 pub const MarkMask = packed struct(u64) {
4177 vers: u8,4209 /// File was accessed
4178 reserved: u8,4210 ACCESS: bool = false,
4179 metadata_len: u16,4211 /// File was modified
4180 mask: u64 align(8),4212 MODIFY: bool = false,
4181 fd: i32,4213 /// Metadata changed
4182 pid: i32,4214 ATTRIB: bool = false,
4215 /// Writtable file closed
4216 CLOSE_WRITE: bool = false,
4217 /// Unwrittable file closed
4218 CLOSE_NOWRITE: bool = false,
4219 /// File was opened
4220 OPEN: bool = false,
4221 /// File was moved from X
4222 MOVED_FROM: bool = false,
4223 /// File was moved to Y
4224 MOVED_TO: bool = false,
4225
4226 /// Subfile was created
4227 CREATE: bool = false,
4228 /// Subfile was deleted
4229 DELETE: bool = false,
4230 /// Self was deleted
4231 DELETE_SELF: bool = false,
4232 /// Self was moved
4233 MOVE_SELF: bool = false,
4234 /// File was opened for exec
4235 OPEN_EXEC: bool = false,
4236 reserved13: u1 = 0,
4237 /// Event queued overflowed
4238 Q_OVERFLOW: bool = false,
4239 /// Filesystem error
4240 FS_ERROR: bool = false,
4241
4242 /// File open in perm check
4243 OPEN_PERM: bool = false,
4244 /// File accessed in perm check
4245 ACCESS_PERM: bool = false,
4246 /// File open/exec in perm check
4247 OPEN_EXEC_PERM: bool = false,
4248 reserved19: u8 = 0,
4249 /// Interested in child events
4250 EVENT_ON_CHILD: bool = false,
4251 /// File was renamed
4252 RENAME: bool = false,
4253 reserved30: u1 = 0,
4254 /// Event occurred against dir
4255 ONDIR: bool = false,
4256 reserved31: u33 = 0,
4257 };
4258
4259 pub const event_metadata = extern struct {
4260 event_len: u32,
4261 vers: u8,
4262 reserved: u8,
4263 metadata_len: u16,
4264 mask: MarkMask align(8),
4265 fd: i32,
4266 pid: i32,
4267
4268 pub const VERSION = 3;
4269 };
4270
4271 pub const response = extern struct {
4272 fd: i32,
4273 response: u32,
4274 };
4275
4276 /// Unique file identifier info record.
4277 ///
4278 /// This structure is used for records of types `EVENT_INFO_TYPE.FID`.
4279 /// `EVENT_INFO_TYPE.DFID` and `EVENT_INFO_TYPE.DFID_NAME`.
4280 ///
4281 /// For `EVENT_INFO_TYPE.DFID_NAME` there is additionally a null terminated
4282 /// name immediately after the file handle.
4283 pub const event_info_fid = extern struct {
4284 hdr: event_info_header,
4285 fsid: kernel_fsid_t,
4286 /// Following is an opaque struct file_handle that can be passed as
4287 /// an argument to open_by_handle_at(2).
4288 handle: [0]u8,
4289 };
4290
4291 /// Variable length info record following event metadata.
4292 pub const event_info_header = extern struct {
4293 info_type: EVENT_INFO_TYPE,
4294 pad: u8,
4295 len: u16,
4296 };
4297
4298 pub const EVENT_INFO_TYPE = enum(u8) {
4299 FID = 1,
4300 DFID_NAME = 2,
4301 DFID = 3,
4302 PIDFD = 4,
4303 ERROR = 5,
4304 OLD_DFID_NAME = 10,
4305 OLD_DFID = 11,
4306 NEW_DFID_NAME = 12,
4307 NEW_DFID = 13,
4308 };
4183};4309};
41844310
4185pub const fanotify_response = extern struct {4311pub const file_handle = extern struct {
4186 fd: i32,4312 handle_bytes: u32,
4187 response: u32,4313 handle_type: i32,
4314 f_handle: [0]u8,
4188};4315};
41894316
4317pub const kernel_fsid_t = fsid_t;
4318pub const fsid_t = [2]i32;
4319
4190pub const S = struct {4320pub const S = struct {
4191 pub const IFMT = 0o170000;4321 pub const IFMT = 0o170000;
41924322
lib/std/posix.zig+55-5
...@@ -4501,7 +4501,7 @@ pub const FanotifyInitError = error{...@@ -4501,7 +4501,7 @@ pub const FanotifyInitError = error{
4501 PermissionDenied,4501 PermissionDenied,
4502} || UnexpectedError;4502} || UnexpectedError;
45034503
4504pub fn fanotify_init(flags: u32, event_f_flags: u32) FanotifyInitError!i32 {4504pub fn fanotify_init(flags: std.os.linux.fanotify.InitFlags, event_f_flags: u32) FanotifyInitError!i32 {
4505 const rc = system.fanotify_init(flags, event_f_flags);4505 const rc = system.fanotify_init(flags, event_f_flags);
4506 switch (errno(rc)) {4506 switch (errno(rc)) {
4507 .SUCCESS => return @intCast(rc),4507 .SUCCESS => return @intCast(rc),
...@@ -4530,16 +4530,28 @@ pub const FanotifyMarkError = error{...@@ -4530,16 +4530,28 @@ pub const FanotifyMarkError = error{
4530 NameTooLong,4530 NameTooLong,
4531} || UnexpectedError;4531} || UnexpectedError;
45324532
4533pub fn fanotify_mark(fanotify_fd: i32, flags: u32, mask: u64, dirfd: i32, pathname: ?[]const u8) FanotifyMarkError!void {4533pub fn fanotify_mark(
4534 fanotify_fd: fd_t,
4535 flags: std.os.linux.fanotify.MarkFlags,
4536 mask: std.os.linux.fanotify.MarkMask,
4537 dirfd: fd_t,
4538 pathname: ?[]const u8,
4539) FanotifyMarkError!void {
4534 if (pathname) |path| {4540 if (pathname) |path| {
4535 const path_c = try toPosixPath(path);4541 const path_c = try toPosixPath(path);
4536 return fanotify_markZ(fanotify_fd, flags, mask, dirfd, &path_c);4542 return fanotify_markZ(fanotify_fd, flags, mask, dirfd, &path_c);
4543 } else {
4544 return fanotify_markZ(fanotify_fd, flags, mask, dirfd, null);
4537 }4545 }
4538
4539 return fanotify_markZ(fanotify_fd, flags, mask, dirfd, null);
4540}4546}
45414547
4542pub fn fanotify_markZ(fanotify_fd: i32, flags: u32, mask: u64, dirfd: i32, pathname: ?[*:0]const u8) FanotifyMarkError!void {4548pub fn fanotify_markZ(
4549 fanotify_fd: fd_t,
4550 flags: std.os.linux.fanotify.MarkFlags,
4551 mask: std.os.linux.fanotify.MarkMask,
4552 dirfd: fd_t,
4553 pathname: ?[*:0]const u8,
4554) FanotifyMarkError!void {
4543 const rc = system.fanotify_mark(fanotify_fd, flags, mask, dirfd, pathname);4555 const rc = system.fanotify_mark(fanotify_fd, flags, mask, dirfd, pathname);
4544 switch (errno(rc)) {4556 switch (errno(rc)) {
4545 .SUCCESS => return,4557 .SUCCESS => return,
...@@ -7274,6 +7286,44 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, signal: usize) PtraceError!...@@ -7274,6 +7286,44 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, signal: usize) PtraceError!
7274 };7286 };
7275}7287}
72767288
7289pub const NameToFileHandleAtError = error{
7290 FileNotFound,
7291 NotDir,
7292 OperationNotSupported,
7293 NameTooLong,
7294 Unexpected,
7295};
7296
7297pub fn name_to_handle_at(
7298 dirfd: fd_t,
7299 pathname: []const u8,
7300 handle: *std.os.linux.file_handle,
7301 mount_id: *i32,
7302 flags: u32,
7303) NameToFileHandleAtError!void {
7304 const pathname_c = try toPosixPath(pathname);
7305 return name_to_handle_atZ(dirfd, &pathname_c, handle, mount_id, flags);
7306}
7307
7308pub fn name_to_handle_atZ(
7309 dirfd: fd_t,
7310 pathname_z: [*:0]const u8,
7311 handle: *std.os.linux.file_handle,
7312 mount_id: *i32,
7313 flags: u32,
7314) NameToFileHandleAtError!void {
7315 switch (errno(system.name_to_handle_at(dirfd, pathname_z, handle, mount_id, flags))) {
7316 .SUCCESS => {},
7317 .FAULT => unreachable, // pathname, mount_id, or handle outside accessible address space
7318 .INVAL => unreachable, // bad flags, or handle_bytes too big
7319 .NOENT => return error.FileNotFound,
7320 .NOTDIR => return error.NotDir,
7321 .OPNOTSUPP => return error.OperationNotSupported,
7322 .OVERFLOW => return error.NameTooLong,
7323 else => |err| return unexpectedErrno(err),
7324 }
7325}
7326
7277pub const IoCtl_SIOCGIFINDEX_Error = error{7327pub const IoCtl_SIOCGIFINDEX_Error = error{
7278 FileSystem,7328 FileSystem,
7279 InterfaceNotFound,7329 InterfaceNotFound,
lib/std/zig/Server.zig+15-1
...@@ -20,10 +20,24 @@ pub const Message = struct {...@@ -20,10 +20,24 @@ pub const Message = struct {
20 test_metadata,20 test_metadata,
21 /// Body is a TestResults21 /// Body is a TestResults
22 test_results,22 test_results,
23 /// Body is a series of strings, delimited by null bytes.
24 /// Each string is a prefixed file path.
25 /// The first byte indicates the file prefix path (see prefixes fields
26 /// of Cache). This byte is sent over the wire incremented so that null
27 /// bytes are not confused with string terminators.
28 /// The remaining bytes is the file path relative to that prefix.
29 /// The prefixes are hard-coded in Compilation.create (cwd, zig lib dir, local cache dir)
30 file_system_inputs,
2331
24 _,32 _,
25 };33 };
2634
35 pub const PathPrefix = enum(u8) {
36 cwd,
37 zig_lib,
38 local_cache,
39 };
40
27 /// Trailing:41 /// Trailing:
28 /// * extra: [extra_len]u32,42 /// * extra: [extra_len]u32,
29 /// * string_bytes: [string_bytes_len]u8,43 /// * string_bytes: [string_bytes_len]u8,
...@@ -58,7 +72,7 @@ pub const Message = struct {...@@ -58,7 +72,7 @@ pub const Message = struct {
58 };72 };
5973
60 /// Trailing:74 /// Trailing:
61 /// * the file system path the emitted binary can be found75 /// * file system path where the emitted binary can be found
62 pub const EmitBinPath = extern struct {76 pub const EmitBinPath = extern struct {
63 flags: Flags,77 flags: Flags,
6478
src/Compilation.zig+71-1
...@@ -235,6 +235,8 @@ astgen_wait_group: WaitGroup = .{},...@@ -235,6 +235,8 @@ astgen_wait_group: WaitGroup = .{},
235235
236llvm_opt_bisect_limit: c_int,236llvm_opt_bisect_limit: c_int,
237237
238file_system_inputs: ?*std.ArrayListUnmanaged(u8),
239
238pub const Emit = struct {240pub const Emit = struct {
239 /// Where the output will go.241 /// Where the output will go.
240 directory: Directory,242 directory: Directory,
...@@ -1157,6 +1159,9 @@ pub const CreateOptions = struct {...@@ -1157,6 +1159,9 @@ pub const CreateOptions = struct {
1157 error_limit: ?Zcu.ErrorInt = null,1159 error_limit: ?Zcu.ErrorInt = null,
1158 global_cc_argv: []const []const u8 = &.{},1160 global_cc_argv: []const []const u8 = &.{},
11591161
1162 /// Tracks all files that can cause the Compilation to be invalidated and need a rebuild.
1163 file_system_inputs: ?*std.ArrayListUnmanaged(u8) = null,
1164
1160 pub const Entry = link.File.OpenOptions.Entry;1165 pub const Entry = link.File.OpenOptions.Entry;
1161};1166};
11621167
...@@ -1332,6 +1337,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -1332,6 +1337,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1332 .gpa = gpa,1337 .gpa = gpa,
1333 .manifest_dir = try options.local_cache_directory.handle.makeOpenPath("h", .{}),1338 .manifest_dir = try options.local_cache_directory.handle.makeOpenPath("h", .{}),
1334 };1339 };
1340 // These correspond to std.zig.Server.Message.PathPrefix.
1335 cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });1341 cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
1336 cache.addPrefix(options.zig_lib_directory);1342 cache.addPrefix(options.zig_lib_directory);
1337 cache.addPrefix(options.local_cache_directory);1343 cache.addPrefix(options.local_cache_directory);
...@@ -1508,6 +1514,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -1508,6 +1514,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1508 .force_undefined_symbols = options.force_undefined_symbols,1514 .force_undefined_symbols = options.force_undefined_symbols,
1509 .link_eh_frame_hdr = link_eh_frame_hdr,1515 .link_eh_frame_hdr = link_eh_frame_hdr,
1510 .global_cc_argv = options.global_cc_argv,1516 .global_cc_argv = options.global_cc_argv,
1517 .file_system_inputs = options.file_system_inputs,
1511 };1518 };
15121519
1513 // Prevent some footguns by making the "any" fields of config reflect1520 // Prevent some footguns by making the "any" fields of config reflect
...@@ -2044,6 +2051,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {...@@ -2044,6 +2051,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
2044 );2051 );
2045 };2052 };
2046 if (is_hit) {2053 if (is_hit) {
2054 // In this case the cache hit contains the full set of file system inputs. Nice!
2055 if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
2056
2047 comp.last_update_was_cache_hit = true;2057 comp.last_update_was_cache_hit = true;
2048 log.debug("CacheMode.whole cache hit for {s}", .{comp.root_name});2058 log.debug("CacheMode.whole cache hit for {s}", .{comp.root_name});
2049 const digest = man.final();2059 const digest = man.final();
...@@ -2103,12 +2113,24 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {...@@ -2103,12 +2113,24 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
2103 .incremental => {},2113 .incremental => {},
2104 }2114 }
21052115
2116 // From this point we add a preliminary set of file system inputs that
2117 // affects both incremental and whole cache mode. For incremental cache
2118 // mode, the long-lived compiler state will track additional file system
2119 // inputs discovered after this point. For whole cache mode, we rely on
2120 // these inputs to make it past AstGen, and once there, we can rely on
2121 // learning file system inputs from the Cache object.
2122
2106 // For compiling C objects, we rely on the cache hash system to avoid duplicating work.2123 // For compiling C objects, we rely on the cache hash system to avoid duplicating work.
2107 // Add a Job for each C object.2124 // Add a Job for each C object.
2108 try comp.c_object_work_queue.ensureUnusedCapacity(comp.c_object_table.count());2125 try comp.c_object_work_queue.ensureUnusedCapacity(comp.c_object_table.count());
2109 for (comp.c_object_table.keys()) |key| {2126 for (comp.c_object_table.keys()) |key| {
2110 comp.c_object_work_queue.writeItemAssumeCapacity(key);2127 comp.c_object_work_queue.writeItemAssumeCapacity(key);
2111 }2128 }
2129 if (comp.file_system_inputs) |fsi| {
2130 for (comp.c_object_table.keys()) |c_object| {
2131 try comp.appendFileSystemInput(fsi, c_object.src.owner.root, c_object.src.src_path);
2132 }
2133 }
21122134
2113 // For compiling Win32 resources, we rely on the cache hash system to avoid duplicating work.2135 // For compiling Win32 resources, we rely on the cache hash system to avoid duplicating work.
2114 // Add a Job for each Win32 resource file.2136 // Add a Job for each Win32 resource file.
...@@ -2117,6 +2139,12 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {...@@ -2117,6 +2139,12 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
2117 for (comp.win32_resource_table.keys()) |key| {2139 for (comp.win32_resource_table.keys()) |key| {
2118 comp.win32_resource_work_queue.writeItemAssumeCapacity(key);2140 comp.win32_resource_work_queue.writeItemAssumeCapacity(key);
2119 }2141 }
2142 if (comp.file_system_inputs) |fsi| {
2143 for (comp.win32_resource_table.keys()) |win32_resource| switch (win32_resource.src) {
2144 .rc => |f| try comp.appendFileSystemInput(fsi, f.owner.root, f.src_path),
2145 .manifest => continue,
2146 };
2147 }
2120 }2148 }
21212149
2122 if (comp.module) |zcu| {2150 if (comp.module) |zcu| {
...@@ -2151,12 +2179,25 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {...@@ -2151,12 +2179,25 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
2151 if (zcu.fileByIndex(file_index).mod.isBuiltin()) continue;2179 if (zcu.fileByIndex(file_index).mod.isBuiltin()) continue;
2152 comp.astgen_work_queue.writeItemAssumeCapacity(file_index);2180 comp.astgen_work_queue.writeItemAssumeCapacity(file_index);
2153 }2181 }
2182 if (comp.file_system_inputs) |fsi| {
2183 for (zcu.import_table.values()) |file_index| {
2184 const file = zcu.fileByIndex(file_index);
2185 try comp.appendFileSystemInput(fsi, file.mod.root, file.sub_file_path);
2186 }
2187 }
21542188
2155 // Put a work item in for checking if any files used with `@embedFile` changed.2189 // Put a work item in for checking if any files used with `@embedFile` changed.
2156 try comp.embed_file_work_queue.ensureUnusedCapacity(zcu.embed_table.count());2190 try comp.embed_file_work_queue.ensureUnusedCapacity(zcu.embed_table.count());
2157 for (zcu.embed_table.values()) |embed_file| {2191 for (zcu.embed_table.values()) |embed_file| {
2158 comp.embed_file_work_queue.writeItemAssumeCapacity(embed_file);2192 comp.embed_file_work_queue.writeItemAssumeCapacity(embed_file);
2159 }2193 }
2194 if (comp.file_system_inputs) |fsi| {
2195 const ip = &zcu.intern_pool;
2196 for (zcu.embed_table.values()) |embed_file| {
2197 const sub_file_path = embed_file.sub_file_path.toSlice(ip);
2198 try comp.appendFileSystemInput(fsi, embed_file.owner.root, sub_file_path);
2199 }
2200 }
21602201
2161 try comp.work_queue.writeItem(.{ .analyze_mod = std_mod });2202 try comp.work_queue.writeItem(.{ .analyze_mod = std_mod });
2162 if (comp.config.is_test) {2203 if (comp.config.is_test) {
...@@ -2210,6 +2251,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {...@@ -2210,6 +2251,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
22102251
2211 switch (comp.cache_use) {2252 switch (comp.cache_use) {
2212 .whole => |whole| {2253 .whole => |whole| {
2254 if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
2255
2213 const digest = man.final();2256 const digest = man.final();
22142257
2215 // Rename the temporary directory into place.2258 // Rename the temporary directory into place.
...@@ -2297,6 +2340,30 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {...@@ -2297,6 +2340,30 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
2297 }2340 }
2298}2341}
22992342
2343fn appendFileSystemInput(
2344 comp: *Compilation,
2345 file_system_inputs: *std.ArrayListUnmanaged(u8),
2346 root: Cache.Path,
2347 sub_file_path: []const u8,
2348) Allocator.Error!void {
2349 const gpa = comp.gpa;
2350 const prefixes = comp.cache_parent.prefixes();
2351 try file_system_inputs.ensureUnusedCapacity(gpa, root.sub_path.len + sub_file_path.len + 3);
2352 if (file_system_inputs.items.len > 0) file_system_inputs.appendAssumeCapacity(0);
2353 for (prefixes, 1..) |prefix_directory, i| {
2354 if (prefix_directory.eql(root.root_dir)) {
2355 file_system_inputs.appendAssumeCapacity(@intCast(i));
2356 if (root.sub_path.len > 0) {
2357 file_system_inputs.appendSliceAssumeCapacity(root.sub_path);
2358 file_system_inputs.appendAssumeCapacity(std.fs.path.sep);
2359 }
2360 file_system_inputs.appendSliceAssumeCapacity(sub_file_path);
2361 return;
2362 }
2363 }
2364 std.debug.panic("missing prefix directory: {}, {s}", .{ root, sub_file_path });
2365}
2366
2300fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void {2367fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void {
2301 if (comp.bin_file) |lf| {2368 if (comp.bin_file) |lf| {
2302 // This is needed before reading the error flags.2369 // This is needed before reading the error flags.
...@@ -4204,6 +4271,9 @@ fn workerAstGenFile(...@@ -4204,6 +4271,9 @@ fn workerAstGenFile(
4204 .token = item.data.token,4271 .token = item.data.token,
4205 } }) catch continue;4272 } }) catch continue;
4206 }4273 }
4274 if (res.is_new) if (comp.file_system_inputs) |fsi| {
4275 comp.appendFileSystemInput(fsi, res.file.mod.root, res.file.sub_file_path) catch continue;
4276 };
4207 const imported_path_digest = pt.zcu.filePathDigest(res.file_index);4277 const imported_path_digest = pt.zcu.filePathDigest(res.file_index);
4208 const imported_root_decl = pt.zcu.fileRootDecl(res.file_index);4278 const imported_root_decl = pt.zcu.fileRootDecl(res.file_index);
4209 break :blk .{ res, imported_path_digest, imported_root_decl };4279 break :blk .{ res, imported_path_digest, imported_root_decl };
...@@ -4574,7 +4644,7 @@ fn reportRetryableEmbedFileError(...@@ -4574,7 +4644,7 @@ fn reportRetryableEmbedFileError(
4574 const gpa = mod.gpa;4644 const gpa = mod.gpa;
4575 const src_loc = embed_file.src_loc;4645 const src_loc = embed_file.src_loc;
4576 const ip = &mod.intern_pool;4646 const ip = &mod.intern_pool;
4577 const err_msg = try Zcu.ErrorMsg.create(gpa, src_loc, "unable to load '{}{s}': {s}", .{4647 const err_msg = try Zcu.ErrorMsg.create(gpa, src_loc, "unable to load '{}/{s}': {s}", .{
4578 embed_file.owner.root,4648 embed_file.owner.root,
4579 embed_file.sub_file_path.toSlice(ip),4649 embed_file.sub_file_path.toSlice(ip),
4580 @errorName(err),4650 @errorName(err),
src/Zcu.zig+1-1
...@@ -728,7 +728,7 @@ pub const File = struct {...@@ -728,7 +728,7 @@ pub const File = struct {
728 source_loaded: bool,728 source_loaded: bool,
729 tree_loaded: bool,729 tree_loaded: bool,
730 zir_loaded: bool,730 zir_loaded: bool,
731 /// Relative to the owning package's root_src_dir.731 /// Relative to the owning package's root source directory.
732 /// Memory is stored in gpa, owned by File.732 /// Memory is stored in gpa, owned by File.
733 sub_file_path: []const u8,733 sub_file_path: []const u8,
734 /// Whether this is populated depends on `source_loaded`.734 /// Whether this is populated depends on `source_loaded`.
src/Zcu/PerThread.zig+1-1
...@@ -2666,7 +2666,7 @@ pub fn reportRetryableAstGenError(...@@ -2666,7 +2666,7 @@ pub fn reportRetryableAstGenError(
2666 },2666 },
2667 };2667 };
26682668
2669 const err_msg = try Zcu.ErrorMsg.create(gpa, src_loc, "unable to load '{}{s}': {s}", .{2669 const err_msg = try Zcu.ErrorMsg.create(gpa, src_loc, "unable to load '{}/{s}': {s}", .{
2670 file.mod.root, file.sub_file_path, @errorName(err),2670 file.mod.root, file.sub_file_path, @errorName(err),
2671 });2671 });
2672 errdefer err_msg.destroy(gpa);2672 errdefer err_msg.destroy(gpa);
src/main.zig+58-34
...@@ -3227,6 +3227,9 @@ fn buildOutputType(...@@ -3227,6 +3227,9 @@ fn buildOutputType(
32273227
3228 process.raiseFileDescriptorLimit();3228 process.raiseFileDescriptorLimit();
32293229
3230 var file_system_inputs: std.ArrayListUnmanaged(u8) = .{};
3231 defer file_system_inputs.deinit(gpa);
3232
3230 const comp = Compilation.create(gpa, arena, .{3233 const comp = Compilation.create(gpa, arena, .{
3231 .zig_lib_directory = zig_lib_directory,3234 .zig_lib_directory = zig_lib_directory,
3232 .local_cache_directory = local_cache_directory,3235 .local_cache_directory = local_cache_directory,
...@@ -3350,6 +3353,7 @@ fn buildOutputType(...@@ -3350,6 +3353,7 @@ fn buildOutputType(
3350 // than to any particular module. This feature can greatly reduce CLI3353 // than to any particular module. This feature can greatly reduce CLI
3351 // noise when --search-prefix and --mod are combined.3354 // noise when --search-prefix and --mod are combined.
3352 .global_cc_argv = try cc_argv.toOwnedSlice(arena),3355 .global_cc_argv = try cc_argv.toOwnedSlice(arena),
3356 .file_system_inputs = &file_system_inputs,
3353 }) catch |err| switch (err) {3357 }) catch |err| switch (err) {
3354 error.LibCUnavailable => {3358 error.LibCUnavailable => {
3355 const triple_name = try target.zigTriple(arena);3359 const triple_name = try target.zigTriple(arena);
...@@ -3433,7 +3437,7 @@ fn buildOutputType(...@@ -3433,7 +3437,7 @@ fn buildOutputType(
3433 defer root_prog_node.end();3437 defer root_prog_node.end();
34343438
3435 if (arg_mode == .translate_c) {3439 if (arg_mode == .translate_c) {
3436 return cmdTranslateC(comp, arena, null, root_prog_node);3440 return cmdTranslateC(comp, arena, null, null, root_prog_node);
3437 }3441 }
34383442
3439 updateModule(comp, color, root_prog_node) catch |err| switch (err) {3443 updateModule(comp, color, root_prog_node) catch |err| switch (err) {
...@@ -4059,6 +4063,7 @@ fn serve(...@@ -4059,6 +4063,7 @@ fn serve(
4059 var child_pid: ?std.process.Child.Id = null;4063 var child_pid: ?std.process.Child.Id = null;
40604064
4061 const main_progress_node = std.Progress.start(.{});4065 const main_progress_node = std.Progress.start(.{});
4066 const file_system_inputs = comp.file_system_inputs.?;
40624067
4063 while (true) {4068 while (true) {
4064 const hdr = try server.receiveMessage();4069 const hdr = try server.receiveMessage();
...@@ -4067,14 +4072,16 @@ fn serve(...@@ -4067,14 +4072,16 @@ fn serve(
4067 .exit => return cleanExit(),4072 .exit => return cleanExit(),
4068 .update => {4073 .update => {
4069 tracy.frameMark();4074 tracy.frameMark();
4075 file_system_inputs.clearRetainingCapacity();
40704076
4071 if (arg_mode == .translate_c) {4077 if (arg_mode == .translate_c) {
4072 var arena_instance = std.heap.ArenaAllocator.init(gpa);4078 var arena_instance = std.heap.ArenaAllocator.init(gpa);
4073 defer arena_instance.deinit();4079 defer arena_instance.deinit();
4074 const arena = arena_instance.allocator();4080 const arena = arena_instance.allocator();
4075 var output: Compilation.CImportResult = undefined;4081 var output: Compilation.CImportResult = undefined;
4076 try cmdTranslateC(comp, arena, &output, main_progress_node);4082 try cmdTranslateC(comp, arena, &output, file_system_inputs, main_progress_node);
4077 defer output.deinit(gpa);4083 defer output.deinit(gpa);
4084 try server.serveStringMessage(.file_system_inputs, file_system_inputs.items);
4078 if (output.errors.errorMessageCount() != 0) {4085 if (output.errors.errorMessageCount() != 0) {
4079 try server.serveErrorBundle(output.errors);4086 try server.serveErrorBundle(output.errors);
4080 } else {4087 } else {
...@@ -4116,6 +4123,7 @@ fn serve(...@@ -4116,6 +4123,7 @@ fn serve(
4116 },4123 },
4117 .hot_update => {4124 .hot_update => {
4118 tracy.frameMark();4125 tracy.frameMark();
4126 file_system_inputs.clearRetainingCapacity();
4119 if (child_pid) |pid| {4127 if (child_pid) |pid| {
4120 try comp.hotCodeSwap(main_progress_node, pid);4128 try comp.hotCodeSwap(main_progress_node, pid);
4121 try serveUpdateResults(&server, comp);4129 try serveUpdateResults(&server, comp);
...@@ -4147,6 +4155,12 @@ fn serve(...@@ -4147,6 +4155,12 @@ fn serve(
41474155
4148fn serveUpdateResults(s: *Server, comp: *Compilation) !void {4156fn serveUpdateResults(s: *Server, comp: *Compilation) !void {
4149 const gpa = comp.gpa;4157 const gpa = comp.gpa;
4158
4159 if (comp.file_system_inputs) |file_system_inputs| {
4160 assert(file_system_inputs.items.len > 0);
4161 try s.serveStringMessage(.file_system_inputs, file_system_inputs.items);
4162 }
4163
4150 var error_bundle = try comp.getAllErrorsAlloc();4164 var error_bundle = try comp.getAllErrorsAlloc();
4151 defer error_bundle.deinit(gpa);4165 defer error_bundle.deinit(gpa);
4152 if (error_bundle.errorMessageCount() > 0) {4166 if (error_bundle.errorMessageCount() > 0) {
...@@ -4434,6 +4448,7 @@ fn cmdTranslateC(...@@ -4434,6 +4448,7 @@ fn cmdTranslateC(
4434 comp: *Compilation,4448 comp: *Compilation,
4435 arena: Allocator,4449 arena: Allocator,
4436 fancy_output: ?*Compilation.CImportResult,4450 fancy_output: ?*Compilation.CImportResult,
4451 file_system_inputs: ?*std.ArrayListUnmanaged(u8),
4437 prog_node: std.Progress.Node,4452 prog_node: std.Progress.Node,
4438) !void {4453) !void {
4439 if (build_options.only_core_functionality) @panic("@translate-c is not available in a zig2.c build");4454 if (build_options.only_core_functionality) @panic("@translate-c is not available in a zig2.c build");
...@@ -4454,7 +4469,10 @@ fn cmdTranslateC(...@@ -4454,7 +4469,10 @@ fn cmdTranslateC(
4454 };4469 };
44554470
4456 if (fancy_output) |p| p.cache_hit = true;4471 if (fancy_output) |p| p.cache_hit = true;
4457 const digest = if (try man.hit()) man.final() else digest: {4472 const digest = if (try man.hit()) digest: {
4473 if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
4474 break :digest man.final();
4475 } else digest: {
4458 if (fancy_output) |p| p.cache_hit = false;4476 if (fancy_output) |p| p.cache_hit = false;
4459 var argv = std.ArrayList([]const u8).init(arena);4477 var argv = std.ArrayList([]const u8).init(arena);
4460 switch (comp.config.c_frontend) {4478 switch (comp.config.c_frontend) {
...@@ -4566,6 +4584,8 @@ fn cmdTranslateC(...@@ -4566,6 +4584,8 @@ fn cmdTranslateC(
4566 @errorName(err),4584 @errorName(err),
4567 });4585 });
45684586
4587 if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
4588
4569 break :digest digest;4589 break :digest digest;
4570 };4590 };
45714591
...@@ -4649,31 +4669,6 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4649,31 +4669,6 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4649 return cleanExit();4669 return cleanExit();
4650}4670}
46514671
4652const usage_build =
4653 \\Usage: zig build [steps] [options]
4654 \\
4655 \\ Build a project from build.zig.
4656 \\
4657 \\Options:
4658 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
4659 \\ -fno-reference-trace Disable reference trace
4660 \\ --summary [mode] Control the printing of the build summary
4661 \\ all Print the build summary in its entirety
4662 \\ failures (Default) Only print failed steps
4663 \\ none Do not print the build summary
4664 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
4665 \\ --build-file [file] Override path to build.zig
4666 \\ --cache-dir [path] Override path to local Zig cache directory
4667 \\ --global-cache-dir [path] Override path to global Zig cache directory
4668 \\ --zig-lib-dir [arg] Override path to Zig lib directory
4669 \\ --build-runner [file] Override path to build runner
4670 \\ --prominent-compile-errors Buffer compile errors and display at end
4671 \\ --seed [integer] For shuffling dependency traversal order (default: random)
4672 \\ --fetch Exit after fetching dependency tree
4673 \\ -h, --help Print this help and exit
4674 \\
4675;
4676
4677fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {4672fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4678 var build_file: ?[]const u8 = null;4673 var build_file: ?[]const u8 = null;
4679 var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena);4674 var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena);
...@@ -4696,6 +4691,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4696,6 +4691,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4696 var verbose_llvm_cpu_features = false;4691 var verbose_llvm_cpu_features = false;
4697 var fetch_only = false;4692 var fetch_only = false;
4698 var system_pkg_dir_path: ?[]const u8 = null;4693 var system_pkg_dir_path: ?[]const u8 = null;
4694 var debug_target: ?[]const u8 = null;
46994695
4700 const argv_index_exe = child_argv.items.len;4696 const argv_index_exe = child_argv.items.len;
4701 _ = try child_argv.addOne();4697 _ = try child_argv.addOne();
...@@ -4703,6 +4699,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4703,6 +4699,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4703 const self_exe_path = try introspect.findZigExePath(arena);4699 const self_exe_path = try introspect.findZigExePath(arena);
4704 try child_argv.append(self_exe_path);4700 try child_argv.append(self_exe_path);
47054701
4702 const argv_index_zig_lib_dir = child_argv.items.len;
4703 _ = try child_argv.addOne();
4704
4706 const argv_index_build_file = child_argv.items.len;4705 const argv_index_build_file = child_argv.items.len;
4707 _ = try child_argv.addOne();4706 _ = try child_argv.addOne();
47084707
...@@ -4752,7 +4751,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4752,7 +4751,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4752 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});4751 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
4753 i += 1;4752 i += 1;
4754 override_lib_dir = args[i];4753 override_lib_dir = args[i];
4755 try child_argv.appendSlice(&.{ arg, args[i] });
4756 continue;4754 continue;
4757 } else if (mem.eql(u8, arg, "--build-runner")) {4755 } else if (mem.eql(u8, arg, "--build-runner")) {
4758 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});4756 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
...@@ -4802,6 +4800,14 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4802,6 +4800,14 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4802 } else {4800 } else {
4803 warn("Zig was compiled without debug extensions. --debug-compile-errors has no effect.", .{});4801 warn("Zig was compiled without debug extensions. --debug-compile-errors has no effect.", .{});
4804 }4802 }
4803 } else if (mem.eql(u8, arg, "--debug-target")) {
4804 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
4805 i += 1;
4806 if (build_options.enable_debug_extensions) {
4807 debug_target = args[i];
4808 } else {
4809 warn("Zig was compiled without debug extensions. --debug-target has no effect.", .{});
4810 }
4805 } else if (mem.eql(u8, arg, "--verbose-link")) {4811 } else if (mem.eql(u8, arg, "--verbose-link")) {
4806 verbose_link = true;4812 verbose_link = true;
4807 } else if (mem.eql(u8, arg, "--verbose-cc")) {4813 } else if (mem.eql(u8, arg, "--verbose-cc")) {
...@@ -4860,11 +4866,27 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4860,11 +4866,27 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4860 });4866 });
4861 defer root_prog_node.end();4867 defer root_prog_node.end();
48624868
4863 const target_query: std.Target.Query = .{};4869 // Normally the build runner is compiled for the host target but here is
4864 const resolved_target: Package.Module.ResolvedTarget = .{4870 // some code to help when debugging edits to the build runner so that you
4865 .result = std.zig.resolveTargetQueryOrFatal(target_query),4871 // can make sure it compiles successfully on other targets.
4866 .is_native_os = true,4872 const resolved_target: Package.Module.ResolvedTarget = t: {
4867 .is_native_abi = true,4873 if (build_options.enable_debug_extensions) {
4874 if (debug_target) |triple| {
4875 const target_query = try std.Target.Query.parse(.{
4876 .arch_os_abi = triple,
4877 });
4878 break :t .{
4879 .result = std.zig.resolveTargetQueryOrFatal(target_query),
4880 .is_native_os = false,
4881 .is_native_abi = false,
4882 };
4883 }
4884 }
4885 break :t .{
4886 .result = std.zig.resolveTargetQueryOrFatal(.{}),
4887 .is_native_os = true,
4888 .is_native_abi = true,
4889 };
4868 };4890 };
48694891
4870 const exe_basename = try std.zig.binNameAlloc(arena, .{4892 const exe_basename = try std.zig.binNameAlloc(arena, .{
...@@ -4890,6 +4912,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4890,6 +4912,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4890 defer zig_lib_directory.handle.close();4912 defer zig_lib_directory.handle.close();
48914913
4892 const cwd_path = try process.getCwdAlloc(arena);4914 const cwd_path = try process.getCwdAlloc(arena);
4915 child_argv.items[argv_index_zig_lib_dir] = zig_lib_directory.path orelse cwd_path;
4916
4893 const build_root = try findBuildRoot(arena, .{4917 const build_root = try findBuildRoot(arena, .{
4894 .cwd_path = cwd_path,4918 .cwd_path = cwd_path,
4895 .build_file = build_file,4919 .build_file = build_file,
test/tests.zig+4-4
...@@ -771,7 +771,7 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -771,7 +771,7 @@ pub fn addCliTests(b: *std.Build) *Step {
771 run_run.expectStdErrEqual("All your codebase are belong to us.\n");771 run_run.expectStdErrEqual("All your codebase are belong to us.\n");
772 run_run.step.dependOn(&init_exe.step);772 run_run.step.dependOn(&init_exe.step);
773773
774 const cleanup = b.addRemoveDirTree(tmp_path);774 const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path });
775 cleanup.step.dependOn(&run_test.step);775 cleanup.step.dependOn(&run_test.step);
776 cleanup.step.dependOn(&run_run.step);776 cleanup.step.dependOn(&run_run.step);
777 cleanup.step.dependOn(&run_bad.step);777 cleanup.step.dependOn(&run_bad.step);
...@@ -816,7 +816,7 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -816,7 +816,7 @@ pub fn addCliTests(b: *std.Build) *Step {
816 });816 });
817 checkfile.setName("check godbolt.org CLI usage generating valid asm");817 checkfile.setName("check godbolt.org CLI usage generating valid asm");
818818
819 const cleanup = b.addRemoveDirTree(tmp_path);819 const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path });
820 cleanup.step.dependOn(&checkfile.step);820 cleanup.step.dependOn(&checkfile.step);
821821
822 step.dependOn(&cleanup.step);822 step.dependOn(&cleanup.step);
...@@ -882,7 +882,7 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -882,7 +882,7 @@ pub fn addCliTests(b: *std.Build) *Step {
882882
883 const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00";883 const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00";
884 const fmt6_path = std.fs.path.join(b.allocator, &.{ tmp_path, "fmt6.zig" }) catch @panic("OOM");884 const fmt6_path = std.fs.path.join(b.allocator, &.{ tmp_path, "fmt6.zig" }) catch @panic("OOM");
885 const write6 = b.addWriteFiles();885 const write6 = b.addUpdateSourceFiles();
886 write6.addBytesToSource(unformatted_code_utf16, fmt6_path);886 write6.addBytesToSource(unformatted_code_utf16, fmt6_path);
887 write6.step.dependOn(&run5.step);887 write6.step.dependOn(&run5.step);
888888
...@@ -902,7 +902,7 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -902,7 +902,7 @@ pub fn addCliTests(b: *std.Build) *Step {
902 });902 });
903 check6.step.dependOn(&run6.step);903 check6.step.dependOn(&run6.step);
904904
905 const cleanup = b.addRemoveDirTree(tmp_path);905 const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path });
906 cleanup.step.dependOn(&check6.step);906 cleanup.step.dependOn(&check6.step);
907907
908 step.dependOn(&cleanup.step);908 step.dependOn(&cleanup.step);