| author | |
| committer | |
| log | 1d20ff11d7efc1f07de9a300abc15ad6ae9d6d6f |
| tree | e72a2989fddfa91571999ade7ef68c87c7c2610f |
| parent | 0d79aa01768d600a19f7a7493afce417da7e3810 |
| parent | 5efcc2e9e7c84893b9e418ca82d8d2d4366dde7c |
| signature |
introduce file system watching features to the zig build system27 files changed, 1552 insertions(+), 417 deletions(-)
build.zig+5-6| ... | ... | @@ -595,7 +595,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { |
| 595 | 595 | run_opt.addArg("-o"); |
| 596 | 596 | run_opt.addFileArg(b.path("stage1/zig1.wasm")); |
| 597 | 597 | |
| 598 | const copy_zig_h = b.addWriteFiles(); | |
| 598 | const copy_zig_h = b.addUpdateSourceFiles(); | |
| 599 | 599 | copy_zig_h.addCopyFileToSource(b.path("lib/zig.h"), "stage1/zig.h"); |
| 600 | 600 | |
| 601 | 601 | const update_zig1_step = b.step("update-zig1", "Update stage1/zig1.wasm"); |
| ... | ... | @@ -1261,7 +1261,9 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath { |
| 1261 | 1261 | }); |
| 1262 | 1262 | |
| 1263 | 1263 | var dir = b.build_root.handle.openDir("doc/langref", .{ .iterate = true }) catch |err| { |
| 1264 | std.debug.panic("unable to open 'doc/langref' directory: {s}", .{@errorName(err)}); | |
| 1264 | std.debug.panic("unable to open '{}doc/langref' directory: {s}", .{ | |
| 1265 | b.build_root, @errorName(err), | |
| 1266 | }); | |
| 1265 | 1267 | }; |
| 1266 | 1268 | defer dir.close(); |
| 1267 | 1269 | |
| ... | ... | @@ -1280,10 +1282,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath { |
| 1280 | 1282 | // in a temporary directory |
| 1281 | 1283 | "--cache-root", b.cache_root.path orelse ".", |
| 1282 | 1284 | }); |
| 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}) }); | |
| 1287 | 1286 | cmd.addArgs(&.{"-i"}); |
| 1288 | 1287 | cmd.addFileArg(b.path(b.fmt("doc/langref/{s}", .{entry.name}))); |
| 1289 | 1288 |
lib/compiler/build_runner.zig+177-85| ... | ... | @@ -8,6 +8,9 @@ const process = std.process; |
| 8 | 8 | const ArrayList = std.ArrayList; |
| 9 | 9 | const File = std.fs.File; |
| 10 | 10 | const Step = std.Build.Step; |
| 11 | const Watch = std.Build.Watch; | |
| 12 | const Allocator = std.mem.Allocator; | |
| 13 | const fatal = std.zig.fatal; | |
| 11 | 14 | |
| 12 | 15 | pub const root = @import("@build"); |
| 13 | 16 | pub const dependencies = @import("@dependencies"); |
| ... | ... | @@ -29,21 +32,15 @@ pub fn main() !void { |
| 29 | 32 | // skip my own exe name |
| 30 | 33 | var arg_idx: usize = 1; |
| 31 | 34 | |
| 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, .{}), | |
| 47 | 44 | }; |
| 48 | 45 | |
| 49 | 46 | const build_root_directory: std.Build.Cache.Directory = .{ |
| ... | ... | @@ -70,6 +67,7 @@ pub fn main() !void { |
| 70 | 67 | .zig_exe = zig_exe, |
| 71 | 68 | .env_map = try process.getEnvMap(arena), |
| 72 | 69 | .global_cache_root = global_cache_directory, |
| 70 | .zig_lib_directory = zig_lib_directory, | |
| 73 | 71 | .host = .{ |
| 74 | 72 | .query = .{}, |
| 75 | 73 | .result = try std.zig.system.resolveTargetQuery(.{}), |
| ... | ... | @@ -97,13 +95,15 @@ pub fn main() !void { |
| 97 | 95 | var dir_list = std.Build.DirList{}; |
| 98 | 96 | var summary: ?Summary = null; |
| 99 | 97 | var max_rss: u64 = 0; |
| 100 | var skip_oom_steps: bool = false; | |
| 98 | var skip_oom_steps = false; | |
| 101 | 99 | var color: Color = .auto; |
| 102 | 100 | 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; | |
| 106 | 104 | var output_tmp_nonce: ?[16]u8 = null; |
| 105 | var watch = false; | |
| 106 | var debounce_interval_ms: u16 = 50; | |
| 107 | 107 | |
| 108 | 108 | while (nextArg(args, &arg_idx)) |arg| { |
| 109 | 109 | if (mem.startsWith(u8, arg, "-Z")) { |
| ... | ... | @@ -185,13 +185,19 @@ pub fn main() !void { |
| 185 | 185 | arg, next_arg, |
| 186 | 186 | }); |
| 187 | 187 | }; |
| 188 | } else if (mem.eql(u8, arg, "--zig-lib-dir")) { | |
| 189 | builder.zig_lib_dir = .{ .cwd_relative = nextArgOrFatal(args, &arg_idx) }; | |
| 190 | 188 | } else if (mem.eql(u8, arg, "--seed")) { |
| 191 | 189 | const next_arg = nextArg(args, &arg_idx) orelse |
| 192 | 190 | fatalWithHint("expected u32 after '{s}'", .{arg}); |
| 193 | 191 | seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| { |
| 194 | fatal("unable to parse seed '{s}' as 32-bit integer: {s}\n", .{ | |
| 192 | fatal("unable to parse seed '{s}' as unsigned 32-bit integer: {s}\n", .{ | |
| 193 | next_arg, @errorName(err), | |
| 194 | }); | |
| 195 | }; | |
| 196 | } else if (mem.eql(u8, arg, "--debounce")) { | |
| 197 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 198 | fatalWithHint("expected u16 after '{s}'", .{arg}); | |
| 199 | debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| { | |
| 200 | fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {s}\n", .{ | |
| 195 | 201 | next_arg, @errorName(err), |
| 196 | 202 | }); |
| 197 | 203 | }; |
| ... | ... | @@ -227,6 +233,8 @@ pub fn main() !void { |
| 227 | 233 | builder.verbose_llvm_cpu_features = true; |
| 228 | 234 | } else if (mem.eql(u8, arg, "--prominent-compile-errors")) { |
| 229 | 235 | prominent_compile_errors = true; |
| 236 | } else if (mem.eql(u8, arg, "--watch")) { | |
| 237 | watch = true; | |
| 230 | 238 | } else if (mem.eql(u8, arg, "-fwine")) { |
| 231 | 239 | builder.enable_wine = true; |
| 232 | 240 | } else if (mem.eql(u8, arg, "-fno-wine")) { |
| ... | ... | @@ -292,6 +300,7 @@ pub fn main() !void { |
| 292 | 300 | const main_progress_node = std.Progress.start(.{ |
| 293 | 301 | .disable_printing = (color == .off), |
| 294 | 302 | }); |
| 303 | defer main_progress_node.end(); | |
| 295 | 304 | |
| 296 | 305 | builder.debug_log_scopes = debug_log_scopes.items; |
| 297 | 306 | builder.resolveInstallPrefix(install_prefix, dir_list); |
| ... | ... | @@ -340,13 +349,16 @@ pub fn main() !void { |
| 340 | 349 | .max_rss_is_default = false, |
| 341 | 350 | .max_rss_mutex = .{}, |
| 342 | 351 | .skip_oom_steps = skip_oom_steps, |
| 352 | .watch = watch, | |
| 343 | 353 | .memory_blocked_steps = std.ArrayList(*Step).init(arena), |
| 354 | .step_stack = .{}, | |
| 344 | 355 | .prominent_compile_errors = prominent_compile_errors, |
| 345 | 356 | |
| 346 | 357 | .claimed_rss = 0, |
| 347 | .summary = summary, | |
| 358 | .summary = summary orelse if (watch) .new else .failures, | |
| 348 | 359 | .ttyconf = ttyconf, |
| 349 | 360 | .stderr = stderr, |
| 361 | .thread_pool = undefined, | |
| 350 | 362 | }; |
| 351 | 363 | |
| 352 | 364 | if (run.max_rss == 0) { |
| ... | ... | @@ -354,18 +366,78 @@ pub fn main() !void { |
| 354 | 366 | run.max_rss_is_default = true; |
| 355 | 367 | } |
| 356 | 368 | |
| 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) { | |
| 366 | 371 | error.UncleanExit => process.exit(1), |
| 367 | 372 | else => return err, |
| 368 | 373 | }; |
| 374 | ||
| 375 | var w = if (watch) try Watch.init() else undefined; | |
| 376 | ||
| 377 | try run.thread_pool.init(thread_pool_options); | |
| 378 | defer run.thread_pool.deinit(); | |
| 379 | ||
| 380 | rebuild: while (true) { | |
| 381 | runStepNames( | |
| 382 | gpa, | |
| 383 | builder, | |
| 384 | targets.items, | |
| 385 | main_progress_node, | |
| 386 | &run, | |
| 387 | ) catch |err| switch (err) { | |
| 388 | error.UncleanExit => { | |
| 389 | assert(!run.watch); | |
| 390 | process.exit(1); | |
| 391 | }, | |
| 392 | else => return err, | |
| 393 | }; | |
| 394 | if (!watch) return cleanExit(); | |
| 395 | ||
| 396 | switch (builtin.os.tag) { | |
| 397 | .linux => {}, | |
| 398 | else => fatal("--watch not yet implemented for {s}", .{@tagName(builtin.os.tag)}), | |
| 399 | } | |
| 400 | ||
| 401 | try w.update(gpa, run.step_stack.keys()); | |
| 402 | ||
| 403 | // Wait until a file system notification arrives. Read all such events | |
| 404 | // until the buffer is empty. Then wait for a debounce interval, resetting | |
| 405 | // if any more events come in. After the debounce interval has passed, | |
| 406 | // trigger a rebuild on all steps with modified inputs, as well as their | |
| 407 | // recursive dependants. | |
| 408 | var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined; | |
| 409 | const caption = std.fmt.bufPrint(&caption_buf, "Watching {d} Directories", .{ | |
| 410 | w.dir_table.entries.len, | |
| 411 | }) catch &caption_buf; | |
| 412 | var debouncing_node = main_progress_node.start(caption, 0); | |
| 413 | var debounce_timeout: Watch.Timeout = .none; | |
| 414 | while (true) switch (try w.wait(gpa, debounce_timeout)) { | |
| 415 | .timeout => { | |
| 416 | debouncing_node.end(); | |
| 417 | markFailedStepsDirty(gpa, run.step_stack.keys()); | |
| 418 | continue :rebuild; | |
| 419 | }, | |
| 420 | .dirty => if (debounce_timeout == .none) { | |
| 421 | debounce_timeout = .{ .ms = debounce_interval_ms }; | |
| 422 | debouncing_node.end(); | |
| 423 | debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0); | |
| 424 | }, | |
| 425 | .clean => {}, | |
| 426 | }; | |
| 427 | } | |
| 428 | } | |
| 429 | ||
| 430 | fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void { | |
| 431 | for (all_steps) |step| switch (step.state) { | |
| 432 | .dependency_failure, .failure, .skipped => step.recursiveReset(gpa), | |
| 433 | else => continue, | |
| 434 | }; | |
| 435 | // Now that all dirty steps have been found, the remaining steps that | |
| 436 | // succeeded from last run shall be marked "cached". | |
| 437 | for (all_steps) |step| switch (step.state) { | |
| 438 | .success => step.result_cached = true, | |
| 439 | else => continue, | |
| 440 | }; | |
| 369 | 441 | } |
| 370 | 442 | |
| 371 | 443 | const Run = struct { |
| ... | ... | @@ -373,27 +445,27 @@ const Run = struct { |
| 373 | 445 | max_rss_is_default: bool, |
| 374 | 446 | max_rss_mutex: std.Thread.Mutex, |
| 375 | 447 | skip_oom_steps: bool, |
| 448 | watch: bool, | |
| 376 | 449 | memory_blocked_steps: std.ArrayList(*Step), |
| 450 | step_stack: std.AutoArrayHashMapUnmanaged(*Step, void), | |
| 377 | 451 | prominent_compile_errors: bool, |
| 452 | thread_pool: std.Thread.Pool, | |
| 378 | 453 | |
| 379 | 454 | claimed_rss: usize, |
| 380 | summary: ?Summary, | |
| 455 | summary: Summary, | |
| 381 | 456 | ttyconf: std.io.tty.Config, |
| 382 | 457 | stderr: File, |
| 383 | 458 | }; |
| 384 | 459 | |
| 385 | fn runStepNames( | |
| 386 | arena: std.mem.Allocator, | |
| 460 | fn prepare( | |
| 461 | gpa: Allocator, | |
| 462 | arena: Allocator, | |
| 387 | 463 | b: *std.Build, |
| 388 | 464 | step_names: []const []const u8, |
| 389 | parent_prog_node: std.Progress.Node, | |
| 390 | thread_pool_options: std.Thread.Pool.Options, | |
| 391 | 465 | run: *Run, |
| 392 | 466 | seed: u32, |
| 393 | 467 | ) !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; | |
| 397 | 469 | |
| 398 | 470 | if (step_names.len == 0) { |
| 399 | 471 | try step_stack.put(gpa, b.default_step, {}); |
| ... | ... | @@ -416,8 +488,8 @@ fn runStepNames( |
| 416 | 488 | rand.shuffle(*Step, starting_steps); |
| 417 | 489 | |
| 418 | 490 | 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(), | |
| 421 | 493 | else => |e| return e, |
| 422 | 494 | }; |
| 423 | 495 | } |
| ... | ... | @@ -442,17 +514,22 @@ fn runStepNames( |
| 442 | 514 | if (run.max_rss_is_default) { |
| 443 | 515 | std.debug.print("note: use --maxrss to override the default", .{}); |
| 444 | 516 | } |
| 445 | return error.UncleanExit; | |
| 517 | return uncleanExit(); | |
| 446 | 518 | } |
| 447 | 519 | } |
| 520 | } | |
| 448 | 521 | |
| 449 | var thread_pool: std.Thread.Pool = undefined; | |
| 450 | try thread_pool.init(thread_pool_options); | |
| 451 | defer thread_pool.deinit(); | |
| 522 | fn 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; | |
| 452 | 531 | |
| 453 | 532 | { |
| 454 | defer parent_prog_node.end(); | |
| 455 | ||
| 456 | 533 | const step_prog = parent_prog_node.start("steps", step_stack.count()); |
| 457 | 534 | defer step_prog.end(); |
| 458 | 535 | |
| ... | ... | @@ -468,7 +545,7 @@ fn runStepNames( |
| 468 | 545 | if (step.state == .skipped_oom) continue; |
| 469 | 546 | |
| 470 | 547 | thread_pool.spawnWg(&wait_group, workerMakeOneStep, .{ |
| 471 | &wait_group, &thread_pool, b, step, step_prog, run, | |
| 548 | &wait_group, b, step, step_prog, run, | |
| 472 | 549 | }); |
| 473 | 550 | } |
| 474 | 551 | } |
| ... | ... | @@ -485,8 +562,6 @@ fn runStepNames( |
| 485 | 562 | var failure_count: usize = 0; |
| 486 | 563 | var pending_count: usize = 0; |
| 487 | 564 | var total_compile_errors: usize = 0; |
| 488 | var compile_error_steps: std.ArrayListUnmanaged(*Step) = .{}; | |
| 489 | defer compile_error_steps.deinit(gpa); | |
| 490 | 565 | |
| 491 | 566 | for (step_stack.keys()) |s| { |
| 492 | 567 | test_fail_count += s.test_results.fail_count; |
| ... | ... | @@ -516,7 +591,6 @@ fn runStepNames( |
| 516 | 591 | const compile_errors_len = s.result_error_bundle.errorMessageCount(); |
| 517 | 592 | if (compile_errors_len > 0) { |
| 518 | 593 | total_compile_errors += compile_errors_len; |
| 519 | try compile_error_steps.append(gpa, s); | |
| 520 | 594 | } |
| 521 | 595 | }, |
| 522 | 596 | } |
| ... | ... | @@ -524,13 +598,22 @@ fn runStepNames( |
| 524 | 598 | |
| 525 | 599 | // A proper command line application defaults to silently succeeding. |
| 526 | 600 | // 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 | } | |
| 529 | 609 | |
| 530 | 610 | const ttyconf = run.ttyconf; |
| 531 | const stderr = run.stderr; | |
| 532 | 611 | |
| 533 | if (run.summary != Summary.none) { | |
| 612 | if (run.summary != .none) { | |
| 613 | std.debug.lockStdErr(); | |
| 614 | defer std.debug.unlockStdErr(); | |
| 615 | const stderr = run.stderr; | |
| 616 | ||
| 534 | 617 | const total_count = success_count + failure_count + pending_count + skipped_count; |
| 535 | 618 | ttyconf.setColor(stderr, .cyan) catch {}; |
| 536 | 619 | stderr.writeAll("Build Summary:") catch {}; |
| ... | ... | @@ -544,25 +627,23 @@ fn runStepNames( |
| 544 | 627 | if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {}; |
| 545 | 628 | if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {}; |
| 546 | 629 | |
| 547 | if (run.summary == null) { | |
| 548 | ttyconf.setColor(stderr, .dim) catch {}; | |
| 549 | stderr.writeAll(" (disable with --summary none)") catch {}; | |
| 550 | ttyconf.setColor(stderr, .reset) catch {}; | |
| 551 | } | |
| 552 | 630 | stderr.writeAll("\n") catch {}; |
| 553 | 631 | |
| 554 | 632 | // Print a fancy tree with build results. |
| 633 | var step_stack_copy = try step_stack.clone(gpa); | |
| 634 | defer step_stack_copy.deinit(gpa); | |
| 635 | ||
| 555 | 636 | var print_node: PrintNode = .{ .parent = null }; |
| 556 | 637 | if (step_names.len == 0) { |
| 557 | 638 | print_node.last = true; |
| 558 | printTreeStep(b, b.default_step, run, stderr, ttyconf, &print_node, &step_stack) catch {}; | |
| 639 | printTreeStep(b, b.default_step, run, stderr, ttyconf, &print_node, &step_stack_copy) catch {}; | |
| 559 | 640 | } else { |
| 560 | 641 | const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: { |
| 561 | 642 | var i: usize = step_names.len; |
| 562 | 643 | while (i > 0) { |
| 563 | 644 | i -= 1; |
| 564 | 645 | const step = b.top_level_steps.get(step_names[i]).?.step; |
| 565 | const found = switch (run.summary orelse .failures) { | |
| 646 | const found = switch (run.summary) { | |
| 566 | 647 | .all, .none => unreachable, |
| 567 | 648 | .failures => step.state != .success, |
| 568 | 649 | .new => !step.result_cached, |
| ... | ... | @@ -574,30 +655,34 @@ fn runStepNames( |
| 574 | 655 | for (step_names, 0..) |step_name, i| { |
| 575 | 656 | const tls = b.top_level_steps.get(step_name).?; |
| 576 | 657 | print_node.last = i + 1 == last_index; |
| 577 | printTreeStep(b, &tls.step, run, stderr, ttyconf, &print_node, &step_stack) catch {}; | |
| 658 | printTreeStep(b, &tls.step, run, stderr, ttyconf, &print_node, &step_stack_copy) catch {}; | |
| 578 | 659 | } |
| 579 | 660 | } |
| 580 | 661 | } |
| 581 | 662 | |
| 582 | if (failure_count == 0) return cleanExit(); | |
| 663 | if (failure_count == 0) { | |
| 664 | if (!run.watch) cleanExit(); | |
| 665 | return; | |
| 666 | } | |
| 583 | 667 | |
| 584 | 668 | // Finally, render compile errors at the bottom of the terminal. |
| 585 | // We use a separate compile_error_steps array list because step_stack is destructively | |
| 586 | // mutated in printTreeStep above. | |
| 587 | 669 | if (run.prominent_compile_errors and total_compile_errors > 0) { |
| 588 | for (compile_error_steps.items) |s| { | |
| 670 | for (step_stack.keys()) |s| { | |
| 589 | 671 | if (s.result_error_bundle.errorMessageCount() > 0) { |
| 590 | 672 | s.result_error_bundle.renderToStdErr(renderOptions(ttyconf)); |
| 591 | 673 | } |
| 592 | 674 | } |
| 593 | 675 | |
| 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 | } | |
| 598 | 683 | } |
| 599 | 684 | |
| 600 | process.exit(1); | |
| 685 | if (!run.watch) return uncleanExit(); | |
| 601 | 686 | } |
| 602 | 687 | |
| 603 | 688 | const PrintNode = struct { |
| ... | ... | @@ -768,7 +853,7 @@ fn printTreeStep( |
| 768 | 853 | step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), |
| 769 | 854 | ) !void { |
| 770 | 855 | const first = step_stack.swapRemove(s); |
| 771 | const summary = run.summary orelse .failures; | |
| 856 | const summary = run.summary; | |
| 772 | 857 | const skip = switch (summary) { |
| 773 | 858 | .none => unreachable, |
| 774 | 859 | .all => false, |
| ... | ... | @@ -889,12 +974,13 @@ fn constructGraphAndCheckForDependencyLoop( |
| 889 | 974 | |
| 890 | 975 | fn workerMakeOneStep( |
| 891 | 976 | wg: *std.Thread.WaitGroup, |
| 892 | thread_pool: *std.Thread.Pool, | |
| 893 | 977 | b: *std.Build, |
| 894 | 978 | s: *Step, |
| 895 | 979 | prog_node: std.Progress.Node, |
| 896 | 980 | run: *Run, |
| 897 | 981 | ) void { |
| 982 | const thread_pool = &run.thread_pool; | |
| 983 | ||
| 898 | 984 | // First, check the conditions for running this step. If they are not met, |
| 899 | 985 | // then we return without doing the step, relying on another worker to |
| 900 | 986 | // queue this step up again when dependencies are met. |
| ... | ... | @@ -974,7 +1060,7 @@ fn workerMakeOneStep( |
| 974 | 1060 | // Successful completion of a step, so we queue up its dependants as well. |
| 975 | 1061 | for (s.dependants.items) |dep| { |
| 976 | 1062 | thread_pool.spawnWg(wg, workerMakeOneStep, .{ |
| 977 | wg, thread_pool, b, dep, prog_node, run, | |
| 1063 | wg, b, dep, prog_node, run, | |
| 978 | 1064 | }); |
| 979 | 1065 | } |
| 980 | 1066 | } |
| ... | ... | @@ -999,7 +1085,7 @@ fn workerMakeOneStep( |
| 999 | 1085 | remaining -= dep.max_rss; |
| 1000 | 1086 | |
| 1001 | 1087 | thread_pool.spawnWg(wg, workerMakeOneStep, .{ |
| 1002 | wg, thread_pool, b, dep, prog_node, run, | |
| 1088 | wg, b, dep, prog_node, run, | |
| 1003 | 1089 | }); |
| 1004 | 1090 | } else { |
| 1005 | 1091 | run.memory_blocked_steps.items[i] = dep; |
| ... | ... | @@ -1124,6 +1210,8 @@ fn usage(b: *std.Build, out_stream: anytype) !void { |
| 1124 | 1210 | \\ --maxrss <bytes> Limit memory usage (default is to use available memory) |
| 1125 | 1211 | \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss |
| 1126 | 1212 | \\ --fetch Exit after fetching dependency tree |
| 1213 | \\ --watch Continuously rebuild when source files are modified | |
| 1214 | \\ --debounce <ms> Delay before rebuilding after changed file detected | |
| 1127 | 1215 | \\ |
| 1128 | 1216 | \\Project-Specific Options: |
| 1129 | 1217 | \\ |
| ... | ... | @@ -1218,13 +1306,22 @@ fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 { |
| 1218 | 1306 | return args[idx..]; |
| 1219 | 1307 | } |
| 1220 | 1308 | |
| 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. | |
| 1221 | 1312 | fn 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(); | |
| 1225 | 1314 | process.exit(0); |
| 1226 | 1315 | } |
| 1227 | 1316 | |
| 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. | |
| 1320 | fn uncleanExit() error{UncleanExit} { | |
| 1321 | std.debug.lockStdErr(); | |
| 1322 | process.exit(1); | |
| 1323 | } | |
| 1324 | ||
| 1228 | 1325 | const Color = std.zig.Color; |
| 1229 | 1326 | const Summary = enum { all, new, failures, none }; |
| 1230 | 1327 | |
| ... | ... | @@ -1249,11 +1346,6 @@ fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn { |
| 1249 | 1346 | process.exit(1); |
| 1250 | 1347 | } |
| 1251 | 1348 | |
| 1252 | fn fatal(comptime f: []const u8, args: anytype) noreturn { | |
| 1253 | std.debug.print(f ++ "\n", args); | |
| 1254 | process.exit(1); | |
| 1255 | } | |
| 1256 | ||
| 1257 | 1349 | fn validateSystemLibraryOptions(b: *std.Build) void { |
| 1258 | 1350 | var bad = false; |
| 1259 | 1351 | for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| { |
lib/compiler/objcopy.zig+2-8| ... | ... | @@ -198,20 +198,14 @@ fn cmdObjCopy( |
| 198 | 198 | return std.process.cleanExit(); |
| 199 | 199 | }, |
| 200 | 200 | .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", .{}); | |
| 205 | 202 | seen_update = true; |
| 206 | 203 | |
| 207 | 204 | try server.serveEmitBinPath(output, .{ |
| 208 | 205 | .flags = .{ .cache_hit = false }, |
| 209 | 206 | }); |
| 210 | 207 | }, |
| 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)}), | |
| 215 | 209 | } |
| 216 | 210 | } |
| 217 | 211 | } |
lib/std/Build.zig+51-61| ... | ... | @@ -20,6 +20,7 @@ const Build = @This(); |
| 20 | 20 | pub const Cache = @import("Build/Cache.zig"); |
| 21 | 21 | pub const Step = @import("Build/Step.zig"); |
| 22 | 22 | pub const Module = @import("Build/Module.zig"); |
| 23 | pub const Watch = @import("Build/Watch.zig"); | |
| 23 | 24 | |
| 24 | 25 | /// Shared state among all Build instances. |
| 25 | 26 | graph: *Graph, |
| ... | ... | @@ -50,11 +51,9 @@ install_path: []const u8, |
| 50 | 51 | sysroot: ?[]const u8 = null, |
| 51 | 52 | search_prefixes: std.ArrayListUnmanaged([]const u8), |
| 52 | 53 | libc_file: ?[]const u8 = null, |
| 53 | installed_files: ArrayList(InstalledFile), | |
| 54 | 54 | /// Path to the directory containing build.zig. |
| 55 | 55 | build_root: Cache.Directory, |
| 56 | 56 | cache_root: Cache.Directory, |
| 57 | zig_lib_dir: ?LazyPath, | |
| 58 | 57 | pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null, |
| 59 | 58 | args: ?[]const []const u8 = null, |
| 60 | 59 | debug_log_scopes: []const []const u8 = &.{}, |
| ... | ... | @@ -117,6 +116,7 @@ pub const Graph = struct { |
| 117 | 116 | zig_exe: [:0]const u8, |
| 118 | 117 | env_map: EnvMap, |
| 119 | 118 | global_cache_root: Cache.Directory, |
| 119 | zig_lib_directory: Cache.Directory, | |
| 120 | 120 | needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .{}, |
| 121 | 121 | /// Information about the native target. Computed before build() is invoked. |
| 122 | 122 | host: ResolvedTarget, |
| ... | ... | @@ -276,7 +276,6 @@ pub fn create( |
| 276 | 276 | .exe_dir = undefined, |
| 277 | 277 | .h_dir = undefined, |
| 278 | 278 | .dest_dir = graph.env_map.get("DESTDIR"), |
| 279 | .installed_files = ArrayList(InstalledFile).init(arena), | |
| 280 | 279 | .install_tls = .{ |
| 281 | 280 | .step = Step.init(.{ |
| 282 | 281 | .id = TopLevelStep.base_id, |
| ... | ... | @@ -294,7 +293,6 @@ pub fn create( |
| 294 | 293 | }), |
| 295 | 294 | .description = "Remove build artifacts from prefix path", |
| 296 | 295 | }, |
| 297 | .zig_lib_dir = null, | |
| 298 | 296 | .install_path = undefined, |
| 299 | 297 | .args = null, |
| 300 | 298 | .host = graph.host, |
| ... | ... | @@ -378,10 +376,8 @@ fn createChildOnly( |
| 378 | 376 | .sysroot = parent.sysroot, |
| 379 | 377 | .search_prefixes = parent.search_prefixes, |
| 380 | 378 | .libc_file = parent.libc_file, |
| 381 | .installed_files = ArrayList(InstalledFile).init(allocator), | |
| 382 | 379 | .build_root = build_root, |
| 383 | 380 | .cache_root = parent.cache_root, |
| 384 | .zig_lib_dir = parent.zig_lib_dir, | |
| 385 | 381 | .debug_log_scopes = parent.debug_log_scopes, |
| 386 | 382 | .debug_compile_errors = parent.debug_compile_errors, |
| 387 | 383 | .debug_pkg_config = parent.debug_pkg_config, |
| ... | ... | @@ -689,7 +685,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile { |
| 689 | 685 | .max_rss = options.max_rss, |
| 690 | 686 | .use_llvm = options.use_llvm, |
| 691 | 687 | .use_lld = options.use_lld, |
| 692 | .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, | |
| 688 | .zig_lib_dir = options.zig_lib_dir, | |
| 693 | 689 | .win32_manifest = options.win32_manifest, |
| 694 | 690 | }); |
| 695 | 691 | } |
| ... | ... | @@ -737,7 +733,7 @@ pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile { |
| 737 | 733 | .max_rss = options.max_rss, |
| 738 | 734 | .use_llvm = options.use_llvm, |
| 739 | 735 | .use_lld = options.use_lld, |
| 740 | .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, | |
| 736 | .zig_lib_dir = options.zig_lib_dir, | |
| 741 | 737 | }); |
| 742 | 738 | } |
| 743 | 739 | |
| ... | ... | @@ -793,7 +789,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *Step.Compile |
| 793 | 789 | .max_rss = options.max_rss, |
| 794 | 790 | .use_llvm = options.use_llvm, |
| 795 | 791 | .use_lld = options.use_lld, |
| 796 | .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, | |
| 792 | .zig_lib_dir = options.zig_lib_dir, | |
| 797 | 793 | .win32_manifest = options.win32_manifest, |
| 798 | 794 | }); |
| 799 | 795 | } |
| ... | ... | @@ -844,7 +840,7 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *Step.Compile |
| 844 | 840 | .max_rss = options.max_rss, |
| 845 | 841 | .use_llvm = options.use_llvm, |
| 846 | 842 | .use_lld = options.use_lld, |
| 847 | .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, | |
| 843 | .zig_lib_dir = options.zig_lib_dir, | |
| 848 | 844 | }); |
| 849 | 845 | } |
| 850 | 846 | |
| ... | ... | @@ -907,7 +903,7 @@ pub fn addTest(b: *Build, options: TestOptions) *Step.Compile { |
| 907 | 903 | .test_runner = options.test_runner, |
| 908 | 904 | .use_llvm = options.use_llvm, |
| 909 | 905 | .use_lld = options.use_lld, |
| 910 | .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, | |
| 906 | .zig_lib_dir = options.zig_lib_dir, | |
| 911 | 907 | }); |
| 912 | 908 | } |
| 913 | 909 | |
| ... | ... | @@ -931,7 +927,7 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *Step.Compile { |
| 931 | 927 | .optimize = options.optimize, |
| 932 | 928 | }, |
| 933 | 929 | .max_rss = options.max_rss, |
| 934 | .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, | |
| 930 | .zig_lib_dir = options.zig_lib_dir, | |
| 935 | 931 | }); |
| 936 | 932 | obj_step.addAssemblyFile(options.source_file); |
| 937 | 933 | return obj_step; |
| ... | ... | @@ -1054,7 +1050,11 @@ pub fn addWriteFiles(b: *Build) *Step.WriteFile { |
| 1054 | 1050 | return Step.WriteFile.create(b); |
| 1055 | 1051 | } |
| 1056 | 1052 | |
| 1057 | pub fn addRemoveDirTree(b: *Build, dir_path: []const u8) *Step.RemoveDir { | |
| 1053 | pub fn addUpdateSourceFiles(b: *Build) *Step.UpdateSourceFiles { | |
| 1054 | return Step.UpdateSourceFiles.create(b); | |
| 1055 | } | |
| 1056 | ||
| 1057 | pub fn addRemoveDirTree(b: *Build, dir_path: LazyPath) *Step.RemoveDir { | |
| 1058 | 1058 | return Step.RemoveDir.create(b, dir_path); |
| 1059 | 1059 | } |
| 1060 | 1060 | |
| ... | ... | @@ -1083,15 +1083,8 @@ fn makeUninstall(uninstall_step: *Step, prog_node: std.Progress.Node) anyerror!v |
| 1083 | 1083 | const uninstall_tls: *TopLevelStep = @fieldParentPtr("step", uninstall_step); |
| 1084 | 1084 | const b: *Build = @fieldParentPtr("uninstall_tls", uninstall_tls); |
| 1085 | 1085 | |
| 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"); | |
| 1095 | 1088 | } |
| 1096 | 1089 | |
| 1097 | 1090 | /// Creates a configuration option to be passed to the build.zig script. |
| ... | ... | @@ -1664,15 +1657,6 @@ pub fn addCheckFile( |
| 1664 | 1657 | return Step.CheckFile.create(b, file_source, options); |
| 1665 | 1658 | } |
| 1666 | 1659 | |
| 1667 | /// deprecated: https://github.com/ziglang/zig/issues/14943 | |
| 1668 | pub 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 | ||
| 1676 | 1660 | pub fn truncateFile(b: *Build, dest_path: []const u8) !void { |
| 1677 | 1661 | if (b.verbose) { |
| 1678 | 1662 | log.info("truncate {s}", .{dest_path}); |
| ... | ... | @@ -2341,36 +2325,52 @@ pub const LazyPath = union(enum) { |
| 2341 | 2325 | } |
| 2342 | 2326 | } |
| 2343 | 2327 | |
| 2344 | /// Returns an absolute path. | |
| 2345 | /// Intended to be used during the make phase only. | |
| 2328 | /// Deprecated, see `getPath3`. | |
| 2346 | 2329 | pub fn getPath(lazy_path: LazyPath, src_builder: *Build) []const u8 { |
| 2347 | 2330 | return getPath2(lazy_path, src_builder, null); |
| 2348 | 2331 | } |
| 2349 | 2332 | |
| 2350 | /// Returns an absolute path. | |
| 2333 | /// Deprecated, see `getPath3`. | |
| 2334 | pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 { | |
| 2335 | const p = getPath3(lazy_path, src_builder, asking_step); | |
| 2336 | return src_builder.pathResolve(&.{ p.root_dir.path orelse ".", p.sub_path }); | |
| 2337 | } | |
| 2338 | ||
| 2351 | 2339 | /// Intended to be used during the make phase only. |
| 2352 | 2340 | /// |
| 2353 | 2341 | /// `asking_step` is only used for debugging purposes; it's the step being |
| 2354 | 2342 | /// run that is asking for the path. |
| 2355 | pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 { | |
| 2343 | pub fn getPath3(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Cache.Path { | |
| 2356 | 2344 | 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 | }, | |
| 2359 | 2353 | .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 | }; | |
| 2367 | 2367 | |
| 2368 | 2368 | if (gen.up > 0) { |
| 2369 | 2369 | const cache_root_path = src_builder.cache_root.path orelse |
| 2370 | 2370 | (src_builder.cache_root.join(src_builder.allocator, &.{"."}) catch @panic("OOM")); |
| 2371 | 2371 | |
| 2372 | 2372 | for (0..gen.up) |_| { |
| 2373 | if (mem.eql(u8, file_path, cache_root_path)) { | |
| 2373 | if (mem.eql(u8, file_path.sub_path, cache_root_path)) { | |
| 2374 | 2374 | // If we hit the cache root and there's still more to go, |
| 2375 | 2375 | // the script attempted to go too far. |
| 2376 | 2376 | dumpBadDirnameHelp(gen.file.step, asking_step, |
| ... | ... | @@ -2384,7 +2384,7 @@ pub const LazyPath = union(enum) { |
| 2384 | 2384 | // path is absolute. |
| 2385 | 2385 | // dirname will return null only if we're at root. |
| 2386 | 2386 | // Typically, we'll stop well before that at the cache root. |
| 2387 | file_path = fs.path.dirname(file_path) orelse { | |
| 2387 | file_path.sub_path = fs.path.dirname(file_path.sub_path) orelse { | |
| 2388 | 2388 | dumpBadDirnameHelp(gen.file.step, asking_step, |
| 2389 | 2389 | \\dirname() reached root. |
| 2390 | 2390 | \\No more directories left to go up. |
| ... | ... | @@ -2395,9 +2395,12 @@ pub const LazyPath = union(enum) { |
| 2395 | 2395 | } |
| 2396 | 2396 | } |
| 2397 | 2397 | |
| 2398 | return src_builder.pathResolve(&.{ file_path, gen.sub_path }); | |
| 2398 | return file_path.join(src_builder.allocator, gen.sub_path) catch @panic("OOM"); | |
| 2399 | }, | |
| 2400 | .dependency => |dep| return .{ | |
| 2401 | .root_dir = dep.dependency.builder.build_root, | |
| 2402 | .sub_path = dep.sub_path, | |
| 2399 | 2403 | }, |
| 2400 | .dependency => |dep| return dep.dependency.builder.pathFromRoot(dep.sub_path), | |
| 2401 | 2404 | } |
| 2402 | 2405 | } |
| 2403 | 2406 | |
| ... | ... | @@ -2512,19 +2515,6 @@ pub const InstallDir = union(enum) { |
| 2512 | 2515 | } |
| 2513 | 2516 | }; |
| 2514 | 2517 | |
| 2515 | pub const InstalledFile = struct { | |
| 2516 | dir: InstallDir, | |
| 2517 | path: []const u8, | |
| 2518 | ||
| 2519 | /// Duplicates the installed file path and directory. | |
| 2520 | pub fn dupe(file: InstalledFile, builder: *Build) InstalledFile { | |
| 2521 | return .{ | |
| 2522 | .dir = file.dir.dupe(builder), | |
| 2523 | .path = builder.dupe(file.path), | |
| 2524 | }; | |
| 2525 | } | |
| 2526 | }; | |
| 2527 | ||
| 2528 | 2518 | /// This function is intended to be called in the `configure` phase only. |
| 2529 | 2519 | /// It returns an absolute directory path, which is potentially going to be a |
| 2530 | 2520 | /// source of API breakage in the future, so keep that in mind when using this |
lib/std/Build/Cache.zig+34| ... | ... | @@ -354,6 +354,19 @@ pub const Manifest = struct { |
| 354 | 354 | /// ``` |
| 355 | 355 | /// var file_contents = cache_hash.files.keys()[file_index].contents.?; |
| 356 | 356 | /// ``` |
| 357 | pub fn addFilePath(m: *Manifest, file_path: Path, max_file_size: ?usize) !usize { | |
| 358 | const gpa = m.cache.gpa; | |
| 359 | try m.files.ensureUnusedCapacity(gpa, 1); | |
| 360 | const resolved_path = try fs.path.resolve(gpa, &.{ | |
| 361 | file_path.root_dir.path orelse ".", | |
| 362 | file_path.subPathOrDot(), | |
| 363 | }); | |
| 364 | errdefer gpa.free(resolved_path); | |
| 365 | const prefixed_path = try m.cache.findPrefixResolved(resolved_path); | |
| 366 | return addFileInner(m, prefixed_path, max_file_size); | |
| 367 | } | |
| 368 | ||
| 369 | /// Deprecated; use `addFilePath`. | |
| 357 | 370 | pub fn addFile(self: *Manifest, file_path: []const u8, max_file_size: ?usize) !usize { |
| 358 | 371 | assert(self.manifest_file == null); |
| 359 | 372 | |
| ... | ... | @@ -362,6 +375,10 @@ pub const Manifest = struct { |
| 362 | 375 | const prefixed_path = try self.cache.findPrefix(file_path); |
| 363 | 376 | errdefer gpa.free(prefixed_path.sub_path); |
| 364 | 377 | |
| 378 | return addFileInner(self, prefixed_path, max_file_size); | |
| 379 | } | |
| 380 | ||
| 381 | fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, max_file_size: ?usize) !usize { | |
| 365 | 382 | const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{}); |
| 366 | 383 | if (gop.found_existing) { |
| 367 | 384 | gop.key_ptr.updateMaxSize(max_file_size); |
| ... | ... | @@ -990,6 +1007,23 @@ pub const Manifest = struct { |
| 990 | 1007 | } |
| 991 | 1008 | self.files.deinit(self.cache.gpa); |
| 992 | 1009 | } |
| 1010 | ||
| 1011 | pub fn populateFileSystemInputs(man: *Manifest, buf: *std.ArrayListUnmanaged(u8)) Allocator.Error!void { | |
| 1012 | assert(@typeInfo(std.zig.Server.Message.PathPrefix).Enum.fields.len == man.cache.prefixes_len); | |
| 1013 | buf.clearRetainingCapacity(); | |
| 1014 | const gpa = man.cache.gpa; | |
| 1015 | const files = man.files.keys(); | |
| 1016 | if (files.len > 0) { | |
| 1017 | for (files) |file| { | |
| 1018 | try buf.ensureUnusedCapacity(gpa, file.prefixed_path.sub_path.len + 2); | |
| 1019 | buf.appendAssumeCapacity(file.prefixed_path.prefix + 1); | |
| 1020 | buf.appendSliceAssumeCapacity(file.prefixed_path.sub_path); | |
| 1021 | buf.appendAssumeCapacity(0); | |
| 1022 | } | |
| 1023 | // The null byte is a separator, not a terminator. | |
| 1024 | buf.items.len -= 1; | |
| 1025 | } | |
| 1026 | } | |
| 993 | 1027 | }; |
| 994 | 1028 | |
| 995 | 1029 | /// On operating systems that support symlinks, does a readlink. On other operating systems, |
lib/std/Build/Cache/Path.zig+57-2| ... | ... | @@ -58,6 +58,20 @@ pub fn openFile( |
| 58 | 58 | return p.root_dir.handle.openFile(joined_path, flags); |
| 59 | 59 | } |
| 60 | 60 | |
| 61 | pub 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 | ||
| 61 | 75 | pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs.Dir { |
| 62 | 76 | var buf: [fs.max_path_bytes]u8 = undefined; |
| 63 | 77 | const joined_path = if (p.sub_path.len == 0) sub_path else p: { |
| ... | ... | @@ -137,16 +151,57 @@ pub fn format( |
| 137 | 151 | } |
| 138 | 152 | if (fmt_string.len > 0) |
| 139 | 153 | std.fmt.invalidFmtError(fmt_string, self); |
| 154 | if (std.fs.path.isAbsolute(self.sub_path)) { | |
| 155 | try writer.writeAll(self.sub_path); | |
| 156 | return; | |
| 157 | } | |
| 140 | 158 | if (self.root_dir.path) |p| { |
| 141 | 159 | try writer.writeAll(p); |
| 142 | try writer.writeAll(fs.path.sep_str); | |
| 160 | if (self.sub_path.len > 0) { | |
| 161 | try writer.writeAll(fs.path.sep_str); | |
| 162 | try writer.writeAll(self.sub_path); | |
| 163 | } | |
| 164 | return; | |
| 143 | 165 | } |
| 144 | 166 | if (self.sub_path.len > 0) { |
| 145 | 167 | try writer.writeAll(self.sub_path); |
| 146 | try writer.writeAll(fs.path.sep_str); | |
| 168 | return; | |
| 147 | 169 | } |
| 170 | try writer.writeByte('.'); | |
| 171 | } | |
| 172 | ||
| 173 | pub 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 | ||
| 177 | pub fn subPathOpt(self: Path) ?[]const u8 { | |
| 178 | return if (self.sub_path.len == 0) null else self.sub_path; | |
| 148 | 179 | } |
| 149 | 180 | |
| 181 | pub 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`. | |
| 186 | pub 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 | ||
| 150 | 205 | const Path = @This(); |
| 151 | 206 | const std = @import("../../std.zig"); |
| 152 | 207 | const fs = std.fs; |
lib/std/Build/Step.zig+235-3| ... | ... | @@ -7,6 +7,16 @@ dependencies: std.ArrayList(*Step), |
| 7 | 7 | /// This field is empty during execution of the user's build script, and |
| 8 | 8 | /// then populated during dependency loop checking in the build runner. |
| 9 | 9 | dependants: 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. | |
| 18 | inputs: Inputs, | |
| 19 | ||
| 10 | 20 | state: State, |
| 11 | 21 | /// Set this field to declare an upper bound on the amount of bytes of memory it will |
| 12 | 22 | /// take to run the step. Zero means no limit. |
| ... | ... | @@ -63,6 +73,11 @@ pub const MakeFn = *const fn (step: *Step, prog_node: std.Progress.Node) anyerro |
| 63 | 73 | pub const State = enum { |
| 64 | 74 | precheck_unstarted, |
| 65 | 75 | precheck_started, |
| 76 | /// This is also used to indicate "dirty" steps that have been modified | |
| 77 | /// after a previous build completed, in which case, the step may or may | |
| 78 | /// not have been completed before. Either way, one or more of its direct | |
| 79 | /// file system inputs have been modified, meaning that the step needs to | |
| 80 | /// be re-evaluated. | |
| 66 | 81 | precheck_done, |
| 67 | 82 | running, |
| 68 | 83 | dependency_failure, |
| ... | ... | @@ -87,6 +102,7 @@ pub const Id = enum { |
| 87 | 102 | fmt, |
| 88 | 103 | translate_c, |
| 89 | 104 | write_file, |
| 105 | update_source_files, | |
| 90 | 106 | run, |
| 91 | 107 | check_file, |
| 92 | 108 | check_object, |
| ... | ... | @@ -107,6 +123,7 @@ pub const Id = enum { |
| 107 | 123 | .fmt => Fmt, |
| 108 | 124 | .translate_c => TranslateC, |
| 109 | 125 | .write_file => WriteFile, |
| 126 | .update_source_files => UpdateSourceFiles, | |
| 110 | 127 | .run => Run, |
| 111 | 128 | .check_file => CheckFile, |
| 112 | 129 | .check_object => CheckObject, |
| ... | ... | @@ -133,6 +150,28 @@ pub const RemoveDir = @import("Step/RemoveDir.zig"); |
| 133 | 150 | pub const Run = @import("Step/Run.zig"); |
| 134 | 151 | pub const TranslateC = @import("Step/TranslateC.zig"); |
| 135 | 152 | pub const WriteFile = @import("Step/WriteFile.zig"); |
| 153 | pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig"); | |
| 154 | ||
| 155 | pub 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 | }; | |
| 136 | 175 | |
| 137 | 176 | pub const StepOptions = struct { |
| 138 | 177 | id: Id, |
| ... | ... | @@ -153,6 +192,7 @@ pub fn init(options: StepOptions) Step { |
| 153 | 192 | .makeFn = options.makeFn, |
| 154 | 193 | .dependencies = std.ArrayList(*Step).init(arena), |
| 155 | 194 | .dependants = .{}, |
| 195 | .inputs = Inputs.init, | |
| 156 | 196 | .state = .precheck_unstarted, |
| 157 | 197 | .max_rss = options.max_rss, |
| 158 | 198 | .debug_stack_trace = blk: { |
| ... | ... | @@ -395,6 +435,44 @@ pub fn evalZigProcess( |
| 395 | 435 | s.result_cached = ebp_hdr.flags.cache_hit; |
| 396 | 436 | result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]); |
| 397 | 437 | }, |
| 438 | .file_system_inputs => { | |
| 439 | s.clearWatchInputs(); | |
| 440 | var it = std.mem.splitScalar(u8, body, 0); | |
| 441 | while (it.next()) |prefixed_path| { | |
| 442 | const prefix_index: std.zig.Server.Message.PathPrefix = @enumFromInt(prefixed_path[0] - 1); | |
| 443 | const sub_path = try arena.dupe(u8, prefixed_path[1..]); | |
| 444 | const sub_path_dirname = std.fs.path.dirname(sub_path) orelse ""; | |
| 445 | switch (prefix_index) { | |
| 446 | .cwd => { | |
| 447 | const path: Build.Cache.Path = .{ | |
| 448 | .root_dir = Build.Cache.Directory.cwd(), | |
| 449 | .sub_path = sub_path_dirname, | |
| 450 | }; | |
| 451 | try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path)); | |
| 452 | }, | |
| 453 | .zig_lib => zl: { | |
| 454 | if (s.cast(Step.Compile)) |compile| { | |
| 455 | if (compile.zig_lib_dir) |lp| { | |
| 456 | try addWatchInput(s, lp); | |
| 457 | break :zl; | |
| 458 | } | |
| 459 | } | |
| 460 | const path: Build.Cache.Path = .{ | |
| 461 | .root_dir = s.owner.graph.zig_lib_directory, | |
| 462 | .sub_path = sub_path_dirname, | |
| 463 | }; | |
| 464 | try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path)); | |
| 465 | }, | |
| 466 | .local_cache => { | |
| 467 | const path: Build.Cache.Path = .{ | |
| 468 | .root_dir = b.cache_root, | |
| 469 | .sub_path = sub_path_dirname, | |
| 470 | }; | |
| 471 | try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path)); | |
| 472 | }, | |
| 473 | } | |
| 474 | } | |
| 475 | }, | |
| 398 | 476 | else => {}, // ignore other messages |
| 399 | 477 | } |
| 400 | 478 | |
| ... | ... | @@ -542,19 +620,36 @@ pub fn allocPrintCmd2( |
| 542 | 620 | return buf.toOwnedSlice(arena); |
| 543 | 621 | } |
| 544 | 622 | |
| 545 | pub 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. | |
| 625 | pub fn cacheHit(s: *Step, man: *Build.Cache.Manifest) !bool { | |
| 546 | 626 | s.result_cached = man.hit() catch |err| return failWithCacheError(s, man, err); |
| 547 | 627 | return s.result_cached; |
| 548 | 628 | } |
| 549 | 629 | |
| 550 | fn 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`. | |
| 634 | pub 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 | ||
| 643 | fn failWithCacheError(s: *Step, man: *const Build.Cache.Manifest, err: anyerror) anyerror { | |
| 551 | 644 | const i = man.failed_file_index orelse return err; |
| 552 | 645 | const pp = man.files.keys()[i].prefixed_path; |
| 553 | 646 | const prefix = man.cache.prefixes()[pp.prefix].path orelse ""; |
| 554 | 647 | return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path }); |
| 555 | 648 | } |
| 556 | 649 | |
| 557 | pub 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. | |
| 652 | pub fn writeManifest(s: *Step, man: *Build.Cache.Manifest) !void { | |
| 558 | 653 | if (s.test_results.isSuccess()) { |
| 559 | 654 | man.writeManifest() catch |err| { |
| 560 | 655 | try s.addError("unable to write cache manifest: {s}", .{@errorName(err)}); |
| ... | ... | @@ -562,6 +657,142 @@ pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void { |
| 562 | 657 | } |
| 563 | 658 | } |
| 564 | 659 | |
| 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`. | |
| 664 | pub fn writeManifestAndWatch(s: *Step, man: *Build.Cache.Manifest) !void { | |
| 665 | try writeManifest(s, man); | |
| 666 | try setWatchInputsFromManifest(s, man); | |
| 667 | } | |
| 668 | ||
| 669 | fn 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`. | |
| 684 | pub fn singleUnchangingWatchInput(step: *Step, lazy_path: Build.LazyPath) Allocator.Error!void { | |
| 685 | if (!step.inputs.populated()) try step.addWatchInput(lazy_path); | |
| 686 | } | |
| 687 | ||
| 688 | pub 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. | |
| 694 | pub 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`. | |
| 719 | pub 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`. | |
| 746 | pub fn addDirectoryWatchInputFromPath(step: *Step, path: Build.Cache.Path) !void { | |
| 747 | return addWatchInputFromPath(step, path, "."); | |
| 748 | } | |
| 749 | ||
| 750 | fn 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 | ||
| 757 | fn 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 | ||
| 764 | fn 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 | ||
| 771 | fn 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. | |
| 786 | pub 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 | ||
| 565 | 796 | test { |
| 566 | 797 | _ = CheckFile; |
| 567 | 798 | _ = CheckObject; |
| ... | ... | @@ -577,4 +808,5 @@ test { |
| 577 | 808 | _ = Run; |
| 578 | 809 | _ = TranslateC; |
| 579 | 810 | _ = WriteFile; |
| 811 | _ = UpdateSourceFiles; | |
| 580 | 812 | } |
lib/std/Build/Step/CheckFile.zig+1| ... | ... | @@ -50,6 +50,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 50 | 50 | _ = prog_node; |
| 51 | 51 | const b = step.owner; |
| 52 | 52 | const check_file: *CheckFile = @fieldParentPtr("step", step); |
| 53 | try step.singleUnchangingWatchInput(check_file.source); | |
| 53 | 54 | |
| 54 | 55 | const src_path = check_file.source.getPath2(b, step); |
| 55 | 56 | const contents = fs.cwd().readFileAlloc(b.allocator, src_path, check_file.max_bytes) catch |err| { |
lib/std/Build/Step/CheckObject.zig+1| ... | ... | @@ -555,6 +555,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 555 | 555 | const b = step.owner; |
| 556 | 556 | const gpa = b.allocator; |
| 557 | 557 | const check_object: *CheckObject = @fieldParentPtr("step", step); |
| 558 | try step.singleUnchangingWatchInput(check_object.source); | |
| 558 | 559 | |
| 559 | 560 | const src_path = check_object.source.getPath2(b, step); |
| 560 | 561 | const contents = fs.cwd().readFileAllocOptions( |
lib/std/Build/Step/ConfigHeader.zig+2| ... | ... | @@ -168,6 +168,8 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 168 | 168 | _ = prog_node; |
| 169 | 169 | const b = step.owner; |
| 170 | 170 | const config_header: *ConfigHeader = @fieldParentPtr("step", step); |
| 171 | if (config_header.style.getPath()) |lp| try step.singleUnchangingWatchInput(lp); | |
| 172 | ||
| 171 | 173 | const gpa = b.allocator; |
| 172 | 174 | const arena = b.allocator; |
| 173 | 175 |
lib/std/Build/Step/InstallDir.zig+16-11| ... | ... | @@ -41,7 +41,6 @@ pub const Options = struct { |
| 41 | 41 | }; |
| 42 | 42 | |
| 43 | 43 | pub fn create(owner: *std.Build, options: Options) *InstallDir { |
| 44 | owner.pushInstalledFile(options.install_dir, options.install_subdir); | |
| 45 | 44 | const install_dir = owner.allocator.create(InstallDir) catch @panic("OOM"); |
| 46 | 45 | install_dir.* = .{ |
| 47 | 46 | .step = Step.init(.{ |
| ... | ... | @@ -60,12 +59,14 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 60 | 59 | _ = prog_node; |
| 61 | 60 | const b = step.owner; |
| 62 | 61 | const install_dir: *InstallDir = @fieldParentPtr("step", step); |
| 62 | step.clearWatchInputs(); | |
| 63 | 63 | const arena = b.allocator; |
| 64 | 64 | const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir); |
| 65 | const src_dir_path = install_dir.options.source_dir.getPath2(b, step); | |
| 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), | |
| 69 | 70 | }); |
| 70 | 71 | }; |
| 71 | 72 | defer src_dir.close(); |
| ... | ... | @@ -89,12 +90,16 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 89 | 90 | } |
| 90 | 91 | |
| 91 | 92 | // relative to src build root |
| 92 | const src_sub_path = b.pathJoin(&.{ src_dir_path, entry.path }); | |
| 93 | const src_sub_path = try src_dir_path.join(arena, entry.path); | |
| 93 | 94 | const dest_path = b.pathJoin(&.{ dest_prefix, entry.path }); |
| 94 | 95 | const cwd = fs.cwd(); |
| 95 | 96 | |
| 96 | 97 | switch (entry.kind) { |
| 97 | .directory => try cwd.makePath(dest_path), | |
| 98 | .directory => { | |
| 99 | if (need_derived_inputs) try step.addDirectoryWatchInputFromPath(src_sub_path); | |
| 100 | try cwd.makePath(dest_path); | |
| 101 | // TODO: set result_cached=false if the directory did not already exist. | |
| 102 | }, | |
| 98 | 103 | .file => { |
| 99 | 104 | for (install_dir.options.blank_extensions) |ext| { |
| 100 | 105 | if (mem.endsWith(u8, entry.path, ext)) { |
| ... | ... | @@ -104,14 +109,14 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 104 | 109 | } |
| 105 | 110 | |
| 106 | 111 | 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, | |
| 109 | 114 | cwd, |
| 110 | 115 | dest_path, |
| 111 | 116 | .{}, |
| 112 | 117 | ) 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), | |
| 115 | 120 | }); |
| 116 | 121 | }; |
| 117 | 122 | all_cached = all_cached and prev_status == .fresh; |
lib/std/Build/Step/InstallFile.zig+2-1| ... | ... | @@ -19,7 +19,6 @@ pub fn create( |
| 19 | 19 | dest_rel_path: []const u8, |
| 20 | 20 | ) *InstallFile { |
| 21 | 21 | assert(dest_rel_path.len != 0); |
| 22 | owner.pushInstalledFile(dir, dest_rel_path); | |
| 23 | 22 | const install_file = owner.allocator.create(InstallFile) catch @panic("OOM"); |
| 24 | 23 | install_file.* = .{ |
| 25 | 24 | .step = Step.init(.{ |
| ... | ... | @@ -40,6 +39,8 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 40 | 39 | _ = prog_node; |
| 41 | 40 | const b = step.owner; |
| 42 | 41 | const install_file: *InstallFile = @fieldParentPtr("step", step); |
| 42 | try step.singleUnchangingWatchInput(install_file.source); | |
| 43 | ||
| 43 | 44 | const full_src_path = install_file.source.getPath2(b, step); |
| 44 | 45 | const full_dest_path = b.getInstallPath(install_file.dir, install_file.dest_rel_path); |
| 45 | 46 | const cwd = std.fs.cwd(); |
lib/std/Build/Step/ObjCopy.zig+1-4| ... | ... | @@ -93,14 +93,11 @@ pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath { |
| 93 | 93 | fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 94 | 94 | const b = step.owner; |
| 95 | 95 | const objcopy: *ObjCopy = @fieldParentPtr("step", step); |
| 96 | try step.singleUnchangingWatchInput(objcopy.input_file); | |
| 96 | 97 | |
| 97 | 98 | var man = b.graph.cache.obtain(); |
| 98 | 99 | defer man.deinit(); |
| 99 | 100 | |
| 100 | // Random bytes to make ObjCopy unique. Refresh this with new random | |
| 101 | // bytes when ObjCopy implementation is modified incompatibly. | |
| 102 | man.hash.add(@as(u32, 0xe18b7baf)); | |
| 103 | ||
| 104 | 101 | const full_src_path = objcopy.input_file.getPath2(b, step); |
| 105 | 102 | _ = try man.addFile(full_src_path, null); |
| 106 | 103 | man.hash.addOptionalBytes(objcopy.only_section); |
lib/std/Build/Step/Options.zig+4| ... | ... | @@ -424,6 +424,9 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 424 | 424 | item.path.getPath2(b, step), |
| 425 | 425 | ); |
| 426 | 426 | } |
| 427 | if (!step.inputs.populated()) for (options.args.items) |item| { | |
| 428 | try step.addWatchInput(item.path); | |
| 429 | }; | |
| 427 | 430 | |
| 428 | 431 | const basename = "options.zig"; |
| 429 | 432 | |
| ... | ... | @@ -520,6 +523,7 @@ test Options { |
| 520 | 523 | .query = .{}, |
| 521 | 524 | .result = try std.zig.system.resolveTargetQuery(.{}), |
| 522 | 525 | }, |
| 526 | .zig_lib_directory = std.Build.Cache.Directory.cwd(), | |
| 523 | 527 | }; |
| 524 | 528 | |
| 525 | 529 | var builder = try std.Build.create( |
lib/std/Build/Step/RemoveDir.zig+13-7| ... | ... | @@ -2,22 +2,23 @@ const std = @import("std"); |
| 2 | 2 | const fs = std.fs; |
| 3 | 3 | const Step = std.Build.Step; |
| 4 | 4 | const RemoveDir = @This(); |
| 5 | const LazyPath = std.Build.LazyPath; | |
| 5 | 6 | |
| 6 | 7 | pub const base_id: Step.Id = .remove_dir; |
| 7 | 8 | |
| 8 | 9 | step: Step, |
| 9 | dir_path: []const u8, | |
| 10 | doomed_path: LazyPath, | |
| 10 | 11 | |
| 11 | pub fn create(owner: *std.Build, dir_path: []const u8) *RemoveDir { | |
| 12 | pub fn create(owner: *std.Build, doomed_path: LazyPath) *RemoveDir { | |
| 12 | 13 | const remove_dir = owner.allocator.create(RemoveDir) catch @panic("OOM"); |
| 13 | 14 | remove_dir.* = .{ |
| 14 | 15 | .step = Step.init(.{ |
| 15 | 16 | .id = base_id, |
| 16 | .name = owner.fmt("RemoveDir {s}", .{dir_path}), | |
| 17 | .name = owner.fmt("RemoveDir {s}", .{doomed_path.getDisplayName()}), | |
| 17 | 18 | .owner = owner, |
| 18 | 19 | .makeFn = make, |
| 19 | 20 | }), |
| 20 | .dir_path = owner.dupePath(dir_path), | |
| 21 | .doomed_path = doomed_path.dupe(owner), | |
| 21 | 22 | }; |
| 22 | 23 | return remove_dir; |
| 23 | 24 | } |
| ... | ... | @@ -30,14 +31,19 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 30 | 31 | const b = step.owner; |
| 31 | 32 | const remove_dir: *RemoveDir = @fieldParentPtr("step", step); |
| 32 | 33 | |
| 33 | b.build_root.handle.deleteTree(remove_dir.dir_path) catch |err| { | |
| 34 | step.clearWatchInputs(); | |
| 35 | try step.addWatchInput(remove_dir.doomed_path); | |
| 36 | ||
| 37 | const full_doomed_path = remove_dir.doomed_path.getPath2(b, step); | |
| 38 | ||
| 39 | b.build_root.handle.deleteTree(full_doomed_path) catch |err| { | |
| 34 | 40 | if (b.build_root.path) |base| { |
| 35 | 41 | return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{ |
| 36 | base, remove_dir.dir_path, @errorName(err), | |
| 42 | base, full_doomed_path, @errorName(err), | |
| 37 | 43 | }); |
| 38 | 44 | } else { |
| 39 | 45 | return step.fail("unable to recursively delete path '{s}': {s}", .{ |
| 40 | remove_dir.dir_path, @errorName(err), | |
| 46 | full_doomed_path, @errorName(err), | |
| 41 | 47 | }); |
| 42 | 48 | } |
| 43 | 49 | }; |
lib/std/Build/Step/Run.zig+4-4| ... | ... | @@ -632,7 +632,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 632 | 632 | // On Windows we don't have rpaths so we have to add .dll search paths to PATH |
| 633 | 633 | run.addPathForDynLibs(artifact); |
| 634 | 634 | } |
| 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.?; | |
| 636 | 636 | |
| 637 | 637 | try argv_list.append(b.fmt("{s}{s}", .{ pa.prefix, file_path })); |
| 638 | 638 | |
| ... | ... | @@ -682,7 +682,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 682 | 682 | _ = try man.addFile(lazy_path.getPath2(b, step), null); |
| 683 | 683 | } |
| 684 | 684 | |
| 685 | if (!has_side_effects and try step.cacheHit(&man)) { | |
| 685 | if (!has_side_effects and try step.cacheHitAndWatch(&man)) { | |
| 686 | 686 | // cache hit, skip running command |
| 687 | 687 | const digest = man.final(); |
| 688 | 688 | |
| ... | ... | @@ -736,7 +736,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 736 | 736 | } |
| 737 | 737 | |
| 738 | 738 | try runCommand(run, argv_list.items, has_side_effects, output_dir_path, prog_node); |
| 739 | if (!has_side_effects) try step.writeManifest(&man); | |
| 739 | if (!has_side_effects) try step.writeManifestAndWatch(&man); | |
| 740 | 740 | return; |
| 741 | 741 | }; |
| 742 | 742 | |
| ... | ... | @@ -812,7 +812,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 812 | 812 | }; |
| 813 | 813 | } |
| 814 | 814 | |
| 815 | if (!has_side_effects) try step.writeManifest(&man); | |
| 815 | if (!has_side_effects) try step.writeManifestAndWatch(&man); | |
| 816 | 816 | |
| 817 | 817 | try populateGeneratedPaths( |
| 818 | 818 | arena, |
lib/std/Build/Step/UpdateSourceFiles.zig created+114| ... | ... | @@ -0,0 +1,114 @@ |
| 1 | //! Writes data to paths relative to the package root, effectively mutating the | |
| 2 | //! package's source files. Be careful with the latter functionality; it should | |
| 3 | //! not be used during the normal build process, but as a utility run by a | |
| 4 | //! developer with intention to update source files, which will then be | |
| 5 | //! committed to version control. | |
| 6 | const std = @import("std"); | |
| 7 | const Step = std.Build.Step; | |
| 8 | const fs = std.fs; | |
| 9 | const ArrayList = std.ArrayList; | |
| 10 | const UpdateSourceFiles = @This(); | |
| 11 | ||
| 12 | step: Step, | |
| 13 | output_source_files: std.ArrayListUnmanaged(OutputSourceFile), | |
| 14 | ||
| 15 | pub const base_id: Step.Id = .update_source_files; | |
| 16 | ||
| 17 | pub const OutputSourceFile = struct { | |
| 18 | contents: Contents, | |
| 19 | sub_path: []const u8, | |
| 20 | }; | |
| 21 | ||
| 22 | pub const Contents = union(enum) { | |
| 23 | bytes: []const u8, | |
| 24 | copy: std.Build.LazyPath, | |
| 25 | }; | |
| 26 | ||
| 27 | pub 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. | |
| 47 | pub 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. | |
| 62 | pub 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 | ||
| 70 | fn 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. | |
| 11 | 4 | const std = @import("std"); |
| 12 | 5 | const Step = std.Build.Step; |
| 13 | 6 | const fs = std.fs; |
| ... | ... | @@ -19,8 +12,6 @@ step: Step, |
| 19 | 12 | // The elements here are pointers because we need stable pointers for the GeneratedFile field. |
| 20 | 13 | files: std.ArrayListUnmanaged(File), |
| 21 | 14 | directories: std.ArrayListUnmanaged(Directory), |
| 22 | ||
| 23 | output_source_files: std.ArrayListUnmanaged(OutputSourceFile), | |
| 24 | 15 | generated_directory: std.Build.GeneratedFile, |
| 25 | 16 | |
| 26 | 17 | pub const base_id: Step.Id = .write_file; |
| ... | ... | @@ -49,12 +40,23 @@ pub const Directory = struct { |
| 49 | 40 | .include_extensions = if (opts.include_extensions) |incs| b.dupeStrings(incs) else null, |
| 50 | 41 | }; |
| 51 | 42 | } |
| 52 | }; | |
| 53 | }; | |
| 54 | 43 | |
| 55 | pub 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 | }; | |
| 58 | 60 | }; |
| 59 | 61 | |
| 60 | 62 | pub const Contents = union(enum) { |
| ... | ... | @@ -73,7 +75,6 @@ pub fn create(owner: *std.Build) *WriteFile { |
| 73 | 75 | }), |
| 74 | 76 | .files = .{}, |
| 75 | 77 | .directories = .{}, |
| 76 | .output_source_files = .{}, | |
| 77 | 78 | .generated_directory = .{ .step = &write_file.step }, |
| 78 | 79 | }; |
| 79 | 80 | return write_file; |
| ... | ... | @@ -150,33 +151,6 @@ pub fn addCopyDirectory( |
| 150 | 151 | }; |
| 151 | 152 | } |
| 152 | 153 | |
| 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. | |
| 158 | pub 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. | |
| 172 | pub fn addBytesToSource(write_file: *WriteFile, bytes: []const u8, sub_path: []const u8) void { | |
| 173 | const b = write_file.step.owner; | |
| 174 | write_file.output_source_files.append(b.allocator, .{ | |
| 175 | .contents = .{ .bytes = bytes }, | |
| 176 | .sub_path = sub_path, | |
| 177 | }) catch @panic("OOM"); | |
| 178 | } | |
| 179 | ||
| 180 | 154 | /// Returns a `LazyPath` representing the base directory that contains all the |
| 181 | 155 | /// files from this `WriteFile`. |
| 182 | 156 | pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath { |
| ... | ... | @@ -200,47 +174,10 @@ fn maybeUpdateName(write_file: *WriteFile) void { |
| 200 | 174 | fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 201 | 175 | _ = prog_node; |
| 202 | 176 | const b = step.owner; |
| 177 | const arena = b.allocator; | |
| 178 | const gpa = arena; | |
| 203 | 179 | 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(); | |
| 244 | 181 | |
| 245 | 182 | // The cache is used here not really as a way to speed things up - because writing |
| 246 | 183 | // the data to a file would probably be very fast - but as a way to find a canonical |
| ... | ... | @@ -252,39 +189,73 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 252 | 189 | var man = b.graph.cache.obtain(); |
| 253 | 190 | defer man.deinit(); |
| 254 | 191 | |
| 255 | // Random bytes to make WriteFile unique. Refresh this with | |
| 256 | // new random bytes when WriteFile implementation is modified | |
| 257 | // in a non-backwards-compatible way. | |
| 258 | man.hash.add(@as(u32, 0xd767ee59)); | |
| 259 | ||
| 260 | 192 | for (write_file.files.items) |file| { |
| 261 | 193 | man.hash.addBytes(file.sub_path); |
| 194 | ||
| 262 | 195 | switch (file.contents) { |
| 263 | 196 | .bytes => |bytes| { |
| 264 | 197 | man.hash.addBytes(bytes); |
| 265 | 198 | }, |
| 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); | |
| 268 | 203 | }, |
| 269 | 204 | } |
| 270 | 205 | } |
| 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| { | |
| 273 | 212 | man.hash.addBytes(dir.sub_path); |
| 274 | 213 | for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext); |
| 275 | 214 | if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc); |
| 215 | ||
| 216 | const need_derived_inputs = try step.addDirectoryWatchInput(dir.source); | |
| 217 | const src_dir_path = dir.source.getPath3(b, step); | |
| 218 | ||
| 219 | var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { | |
| 220 | return step.fail("unable to open source directory '{}': {s}", .{ | |
| 221 | src_dir_path, @errorName(err), | |
| 222 | }); | |
| 223 | }; | |
| 224 | open_dir_cache_elem.* = src_dir; | |
| 225 | open_dirs_count += 1; | |
| 226 | ||
| 227 | var it = try src_dir.walk(gpa); | |
| 228 | defer it.deinit(); | |
| 229 | while (try it.next()) |entry| { | |
| 230 | if (!dir.options.pathIncluded(entry.path)) continue; | |
| 231 | ||
| 232 | switch (entry.kind) { | |
| 233 | .directory => { | |
| 234 | if (need_derived_inputs) { | |
| 235 | const entry_path = try src_dir_path.join(arena, entry.path); | |
| 236 | try step.addDirectoryWatchInputFromPath(entry_path); | |
| 237 | } | |
| 238 | }, | |
| 239 | .file => { | |
| 240 | const entry_path = try src_dir_path.join(arena, entry.path); | |
| 241 | _ = try man.addFilePath(entry_path, null); | |
| 242 | }, | |
| 243 | else => continue, | |
| 244 | } | |
| 245 | } | |
| 276 | 246 | } |
| 277 | 247 | |
| 278 | 248 | if (try step.cacheHit(&man)) { |
| 279 | 249 | const digest = man.final(); |
| 280 | write_file.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); | |
| 250 | write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest }); | |
| 251 | step.result_cached = true; | |
| 281 | 252 | return; |
| 282 | 253 | } |
| 283 | 254 | |
| 284 | 255 | const digest = man.final(); |
| 285 | 256 | const cache_path = "o" ++ fs.path.sep_str ++ digest; |
| 286 | 257 | |
| 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 }); | |
| 288 | 259 | |
| 289 | 260 | var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { |
| 290 | 261 | return step.fail("unable to make path '{}{s}': {s}", .{ |
| ... | ... | @@ -337,8 +308,9 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 337 | 308 | }, |
| 338 | 309 | } |
| 339 | 310 | } |
| 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); | |
| 342 | 314 | const dest_dirname = dir.sub_path; |
| 343 | 315 | |
| 344 | 316 | if (dest_dirname.len != 0) { |
| ... | ... | @@ -349,44 +321,25 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 349 | 321 | }; |
| 350 | 322 | } |
| 351 | 323 | |
| 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; | |
| 358 | 328 | |
| 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); | |
| 372 | 330 | const dest_path = b.pathJoin(&.{ dest_dirname, entry.path }); |
| 373 | 331 | switch (entry.kind) { |
| 374 | 332 | .directory => try cache_dir.makePath(dest_path), |
| 375 | 333 | .file => { |
| 376 | 334 | 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, | |
| 379 | 337 | cache_dir, |
| 380 | 338 | dest_path, |
| 381 | 339 | .{}, |
| 382 | 340 | ) 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), | |
| 390 | 343 | }); |
| 391 | 344 | }; |
| 392 | 345 | _ = prev_status; |
| ... | ... | @@ -398,3 +351,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void { |
| 398 | 351 | |
| 399 | 352 | try step.writeManifest(&man); |
| 400 | 353 | } |
| 354 | ||
| 355 | fn closeDirs(dirs: []fs.Dir) void { | |
| 356 | for (dirs) |*d| d.close(); | |
| 357 | } |
lib/std/Build/Watch.zig created+363| ... | ... | @@ -0,0 +1,363 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const std = @import("../std.zig"); | |
| 3 | const Watch = @This(); | |
| 4 | const Step = std.Build.Step; | |
| 5 | const Allocator = std.mem.Allocator; | |
| 6 | const assert = std.debug.assert; | |
| 7 | const fatal = std.zig.fatal; | |
| 8 | ||
| 9 | dir_table: DirTable, | |
| 10 | os: Os, | |
| 11 | generation: 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. | |
| 17 | const DirTable = std.ArrayHashMapUnmanaged(Cache.Path, void, Cache.Path.TableAdapter, false); | |
| 18 | ||
| 19 | /// Special key of "." means any changes in this directory trigger the steps. | |
| 20 | const ReactionSet = std.StringArrayHashMapUnmanaged(StepSet); | |
| 21 | const StepSet = std.AutoArrayHashMapUnmanaged(*Step, Generation); | |
| 22 | ||
| 23 | const Generation = u8; | |
| 24 | ||
| 25 | const Hash = std.hash.Wyhash; | |
| 26 | const Cache = std.Build.Cache; | |
| 27 | ||
| 28 | const 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 | ||
| 243 | pub 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 | ||
| 277 | pub 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 | ||
| 300 | fn 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 | ||
| 310 | fn 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 | ||
| 321 | pub 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 | ||
| 328 | pub 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 | ||
| 340 | pub 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 | ||
| 350 | pub fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult { | |
| 351 | switch (builtin.os.tag) { | |
| 352 | .linux => { | |
| 353 | const events_len = try std.posix.poll(&w.os.poll_fds, timeout.to_i32_ms()); | |
| 354 | return if (events_len == 0) | |
| 355 | .timeout | |
| 356 | else if (try Os.markDirtySteps(w, gpa)) | |
| 357 | .dirty | |
| 358 | else | |
| 359 | .clean; | |
| 360 | }, | |
| 361 | else => @compileError("unimplemented"), | |
| 362 | } | |
| 363 | } |
lib/std/os/linux.zig+182-52| ... | ... | @@ -698,12 +698,42 @@ pub fn inotify_rm_watch(fd: i32, wd: i32) usize { |
| 698 | 698 | return syscall2(.inotify_rm_watch, @as(usize, @bitCast(@as(isize, fd))), @as(usize, @bitCast(@as(isize, wd)))); |
| 699 | 699 | } |
| 700 | 700 | |
| 701 | pub fn fanotify_init(flags: u32, event_f_flags: u32) usize { | |
| 702 | return syscall2(.fanotify_init, flags, event_f_flags); | |
| 701 | pub fn fanotify_init(flags: fanotify.InitFlags, event_f_flags: u32) usize { | |
| 702 | return syscall2(.fanotify_init, @as(u32, @bitCast(flags)), event_f_flags); | |
| 703 | 703 | } |
| 704 | 704 | |
| 705 | pub 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)); | |
| 705 | pub 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 | ||
| 722 | pub fn name_to_handle_at( | |
| 723 | dirfd: fd_t, | |
| 724 | pathname: [*:0]const u8, | |
| 725 | handle: *std.os.linux.file_handle, | |
| 726 | mount_id: *i32, | |
| 727 | flags: u32, | |
| 728 | ) usize { | |
| 729 | return syscall5( | |
| 730 | .name_to_handle_at, | |
| 731 | @as(u32, @bitCast(dirfd)), | |
| 732 | @intFromPtr(pathname), | |
| 733 | @intFromPtr(handle), | |
| 734 | @intFromPtr(mount_id), | |
| 735 | flags, | |
| 736 | ); | |
| 707 | 737 | } |
| 708 | 738 | |
| 709 | 739 | pub fn readlink(noalias path: [*:0]const u8, noalias buf_ptr: [*]u8, buf_len: usize) usize { |
| ... | ... | @@ -2916,6 +2946,8 @@ pub const AT = struct { |
| 2916 | 2946 | |
| 2917 | 2947 | /// Apply to the entire subtree |
| 2918 | 2948 | pub const RECURSIVE = 0x8000; |
| 2949 | ||
| 2950 | pub const HANDLE_FID = REMOVEDIR; | |
| 2919 | 2951 | }; |
| 2920 | 2952 | |
| 2921 | 2953 | pub const FALLOC = struct { |
| ... | ... | @@ -4135,58 +4167,156 @@ pub const IN = struct { |
| 4135 | 4167 | pub const ONESHOT = 0x80000000; |
| 4136 | 4168 | }; |
| 4137 | 4169 | |
| 4138 | pub 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 | ||
| 4175 | pub 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, | |
| 4170 | pub 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 | }; | |
| 4183 | 4309 | }; |
| 4184 | 4310 | |
| 4185 | pub const fanotify_response = extern struct { | |
| 4186 | fd: i32, | |
| 4187 | response: u32, | |
| 4311 | pub const file_handle = extern struct { | |
| 4312 | handle_bytes: u32, | |
| 4313 | handle_type: i32, | |
| 4314 | f_handle: [0]u8, | |
| 4188 | 4315 | }; |
| 4189 | 4316 | |
| 4317 | pub const kernel_fsid_t = fsid_t; | |
| 4318 | pub const fsid_t = [2]i32; | |
| 4319 | ||
| 4190 | 4320 | pub const S = struct { |
| 4191 | 4321 | pub const IFMT = 0o170000; |
| 4192 | 4322 |
lib/std/posix.zig+55-5| ... | ... | @@ -4501,7 +4501,7 @@ pub const FanotifyInitError = error{ |
| 4501 | 4501 | PermissionDenied, |
| 4502 | 4502 | } || UnexpectedError; |
| 4503 | 4503 | |
| 4504 | pub fn fanotify_init(flags: u32, event_f_flags: u32) FanotifyInitError!i32 { | |
| 4504 | pub fn fanotify_init(flags: std.os.linux.fanotify.InitFlags, event_f_flags: u32) FanotifyInitError!i32 { | |
| 4505 | 4505 | const rc = system.fanotify_init(flags, event_f_flags); |
| 4506 | 4506 | switch (errno(rc)) { |
| 4507 | 4507 | .SUCCESS => return @intCast(rc), |
| ... | ... | @@ -4530,16 +4530,28 @@ pub const FanotifyMarkError = error{ |
| 4530 | 4530 | NameTooLong, |
| 4531 | 4531 | } || UnexpectedError; |
| 4532 | 4532 | |
| 4533 | pub fn fanotify_mark(fanotify_fd: i32, flags: u32, mask: u64, dirfd: i32, pathname: ?[]const u8) FanotifyMarkError!void { | |
| 4533 | pub fn fanotify_mark( | |
| 4534 | fanotify_fd: fd_t, | |
| 4535 | flags: std.os.linux.fanotify.MarkFlags, | |
| 4536 | mask: std.os.linux.fanotify.MarkMask, | |
| 4537 | dirfd: fd_t, | |
| 4538 | pathname: ?[]const u8, | |
| 4539 | ) FanotifyMarkError!void { | |
| 4534 | 4540 | if (pathname) |path| { |
| 4535 | 4541 | const path_c = try toPosixPath(path); |
| 4536 | 4542 | return fanotify_markZ(fanotify_fd, flags, mask, dirfd, &path_c); |
| 4543 | } else { | |
| 4544 | return fanotify_markZ(fanotify_fd, flags, mask, dirfd, null); | |
| 4537 | 4545 | } |
| 4538 | ||
| 4539 | return fanotify_markZ(fanotify_fd, flags, mask, dirfd, null); | |
| 4540 | 4546 | } |
| 4541 | 4547 | |
| 4542 | pub fn fanotify_markZ(fanotify_fd: i32, flags: u32, mask: u64, dirfd: i32, pathname: ?[*:0]const u8) FanotifyMarkError!void { | |
| 4548 | pub fn fanotify_markZ( | |
| 4549 | fanotify_fd: fd_t, | |
| 4550 | flags: std.os.linux.fanotify.MarkFlags, | |
| 4551 | mask: std.os.linux.fanotify.MarkMask, | |
| 4552 | dirfd: fd_t, | |
| 4553 | pathname: ?[*:0]const u8, | |
| 4554 | ) FanotifyMarkError!void { | |
| 4543 | 4555 | const rc = system.fanotify_mark(fanotify_fd, flags, mask, dirfd, pathname); |
| 4544 | 4556 | switch (errno(rc)) { |
| 4545 | 4557 | .SUCCESS => return, |
| ... | ... | @@ -7274,6 +7286,44 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, signal: usize) PtraceError! |
| 7274 | 7286 | }; |
| 7275 | 7287 | } |
| 7276 | 7288 | |
| 7289 | pub const NameToFileHandleAtError = error{ | |
| 7290 | FileNotFound, | |
| 7291 | NotDir, | |
| 7292 | OperationNotSupported, | |
| 7293 | NameTooLong, | |
| 7294 | Unexpected, | |
| 7295 | }; | |
| 7296 | ||
| 7297 | pub 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 | ||
| 7308 | pub 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 | ||
| 7277 | 7327 | pub const IoCtl_SIOCGIFINDEX_Error = error{ |
| 7278 | 7328 | FileSystem, |
| 7279 | 7329 | InterfaceNotFound, |
lib/std/zig/Server.zig+15-1| ... | ... | @@ -20,10 +20,24 @@ pub const Message = struct { |
| 20 | 20 | test_metadata, |
| 21 | 21 | /// Body is a TestResults |
| 22 | 22 | test_results, |
| 23 | /// Body is a series of strings, delimited by null bytes. | |
| 24 | /// Each string is a prefixed file path. | |
| 25 | /// The first byte indicates the file prefix path (see prefixes fields | |
| 26 | /// of Cache). This byte is sent over the wire incremented so that null | |
| 27 | /// bytes are not confused with string terminators. | |
| 28 | /// The remaining bytes is the file path relative to that prefix. | |
| 29 | /// The prefixes are hard-coded in Compilation.create (cwd, zig lib dir, local cache dir) | |
| 30 | file_system_inputs, | |
| 23 | 31 | |
| 24 | 32 | _, |
| 25 | 33 | }; |
| 26 | 34 | |
| 35 | pub const PathPrefix = enum(u8) { | |
| 36 | cwd, | |
| 37 | zig_lib, | |
| 38 | local_cache, | |
| 39 | }; | |
| 40 | ||
| 27 | 41 | /// Trailing: |
| 28 | 42 | /// * extra: [extra_len]u32, |
| 29 | 43 | /// * string_bytes: [string_bytes_len]u8, |
| ... | ... | @@ -58,7 +72,7 @@ pub const Message = struct { |
| 58 | 72 | }; |
| 59 | 73 | |
| 60 | 74 | /// Trailing: |
| 61 | /// * the file system path the emitted binary can be found | |
| 75 | /// * file system path where the emitted binary can be found | |
| 62 | 76 | pub const EmitBinPath = extern struct { |
| 63 | 77 | flags: Flags, |
| 64 | 78 |
src/Compilation.zig+71-1| ... | ... | @@ -235,6 +235,8 @@ astgen_wait_group: WaitGroup = .{}, |
| 235 | 235 | |
| 236 | 236 | llvm_opt_bisect_limit: c_int, |
| 237 | 237 | |
| 238 | file_system_inputs: ?*std.ArrayListUnmanaged(u8), | |
| 239 | ||
| 238 | 240 | pub const Emit = struct { |
| 239 | 241 | /// Where the output will go. |
| 240 | 242 | directory: Directory, |
| ... | ... | @@ -1157,6 +1159,9 @@ pub const CreateOptions = struct { |
| 1157 | 1159 | error_limit: ?Zcu.ErrorInt = null, |
| 1158 | 1160 | global_cc_argv: []const []const u8 = &.{}, |
| 1159 | 1161 | |
| 1162 | /// Tracks all files that can cause the Compilation to be invalidated and need a rebuild. | |
| 1163 | file_system_inputs: ?*std.ArrayListUnmanaged(u8) = null, | |
| 1164 | ||
| 1160 | 1165 | pub const Entry = link.File.OpenOptions.Entry; |
| 1161 | 1166 | }; |
| 1162 | 1167 | |
| ... | ... | @@ -1332,6 +1337,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1332 | 1337 | .gpa = gpa, |
| 1333 | 1338 | .manifest_dir = try options.local_cache_directory.handle.makeOpenPath("h", .{}), |
| 1334 | 1339 | }; |
| 1340 | // These correspond to std.zig.Server.Message.PathPrefix. | |
| 1335 | 1341 | cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() }); |
| 1336 | 1342 | cache.addPrefix(options.zig_lib_directory); |
| 1337 | 1343 | cache.addPrefix(options.local_cache_directory); |
| ... | ... | @@ -1508,6 +1514,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1508 | 1514 | .force_undefined_symbols = options.force_undefined_symbols, |
| 1509 | 1515 | .link_eh_frame_hdr = link_eh_frame_hdr, |
| 1510 | 1516 | .global_cc_argv = options.global_cc_argv, |
| 1517 | .file_system_inputs = options.file_system_inputs, | |
| 1511 | 1518 | }; |
| 1512 | 1519 | |
| 1513 | 1520 | // Prevent some footguns by making the "any" fields of config reflect |
| ... | ... | @@ -2044,6 +2051,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { |
| 2044 | 2051 | ); |
| 2045 | 2052 | }; |
| 2046 | 2053 | if (is_hit) { |
| 2054 | // In this case the cache hit contains the full set of file system inputs. Nice! | |
| 2055 | if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf); | |
| 2056 | ||
| 2047 | 2057 | comp.last_update_was_cache_hit = true; |
| 2048 | 2058 | log.debug("CacheMode.whole cache hit for {s}", .{comp.root_name}); |
| 2049 | 2059 | const digest = man.final(); |
| ... | ... | @@ -2103,12 +2113,24 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { |
| 2103 | 2113 | .incremental => {}, |
| 2104 | 2114 | } |
| 2105 | 2115 | |
| 2116 | // From this point we add a preliminary set of file system inputs that | |
| 2117 | // affects both incremental and whole cache mode. For incremental cache | |
| 2118 | // mode, the long-lived compiler state will track additional file system | |
| 2119 | // inputs discovered after this point. For whole cache mode, we rely on | |
| 2120 | // these inputs to make it past AstGen, and once there, we can rely on | |
| 2121 | // learning file system inputs from the Cache object. | |
| 2122 | ||
| 2106 | 2123 | // For compiling C objects, we rely on the cache hash system to avoid duplicating work. |
| 2107 | 2124 | // Add a Job for each C object. |
| 2108 | 2125 | try comp.c_object_work_queue.ensureUnusedCapacity(comp.c_object_table.count()); |
| 2109 | 2126 | for (comp.c_object_table.keys()) |key| { |
| 2110 | 2127 | comp.c_object_work_queue.writeItemAssumeCapacity(key); |
| 2111 | 2128 | } |
| 2129 | if (comp.file_system_inputs) |fsi| { | |
| 2130 | for (comp.c_object_table.keys()) |c_object| { | |
| 2131 | try comp.appendFileSystemInput(fsi, c_object.src.owner.root, c_object.src.src_path); | |
| 2132 | } | |
| 2133 | } | |
| 2112 | 2134 | |
| 2113 | 2135 | // For compiling Win32 resources, we rely on the cache hash system to avoid duplicating work. |
| 2114 | 2136 | // Add a Job for each Win32 resource file. |
| ... | ... | @@ -2117,6 +2139,12 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { |
| 2117 | 2139 | for (comp.win32_resource_table.keys()) |key| { |
| 2118 | 2140 | comp.win32_resource_work_queue.writeItemAssumeCapacity(key); |
| 2119 | 2141 | } |
| 2142 | if (comp.file_system_inputs) |fsi| { | |
| 2143 | for (comp.win32_resource_table.keys()) |win32_resource| switch (win32_resource.src) { | |
| 2144 | .rc => |f| try comp.appendFileSystemInput(fsi, f.owner.root, f.src_path), | |
| 2145 | .manifest => continue, | |
| 2146 | }; | |
| 2147 | } | |
| 2120 | 2148 | } |
| 2121 | 2149 | |
| 2122 | 2150 | if (comp.module) |zcu| { |
| ... | ... | @@ -2151,12 +2179,25 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { |
| 2151 | 2179 | if (zcu.fileByIndex(file_index).mod.isBuiltin()) continue; |
| 2152 | 2180 | comp.astgen_work_queue.writeItemAssumeCapacity(file_index); |
| 2153 | 2181 | } |
| 2182 | if (comp.file_system_inputs) |fsi| { | |
| 2183 | for (zcu.import_table.values()) |file_index| { | |
| 2184 | const file = zcu.fileByIndex(file_index); | |
| 2185 | try comp.appendFileSystemInput(fsi, file.mod.root, file.sub_file_path); | |
| 2186 | } | |
| 2187 | } | |
| 2154 | 2188 | |
| 2155 | 2189 | // Put a work item in for checking if any files used with `@embedFile` changed. |
| 2156 | 2190 | try comp.embed_file_work_queue.ensureUnusedCapacity(zcu.embed_table.count()); |
| 2157 | 2191 | for (zcu.embed_table.values()) |embed_file| { |
| 2158 | 2192 | comp.embed_file_work_queue.writeItemAssumeCapacity(embed_file); |
| 2159 | 2193 | } |
| 2194 | if (comp.file_system_inputs) |fsi| { | |
| 2195 | const ip = &zcu.intern_pool; | |
| 2196 | for (zcu.embed_table.values()) |embed_file| { | |
| 2197 | const sub_file_path = embed_file.sub_file_path.toSlice(ip); | |
| 2198 | try comp.appendFileSystemInput(fsi, embed_file.owner.root, sub_file_path); | |
| 2199 | } | |
| 2200 | } | |
| 2160 | 2201 | |
| 2161 | 2202 | try comp.work_queue.writeItem(.{ .analyze_mod = std_mod }); |
| 2162 | 2203 | if (comp.config.is_test) { |
| ... | ... | @@ -2210,6 +2251,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { |
| 2210 | 2251 | |
| 2211 | 2252 | switch (comp.cache_use) { |
| 2212 | 2253 | .whole => |whole| { |
| 2254 | if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf); | |
| 2255 | ||
| 2213 | 2256 | const digest = man.final(); |
| 2214 | 2257 | |
| 2215 | 2258 | // Rename the temporary directory into place. |
| ... | ... | @@ -2297,6 +2340,30 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { |
| 2297 | 2340 | } |
| 2298 | 2341 | } |
| 2299 | 2342 | |
| 2343 | fn 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 | ||
| 2300 | 2367 | fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void { |
| 2301 | 2368 | if (comp.bin_file) |lf| { |
| 2302 | 2369 | // This is needed before reading the error flags. |
| ... | ... | @@ -4204,6 +4271,9 @@ fn workerAstGenFile( |
| 4204 | 4271 | .token = item.data.token, |
| 4205 | 4272 | } }) catch continue; |
| 4206 | 4273 | } |
| 4274 | if (res.is_new) if (comp.file_system_inputs) |fsi| { | |
| 4275 | comp.appendFileSystemInput(fsi, res.file.mod.root, res.file.sub_file_path) catch continue; | |
| 4276 | }; | |
| 4207 | 4277 | const imported_path_digest = pt.zcu.filePathDigest(res.file_index); |
| 4208 | 4278 | const imported_root_decl = pt.zcu.fileRootDecl(res.file_index); |
| 4209 | 4279 | break :blk .{ res, imported_path_digest, imported_root_decl }; |
| ... | ... | @@ -4574,7 +4644,7 @@ fn reportRetryableEmbedFileError( |
| 4574 | 4644 | const gpa = mod.gpa; |
| 4575 | 4645 | const src_loc = embed_file.src_loc; |
| 4576 | 4646 | const ip = &mod.intern_pool; |
| 4577 | const err_msg = try Zcu.ErrorMsg.create(gpa, src_loc, "unable to load '{}{s}': {s}", .{ | |
| 4647 | const err_msg = try Zcu.ErrorMsg.create(gpa, src_loc, "unable to load '{}/{s}': {s}", .{ | |
| 4578 | 4648 | embed_file.owner.root, |
| 4579 | 4649 | embed_file.sub_file_path.toSlice(ip), |
| 4580 | 4650 | @errorName(err), |
src/Zcu.zig+1-1| ... | ... | @@ -728,7 +728,7 @@ pub const File = struct { |
| 728 | 728 | source_loaded: bool, |
| 729 | 729 | tree_loaded: bool, |
| 730 | 730 | zir_loaded: bool, |
| 731 | /// Relative to the owning package's root_src_dir. | |
| 731 | /// Relative to the owning package's root source directory. | |
| 732 | 732 | /// Memory is stored in gpa, owned by File. |
| 733 | 733 | sub_file_path: []const u8, |
| 734 | 734 | /// Whether this is populated depends on `source_loaded`. |
src/Zcu/PerThread.zig+1-1| ... | ... | @@ -2666,7 +2666,7 @@ pub fn reportRetryableAstGenError( |
| 2666 | 2666 | }, |
| 2667 | 2667 | }; |
| 2668 | 2668 | |
| 2669 | const err_msg = try Zcu.ErrorMsg.create(gpa, src_loc, "unable to load '{}{s}': {s}", .{ | |
| 2669 | const err_msg = try Zcu.ErrorMsg.create(gpa, src_loc, "unable to load '{}/{s}': {s}", .{ | |
| 2670 | 2670 | file.mod.root, file.sub_file_path, @errorName(err), |
| 2671 | 2671 | }); |
| 2672 | 2672 | errdefer err_msg.destroy(gpa); |
src/main.zig+58-34| ... | ... | @@ -3227,6 +3227,9 @@ fn buildOutputType( |
| 3227 | 3227 | |
| 3228 | 3228 | process.raiseFileDescriptorLimit(); |
| 3229 | 3229 | |
| 3230 | var file_system_inputs: std.ArrayListUnmanaged(u8) = .{}; | |
| 3231 | defer file_system_inputs.deinit(gpa); | |
| 3232 | ||
| 3230 | 3233 | const comp = Compilation.create(gpa, arena, .{ |
| 3231 | 3234 | .zig_lib_directory = zig_lib_directory, |
| 3232 | 3235 | .local_cache_directory = local_cache_directory, |
| ... | ... | @@ -3350,6 +3353,7 @@ fn buildOutputType( |
| 3350 | 3353 | // than to any particular module. This feature can greatly reduce CLI |
| 3351 | 3354 | // noise when --search-prefix and --mod are combined. |
| 3352 | 3355 | .global_cc_argv = try cc_argv.toOwnedSlice(arena), |
| 3356 | .file_system_inputs = &file_system_inputs, | |
| 3353 | 3357 | }) catch |err| switch (err) { |
| 3354 | 3358 | error.LibCUnavailable => { |
| 3355 | 3359 | const triple_name = try target.zigTriple(arena); |
| ... | ... | @@ -3433,7 +3437,7 @@ fn buildOutputType( |
| 3433 | 3437 | defer root_prog_node.end(); |
| 3434 | 3438 | |
| 3435 | 3439 | if (arg_mode == .translate_c) { |
| 3436 | return cmdTranslateC(comp, arena, null, root_prog_node); | |
| 3440 | return cmdTranslateC(comp, arena, null, null, root_prog_node); | |
| 3437 | 3441 | } |
| 3438 | 3442 | |
| 3439 | 3443 | updateModule(comp, color, root_prog_node) catch |err| switch (err) { |
| ... | ... | @@ -4059,6 +4063,7 @@ fn serve( |
| 4059 | 4063 | var child_pid: ?std.process.Child.Id = null; |
| 4060 | 4064 | |
| 4061 | 4065 | const main_progress_node = std.Progress.start(.{}); |
| 4066 | const file_system_inputs = comp.file_system_inputs.?; | |
| 4062 | 4067 | |
| 4063 | 4068 | while (true) { |
| 4064 | 4069 | const hdr = try server.receiveMessage(); |
| ... | ... | @@ -4067,14 +4072,16 @@ fn serve( |
| 4067 | 4072 | .exit => return cleanExit(), |
| 4068 | 4073 | .update => { |
| 4069 | 4074 | tracy.frameMark(); |
| 4075 | file_system_inputs.clearRetainingCapacity(); | |
| 4070 | 4076 | |
| 4071 | 4077 | if (arg_mode == .translate_c) { |
| 4072 | 4078 | var arena_instance = std.heap.ArenaAllocator.init(gpa); |
| 4073 | 4079 | defer arena_instance.deinit(); |
| 4074 | 4080 | const arena = arena_instance.allocator(); |
| 4075 | 4081 | var output: Compilation.CImportResult = undefined; |
| 4076 | try cmdTranslateC(comp, arena, &output, main_progress_node); | |
| 4082 | try cmdTranslateC(comp, arena, &output, file_system_inputs, main_progress_node); | |
| 4077 | 4083 | defer output.deinit(gpa); |
| 4084 | try server.serveStringMessage(.file_system_inputs, file_system_inputs.items); | |
| 4078 | 4085 | if (output.errors.errorMessageCount() != 0) { |
| 4079 | 4086 | try server.serveErrorBundle(output.errors); |
| 4080 | 4087 | } else { |
| ... | ... | @@ -4116,6 +4123,7 @@ fn serve( |
| 4116 | 4123 | }, |
| 4117 | 4124 | .hot_update => { |
| 4118 | 4125 | tracy.frameMark(); |
| 4126 | file_system_inputs.clearRetainingCapacity(); | |
| 4119 | 4127 | if (child_pid) |pid| { |
| 4120 | 4128 | try comp.hotCodeSwap(main_progress_node, pid); |
| 4121 | 4129 | try serveUpdateResults(&server, comp); |
| ... | ... | @@ -4147,6 +4155,12 @@ fn serve( |
| 4147 | 4155 | |
| 4148 | 4156 | fn serveUpdateResults(s: *Server, comp: *Compilation) !void { |
| 4149 | 4157 | const gpa = comp.gpa; |
| 4158 | ||
| 4159 | if (comp.file_system_inputs) |file_system_inputs| { | |
| 4160 | assert(file_system_inputs.items.len > 0); | |
| 4161 | try s.serveStringMessage(.file_system_inputs, file_system_inputs.items); | |
| 4162 | } | |
| 4163 | ||
| 4150 | 4164 | var error_bundle = try comp.getAllErrorsAlloc(); |
| 4151 | 4165 | defer error_bundle.deinit(gpa); |
| 4152 | 4166 | if (error_bundle.errorMessageCount() > 0) { |
| ... | ... | @@ -4434,6 +4448,7 @@ fn cmdTranslateC( |
| 4434 | 4448 | comp: *Compilation, |
| 4435 | 4449 | arena: Allocator, |
| 4436 | 4450 | fancy_output: ?*Compilation.CImportResult, |
| 4451 | file_system_inputs: ?*std.ArrayListUnmanaged(u8), | |
| 4437 | 4452 | prog_node: std.Progress.Node, |
| 4438 | 4453 | ) !void { |
| 4439 | 4454 | if (build_options.only_core_functionality) @panic("@translate-c is not available in a zig2.c build"); |
| ... | ... | @@ -4454,7 +4469,10 @@ fn cmdTranslateC( |
| 4454 | 4469 | }; |
| 4455 | 4470 | |
| 4456 | 4471 | if (fancy_output) |p| p.cache_hit = true; |
| 4457 | const digest = if (try man.hit()) man.final() else digest: { | |
| 4472 | const digest = if (try man.hit()) digest: { | |
| 4473 | if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf); | |
| 4474 | break :digest man.final(); | |
| 4475 | } else digest: { | |
| 4458 | 4476 | if (fancy_output) |p| p.cache_hit = false; |
| 4459 | 4477 | var argv = std.ArrayList([]const u8).init(arena); |
| 4460 | 4478 | switch (comp.config.c_frontend) { |
| ... | ... | @@ -4566,6 +4584,8 @@ fn cmdTranslateC( |
| 4566 | 4584 | @errorName(err), |
| 4567 | 4585 | }); |
| 4568 | 4586 | |
| 4587 | if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf); | |
| 4588 | ||
| 4569 | 4589 | break :digest digest; |
| 4570 | 4590 | }; |
| 4571 | 4591 | |
| ... | ... | @@ -4649,31 +4669,6 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4649 | 4669 | return cleanExit(); |
| 4650 | 4670 | } |
| 4651 | 4671 | |
| 4652 | const 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 | ||
| 4677 | 4672 | fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4678 | 4673 | var build_file: ?[]const u8 = null; |
| 4679 | 4674 | var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena); |
| ... | ... | @@ -4696,6 +4691,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4696 | 4691 | var verbose_llvm_cpu_features = false; |
| 4697 | 4692 | var fetch_only = false; |
| 4698 | 4693 | var system_pkg_dir_path: ?[]const u8 = null; |
| 4694 | var debug_target: ?[]const u8 = null; | |
| 4699 | 4695 | |
| 4700 | 4696 | const argv_index_exe = child_argv.items.len; |
| 4701 | 4697 | _ = try child_argv.addOne(); |
| ... | ... | @@ -4703,6 +4699,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4703 | 4699 | const self_exe_path = try introspect.findZigExePath(arena); |
| 4704 | 4700 | try child_argv.append(self_exe_path); |
| 4705 | 4701 | |
| 4702 | const argv_index_zig_lib_dir = child_argv.items.len; | |
| 4703 | _ = try child_argv.addOne(); | |
| 4704 | ||
| 4706 | 4705 | const argv_index_build_file = child_argv.items.len; |
| 4707 | 4706 | _ = try child_argv.addOne(); |
| 4708 | 4707 | |
| ... | ... | @@ -4752,7 +4751,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4752 | 4751 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); |
| 4753 | 4752 | i += 1; |
| 4754 | 4753 | override_lib_dir = args[i]; |
| 4755 | try child_argv.appendSlice(&.{ arg, args[i] }); | |
| 4756 | 4754 | continue; |
| 4757 | 4755 | } else if (mem.eql(u8, arg, "--build-runner")) { |
| 4758 | 4756 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); |
| ... | ... | @@ -4802,6 +4800,14 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4802 | 4800 | } else { |
| 4803 | 4801 | warn("Zig was compiled without debug extensions. --debug-compile-errors has no effect.", .{}); |
| 4804 | 4802 | } |
| 4803 | } else if (mem.eql(u8, arg, "--debug-target")) { | |
| 4804 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); | |
| 4805 | i += 1; | |
| 4806 | if (build_options.enable_debug_extensions) { | |
| 4807 | debug_target = args[i]; | |
| 4808 | } else { | |
| 4809 | warn("Zig was compiled without debug extensions. --debug-target has no effect.", .{}); | |
| 4810 | } | |
| 4805 | 4811 | } else if (mem.eql(u8, arg, "--verbose-link")) { |
| 4806 | 4812 | verbose_link = true; |
| 4807 | 4813 | } else if (mem.eql(u8, arg, "--verbose-cc")) { |
| ... | ... | @@ -4860,11 +4866,27 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4860 | 4866 | }); |
| 4861 | 4867 | defer root_prog_node.end(); |
| 4862 | 4868 | |
| 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 | }; | |
| 4868 | 4890 | }; |
| 4869 | 4891 | |
| 4870 | 4892 | const exe_basename = try std.zig.binNameAlloc(arena, .{ |
| ... | ... | @@ -4890,6 +4912,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 4890 | 4912 | defer zig_lib_directory.handle.close(); |
| 4891 | 4913 | |
| 4892 | 4914 | const cwd_path = try process.getCwdAlloc(arena); |
| 4915 | child_argv.items[argv_index_zig_lib_dir] = zig_lib_directory.path orelse cwd_path; | |
| 4916 | ||
| 4893 | 4917 | const build_root = try findBuildRoot(arena, .{ |
| 4894 | 4918 | .cwd_path = cwd_path, |
| 4895 | 4919 | .build_file = build_file, |
test/tests.zig+4-4| ... | ... | @@ -771,7 +771,7 @@ pub fn addCliTests(b: *std.Build) *Step { |
| 771 | 771 | run_run.expectStdErrEqual("All your codebase are belong to us.\n"); |
| 772 | 772 | run_run.step.dependOn(&init_exe.step); |
| 773 | 773 | |
| 774 | const cleanup = b.addRemoveDirTree(tmp_path); | |
| 774 | const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path }); | |
| 775 | 775 | cleanup.step.dependOn(&run_test.step); |
| 776 | 776 | cleanup.step.dependOn(&run_run.step); |
| 777 | 777 | cleanup.step.dependOn(&run_bad.step); |
| ... | ... | @@ -816,7 +816,7 @@ pub fn addCliTests(b: *std.Build) *Step { |
| 816 | 816 | }); |
| 817 | 817 | checkfile.setName("check godbolt.org CLI usage generating valid asm"); |
| 818 | 818 | |
| 819 | const cleanup = b.addRemoveDirTree(tmp_path); | |
| 819 | const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path }); | |
| 820 | 820 | cleanup.step.dependOn(&checkfile.step); |
| 821 | 821 | |
| 822 | 822 | step.dependOn(&cleanup.step); |
| ... | ... | @@ -882,7 +882,7 @@ pub fn addCliTests(b: *std.Build) *Step { |
| 882 | 882 | |
| 883 | 883 | const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00"; |
| 884 | 884 | const fmt6_path = std.fs.path.join(b.allocator, &.{ tmp_path, "fmt6.zig" }) catch @panic("OOM"); |
| 885 | const write6 = b.addWriteFiles(); | |
| 885 | const write6 = b.addUpdateSourceFiles(); | |
| 886 | 886 | write6.addBytesToSource(unformatted_code_utf16, fmt6_path); |
| 887 | 887 | write6.step.dependOn(&run5.step); |
| 888 | 888 | |
| ... | ... | @@ -902,7 +902,7 @@ pub fn addCliTests(b: *std.Build) *Step { |
| 902 | 902 | }); |
| 903 | 903 | check6.step.dependOn(&run6.step); |
| 904 | 904 | |
| 905 | const cleanup = b.addRemoveDirTree(tmp_path); | |
| 905 | const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path }); | |
| 906 | 906 | cleanup.step.dependOn(&check6.step); |
| 907 | 907 | |
| 908 | 908 | step.dependOn(&cleanup.step); |