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 {
595595 run_opt.addArg("-o");
596596 run_opt.addFileArg(b.path("stage1/zig1.wasm"));
597597
598 const copy_zig_h = b.addWriteFiles();
598 const copy_zig_h = b.addUpdateSourceFiles();
599599 copy_zig_h.addCopyFileToSource(b.path("lib/zig.h"), "stage1/zig.h");
600600
601601 const update_zig1_step = b.step("update-zig1", "Update stage1/zig1.wasm");
......@@ -1261,7 +1261,9 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
12611261 });
12621262
12631263 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 });
12651267 };
12661268 defer dir.close();
12671269
......@@ -1280,10 +1282,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
12801282 // in a temporary directory
12811283 "--cache-root", b.cache_root.path orelse ".",
12821284 });
1283 if (b.zig_lib_dir) |p| {
1284 cmd.addArg("--zig-lib-dir");
1285 cmd.addDirectoryArg(p);
1286 }
1285 cmd.addArgs(&.{ "--zig-lib-dir", b.fmt("{}", .{b.graph.zig_lib_directory}) });
12871286 cmd.addArgs(&.{"-i"});
12881287 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;
88const ArrayList = std.ArrayList;
99const File = std.fs.File;
1010const Step = std.Build.Step;
11const Watch = std.Build.Watch;
12const Allocator = std.mem.Allocator;
13const fatal = std.zig.fatal;
1114
1215pub const root = @import("@build");
1316pub const dependencies = @import("@dependencies");
......@@ -29,21 +32,15 @@ pub fn main() !void {
2932 // skip my own exe name
3033 var arg_idx: usize = 1;
3134
32 const zig_exe = nextArg(args, &arg_idx) orelse {
33 std.debug.print("Expected path to zig compiler\n", .{});
34 return error.InvalidArgs;
35 };
36 const build_root = nextArg(args, &arg_idx) orelse {
37 std.debug.print("Expected build root directory path\n", .{});
38 return error.InvalidArgs;
39 };
40 const cache_root = nextArg(args, &arg_idx) orelse {
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;
35 const zig_exe = nextArg(args, &arg_idx) orelse fatal("missing zig compiler path", .{});
36 const zig_lib_dir = nextArg(args, &arg_idx) orelse fatal("missing zig lib directory path", .{});
37 const build_root = nextArg(args, &arg_idx) orelse fatal("missing build root directory path", .{});
38 const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{});
39 const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{});
40
41 const zig_lib_directory: std.Build.Cache.Directory = .{
42 .path = zig_lib_dir,
43 .handle = try std.fs.cwd().openDir(zig_lib_dir, .{}),
4744 };
4845
4946 const build_root_directory: std.Build.Cache.Directory = .{
......@@ -70,6 +67,7 @@ pub fn main() !void {
7067 .zig_exe = zig_exe,
7168 .env_map = try process.getEnvMap(arena),
7269 .global_cache_root = global_cache_directory,
70 .zig_lib_directory = zig_lib_directory,
7371 .host = .{
7472 .query = .{},
7573 .result = try std.zig.system.resolveTargetQuery(.{}),
......@@ -97,13 +95,15 @@ pub fn main() !void {
9795 var dir_list = std.Build.DirList{};
9896 var summary: ?Summary = null;
9997 var max_rss: u64 = 0;
100 var skip_oom_steps: bool = false;
98 var skip_oom_steps = false;
10199 var color: Color = .auto;
102100 var seed: u32 = 0;
103 var prominent_compile_errors: bool = false;
104 var help_menu: bool = false;
105 var steps_menu: bool = false;
101 var prominent_compile_errors = false;
102 var help_menu = false;
103 var steps_menu = false;
106104 var output_tmp_nonce: ?[16]u8 = null;
105 var watch = false;
106 var debounce_interval_ms: u16 = 50;
107107
108108 while (nextArg(args, &arg_idx)) |arg| {
109109 if (mem.startsWith(u8, arg, "-Z")) {
......@@ -185,13 +185,19 @@ pub fn main() !void {
185185 arg, next_arg,
186186 });
187187 };
188 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
189 builder.zig_lib_dir = .{ .cwd_relative = nextArgOrFatal(args, &arg_idx) };
190188 } else if (mem.eql(u8, arg, "--seed")) {
191189 const next_arg = nextArg(args, &arg_idx) orelse
192190 fatalWithHint("expected u32 after '{s}'", .{arg});
193191 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", .{
195201 next_arg, @errorName(err),
196202 });
197203 };
......@@ -227,6 +233,8 @@ pub fn main() !void {
227233 builder.verbose_llvm_cpu_features = true;
228234 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
229235 prominent_compile_errors = true;
236 } else if (mem.eql(u8, arg, "--watch")) {
237 watch = true;
230238 } else if (mem.eql(u8, arg, "-fwine")) {
231239 builder.enable_wine = true;
232240 } else if (mem.eql(u8, arg, "-fno-wine")) {
......@@ -292,6 +300,7 @@ pub fn main() !void {
292300 const main_progress_node = std.Progress.start(.{
293301 .disable_printing = (color == .off),
294302 });
303 defer main_progress_node.end();
295304
296305 builder.debug_log_scopes = debug_log_scopes.items;
297306 builder.resolveInstallPrefix(install_prefix, dir_list);
......@@ -340,13 +349,16 @@ pub fn main() !void {
340349 .max_rss_is_default = false,
341350 .max_rss_mutex = .{},
342351 .skip_oom_steps = skip_oom_steps,
352 .watch = watch,
343353 .memory_blocked_steps = std.ArrayList(*Step).init(arena),
354 .step_stack = .{},
344355 .prominent_compile_errors = prominent_compile_errors,
345356
346357 .claimed_rss = 0,
347 .summary = summary,
358 .summary = summary orelse if (watch) .new else .failures,
348359 .ttyconf = ttyconf,
349360 .stderr = stderr,
361 .thread_pool = undefined,
350362 };
351363
352364 if (run.max_rss == 0) {
......@@ -354,18 +366,78 @@ pub fn main() !void {
354366 run.max_rss_is_default = true;
355367 }
356368
357 runStepNames(
358 arena,
359 builder,
360 targets.items,
361 main_progress_node,
362 thread_pool_options,
363 &run,
364 seed,
365 ) catch |err| switch (err) {
369 const gpa = arena;
370 prepare(gpa, arena, builder, targets.items, &run, seed) catch |err| switch (err) {
366371 error.UncleanExit => process.exit(1),
367372 else => return err,
368373 };
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 };
369441}
370442
371443const Run = struct {
......@@ -373,27 +445,27 @@ const Run = struct {
373445 max_rss_is_default: bool,
374446 max_rss_mutex: std.Thread.Mutex,
375447 skip_oom_steps: bool,
448 watch: bool,
376449 memory_blocked_steps: std.ArrayList(*Step),
450 step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),
377451 prominent_compile_errors: bool,
452 thread_pool: std.Thread.Pool,
378453
379454 claimed_rss: usize,
380 summary: ?Summary,
455 summary: Summary,
381456 ttyconf: std.io.tty.Config,
382457 stderr: File,
383458};
384459
385fn runStepNames(
386 arena: std.mem.Allocator,
460fn prepare(
461 gpa: Allocator,
462 arena: Allocator,
387463 b: *std.Build,
388464 step_names: []const []const u8,
389 parent_prog_node: std.Progress.Node,
390 thread_pool_options: std.Thread.Pool.Options,
391465 run: *Run,
392466 seed: u32,
393467) !void {
394 const gpa = b.allocator;
395 var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{};
396 defer step_stack.deinit(gpa);
468 const step_stack = &run.step_stack;
397469
398470 if (step_names.len == 0) {
399471 try step_stack.put(gpa, b.default_step, {});
......@@ -416,8 +488,8 @@ fn runStepNames(
416488 rand.shuffle(*Step, starting_steps);
417489
418490 for (starting_steps) |s| {
419 constructGraphAndCheckForDependencyLoop(b, s, &step_stack, rand) catch |err| switch (err) {
420 error.DependencyLoopDetected => return error.UncleanExit,
491 constructGraphAndCheckForDependencyLoop(b, s, &run.step_stack, rand) catch |err| switch (err) {
492 error.DependencyLoopDetected => return uncleanExit(),
421493 else => |e| return e,
422494 };
423495 }
......@@ -442,17 +514,22 @@ fn runStepNames(
442514 if (run.max_rss_is_default) {
443515 std.debug.print("note: use --maxrss to override the default", .{});
444516 }
445 return error.UncleanExit;
517 return uncleanExit();
446518 }
447519 }
520}
448521
449 var thread_pool: std.Thread.Pool = undefined;
450 try thread_pool.init(thread_pool_options);
451 defer thread_pool.deinit();
522fn runStepNames(
523 gpa: Allocator,
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
453532 {
454 defer parent_prog_node.end();
455
456533 const step_prog = parent_prog_node.start("steps", step_stack.count());
457534 defer step_prog.end();
458535
......@@ -468,7 +545,7 @@ fn runStepNames(
468545 if (step.state == .skipped_oom) continue;
469546
470547 thread_pool.spawnWg(&wait_group, workerMakeOneStep, .{
471 &wait_group, &thread_pool, b, step, step_prog, run,
548 &wait_group, b, step, step_prog, run,
472549 });
473550 }
474551 }
......@@ -485,8 +562,6 @@ fn runStepNames(
485562 var failure_count: usize = 0;
486563 var pending_count: usize = 0;
487564 var total_compile_errors: usize = 0;
488 var compile_error_steps: std.ArrayListUnmanaged(*Step) = .{};
489 defer compile_error_steps.deinit(gpa);
490565
491566 for (step_stack.keys()) |s| {
492567 test_fail_count += s.test_results.fail_count;
......@@ -516,7 +591,6 @@ fn runStepNames(
516591 const compile_errors_len = s.result_error_bundle.errorMessageCount();
517592 if (compile_errors_len > 0) {
518593 total_compile_errors += compile_errors_len;
519 try compile_error_steps.append(gpa, s);
520594 }
521595 },
522596 }
......@@ -524,13 +598,22 @@ fn runStepNames(
524598
525599 // A proper command line application defaults to silently succeeding.
526600 // The user may request verbose mode if they have a different preference.
527 const failures_only = run.summary != .all and run.summary != .new;
528 if (failure_count == 0 and failures_only) return cleanExit();
601 const failures_only = switch (run.summary) {
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
530610 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
534617 const total_count = success_count + failure_count + pending_count + skipped_count;
535618 ttyconf.setColor(stderr, .cyan) catch {};
536619 stderr.writeAll("Build Summary:") catch {};
......@@ -544,25 +627,23 @@ fn runStepNames(
544627 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
545628 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 }
552630 stderr.writeAll("\n") catch {};
553631
554632 // 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
555636 var print_node: PrintNode = .{ .parent = null };
556637 if (step_names.len == 0) {
557638 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 {};
559640 } else {
560641 const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: {
561642 var i: usize = step_names.len;
562643 while (i > 0) {
563644 i -= 1;
564645 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) {
566647 .all, .none => unreachable,
567648 .failures => step.state != .success,
568649 .new => !step.result_cached,
......@@ -574,30 +655,34 @@ fn runStepNames(
574655 for (step_names, 0..) |step_name, i| {
575656 const tls = b.top_level_steps.get(step_name).?;
576657 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 {};
578659 }
579660 }
580661 }
581662
582 if (failure_count == 0) return cleanExit();
663 if (failure_count == 0) {
664 if (!run.watch) cleanExit();
665 return;
666 }
583667
584668 // 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.
587669 if (run.prominent_compile_errors and total_compile_errors > 0) {
588 for (compile_error_steps.items) |s| {
670 for (step_stack.keys()) |s| {
589671 if (s.result_error_bundle.errorMessageCount() > 0) {
590672 s.result_error_bundle.renderToStdErr(renderOptions(ttyconf));
591673 }
592674 }
593675
594 // Signal to parent process that we have printed compile errors. The
595 // parent process may choose to omit the "following command failed"
596 // line in this case.
597 process.exit(2);
676 if (!run.watch) {
677 // Signal to parent process that we have printed compile errors. The
678 // parent process may choose to omit the "following command failed"
679 // line in this case.
680 std.debug.lockStdErr();
681 process.exit(2);
682 }
598683 }
599684
600 process.exit(1);
685 if (!run.watch) return uncleanExit();
601686}
602687
603688const PrintNode = struct {
......@@ -768,7 +853,7 @@ fn printTreeStep(
768853 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
769854) !void {
770855 const first = step_stack.swapRemove(s);
771 const summary = run.summary orelse .failures;
856 const summary = run.summary;
772857 const skip = switch (summary) {
773858 .none => unreachable,
774859 .all => false,
......@@ -889,12 +974,13 @@ fn constructGraphAndCheckForDependencyLoop(
889974
890975fn workerMakeOneStep(
891976 wg: *std.Thread.WaitGroup,
892 thread_pool: *std.Thread.Pool,
893977 b: *std.Build,
894978 s: *Step,
895979 prog_node: std.Progress.Node,
896980 run: *Run,
897981) void {
982 const thread_pool = &run.thread_pool;
983
898984 // First, check the conditions for running this step. If they are not met,
899985 // then we return without doing the step, relying on another worker to
900986 // queue this step up again when dependencies are met.
......@@ -974,7 +1060,7 @@ fn workerMakeOneStep(
9741060 // Successful completion of a step, so we queue up its dependants as well.
9751061 for (s.dependants.items) |dep| {
9761062 thread_pool.spawnWg(wg, workerMakeOneStep, .{
977 wg, thread_pool, b, dep, prog_node, run,
1063 wg, b, dep, prog_node, run,
9781064 });
9791065 }
9801066 }
......@@ -999,7 +1085,7 @@ fn workerMakeOneStep(
9991085 remaining -= dep.max_rss;
10001086
10011087 thread_pool.spawnWg(wg, workerMakeOneStep, .{
1002 wg, thread_pool, b, dep, prog_node, run,
1088 wg, b, dep, prog_node, run,
10031089 });
10041090 } else {
10051091 run.memory_blocked_steps.items[i] = dep;
......@@ -1124,6 +1210,8 @@ fn usage(b: *std.Build, out_stream: anytype) !void {
11241210 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
11251211 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
11261212 \\ --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
11271215 \\
11281216 \\Project-Specific Options:
11291217 \\
......@@ -1218,13 +1306,22 @@ fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 {
12181306 return args[idx..];
12191307}
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.
12211312fn cleanExit() void {
1222 // Perhaps in the future there could be an Advanced Options flag such as
1223 // --debug-build-runner-leaks which would make this function return instead
1224 // of calling exit.
1313 std.debug.lockStdErr();
12251314 process.exit(0);
12261315}
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
12281325const Color = std.zig.Color;
12291326const Summary = enum { all, new, failures, none };
12301327
......@@ -1249,11 +1346,6 @@ fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
12491346 process.exit(1);
12501347}
12511348
1252fn fatal(comptime f: []const u8, args: anytype) noreturn {
1253 std.debug.print(f ++ "\n", args);
1254 process.exit(1);
1255}
1256
12571349fn validateSystemLibraryOptions(b: *std.Build) void {
12581350 var bad = false;
12591351 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(
198198 return std.process.cleanExit();
199199 },
200200 .update => {
201 if (seen_update) {
202 std.debug.print("zig objcopy only supports 1 update for now\n", .{});
203 std.process.exit(1);
204 }
201 if (seen_update) fatal("zig objcopy only supports 1 update for now", .{});
205202 seen_update = true;
206203
207204 try server.serveEmitBinPath(output, .{
208205 .flags = .{ .cache_hit = false },
209206 });
210207 },
211 else => {
212 std.debug.print("unsupported message: {s}", .{@tagName(hdr.tag)});
213 std.process.exit(1);
214 },
208 else => fatal("unsupported message: {s}", .{@tagName(hdr.tag)}),
215209 }
216210 }
217211 }
lib/std/Build.zig+51-61
......@@ -20,6 +20,7 @@ const Build = @This();
2020pub const Cache = @import("Build/Cache.zig");
2121pub const Step = @import("Build/Step.zig");
2222pub const Module = @import("Build/Module.zig");
23pub const Watch = @import("Build/Watch.zig");
2324
2425/// Shared state among all Build instances.
2526graph: *Graph,
......@@ -50,11 +51,9 @@ install_path: []const u8,
5051sysroot: ?[]const u8 = null,
5152search_prefixes: std.ArrayListUnmanaged([]const u8),
5253libc_file: ?[]const u8 = null,
53installed_files: ArrayList(InstalledFile),
5454/// Path to the directory containing build.zig.
5555build_root: Cache.Directory,
5656cache_root: Cache.Directory,
57zig_lib_dir: ?LazyPath,
5857pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
5958args: ?[]const []const u8 = null,
6059debug_log_scopes: []const []const u8 = &.{},
......@@ -117,6 +116,7 @@ pub const Graph = struct {
117116 zig_exe: [:0]const u8,
118117 env_map: EnvMap,
119118 global_cache_root: Cache.Directory,
119 zig_lib_directory: Cache.Directory,
120120 needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .{},
121121 /// Information about the native target. Computed before build() is invoked.
122122 host: ResolvedTarget,
......@@ -276,7 +276,6 @@ pub fn create(
276276 .exe_dir = undefined,
277277 .h_dir = undefined,
278278 .dest_dir = graph.env_map.get("DESTDIR"),
279 .installed_files = ArrayList(InstalledFile).init(arena),
280279 .install_tls = .{
281280 .step = Step.init(.{
282281 .id = TopLevelStep.base_id,
......@@ -294,7 +293,6 @@ pub fn create(
294293 }),
295294 .description = "Remove build artifacts from prefix path",
296295 },
297 .zig_lib_dir = null,
298296 .install_path = undefined,
299297 .args = null,
300298 .host = graph.host,
......@@ -378,10 +376,8 @@ fn createChildOnly(
378376 .sysroot = parent.sysroot,
379377 .search_prefixes = parent.search_prefixes,
380378 .libc_file = parent.libc_file,
381 .installed_files = ArrayList(InstalledFile).init(allocator),
382379 .build_root = build_root,
383380 .cache_root = parent.cache_root,
384 .zig_lib_dir = parent.zig_lib_dir,
385381 .debug_log_scopes = parent.debug_log_scopes,
386382 .debug_compile_errors = parent.debug_compile_errors,
387383 .debug_pkg_config = parent.debug_pkg_config,
......@@ -689,7 +685,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile {
689685 .max_rss = options.max_rss,
690686 .use_llvm = options.use_llvm,
691687 .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,
693689 .win32_manifest = options.win32_manifest,
694690 });
695691}
......@@ -737,7 +733,7 @@ pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile {
737733 .max_rss = options.max_rss,
738734 .use_llvm = options.use_llvm,
739735 .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,
741737 });
742738}
743739
......@@ -793,7 +789,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *Step.Compile
793789 .max_rss = options.max_rss,
794790 .use_llvm = options.use_llvm,
795791 .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,
797793 .win32_manifest = options.win32_manifest,
798794 });
799795}
......@@ -844,7 +840,7 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *Step.Compile
844840 .max_rss = options.max_rss,
845841 .use_llvm = options.use_llvm,
846842 .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,
848844 });
849845}
850846
......@@ -907,7 +903,7 @@ pub fn addTest(b: *Build, options: TestOptions) *Step.Compile {
907903 .test_runner = options.test_runner,
908904 .use_llvm = options.use_llvm,
909905 .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,
911907 });
912908}
913909
......@@ -931,7 +927,7 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *Step.Compile {
931927 .optimize = options.optimize,
932928 },
933929 .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,
935931 });
936932 obj_step.addAssemblyFile(options.source_file);
937933 return obj_step;
......@@ -1054,7 +1050,11 @@ pub fn addWriteFiles(b: *Build) *Step.WriteFile {
10541050 return Step.WriteFile.create(b);
10551051}
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 {
10581058 return Step.RemoveDir.create(b, dir_path);
10591059}
10601060
......@@ -1083,15 +1083,8 @@ fn makeUninstall(uninstall_step: *Step, prog_node: std.Progress.Node) anyerror!v
10831083 const uninstall_tls: *TopLevelStep = @fieldParentPtr("step", uninstall_step);
10841084 const b: *Build = @fieldParentPtr("uninstall_tls", uninstall_tls);
10851085
1086 for (b.installed_files.items) |installed_file| {
1087 const full_path = b.getInstallPath(installed_file.dir, installed_file.path);
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
1086 _ = b;
1087 @panic("TODO implement https://github.com/ziglang/zig/issues/14943");
10951088}
10961089
10971090/// Creates a configuration option to be passed to the build.zig script.
......@@ -1664,15 +1657,6 @@ pub fn addCheckFile(
16641657 return Step.CheckFile.create(b, file_source, options);
16651658}
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
16761660pub fn truncateFile(b: *Build, dest_path: []const u8) !void {
16771661 if (b.verbose) {
16781662 log.info("truncate {s}", .{dest_path});
......@@ -2341,36 +2325,52 @@ pub const LazyPath = union(enum) {
23412325 }
23422326 }
23432327
2344 /// Returns an absolute path.
2345 /// Intended to be used during the make phase only.
2328 /// Deprecated, see `getPath3`.
23462329 pub fn getPath(lazy_path: LazyPath, src_builder: *Build) []const u8 {
23472330 return getPath2(lazy_path, src_builder, null);
23482331 }
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
23512339 /// Intended to be used during the make phase only.
23522340 ///
23532341 /// `asking_step` is only used for debugging purposes; it's the step being
23542342 /// 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 {
23562344 switch (lazy_path) {
2357 .src_path => |sp| return sp.owner.pathFromRoot(sp.sub_path),
2358 .cwd_relative => |p| return src_builder.pathFromCwd(p),
2345 .src_path => |sp| return .{
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 },
23592353 .generated => |gen| {
2360 var file_path: []const u8 = gen.file.step.owner.pathFromRoot(gen.file.path orelse {
2361 std.debug.lockStdErr();
2362 const stderr = std.io.getStdErr();
2363 dumpBadGetPathHelp(gen.file.step, stderr, src_builder, asking_step) catch {};
2364 std.debug.unlockStdErr();
2365 @panic("misconfigured build script");
2366 });
2354 // TODO make gen.file.path not be absolute and use that as the
2355 // basis for not traversing up too many directories.
2356
2357 var file_path: Cache.Path = .{
2358 .root_dir = gen.file.step.owner.build_root,
2359 .sub_path = gen.file.path orelse {
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
23682368 if (gen.up > 0) {
23692369 const cache_root_path = src_builder.cache_root.path orelse
23702370 (src_builder.cache_root.join(src_builder.allocator, &.{"."}) catch @panic("OOM"));
23712371
23722372 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)) {
23742374 // If we hit the cache root and there's still more to go,
23752375 // the script attempted to go too far.
23762376 dumpBadDirnameHelp(gen.file.step, asking_step,
......@@ -2384,7 +2384,7 @@ pub const LazyPath = union(enum) {
23842384 // path is absolute.
23852385 // dirname will return null only if we're at root.
23862386 // 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 {
23882388 dumpBadDirnameHelp(gen.file.step, asking_step,
23892389 \\dirname() reached root.
23902390 \\No more directories left to go up.
......@@ -2395,9 +2395,12 @@ pub const LazyPath = union(enum) {
23952395 }
23962396 }
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,
23992403 },
2400 .dependency => |dep| return dep.dependency.builder.pathFromRoot(dep.sub_path),
24012404 }
24022405 }
24032406
......@@ -2512,19 +2515,6 @@ pub const InstallDir = union(enum) {
25122515 }
25132516};
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
25282518/// This function is intended to be called in the `configure` phase only.
25292519/// It returns an absolute directory path, which is potentially going to be a
25302520/// 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 {
354354 /// ```
355355 /// var file_contents = cache_hash.files.keys()[file_index].contents.?;
356356 /// ```
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`.
357370 pub fn addFile(self: *Manifest, file_path: []const u8, max_file_size: ?usize) !usize {
358371 assert(self.manifest_file == null);
359372
......@@ -362,6 +375,10 @@ pub const Manifest = struct {
362375 const prefixed_path = try self.cache.findPrefix(file_path);
363376 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 {
365382 const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{});
366383 if (gop.found_existing) {
367384 gop.key_ptr.updateMaxSize(max_file_size);
......@@ -990,6 +1007,23 @@ pub const Manifest = struct {
9901007 }
9911008 self.files.deinit(self.cache.gpa);
9921009 }
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 }
9931027};
9941028
9951029/// 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(
5858 return p.root_dir.handle.openFile(joined_path, flags);
5959}
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
6175pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs.Dir {
6276 var buf: [fs.max_path_bytes]u8 = undefined;
6377 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
......@@ -137,16 +151,57 @@ pub fn format(
137151 }
138152 if (fmt_string.len > 0)
139153 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 }
140158 if (self.root_dir.path) |p| {
141159 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;
143165 }
144166 if (self.sub_path.len > 0) {
145167 try writer.writeAll(self.sub_path);
146 try writer.writeAll(fs.path.sep_str);
168 return;
147169 }
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;
148179}
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
150205const Path = @This();
151206const std = @import("../../std.zig");
152207const fs = std.fs;
lib/std/Build/Step.zig+235-3
......@@ -7,6 +7,16 @@ dependencies: std.ArrayList(*Step),
77/// This field is empty during execution of the user's build script, and
88/// then populated during dependency loop checking in the build runner.
99dependants: 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
1020state: State,
1121/// Set this field to declare an upper bound on the amount of bytes of memory it will
1222/// 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
6373pub const State = enum {
6474 precheck_unstarted,
6575 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.
6681 precheck_done,
6782 running,
6883 dependency_failure,
......@@ -87,6 +102,7 @@ pub const Id = enum {
87102 fmt,
88103 translate_c,
89104 write_file,
105 update_source_files,
90106 run,
91107 check_file,
92108 check_object,
......@@ -107,6 +123,7 @@ pub const Id = enum {
107123 .fmt => Fmt,
108124 .translate_c => TranslateC,
109125 .write_file => WriteFile,
126 .update_source_files => UpdateSourceFiles,
110127 .run => Run,
111128 .check_file => CheckFile,
112129 .check_object => CheckObject,
......@@ -133,6 +150,28 @@ pub const RemoveDir = @import("Step/RemoveDir.zig");
133150pub const Run = @import("Step/Run.zig");
134151pub const TranslateC = @import("Step/TranslateC.zig");
135152pub 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
137176pub const StepOptions = struct {
138177 id: Id,
......@@ -153,6 +192,7 @@ pub fn init(options: StepOptions) Step {
153192 .makeFn = options.makeFn,
154193 .dependencies = std.ArrayList(*Step).init(arena),
155194 .dependants = .{},
195 .inputs = Inputs.init,
156196 .state = .precheck_unstarted,
157197 .max_rss = options.max_rss,
158198 .debug_stack_trace = blk: {
......@@ -395,6 +435,44 @@ pub fn evalZigProcess(
395435 s.result_cached = ebp_hdr.flags.cache_hit;
396436 result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
397437 },
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 },
398476 else => {}, // ignore other messages
399477 }
400478
......@@ -542,19 +620,36 @@ pub fn allocPrintCmd2(
542620 return buf.toOwnedSlice(arena);
543621}
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 {
546626 s.result_cached = man.hit() catch |err| return failWithCacheError(s, man, err);
547627 return s.result_cached;
548628}
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 {
551644 const i = man.failed_file_index orelse return err;
552645 const pp = man.files.keys()[i].prefixed_path;
553646 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
554647 return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path });
555648}
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 {
558653 if (s.test_results.isSuccess()) {
559654 man.writeManifest() catch |err| {
560655 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 {
562657 }
563658}
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
565796test {
566797 _ = CheckFile;
567798 _ = CheckObject;
......@@ -577,4 +808,5 @@ test {
577808 _ = Run;
578809 _ = TranslateC;
579810 _ = WriteFile;
811 _ = UpdateSourceFiles;
580812}
lib/std/Build/Step/CheckFile.zig+1
......@@ -50,6 +50,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
5050 _ = prog_node;
5151 const b = step.owner;
5252 const check_file: *CheckFile = @fieldParentPtr("step", step);
53 try step.singleUnchangingWatchInput(check_file.source);
5354
5455 const src_path = check_file.source.getPath2(b, step);
5556 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 {
555555 const b = step.owner;
556556 const gpa = b.allocator;
557557 const check_object: *CheckObject = @fieldParentPtr("step", step);
558 try step.singleUnchangingWatchInput(check_object.source);
558559
559560 const src_path = check_object.source.getPath2(b, step);
560561 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 {
168168 _ = prog_node;
169169 const b = step.owner;
170170 const config_header: *ConfigHeader = @fieldParentPtr("step", step);
171 if (config_header.style.getPath()) |lp| try step.singleUnchangingWatchInput(lp);
172
171173 const gpa = b.allocator;
172174 const arena = b.allocator;
173175
lib/std/Build/Step/InstallDir.zig+16-11
......@@ -41,7 +41,6 @@ pub const Options = struct {
4141};
4242
4343pub fn create(owner: *std.Build, options: Options) *InstallDir {
44 owner.pushInstalledFile(options.install_dir, options.install_subdir);
4544 const install_dir = owner.allocator.create(InstallDir) catch @panic("OOM");
4645 install_dir.* = .{
4746 .step = Step.init(.{
......@@ -60,12 +59,14 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
6059 _ = prog_node;
6160 const b = step.owner;
6261 const install_dir: *InstallDir = @fieldParentPtr("step", step);
62 step.clearWatchInputs();
6363 const arena = b.allocator;
6464 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);
66 var src_dir = b.build_root.handle.openDir(src_dir_path, .{ .iterate = true }) catch |err| {
67 return step.fail("unable to open source directory '{}{s}': {s}", .{
68 b.build_root, src_dir_path, @errorName(err),
65 const src_dir_path = install_dir.options.source_dir.getPath3(b, step);
66 const need_derived_inputs = try step.addDirectoryWatchInput(install_dir.options.source_dir);
67 var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
68 return step.fail("unable to open source directory '{}': {s}", .{
69 src_dir_path, @errorName(err),
6970 });
7071 };
7172 defer src_dir.close();
......@@ -89,12 +90,16 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
8990 }
9091
9192 // 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);
9394 const dest_path = b.pathJoin(&.{ dest_prefix, entry.path });
9495 const cwd = fs.cwd();
9596
9697 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 },
98103 .file => {
99104 for (install_dir.options.blank_extensions) |ext| {
100105 if (mem.endsWith(u8, entry.path, ext)) {
......@@ -104,14 +109,14 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
104109 }
105110
106111 const prev_status = fs.Dir.updateFile(
107 b.build_root.handle,
108 src_sub_path,
112 src_sub_path.root_dir.handle,
113 src_sub_path.sub_path,
109114 cwd,
110115 dest_path,
111116 .{},
112117 ) catch |err| {
113 return step.fail("unable to update file from '{}{s}' to '{s}': {s}", .{
114 b.build_root, src_sub_path, dest_path, @errorName(err),
118 return step.fail("unable to update file from '{}' to '{s}': {s}", .{
119 src_sub_path, dest_path, @errorName(err),
115120 });
116121 };
117122 all_cached = all_cached and prev_status == .fresh;
lib/std/Build/Step/InstallFile.zig+2-1
......@@ -19,7 +19,6 @@ pub fn create(
1919 dest_rel_path: []const u8,
2020) *InstallFile {
2121 assert(dest_rel_path.len != 0);
22 owner.pushInstalledFile(dir, dest_rel_path);
2322 const install_file = owner.allocator.create(InstallFile) catch @panic("OOM");
2423 install_file.* = .{
2524 .step = Step.init(.{
......@@ -40,6 +39,8 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
4039 _ = prog_node;
4140 const b = step.owner;
4241 const install_file: *InstallFile = @fieldParentPtr("step", step);
42 try step.singleUnchangingWatchInput(install_file.source);
43
4344 const full_src_path = install_file.source.getPath2(b, step);
4445 const full_dest_path = b.getInstallPath(install_file.dir, install_file.dest_rel_path);
4546 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 {
9393fn make(step: *Step, prog_node: std.Progress.Node) !void {
9494 const b = step.owner;
9595 const objcopy: *ObjCopy = @fieldParentPtr("step", step);
96 try step.singleUnchangingWatchInput(objcopy.input_file);
9697
9798 var man = b.graph.cache.obtain();
9899 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
104101 const full_src_path = objcopy.input_file.getPath2(b, step);
105102 _ = try man.addFile(full_src_path, null);
106103 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 {
424424 item.path.getPath2(b, step),
425425 );
426426 }
427 if (!step.inputs.populated()) for (options.args.items) |item| {
428 try step.addWatchInput(item.path);
429 };
427430
428431 const basename = "options.zig";
429432
......@@ -520,6 +523,7 @@ test Options {
520523 .query = .{},
521524 .result = try std.zig.system.resolveTargetQuery(.{}),
522525 },
526 .zig_lib_directory = std.Build.Cache.Directory.cwd(),
523527 };
524528
525529 var builder = try std.Build.create(
lib/std/Build/Step/RemoveDir.zig+13-7
......@@ -2,22 +2,23 @@ const std = @import("std");
22const fs = std.fs;
33const Step = std.Build.Step;
44const RemoveDir = @This();
5const LazyPath = std.Build.LazyPath;
56
67pub const base_id: Step.Id = .remove_dir;
78
89step: 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 {
1213 const remove_dir = owner.allocator.create(RemoveDir) catch @panic("OOM");
1314 remove_dir.* = .{
1415 .step = Step.init(.{
1516 .id = base_id,
16 .name = owner.fmt("RemoveDir {s}", .{dir_path}),
17 .name = owner.fmt("RemoveDir {s}", .{doomed_path.getDisplayName()}),
1718 .owner = owner,
1819 .makeFn = make,
1920 }),
20 .dir_path = owner.dupePath(dir_path),
21 .doomed_path = doomed_path.dupe(owner),
2122 };
2223 return remove_dir;
2324}
......@@ -30,14 +31,19 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
3031 const b = step.owner;
3132 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| {
3440 if (b.build_root.path) |base| {
3541 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),
3743 });
3844 } else {
3945 return step.fail("unable to recursively delete path '{s}': {s}", .{
40 remove_dir.dir_path, @errorName(err),
46 full_doomed_path, @errorName(err),
4147 });
4248 }
4349 };
lib/std/Build/Step/Run.zig+4-4
......@@ -632,7 +632,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
632632 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
633633 run.addPathForDynLibs(artifact);
634634 }
635 const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?; // the path is guaranteed to be set
635 const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?;
636636
637637 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 {
682682 _ = try man.addFile(lazy_path.getPath2(b, step), null);
683683 }
684684
685 if (!has_side_effects and try step.cacheHit(&man)) {
685 if (!has_side_effects and try step.cacheHitAndWatch(&man)) {
686686 // cache hit, skip running command
687687 const digest = man.final();
688688
......@@ -736,7 +736,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
736736 }
737737
738738 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);
740740 return;
741741 };
742742
......@@ -812,7 +812,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
812812 };
813813 }
814814
815 if (!has_side_effects) try step.writeManifest(&man);
815 if (!has_side_effects) try step.writeManifestAndWatch(&man);
816816
817817 try populateGeneratedPaths(
818818 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//! WriteFile is primarily used to create a directory in an appropriate
2//! location inside the local cache which has a set of files that have either
3//! been generated 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.
1//! WriteFile is used to create a directory in an appropriate location inside
2//! the local cache which has a set of files that have either been generated
3//! during the build, or are copied from the source package.
114const std = @import("std");
125const Step = std.Build.Step;
136const fs = std.fs;
......@@ -19,8 +12,6 @@ step: Step,
1912// The elements here are pointers because we need stable pointers for the GeneratedFile field.
2013files: std.ArrayListUnmanaged(File),
2114directories: std.ArrayListUnmanaged(Directory),
22
23output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
2415generated_directory: std.Build.GeneratedFile,
2516
2617pub const base_id: Step.Id = .write_file;
......@@ -49,12 +40,23 @@ pub const Directory = struct {
4940 .include_extensions = if (opts.include_extensions) |incs| b.dupeStrings(incs) else null,
5041 };
5142 }
52 };
53};
5443
55pub const OutputSourceFile = struct {
56 contents: Contents,
57 sub_path: []const u8,
44 pub fn pathIncluded(opts: Options, path: []const u8) bool {
45 for (opts.exclude_extensions) |ext| {
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 };
5860};
5961
6062pub const Contents = union(enum) {
......@@ -73,7 +75,6 @@ pub fn create(owner: *std.Build) *WriteFile {
7375 }),
7476 .files = .{},
7577 .directories = .{},
76 .output_source_files = .{},
7778 .generated_directory = .{ .step = &write_file.step },
7879 };
7980 return write_file;
......@@ -150,33 +151,6 @@ pub fn addCopyDirectory(
150151 };
151152}
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
180154/// Returns a `LazyPath` representing the base directory that contains all the
181155/// files from this `WriteFile`.
182156pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath {
......@@ -200,47 +174,10 @@ fn maybeUpdateName(write_file: *WriteFile) void {
200174fn make(step: *Step, prog_node: std.Progress.Node) !void {
201175 _ = prog_node;
202176 const b = step.owner;
177 const arena = b.allocator;
178 const gpa = arena;
203179 const write_file: *WriteFile = @fieldParentPtr("step", step);
204
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 }
180 step.clearWatchInputs();
244181
245182 // The cache is used here not really as a way to speed things up - because writing
246183 // 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 {
252189 var man = b.graph.cache.obtain();
253190 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
260192 for (write_file.files.items) |file| {
261193 man.hash.addBytes(file.sub_path);
194
262195 switch (file.contents) {
263196 .bytes => |bytes| {
264197 man.hash.addBytes(bytes);
265198 },
266 .copy => |file_source| {
267 _ = try man.addFile(file_source.getPath2(b, step), null);
199 .copy => |lazy_path| {
200 const path = lazy_path.getPath3(b, step);
201 _ = try man.addFilePath(path, null);
202 try step.addWatchInput(lazy_path);
268203 },
269204 }
270205 }
271 for (write_file.directories.items) |dir| {
272 man.hash.addBytes(dir.source.getPath2(b, step));
206
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| {
273212 man.hash.addBytes(dir.sub_path);
274213 for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext);
275214 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 }
276246 }
277247
278248 if (try step.cacheHit(&man)) {
279249 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;
281252 return;
282253 }
283254
284255 const digest = man.final();
285256 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
289260 var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {
290261 return step.fail("unable to make path '{}{s}': {s}", .{
......@@ -337,8 +308,9 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
337308 },
338309 }
339310 }
340 for (write_file.directories.items) |dir| {
341 const full_src_dir_path = dir.source.getPath2(b, step);
311
312 for (write_file.directories.items, open_dir_cache) |dir, already_open_dir| {
313 const src_dir_path = dir.source.getPath3(b, step);
342314 const dest_dirname = dir.sub_path;
343315
344316 if (dest_dirname.len != 0) {
......@@ -349,44 +321,25 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
349321 };
350322 }
351323
352 var src_dir = b.build_root.handle.openDir(full_src_dir_path, .{ .iterate = true }) catch |err| {
353 return step.fail("unable to open source directory '{s}': {s}", .{
354 full_src_dir_path, @errorName(err),
355 });
356 };
357 defer src_dir.close();
324 var it = try already_open_dir.walk(gpa);
325 defer it.deinit();
326 while (try it.next()) |entry| {
327 if (!dir.options.pathIncluded(entry.path)) continue;
358328
359 var it = try src_dir.walk(b.allocator);
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 });
329 const src_entry_path = try src_dir_path.join(arena, entry.path);
372330 const dest_path = b.pathJoin(&.{ dest_dirname, entry.path });
373331 switch (entry.kind) {
374332 .directory => try cache_dir.makePath(dest_path),
375333 .file => {
376334 const prev_status = fs.Dir.updateFile(
377 cwd,
378 full_src_entry_path,
335 src_entry_path.root_dir.handle,
336 src_entry_path.sub_path,
379337 cache_dir,
380338 dest_path,
381339 .{},
382340 ) catch |err| {
383 return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{
384 full_src_entry_path,
385 b.cache_root,
386 cache_path,
387 fs.path.sep,
388 dest_path,
389 @errorName(err),
341 return step.fail("unable to update file from '{}' to '{}{s}{c}{s}': {s}", .{
342 src_entry_path, b.cache_root, cache_path, fs.path.sep, dest_path, @errorName(err),
390343 });
391344 };
392345 _ = prev_status;
......@@ -398,3 +351,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
398351
399352 try step.writeManifest(&man);
400353}
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 {
698698 return syscall2(.inotify_rm_watch, @as(usize, @bitCast(@as(isize, fd))), @as(usize, @bitCast(@as(isize, wd))));
699699}
700700
701pub fn fanotify_init(flags: u32, event_f_flags: u32) usize {
702 return syscall2(.fanotify_init, flags, event_f_flags);
701pub fn fanotify_init(flags: fanotify.InitFlags, event_f_flags: u32) usize {
702 return syscall2(.fanotify_init, @as(u32, @bitCast(flags)), event_f_flags);
703703}
704704
705pub fn fanotify_mark(fd: i32, flags: u32, mask: u64, dirfd: i32, pathname: ?[*:0]const u8) usize {
706 return syscall5(.fanotify_mark, @as(usize, @bitCast(@as(isize, fd))), flags, mask, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(pathname));
705pub fn fanotify_mark(
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 );
707737}
708738
709739pub fn readlink(noalias path: [*:0]const u8, noalias buf_ptr: [*]u8, buf_len: usize) usize {
......@@ -2916,6 +2946,8 @@ pub const AT = struct {
29162946
29172947 /// Apply to the entire subtree
29182948 pub const RECURSIVE = 0x8000;
2949
2950 pub const HANDLE_FID = REMOVEDIR;
29192951};
29202952
29212953pub const FALLOC = struct {
......@@ -4135,58 +4167,156 @@ pub const IN = struct {
41354167 pub const ONESHOT = 0x80000000;
41364168};
41374169
4138pub const FAN = struct {
4139 pub const ACCESS = 0x00000001;
4140 pub const MODIFY = 0x00000002;
4141 pub const CLOSE_WRITE = 0x00000008;
4142 pub const CLOSE_NOWRITE = 0x00000010;
4143 pub const OPEN = 0x00000020;
4144 pub const Q_OVERFLOW = 0x00004000;
4145 pub const OPEN_PERM = 0x00010000;
4146 pub const ACCESS_PERM = 0x00020000;
4147 pub const ONDIR = 0x40000000;
4148 pub const EVENT_ON_CHILD = 0x08000000;
4149 pub const CLOSE = CLOSE_WRITE | CLOSE_NOWRITE;
4150 pub const CLOEXEC = 0x00000001;
4151 pub const NONBLOCK = 0x00000002;
4152 pub const CLASS_NOTIF = 0x00000000;
4153 pub const CLASS_CONTENT = 0x00000004;
4154 pub const CLASS_PRE_CONTENT = 0x00000008;
4155 pub const ALL_CLASS_BITS = CLASS_NOTIF | CLASS_CONTENT | CLASS_PRE_CONTENT;
4156 pub const UNLIMITED_QUEUE = 0x00000010;
4157 pub const UNLIMITED_MARKS = 0x00000020;
4158 pub const ALL_INIT_FLAGS = CLOEXEC | NONBLOCK | ALL_CLASS_BITS | UNLIMITED_QUEUE | UNLIMITED_MARKS;
4159 pub const MARK_ADD = 0x00000001;
4160 pub const MARK_REMOVE = 0x00000002;
4161 pub const MARK_DONT_FOLLOW = 0x00000004;
4162 pub const MARK_ONLYDIR = 0x00000008;
4163 pub const MARK_MOUNT = 0x00000010;
4164 pub const MARK_IGNORED_MASK = 0x00000020;
4165 pub const MARK_IGNORED_SURV_MODIFY = 0x00000040;
4166 pub const MARK_FLUSH = 0x00000080;
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;
4168 pub const ALL_EVENTS = ACCESS | MODIFY | CLOSE | OPEN;
4169 pub const ALL_PERM_EVENTS = OPEN_PERM | ACCESS_PERM;
4170 pub const ALL_OUTGOING_EVENTS = ALL_EVENTS | ALL_PERM_EVENTS | Q_OVERFLOW;
4171 pub const ALLOW = 0x01;
4172 pub const DENY = 0x02;
4173};
4174
4175pub const fanotify_event_metadata = extern struct {
4176 event_len: u32,
4177 vers: u8,
4178 reserved: u8,
4179 metadata_len: u16,
4180 mask: u64 align(8),
4181 fd: i32,
4182 pid: i32,
4170pub const fanotify = struct {
4171 pub const InitFlags = packed struct(u32) {
4172 CLOEXEC: bool = false,
4173 NONBLOCK: bool = false,
4174 CLASS: enum(u2) {
4175 NOTIF = 0,
4176 CONTENT = 1,
4177 PRE_CONTENT = 2,
4178 } = .NOTIF,
4179 UNLIMITED_QUEUE: bool = false,
4180 UNLIMITED_MARKS: bool = false,
4181 ENABLE_AUDIT: bool = false,
4182 REPORT_PIDFD: bool = false,
4183 REPORT_TID: bool = false,
4184 REPORT_FID: bool = false,
4185 REPORT_DIR_FID: bool = false,
4186 REPORT_NAME: bool = false,
4187 REPORT_TARGET_FID: bool = false,
4188 _: u19 = 0,
4189 };
4190
4191 pub const MarkFlags = packed struct(u32) {
4192 ADD: bool = false,
4193 REMOVE: bool = false,
4194 DONT_FOLLOW: bool = false,
4195 ONLYDIR: bool = false,
4196 MOUNT: bool = false,
4197 /// Mutually exclusive with `IGNORE`
4198 IGNORED_MASK: bool = false,
4199 IGNORED_SURV_MODIFY: bool = false,
4200 FLUSH: bool = false,
4201 FILESYSTEM: bool = false,
4202 EVICTABLE: bool = false,
4203 /// Mutually exclusive with `IGNORED_MASK`
4204 IGNORE: bool = false,
4205 _: u21 = 0,
4206 };
4207
4208 pub const MarkMask = packed struct(u64) {
4209 /// File was accessed
4210 ACCESS: bool = false,
4211 /// File was modified
4212 MODIFY: bool = false,
4213 /// Metadata changed
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 };
41834309};
41844310
4185pub const fanotify_response = extern struct {
4186 fd: i32,
4187 response: u32,
4311pub const file_handle = extern struct {
4312 handle_bytes: u32,
4313 handle_type: i32,
4314 f_handle: [0]u8,
41884315};
41894316
4317pub const kernel_fsid_t = fsid_t;
4318pub const fsid_t = [2]i32;
4319
41904320pub const S = struct {
41914321 pub const IFMT = 0o170000;
41924322
lib/std/posix.zig+55-5
......@@ -4501,7 +4501,7 @@ pub const FanotifyInitError = error{
45014501 PermissionDenied,
45024502} || 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 {
45054505 const rc = system.fanotify_init(flags, event_f_flags);
45064506 switch (errno(rc)) {
45074507 .SUCCESS => return @intCast(rc),
......@@ -4530,16 +4530,28 @@ pub const FanotifyMarkError = error{
45304530 NameTooLong,
45314531} || 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 {
45344540 if (pathname) |path| {
45354541 const path_c = try toPosixPath(path);
45364542 return fanotify_markZ(fanotify_fd, flags, mask, dirfd, &path_c);
4543 } else {
4544 return fanotify_markZ(fanotify_fd, flags, mask, dirfd, null);
45374545 }
4538
4539 return fanotify_markZ(fanotify_fd, flags, mask, dirfd, null);
45404546}
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 {
45434555 const rc = system.fanotify_mark(fanotify_fd, flags, mask, dirfd, pathname);
45444556 switch (errno(rc)) {
45454557 .SUCCESS => return,
......@@ -7274,6 +7286,44 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, signal: usize) PtraceError!
72747286 };
72757287}
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
72777327pub const IoCtl_SIOCGIFINDEX_Error = error{
72787328 FileSystem,
72797329 InterfaceNotFound,
lib/std/zig/Server.zig+15-1
......@@ -20,10 +20,24 @@ pub const Message = struct {
2020 test_metadata,
2121 /// Body is a TestResults
2222 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
2432 _,
2533 };
2634
35 pub const PathPrefix = enum(u8) {
36 cwd,
37 zig_lib,
38 local_cache,
39 };
40
2741 /// Trailing:
2842 /// * extra: [extra_len]u32,
2943 /// * string_bytes: [string_bytes_len]u8,
......@@ -58,7 +72,7 @@ pub const Message = struct {
5872 };
5973
6074 /// Trailing:
61 /// * the file system path the emitted binary can be found
75 /// * file system path where the emitted binary can be found
6276 pub const EmitBinPath = extern struct {
6377 flags: Flags,
6478
src/Compilation.zig+71-1
......@@ -235,6 +235,8 @@ astgen_wait_group: WaitGroup = .{},
235235
236236llvm_opt_bisect_limit: c_int,
237237
238file_system_inputs: ?*std.ArrayListUnmanaged(u8),
239
238240pub const Emit = struct {
239241 /// Where the output will go.
240242 directory: Directory,
......@@ -1157,6 +1159,9 @@ pub const CreateOptions = struct {
11571159 error_limit: ?Zcu.ErrorInt = null,
11581160 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
11601165 pub const Entry = link.File.OpenOptions.Entry;
11611166};
11621167
......@@ -1332,6 +1337,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
13321337 .gpa = gpa,
13331338 .manifest_dir = try options.local_cache_directory.handle.makeOpenPath("h", .{}),
13341339 };
1340 // These correspond to std.zig.Server.Message.PathPrefix.
13351341 cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
13361342 cache.addPrefix(options.zig_lib_directory);
13371343 cache.addPrefix(options.local_cache_directory);
......@@ -1508,6 +1514,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
15081514 .force_undefined_symbols = options.force_undefined_symbols,
15091515 .link_eh_frame_hdr = link_eh_frame_hdr,
15101516 .global_cc_argv = options.global_cc_argv,
1517 .file_system_inputs = options.file_system_inputs,
15111518 };
15121519
15131520 // 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 {
20442051 );
20452052 };
20462053 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
20472057 comp.last_update_was_cache_hit = true;
20482058 log.debug("CacheMode.whole cache hit for {s}", .{comp.root_name});
20492059 const digest = man.final();
......@@ -2103,12 +2113,24 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
21032113 .incremental => {},
21042114 }
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
21062123 // For compiling C objects, we rely on the cache hash system to avoid duplicating work.
21072124 // Add a Job for each C object.
21082125 try comp.c_object_work_queue.ensureUnusedCapacity(comp.c_object_table.count());
21092126 for (comp.c_object_table.keys()) |key| {
21102127 comp.c_object_work_queue.writeItemAssumeCapacity(key);
21112128 }
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
21132135 // For compiling Win32 resources, we rely on the cache hash system to avoid duplicating work.
21142136 // Add a Job for each Win32 resource file.
......@@ -2117,6 +2139,12 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
21172139 for (comp.win32_resource_table.keys()) |key| {
21182140 comp.win32_resource_work_queue.writeItemAssumeCapacity(key);
21192141 }
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 }
21202148 }
21212149
21222150 if (comp.module) |zcu| {
......@@ -2151,12 +2179,25 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
21512179 if (zcu.fileByIndex(file_index).mod.isBuiltin()) continue;
21522180 comp.astgen_work_queue.writeItemAssumeCapacity(file_index);
21532181 }
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
21552189 // Put a work item in for checking if any files used with `@embedFile` changed.
21562190 try comp.embed_file_work_queue.ensureUnusedCapacity(zcu.embed_table.count());
21572191 for (zcu.embed_table.values()) |embed_file| {
21582192 comp.embed_file_work_queue.writeItemAssumeCapacity(embed_file);
21592193 }
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
21612202 try comp.work_queue.writeItem(.{ .analyze_mod = std_mod });
21622203 if (comp.config.is_test) {
......@@ -2210,6 +2251,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
22102251
22112252 switch (comp.cache_use) {
22122253 .whole => |whole| {
2254 if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
2255
22132256 const digest = man.final();
22142257
22152258 // Rename the temporary directory into place.
......@@ -2297,6 +2340,30 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
22972340 }
22982341}
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
23002367fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void {
23012368 if (comp.bin_file) |lf| {
23022369 // This is needed before reading the error flags.
......@@ -4204,6 +4271,9 @@ fn workerAstGenFile(
42044271 .token = item.data.token,
42054272 } }) catch continue;
42064273 }
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 };
42074277 const imported_path_digest = pt.zcu.filePathDigest(res.file_index);
42084278 const imported_root_decl = pt.zcu.fileRootDecl(res.file_index);
42094279 break :blk .{ res, imported_path_digest, imported_root_decl };
......@@ -4574,7 +4644,7 @@ fn reportRetryableEmbedFileError(
45744644 const gpa = mod.gpa;
45754645 const src_loc = embed_file.src_loc;
45764646 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}", .{
45784648 embed_file.owner.root,
45794649 embed_file.sub_file_path.toSlice(ip),
45804650 @errorName(err),
src/Zcu.zig+1-1
......@@ -728,7 +728,7 @@ pub const File = struct {
728728 source_loaded: bool,
729729 tree_loaded: bool,
730730 zir_loaded: bool,
731 /// Relative to the owning package's root_src_dir.
731 /// Relative to the owning package's root source directory.
732732 /// Memory is stored in gpa, owned by File.
733733 sub_file_path: []const u8,
734734 /// Whether this is populated depends on `source_loaded`.
src/Zcu/PerThread.zig+1-1
......@@ -2666,7 +2666,7 @@ pub fn reportRetryableAstGenError(
26662666 },
26672667 };
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}", .{
26702670 file.mod.root, file.sub_file_path, @errorName(err),
26712671 });
26722672 errdefer err_msg.destroy(gpa);
src/main.zig+58-34
......@@ -3227,6 +3227,9 @@ fn buildOutputType(
32273227
32283228 process.raiseFileDescriptorLimit();
32293229
3230 var file_system_inputs: std.ArrayListUnmanaged(u8) = .{};
3231 defer file_system_inputs.deinit(gpa);
3232
32303233 const comp = Compilation.create(gpa, arena, .{
32313234 .zig_lib_directory = zig_lib_directory,
32323235 .local_cache_directory = local_cache_directory,
......@@ -3350,6 +3353,7 @@ fn buildOutputType(
33503353 // than to any particular module. This feature can greatly reduce CLI
33513354 // noise when --search-prefix and --mod are combined.
33523355 .global_cc_argv = try cc_argv.toOwnedSlice(arena),
3356 .file_system_inputs = &file_system_inputs,
33533357 }) catch |err| switch (err) {
33543358 error.LibCUnavailable => {
33553359 const triple_name = try target.zigTriple(arena);
......@@ -3433,7 +3437,7 @@ fn buildOutputType(
34333437 defer root_prog_node.end();
34343438
34353439 if (arg_mode == .translate_c) {
3436 return cmdTranslateC(comp, arena, null, root_prog_node);
3440 return cmdTranslateC(comp, arena, null, null, root_prog_node);
34373441 }
34383442
34393443 updateModule(comp, color, root_prog_node) catch |err| switch (err) {
......@@ -4059,6 +4063,7 @@ fn serve(
40594063 var child_pid: ?std.process.Child.Id = null;
40604064
40614065 const main_progress_node = std.Progress.start(.{});
4066 const file_system_inputs = comp.file_system_inputs.?;
40624067
40634068 while (true) {
40644069 const hdr = try server.receiveMessage();
......@@ -4067,14 +4072,16 @@ fn serve(
40674072 .exit => return cleanExit(),
40684073 .update => {
40694074 tracy.frameMark();
4075 file_system_inputs.clearRetainingCapacity();
40704076
40714077 if (arg_mode == .translate_c) {
40724078 var arena_instance = std.heap.ArenaAllocator.init(gpa);
40734079 defer arena_instance.deinit();
40744080 const arena = arena_instance.allocator();
40754081 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);
40774083 defer output.deinit(gpa);
4084 try server.serveStringMessage(.file_system_inputs, file_system_inputs.items);
40784085 if (output.errors.errorMessageCount() != 0) {
40794086 try server.serveErrorBundle(output.errors);
40804087 } else {
......@@ -4116,6 +4123,7 @@ fn serve(
41164123 },
41174124 .hot_update => {
41184125 tracy.frameMark();
4126 file_system_inputs.clearRetainingCapacity();
41194127 if (child_pid) |pid| {
41204128 try comp.hotCodeSwap(main_progress_node, pid);
41214129 try serveUpdateResults(&server, comp);
......@@ -4147,6 +4155,12 @@ fn serve(
41474155
41484156fn serveUpdateResults(s: *Server, comp: *Compilation) !void {
41494157 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
41504164 var error_bundle = try comp.getAllErrorsAlloc();
41514165 defer error_bundle.deinit(gpa);
41524166 if (error_bundle.errorMessageCount() > 0) {
......@@ -4434,6 +4448,7 @@ fn cmdTranslateC(
44344448 comp: *Compilation,
44354449 arena: Allocator,
44364450 fancy_output: ?*Compilation.CImportResult,
4451 file_system_inputs: ?*std.ArrayListUnmanaged(u8),
44374452 prog_node: std.Progress.Node,
44384453) !void {
44394454 if (build_options.only_core_functionality) @panic("@translate-c is not available in a zig2.c build");
......@@ -4454,7 +4469,10 @@ fn cmdTranslateC(
44544469 };
44554470
44564471 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: {
44584476 if (fancy_output) |p| p.cache_hit = false;
44594477 var argv = std.ArrayList([]const u8).init(arena);
44604478 switch (comp.config.c_frontend) {
......@@ -4566,6 +4584,8 @@ fn cmdTranslateC(
45664584 @errorName(err),
45674585 });
45684586
4587 if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
4588
45694589 break :digest digest;
45704590 };
45714591
......@@ -4649,31 +4669,6 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
46494669 return cleanExit();
46504670}
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
46774672fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
46784673 var build_file: ?[]const u8 = null;
46794674 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 {
46964691 var verbose_llvm_cpu_features = false;
46974692 var fetch_only = false;
46984693 var system_pkg_dir_path: ?[]const u8 = null;
4694 var debug_target: ?[]const u8 = null;
46994695
47004696 const argv_index_exe = child_argv.items.len;
47014697 _ = try child_argv.addOne();
......@@ -4703,6 +4699,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
47034699 const self_exe_path = try introspect.findZigExePath(arena);
47044700 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
47064705 const argv_index_build_file = child_argv.items.len;
47074706 _ = try child_argv.addOne();
47084707
......@@ -4752,7 +4751,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
47524751 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
47534752 i += 1;
47544753 override_lib_dir = args[i];
4755 try child_argv.appendSlice(&.{ arg, args[i] });
47564754 continue;
47574755 } else if (mem.eql(u8, arg, "--build-runner")) {
47584756 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 {
48024800 } else {
48034801 warn("Zig was compiled without debug extensions. --debug-compile-errors has no effect.", .{});
48044802 }
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 }
48054811 } else if (mem.eql(u8, arg, "--verbose-link")) {
48064812 verbose_link = true;
48074813 } else if (mem.eql(u8, arg, "--verbose-cc")) {
......@@ -4860,11 +4866,27 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
48604866 });
48614867 defer root_prog_node.end();
48624868
4863 const target_query: std.Target.Query = .{};
4864 const resolved_target: Package.Module.ResolvedTarget = .{
4865 .result = std.zig.resolveTargetQueryOrFatal(target_query),
4866 .is_native_os = true,
4867 .is_native_abi = true,
4869 // Normally the build runner is compiled for the host target but here is
4870 // some code to help when debugging edits to the build runner so that you
4871 // can make sure it compiles successfully on other targets.
4872 const resolved_target: Package.Module.ResolvedTarget = t: {
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 };
48684890 };
48694891
48704892 const exe_basename = try std.zig.binNameAlloc(arena, .{
......@@ -4890,6 +4912,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
48904912 defer zig_lib_directory.handle.close();
48914913
48924914 const cwd_path = try process.getCwdAlloc(arena);
4915 child_argv.items[argv_index_zig_lib_dir] = zig_lib_directory.path orelse cwd_path;
4916
48934917 const build_root = try findBuildRoot(arena, .{
48944918 .cwd_path = cwd_path,
48954919 .build_file = build_file,
test/tests.zig+4-4
......@@ -771,7 +771,7 @@ pub fn addCliTests(b: *std.Build) *Step {
771771 run_run.expectStdErrEqual("All your codebase are belong to us.\n");
772772 run_run.step.dependOn(&init_exe.step);
773773
774 const cleanup = b.addRemoveDirTree(tmp_path);
774 const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path });
775775 cleanup.step.dependOn(&run_test.step);
776776 cleanup.step.dependOn(&run_run.step);
777777 cleanup.step.dependOn(&run_bad.step);
......@@ -816,7 +816,7 @@ pub fn addCliTests(b: *std.Build) *Step {
816816 });
817817 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 });
820820 cleanup.step.dependOn(&checkfile.step);
821821
822822 step.dependOn(&cleanup.step);
......@@ -882,7 +882,7 @@ pub fn addCliTests(b: *std.Build) *Step {
882882
883883 const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00";
884884 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();
886886 write6.addBytesToSource(unformatted_code_utf16, fmt6_path);
887887 write6.step.dependOn(&run5.step);
888888
......@@ -902,7 +902,7 @@ pub fn addCliTests(b: *std.Build) *Step {
902902 });
903903 check6.step.dependOn(&run6.step);
904904
905 const cleanup = b.addRemoveDirTree(tmp_path);
905 const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path });
906906 cleanup.step.dependOn(&check6.step);
907907
908908 step.dependOn(&cleanup.step);