zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit afd7507a197d516c7240f92fc19e4f43adb0b79c (tree)
parent 0505318efe0d2757a344dded9ae1607f948f7511
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Tue, 17 Feb 2026 20:36:45 -0800

make runner: prepare steps for execution

Diffstat:
Mlib/compiler/configure_runner.zig | 142++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mlib/compiler/maker.zig | 678+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Dlib/compiler/maker/Package.zig | 30------------------------------
Mlib/compiler/maker/Step.zig | 122+++++++++++++++++++++++++++++++------------------------------------------------
Mlib/std/Build.zig | 4++++
Mlib/std/Build/Step/Run.zig | 2++
Mlib/std/zig/Configuration.zig | 32+++++++++++++++++++++-----------
7 files changed, 506 insertions(+), 504 deletions(-)

diff --git a/lib/compiler/configure_runner.zig b/lib/compiler/configure_runner.zig @@ -186,6 +186,8 @@ pub fn main(init: process.Init.Minimal) !void { // but it is handled by the parent process. The build runner // only sees this flag. graph.system_package_mode = true; + } else if (mem.eql(u8, arg, "--have-run-args")) { + graph.have_run_args = true; } else { fatalWithHint("unrecognized argument: '{s}'", .{arg}); } @@ -226,13 +228,69 @@ pub fn main(init: process.Init.Minimal) !void { process.exit(0); } +const Serialize = struct { + arena: Allocator, + wc: *Configuration.Wip, + module_map: std.AutoArrayHashMapUnmanaged(*std.Build.Module, Configuration.Module.Index) = .empty, + package_map: std.AutoArrayHashMapUnmanaged(*std.Build, Configuration.Package.Index) = .empty, + + fn builderToPackage(s: *Serialize, b: *std.Build) !Configuration.Package.Index { + if (b.pkg_hash.len == 0) return .root; + const arena = s.arena; + const wc = s.wc; + const gop = try s.package_map.getOrPut(arena, b); + if (!gop.found_existing) { + gop.value_ptr.* = @enumFromInt(try wc.addExtra(@as(Configuration.Package, .{ + .hash = try wc.addString(b.pkg_hash), + .dep_prefix = try wc.addString(b.dep_prefix), + }))); + } + return gop.value_ptr.*; + } + + fn addOptionalLazyPath(s: *Serialize, lp: ?std.Build.LazyPath) !Configuration.OptionalLazyPath { + const wc = s.wc; + return @enumFromInt(switch (lp orelse return .none) { + .src_path => |src_path| i: { + const sub_path = try wc.addString(src_path.sub_path); + break :i try wc.addExtra(@as(Configuration.LazyPath.SourcePath, .{ + .flags = .{}, + .owner = try s.builderToPackage(src_path.owner), + .sub_path = sub_path, + })); + }, + .generated => |generated| i: { + const sub_path = try wc.addString(generated.sub_path); + break :i try wc.addExtra(@as(Configuration.LazyPath.Generated, .{ + .flags = .{ .up = @intCast(generated.up) }, + .sub_path = sub_path, + })); + }, + .cwd_relative => |cwd_relative_sub_path| i: { + const sub_path = try wc.addString(cwd_relative_sub_path); + break :i try wc.addExtra(@as(Configuration.LazyPath.Relative, .{ + .flags = .{ .base = .cwd }, + .sub_path = sub_path, + })); + }, + .dependency => |dependency| i: { + const sub_path = try wc.addString(dependency.sub_path); + break :i try wc.addExtra(@as(Configuration.LazyPath.SourcePath, .{ + .flags = .{}, + .owner = try s.builderToPackage(dependency.dependency.builder), + .sub_path = sub_path, + })); + }, + }); + } +}; + fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { const graph = b.graph; const arena = graph.arena; const gpa = wc.gpa; - var module_map: std.AutoArrayHashMapUnmanaged(*std.Build.Module, Configuration.Module.Index) = .empty; - defer module_map.deinit(gpa); + var s: Serialize = .{ .wc = wc, .arena = arena }; // Starting from all top-level steps in `b`, traverse the entire step graph // and add all step dependencies implied by module graphs. @@ -267,6 +325,7 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { try wc.steps.ensureTotalCapacity(gpa, step_map.entries.capacity); wc.steps.appendAssumeCapacity(.{ .name = try wc.addString(step.name), + .owner = try s.builderToPackage(step.owner), .deps = deps, .max_rss = .fromBytes(step.max_rss), .extra_index = switch (step.tag) { @@ -367,7 +426,7 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { .install_name = c.install_name != null, .entitlements = c.entitlements != null, }, - .root_module = try addModule(wc, &module_map, c.root_module), + .root_module = try addModule(&s, c.root_module), .root_name = try wc.addString(c.name), })); @@ -383,13 +442,13 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { }, .dest_dir = try addInstallDir(wc, ia.dest_dir), .dest_sub_path = try wc.addString(ia.dest_sub_path), - .emitted_bin = try addOptionalLazyPath(wc, ia.emitted_bin), + .emitted_bin = try s.addOptionalLazyPath(ia.emitted_bin), .implib_dir = try addInstallDir(wc, ia.implib_dir), - .emitted_implib = try addOptionalLazyPath(wc, ia.emitted_implib), + .emitted_implib = try s.addOptionalLazyPath(ia.emitted_implib), .pdb_dir = try addInstallDir(wc, ia.pdb_dir), - .emitted_pdb = try addOptionalLazyPath(wc, ia.emitted_pdb), + .emitted_pdb = try s.addOptionalLazyPath(ia.emitted_pdb), .h_dir = try addInstallDir(wc, ia.h_dir), - .emitted_h = try addOptionalLazyPath(wc, ia.emitted_h), + .emitted_h = try s.addOptionalLazyPath(ia.emitted_h), .artifact = stepIndex(&step_map, &ia.artifact.step), })); }, @@ -440,7 +499,7 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { }, .file_inputs_len = @intCast(run.file_inputs.items.len), .args_len = @intCast(run.argv.items.len), - .cwd = try addOptionalLazyPath(wc, run.cwd), + .cwd = try s.addOptionalLazyPath(run.cwd), .captured_stdout = captured_stdout, .captured_stderr = captured_stderr, })); @@ -469,13 +528,11 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { }); } -fn addModule( - wc: *Configuration.Wip, - module_map: *std.AutoArrayHashMapUnmanaged(*std.Build.Module, Configuration.Module.Index), - m: *std.Build.Module, -) !Configuration.Module.Index { - if (module_map.get(m)) |index| return index; +fn addModule(s: *Serialize, m: *std.Build.Module) !Configuration.Module.Index { + if (s.module_map.get(m)) |index| return index; + const wc = s.wc; + const arena = s.arena; const gpa = wc.gpa; const import_table: Configuration.ImportTable = @enumFromInt(wc.extra.items.len); const import_table_extra_len = 1 + 2 * m.import_table.entries.len; @@ -494,7 +551,7 @@ fn addModule( @intFromEnum(import_table) + 1 + m.import_table.entries.len.., ) |dep, extra_index| { log.err("TODO module dependencies can be cyclic", .{}); - wc.extra.items[extra_index] = @intFromEnum(try addModule(wc, module_map, dep)); + wc.extra.items[extra_index] = @intFromEnum(try addModule(s, dep)); } const module_index: Configuration.Module.Index = @enumFromInt(try wc.addExtra(@as(Configuration.Module, .{ @@ -528,15 +585,15 @@ fn addModule( .link_libcpp = .init(m.strip), .no_builtin = .init(m.strip), }, - .owner = try builderToPackage(wc, m.owner), - .root_source_file = try addOptionalLazyPath(wc, m.root_source_file), + .owner = try s.builderToPackage(m.owner), + .root_source_file = try s.addOptionalLazyPath(m.root_source_file), .import_table = import_table, .resolved_target = try addOptionalResolvedTarget(wc, m.resolved_target), }))); log.err("TODO serialize the trailing Module data", .{}); - try module_map.putNoClobber(gpa, m, module_index); + try s.module_map.putNoClobber(arena, m, module_index); return module_index; } @@ -553,46 +610,6 @@ fn addOptionalResolvedTarget( }))); } -fn addOptionalLazyPath(wc: *Configuration.Wip, lp: ?std.Build.LazyPath) !Configuration.OptionalLazyPath { - return @enumFromInt(switch (lp orelse return .none) { - .src_path => |src_path| i: { - const sub_path = try wc.addString(src_path.sub_path); - break :i try wc.addExtra(@as(Configuration.LazyPath.SourcePath, .{ - .flags = .{}, - .owner = try builderToPackage(wc, src_path.owner), - .sub_path = sub_path, - })); - }, - .generated => |generated| i: { - const sub_path = try wc.addString(generated.sub_path); - break :i try wc.addExtra(@as(Configuration.LazyPath.Generated, .{ - .flags = .{ .up = @intCast(generated.up) }, - .sub_path = sub_path, - })); - }, - .cwd_relative => |cwd_relative_sub_path| i: { - const sub_path = try wc.addString(cwd_relative_sub_path); - break :i try wc.addExtra(@as(Configuration.LazyPath.Relative, .{ - .flags = .{ .base = .cwd }, - .sub_path = sub_path, - })); - }, - .dependency => |dependency| i: { - const sub_path = try wc.addString(dependency.sub_path); - break :i try wc.addExtra(@as(Configuration.LazyPath.SourcePath, .{ - .flags = .{}, - .owner = try builderToPackage(wc, dependency.dependency.builder), - .sub_path = sub_path, - })); - }, - }); -} - -fn builderToPackage(wc: *Configuration.Wip, b: *std.Build) !Configuration.Package { - if (b.pkg_hash.len == 0) return .root; - return .fromHash(try wc.addString(b.pkg_hash)); -} - fn addInstallDir(wc: *Configuration.Wip, install_dir: ?std.Build.InstallDir) !Configuration.InstallDir { switch (install_dir orelse return .none) { .prefix => return .prefix, @@ -665,9 +682,7 @@ fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 { fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 { return nextArg(args, idx) orelse { - fatal("expected argument after {q}\n access the help menu with \"zig build -h\"", .{ - args[idx.* - 1], - }); + fatalWithHint("expected argument after: {s}", .{args[idx.* - 1]}); }; } @@ -700,7 +715,8 @@ const MultilineErrors = enum { indent, newline, none }; const Summary = enum { all, new, failures, line, none }; fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn { - fatal(f ++ "\n access the help menu with \"zig build -h\"", args); + log.info("to access the help menu: zig build -h", .{}); + fatal(f, args); } fn serializeSystemIntegrationOptions(graph: *std.Build.Graph, wc: *Configuration.Wip) Allocator.Error!void { @@ -725,7 +741,7 @@ fn serializeSystemIntegrationOptions(graph: *std.Build.Graph, wc: *Configuration }); } if (bad) { - log.info("access the help menu with \"zig build -h\"", .{}); + log.info("help menu contains available options: zig build -h", .{}); process.exit(1); } } diff --git a/lib/compiler/maker.zig b/lib/compiler/maker.zig @@ -17,7 +17,7 @@ const process = std.process; const Fuzz = @import("maker/Fuzz.zig"); const Graph = @import("maker/Graph.zig"); -const Step = void; // @import("maker/Step.zig"); +const Step = @import("maker/Step.zig"); const Watch = @import("maker/Watch.zig"); const WebServer = @import("maker/WebServer.zig"); @@ -100,8 +100,8 @@ pub fn main(init: process.Init.Minimal) !void { graph.cache.addPrefix(global_cache_directory); graph.cache.hash.addBytes(builtin.zig_version_string); - var targets = std.array_list.Managed([]const u8).init(arena); - var debug_log_scopes = std.array_list.Managed([]const u8).init(arena); + var step_names: std.ArrayList([]const u8) = .empty; + var debug_log_scopes: std.ArrayList([]const u8) = .empty; var help_menu = false; var steps_menu = false; var print_configuration = false; @@ -151,29 +151,6 @@ pub fn main(init: process.Init.Minimal) !void { } } - const scanned_config: ScannedConfig = sc: { - const configuration = c: { - var file = cwd.openFile(io, configure_path, .{}) catch |err| - fatal("failed to open configuration file {s}: {t}", .{ configure_path, err }); - defer file.close(io); - break :c Configuration.loadFile(arena, io, file) catch |err| - fatal("failed to load configuration file {s}: {t}", .{ configure_path, err }); - }; - var top_level_steps: std.ArrayList(Configuration.Step.Index) = .empty; - for (configuration.steps, 0..) |*conf_step, step_index| { - const flags: Configuration.Step.Flags = @bitCast(configuration.extra[conf_step.extra_index]); - if (flags.tag == .top_level) { - try top_level_steps.append(arena, @enumFromInt(step_index)); - } - } - break :sc .{ - .configuration = configuration, - .top_level_steps = top_level_steps.items, - }; - }; - - log.err("TODO handle user -D options", .{}); - while (nextArg(args, &arg_idx)) |arg| { if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { @@ -291,7 +268,7 @@ pub fn main(init: process.Init.Minimal) !void { }; } else if (mem.eql(u8, arg, "--debug-log")) { const next_arg = nextArgOrFatal(args, &arg_idx); - try debug_log_scopes.append(next_arg); + try debug_log_scopes.append(arena, next_arg); } else if (mem.eql(u8, arg, "--debug-pkg-config")) { debug_pkg_config = true; } else if (mem.eql(u8, arg, "--debug-rt")) { @@ -395,7 +372,7 @@ pub fn main(init: process.Init.Minimal) !void { fatalWithHint("unrecognized argument: '{s}'", .{arg}); } } else { - try targets.append(arg); + try step_names.append(arena, arg); } } @@ -408,6 +385,29 @@ pub fn main(init: process.Init.Minimal) !void { .off => .no_color, }; + const scanned_config: ScannedConfig = sc: { + const configuration = c: { + var file = cwd.openFile(io, configure_path, .{}) catch |err| + fatal("failed to open configuration file {s}: {t}", .{ configure_path, err }); + defer file.close(io); + break :c Configuration.loadFile(arena, io, file) catch |err| + fatal("failed to load configuration file {s}: {t}", .{ configure_path, err }); + }; + var top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index) = .empty; + for (configuration.steps, 0..) |*conf_step, step_index_usize| { + const step_index: Configuration.Step.Index = @enumFromInt(step_index_usize); + const flags: Configuration.Step.Flags = @bitCast(configuration.extra[conf_step.extra_index]); + if (flags.tag == .top_level) { + const name = step_index.ptr(&configuration).name.slice(&configuration); + try top_level_steps.put(arena, name, step_index); + } + } + break :sc .{ + .configuration = configuration, + .top_level_steps = top_level_steps, + }; + }; + if (help_menu) { var w = initStdoutWriter(io); scanned_config.printUsage(&graph, w) catch |err| switch (err) { @@ -467,10 +467,17 @@ pub fn main(init: process.Init.Minimal) !void { .sub_path = cwd_relative, } else try install_prefix_path.join(arena, "include"); - if (true) @panic("TODO"); - var run: Run = .{ .gpa = gpa, + .graph = &graph, + .scanned_config = &scanned_config, + .install_paths = .{ + .prefix = install_prefix_path, + .lib = install_lib_path, + .bin = install_bin_path, + .include = install_include_path, + }, + .steps = try arena.alloc(Step, scanned_config.configuration.steps.len), .available_rss = max_rss, .max_rss_is_default = false, @@ -486,13 +493,6 @@ pub fn main(init: process.Init.Minimal) !void { .error_style = error_style, .multiline_errors = multiline_errors, .summary = summary orelse if (watch or webui_listen != null) .line else .failures, - - .install_paths = .{ - .prefix = install_prefix_path, - .lib = install_lib_path, - .bin = install_bin_path, - .include = install_include_path, - }, }; defer { run.memory_blocked_steps.deinit(gpa); @@ -504,17 +504,16 @@ pub fn main(init: process.Init.Minimal) !void { run.max_rss_is_default = true; } - prepare(arena, &graph, targets.items, &run) catch |err| switch (err) { + run.prepare(step_names.items) catch |err| switch (err) { error.DependencyLoopDetected, error.InsufficientMemory => { - // Perhaps in the future there could be an Advanced Options flag - // such as --debug-build-runner-leaks which would make this code - // return instead of calling exit. _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; process.exit(1); }, else => |e| return e, }; + if (true) @panic("TODO"); + var w: Watch = w: { if (!watch) break :w undefined; if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag}); @@ -547,7 +546,7 @@ pub fn main(init: process.Init.Minimal) !void { }) { if (run.web_server) |*ws| ws.startBuild(); - try runStepNames(graph, targets.items, main_progress_node, &run, fuzz); + try run.makeStepNames(step_names, main_progress_node, fuzz); if (run.web_server) |*web_server| { if (fuzz) |mode| if (mode != .forever) fatal( @@ -628,6 +627,10 @@ fn countSubProcesses(all_steps: []const *Step) usize { const Run = struct { gpa: Allocator, + graph: *Graph, + install_paths: InstallPaths, + scanned_config: *const ScannedConfig, + steps: []Step, available_rss: usize, max_rss_is_default: bool, @@ -637,309 +640,331 @@ const Run = struct { watch: bool, web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn, /// Allocated into `gpa`. - memory_blocked_steps: std.ArrayList(*Step), + memory_blocked_steps: std.ArrayList(Configuration.Step.Index), /// Allocated into `gpa`. - step_stack: std.AutoArrayHashMapUnmanaged(*Step, void), + step_stack: std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void), error_style: ErrorStyle, multiline_errors: MultilineErrors, summary: Summary, -}; -fn prepare(graph: *Graph, step_names: []const []const u8, run: *Run) !void { - const arena = graph.arena; - const seed: u32 = graph.random_seed; - const gpa = run.gpa; - const step_stack = &run.step_stack; + const InstallPaths = struct { + prefix: Path, + lib: Path, + bin: Path, + include: Path, + }; - if (step_names.len == 0) { - try step_stack.put(gpa, graph.configuration.default_step, {}); - } else { - try step_stack.ensureUnusedCapacity(gpa, step_names.len); - for (0..step_names.len) |i| { - const step_name = step_names[step_names.len - i - 1]; - const s = run.top_level_steps.get(step_name) orelse { - log.info("access the help menu with 'zig build -h'", .{}); - fatal("no such step: {s}", .{step_name}); - }; - step_stack.putAssumeCapacity(&s.step, {}); - } + fn stepByIndex(run: *const Run, i: Configuration.Step.Index) *Step { + return &run.steps[@intFromEnum(i)]; } - const starting_steps = try arena.dupe(*Step, step_stack.keys()); + fn prepare(run: *Run, step_names: []const []const u8) !void { + const gpa = run.gpa; + const graph = run.graph; + const arena = graph.arena; + const seed: u32 = graph.random_seed; + const step_stack = &run.step_stack; + const c = &run.scanned_config.configuration; - var rng = std.Random.DefaultPrng.init(seed); - const rand = rng.random(); - rand.shuffle(*Step, starting_steps); + @memset(run.steps, .{}); - for (starting_steps) |s| { - try constructGraphAndCheckForDependencyLoop(gpa, s, &run.step_stack, rand); - } + if (step_names.len == 0) { + try step_stack.put(gpa, c.default_step, {}); + } else { + try step_stack.ensureUnusedCapacity(gpa, step_names.len); + for (0..step_names.len) |i| { + const step_name = step_names[step_names.len - i - 1]; + const s = run.scanned_config.top_level_steps.get(step_name) orelse { + log.info("to list available steps: zig build -l", .{}); + fatal("no such step: {s}", .{step_name}); + }; + step_stack.putAssumeCapacity(s, {}); + } + } - { - // Check that we have enough memory to complete the build. - var any_problems = false; - var max_needed: usize = 0; - for (step_stack.keys()) |s| { - if (s.max_rss == 0) continue; - max_needed = @max(max_needed, s.max_rss); - if (s.max_rss > run.available_rss) { - if (run.skip_oom_steps) { - s.state = .skipped_oom; - for (s.dependants.items) |dependant| { - dependant.pending_deps -= 1; + const starting_steps = try arena.dupe(Configuration.Step.Index, step_stack.keys()); + + var rng = std.Random.DefaultPrng.init(seed); + const rand = rng.random(); + rand.shuffle(Configuration.Step.Index, starting_steps); + + for (starting_steps) |s| { + try constructGraphAndCheckForDependencyLoop(gpa, c, run.steps, s, &run.step_stack, rand); + } + + { + // Check that we have enough memory to complete the build. + var any_problems = false; + var max_needed: usize = 0; + for (step_stack.keys()) |step_index| { + const make_step = run.stepByIndex(step_index); + const conf_step = step_index.ptr(c); + const max_rss = conf_step.max_rss.toBytes(); + if (max_rss == 0) continue; + max_needed = @max(max_needed, max_rss); + if (max_rss > run.available_rss) { + if (run.skip_oom_steps) { + make_step.state = .skipped_oom; + for (make_step.dependants.items) |dependant| { + dependant.pending_deps -= 1; + } + } else { + log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{ + conf_step.owner.depPrefixSlice(c), + conf_step.name.slice(c), + max_rss, + run.available_rss, + }); + any_problems = true; } - } else { - std.log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{ - s.owner.dep_prefix, s.name, s.max_rss, run.available_rss, - }); - any_problems = true; } } - } - if (any_problems) { - if (run.max_rss_is_default) { - std.log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{ - max_needed, - }); + if (any_problems) { + if (run.max_rss_is_default) { + std.log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{ + max_needed, + }); + } + return error.InsufficientMemory; } - return error.InsufficientMemory; } } -} -fn runStepNames( - graph: *Graph, - step_names: []const []const u8, - parent_prog_node: std.Progress.Node, - run: *Run, - fuzz: ?Fuzz.Mode, -) !void { - const gpa = run.gpa; - const io = graph.io; - const step_stack = &run.step_stack; + fn makeStepNames( + run: *Run, + step_names: []const []const u8, + parent_prog_node: std.Progress.Node, + fuzz: ?Fuzz.Mode, + ) !void { + const graph = run.graph; + const gpa = run.gpa; + const io = graph.io; + const step_stack = &run.step_stack; + const top_level_steps = &run.scanned_config.top_level_steps; - { - // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer, - // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking - // a step is initial when it actually became ready due to an earlier initial step. - var initial_set: std.ArrayList(*Step) = .empty; - defer initial_set.deinit(gpa); - try initial_set.ensureUnusedCapacity(gpa, step_stack.count()); - for (step_stack.keys()) |s| { - if (s.state == .precheck_done and s.pending_deps == 0) { - initial_set.appendAssumeCapacity(s); + { + // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer, + // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking + // a step is initial when it actually became ready due to an earlier initial step. + var initial_set: std.ArrayList(*Step) = .empty; + defer initial_set.deinit(gpa); + try initial_set.ensureUnusedCapacity(gpa, step_stack.count()); + for (step_stack.keys()) |s| { + if (s.state == .precheck_done and s.pending_deps == 0) { + initial_set.appendAssumeCapacity(s); + } } - } - const step_prog = parent_prog_node.start("steps", step_stack.count()); - defer step_prog.end(); + const step_prog = parent_prog_node.start("steps", step_stack.count()); + defer step_prog.end(); - var group: Io.Group = .init; - defer group.cancel(io); - // Start working on all of the initial steps... - for (initial_set.items) |s| try stepReady(&group, s, step_prog, run); - // ...and `makeStep` will trigger every other step when their last dependency finishes. - try group.await(io); - } + var group: Io.Group = .init; + defer group.cancel(io); + // Start working on all of the initial steps... + for (initial_set.items) |s| try stepReady(&group, s, step_prog, run); + // ...and `makeStep` will trigger every other step when their last dependency finishes. + try group.await(io); + } - assert(run.memory_blocked_steps.items.len == 0); + assert(run.memory_blocked_steps.items.len == 0); - var test_pass_count: usize = 0; - var test_skip_count: usize = 0; - var test_fail_count: usize = 0; - var test_crash_count: usize = 0; - var test_timeout_count: usize = 0; + var test_pass_count: usize = 0; + var test_skip_count: usize = 0; + var test_fail_count: usize = 0; + var test_crash_count: usize = 0; + var test_timeout_count: usize = 0; - var test_count: usize = 0; + var test_count: usize = 0; - var success_count: usize = 0; - var skipped_count: usize = 0; - var failure_count: usize = 0; - var pending_count: usize = 0; - var total_compile_errors: usize = 0; + var success_count: usize = 0; + var skipped_count: usize = 0; + var failure_count: usize = 0; + var pending_count: usize = 0; + var total_compile_errors: usize = 0; - var cleanup_task = io.async(cleanTmpFiles, .{ io, step_stack.keys() }); - defer cleanup_task.await(io); + var cleanup_task = io.async(cleanTmpFiles, .{ io, step_stack.keys() }); + defer cleanup_task.await(io); - for (step_stack.keys()) |s| { - test_pass_count += s.test_results.passCount(); - test_skip_count += s.test_results.skip_count; - test_fail_count += s.test_results.fail_count; - test_crash_count += s.test_results.crash_count; - test_timeout_count += s.test_results.timeout_count; + for (step_stack.keys()) |s| { + test_pass_count += s.test_results.passCount(); + test_skip_count += s.test_results.skip_count; + test_fail_count += s.test_results.fail_count; + test_crash_count += s.test_results.crash_count; + test_timeout_count += s.test_results.timeout_count; - test_count += s.test_results.test_count; + test_count += s.test_results.test_count; - switch (s.state) { - .precheck_unstarted => unreachable, - .precheck_started => unreachable, - .precheck_done => unreachable, - .dependency_failure => pending_count += 1, - .success => success_count += 1, - .skipped, .skipped_oom => skipped_count += 1, - .failure => { - failure_count += 1; - const compile_errors_len = s.result_error_bundle.errorMessageCount(); - if (compile_errors_len > 0) { - total_compile_errors += compile_errors_len; - } - }, - } - } - - if (fuzz) |mode| blk: { - switch (builtin.os.tag) { - // Current implementation depends on two things that need to be ported to Windows: - // * Memory-mapping to share data between the fuzzer and build runner. - // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving - // many addresses to source locations). - .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}), - else => {}, - } - if (@bitSizeOf(usize) != 64) { - // Current implementation depends on posix.mmap()'s second parameter, `length: usize`, - // being compatible with file system's u64 return value. This is not the case - // on 32-bit platforms. - // Affects or affected by issues #5185, #22523, and #22464. - fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)}); + switch (s.state) { + .precheck_unstarted => unreachable, + .precheck_started => unreachable, + .precheck_done => unreachable, + .dependency_failure => pending_count += 1, + .success => success_count += 1, + .skipped, .skipped_oom => skipped_count += 1, + .failure => { + failure_count += 1; + const compile_errors_len = s.result_error_bundle.errorMessageCount(); + if (compile_errors_len > 0) { + total_compile_errors += compile_errors_len; + } + }, + } } - switch (mode) { - .forever => break :blk, - .limit => {}, - } + if (fuzz) |mode| blk: { + switch (builtin.os.tag) { + // Current implementation depends on two things that need to be ported to Windows: + // * Memory-mapping to share data between the fuzzer and build runner. + // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving + // many addresses to source locations). + .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}), + else => {}, + } + if (@bitSizeOf(usize) != 64) { + // Current implementation depends on posix.mmap()'s second parameter, `length: usize`, + // being compatible with file system's u64 return value. This is not the case + // on 32-bit platforms. + // Affects or affected by issues #5185, #22523, and #22464. + fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)}); + } - assert(mode == .limit); - var f = Fuzz.init( - gpa, - io, - step_stack.keys(), - parent_prog_node, - mode, - ) catch |err| fatal("failed to start fuzzer: {t}", .{err}); - defer f.deinit(); - - f.start(); - try f.waitAndPrintReport(); - } + switch (mode) { + .forever => break :blk, + .limit => {}, + } - // Every test has a state - assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count); + assert(mode == .limit); + var f = Fuzz.init( + gpa, + io, + step_stack.keys(), + parent_prog_node, + mode, + ) catch |err| fatal("failed to start fuzzer: {t}", .{err}); + defer f.deinit(); + + f.start(); + try f.waitAndPrintReport(); + } - if (failure_count == 0) { - std.Progress.setStatus(.success); - } else { - std.Progress.setStatus(.failure); - } + // Every test has a state + assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count); - summary: { - switch (run.summary) { - .all, .new, .line => {}, - .failures => if (failure_count == 0) break :summary, - .none => break :summary, + if (failure_count == 0) { + std.Progress.setStatus(.success); + } else { + std.Progress.setStatus(.failure); } - const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); - defer io.unlockStderr(); - const t = stderr.terminal(); - const w = &stderr.file_writer.interface; - - const total_count = success_count + failure_count + pending_count + skipped_count; - t.setColor(.cyan) catch {}; - t.setColor(.bold) catch {}; - w.writeAll("Build Summary: ") catch {}; - t.setColor(.reset) catch {}; - w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {}; - { - t.setColor(.dim) catch {}; - var first = true; - if (skipped_count > 0) { - w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {}; - first = false; - } - if (failure_count > 0) { - w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {}; - first = false; + summary: { + switch (run.summary) { + .all, .new, .line => {}, + .failures => if (failure_count == 0) break :summary, + .none => break :summary, } - if (!first) w.writeByte(')') catch {}; - t.setColor(.reset) catch {}; - } - if (test_count > 0) { - w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {}; - t.setColor(.dim) catch {}; - var first = true; - if (test_skip_count > 0) { - w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {}; - first = false; - } - if (test_fail_count > 0) { - w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {}; - first = false; - } - if (test_crash_count > 0) { - w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {}; - first = false; + const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); + defer io.unlockStderr(); + const t = stderr.terminal(); + const w = &stderr.file_writer.interface; + + const total_count = success_count + failure_count + pending_count + skipped_count; + t.setColor(.cyan) catch {}; + t.setColor(.bold) catch {}; + w.writeAll("Build Summary: ") catch {}; + t.setColor(.reset) catch {}; + w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {}; + { + t.setColor(.dim) catch {}; + var first = true; + if (skipped_count > 0) { + w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {}; + first = false; + } + if (failure_count > 0) { + w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {}; + first = false; + } + if (!first) w.writeByte(')') catch {}; + t.setColor(.reset) catch {}; } - if (test_timeout_count > 0) { - w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {}; - first = false; + + if (test_count > 0) { + w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {}; + t.setColor(.dim) catch {}; + var first = true; + if (test_skip_count > 0) { + w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {}; + first = false; + } + if (test_fail_count > 0) { + w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {}; + first = false; + } + if (test_crash_count > 0) { + w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {}; + first = false; + } + if (test_timeout_count > 0) { + w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {}; + first = false; + } + if (!first) w.writeByte(')') catch {}; + t.setColor(.reset) catch {}; } - if (!first) w.writeByte(')') catch {}; - t.setColor(.reset) catch {}; - } - w.writeAll("\n") catch {}; + w.writeAll("\n") catch {}; - if (run.summary == .line) break :summary; + if (run.summary == .line) break :summary; - // Print a fancy tree with build results. - var step_stack_copy = try step_stack.clone(gpa); - defer step_stack_copy.deinit(gpa); + // Print a fancy tree with build results. + var step_stack_copy = try step_stack.clone(gpa); + defer step_stack_copy.deinit(gpa); - var print_node: PrintNode = .{ .parent = null }; - if (step_names.len == 0) { - print_node.last = true; - printTreeStep(graph, graph.default_step, run, t, &print_node, &step_stack_copy) catch {}; - } else { - const last_index = if (run.summary == .all) run.top_level_steps.count() else blk: { - var i: usize = step_names.len; - while (i > 0) { - i -= 1; - const step = run.top_level_steps.get(step_names[i]).?.step; - const found = switch (run.summary) { - .all, .line, .none => unreachable, - .failures => step.state != .success, - .new => !step.result_cached, - }; - if (found) break :blk i; + var print_node: PrintNode = .{ .parent = null }; + if (step_names.len == 0) { + print_node.last = true; + printTreeStep(graph, graph.default_step, run, t, &print_node, &step_stack_copy) catch {}; + } else { + const last_index = if (run.summary == .all) top_level_steps.count() else blk: { + var i: usize = step_names.len; + while (i > 0) { + i -= 1; + const step = top_level_steps.get(step_names[i]).?.step; + const found = switch (run.summary) { + .all, .line, .none => unreachable, + .failures => step.state != .success, + .new => !step.result_cached, + }; + if (found) break :blk i; + } + break :blk top_level_steps.count(); + }; + for (step_names, 0..) |step_name, i| { + const tls = top_level_steps.get(step_name).?; + print_node.last = i + 1 == last_index; + printTreeStep(graph, &tls.step, run, t, &print_node, &step_stack_copy) catch {}; } - break :blk run.top_level_steps.count(); - }; - for (step_names, 0..) |step_name, i| { - const tls = run.top_level_steps.get(step_name).?; - print_node.last = i + 1 == last_index; - printTreeStep(graph, &tls.step, run, t, &print_node, &step_stack_copy) catch {}; } + w.writeByte('\n') catch {}; } - w.writeByte('\n') catch {}; - } - if (run.watch or run.web_server != null) return; + if (run.watch or run.web_server != null) return; - // Perhaps in the future there could be an Advanced Options flag such as - // --debug-build-runner-leaks which would make this code return instead of - // calling exit. + // Perhaps in the future there could be an Advanced Options flag such as + // --debug-build-runner-leaks which would make this code return instead of + // calling exit. - const code: u8 = code: { - if (failure_count == 0) break :code 0; // success - if (run.error_style.verboseContext()) break :code 1; // failure; print build command - break :code 2; // failure; do not print build command - }; - _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; - process.exit(code); -} + const code: u8 = code: { + if (failure_count == 0) break :code 0; // success + if (run.error_style.verboseContext()) break :code 1; // failure; print build command + break :code 2; // failure; do not print build command + }; + _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; + process.exit(code); + } +}; const PrintNode = struct { parent: ?*PrintNode, @@ -1221,40 +1246,47 @@ fn printTreeStep( /// random order fn constructGraphAndCheckForDependencyLoop( gpa: Allocator, - s: *Step, - step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), + c: *const Configuration, + steps: []Step, + step_index: Configuration.Step.Index, + step_stack: *std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void), rand: std.Random, -) !void { +) error{ DependencyLoopDetected, OutOfMemory }!void { + const s: *Step = &steps[@intFromEnum(step_index)]; switch (s.state) { .precheck_started => { - std.debug.print("dependency loop detected:\n {s}\n", .{s.name}); + log.err("dependency loop detected: {s}", .{step_index.ptr(c).name.slice(c)}); return error.DependencyLoopDetected; }, .precheck_unstarted => { s.state = .precheck_started; - try step_stack.ensureUnusedCapacity(gpa, s.dependencies.items.len); + const step = step_index.ptr(c); + const dependencies = step.deps.slice(c); + try step_stack.ensureUnusedCapacity(gpa, dependencies.len); // We dupe to avoid shuffling the steps in the summary, it depends - // on s.dependencies' order. - const deps = try gpa.dupe(*Step, s.dependencies.items); + // on dependencies' order. + const deps = try gpa.dupe(Configuration.Step.Index, dependencies); defer gpa.free(deps); - rand.shuffle(*Step, deps); + rand.shuffle(Configuration.Step.Index, deps); for (deps) |dep| { + const dep_step: *Step = &steps[@intFromEnum(dep)]; try step_stack.put(gpa, dep, {}); - try dep.dependants.append(gpa, s); - constructGraphAndCheckForDependencyLoop(gpa, dep, step_stack, rand) catch |err| { - if (err == error.DependencyLoopDetected) { - std.debug.print(" {s}\n", .{s.name}); - } - return err; + try dep_step.dependants.append(gpa, s); + constructGraphAndCheckForDependencyLoop(gpa, c, steps, dep, step_stack, rand) catch |err| switch (err) { + error.DependencyLoopDetected => { + log.info("needed by: {s}", .{step_index.ptr(c).name.slice(c)}); + return err; + }, + else => return err, }; } s.state = .precheck_done; - s.pending_deps = @intCast(s.dependencies.items.len); + s.pending_deps = @intCast(dependencies.len); }, .precheck_done => {}, @@ -1492,8 +1524,7 @@ fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 { fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 { return nextArg(args, idx) orelse { - log.info("access the help menu with \"zig build -h\"", .{}); - fatal("expected argument after {q}", .{args[idx.* - 1]}); + fatalWithHint("expected argument after {q}", .{args[idx.* - 1]}); }; } @@ -1532,7 +1563,7 @@ const MultilineErrors = enum { indent, newline, none }; const Summary = enum { all, new, failures, line, none }; fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn { - log.info("access the help menu with 'zig build -h'", .{}); + log.info("to access the help menu: zig build -h", .{}); fatal(f, args); } @@ -1547,13 +1578,6 @@ fn cleanTmpFiles(io: Io, steps: []const *Step) void { } } -const InstallPaths = struct { - prefix: Path, - lib: Path, - bin: Path, - include: Path, -}; - var stdio_buffer_allocation: [256]u8 = undefined; var stdout_writer_allocation: Io.File.Writer = undefined; @@ -1564,17 +1588,20 @@ fn initStdoutWriter(io: Io) *Writer { const ScannedConfig = struct { configuration: Configuration, - top_level_steps: []const Configuration.Step.Index, + top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index), fn print(sc: *const ScannedConfig, w: *Writer) Writer.Error!void { + const c = &sc.configuration; var serializer: std.zon.Serializer = .{ .writer = w }; var s = try serializer.beginStruct(.{}); - try s.field("default_step", @intFromEnum(sc.configuration.default_step), .{}); + try s.field("default_step", @intFromEnum(c.default_step), .{}); { - var tuple = try s.beginTupleField("top_level_steps", .{}); - for (sc.top_level_steps) |step| try tuple.field(@intFromEnum(step), .{}); - try tuple.end(); + var ss = try s.beginStructField("top_level_steps", .{}); + for (sc.top_level_steps.keys(), sc.top_level_steps.values()) |name, step| { + try ss.field(name, @intFromEnum(step), .{}); + } + try ss.end(); } try s.end(); @@ -1583,9 +1610,8 @@ const ScannedConfig = struct { fn printSteps(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void { const arena = graph.arena; const c = &sc.configuration; - for (sc.top_level_steps) |step_index| { + for (sc.top_level_steps.keys(), sc.top_level_steps.values()) |name, step_index| { const step = step_index.ptr(c); - const name = step.name.slice(c); const decorated_name = if (step_index == c.default_step) try fmt.allocPrint(arena, "{s} (default)", .{name}) else @@ -1679,8 +1705,8 @@ const ScannedConfig = struct { try w.writeAll( \\ \\General Options: - \\ -h, --help Print this help and exit - \\ -l, --list-steps Print available steps + \\ -h, --help Print this help to stdout and exit + \\ -l, --list-steps Print available steps to stdout and exit \\ \\ -p, --prefix [path] Where to install files (default: zig-out) \\ --prefix-lib-dir [path] Where to install libraries diff --git a/lib/compiler/maker/Package.zig b/lib/compiler/maker/Package.zig @@ -1,30 +0,0 @@ -const Package = @This(); - -const std = @import("std"); - -install_prefix: []const u8, -install_path: []const u8, -dest_dir: ?[]const u8, -lib_dir: []const u8, -exe_dir: []const u8, -h_dir: []const u8, -/// Path to the directory containing build.zig. -build_root: std.Build.Cache.Path, - -fn determineAndApplyInstallPrefix(p: *Package) error{OutOfMemory}!void { - // Create an installation directory local to this package. This will be used when - // dependant packages require a standard prefix, such as include directories for C headers. - var hash = p.graph.cache.hash; - // Random bytes to make unique. Refresh this with new random bytes when - // implementation is modified in a non-backwards-compatible way. - hash.add(@as(u32, 0xd8cb0056)); - hash.addBytes(p.dep_prefix); - - var wyhash = std.hash.Wyhash.init(0); - hashUserInputOptionsMap(p.allocator, p.user_input_options, &wyhash); - hash.add(wyhash.final()); - - const digest = hash.final(); - const install_prefix = try p.cache_root.join(p.allocator, &.{ "i", &digest }); - p.resolveInstallPrefix(install_prefix, .{}); -} diff --git a/lib/compiler/maker/Step.zig b/lib/compiler/maker/Step.zig @@ -1,19 +1,27 @@ +//! The state that maker needs in order to process a step. const Step = @This(); +const builtin = @import("builtin"); + const std = @import("std"); -const Io = std.Io; const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; +const Io = std.Io; +const LazyPath = std.Build.Configuration.LazyPath; +const Package = std.Build.Configuration.Package; +const Path = std.Build.Cache.Path; const assert = std.debug.assert; const WebServer = @import("WebServer.zig"); -pub const Compile = @import("Step/Compile.zig"); -pub const Run = @import("Step/Run.zig"); +pub const Compile = void; // @import("Step/Compile.zig"); +pub const Run = void; // @import("Step/Run.zig"); -state: State, -makeFn: MakeFn, -dependants: std.ArrayList(*Step), +/// Avoid false sharing. +_: void align(std.atomic.cache_line) = {}, + +state: State = .precheck_unstarted, +dependants: std.ArrayList(*Step) = .empty, /// Collects the set of files that retrigger this step to run. /// /// This is used by the build system's implementation of `--watch` but it can @@ -23,20 +31,19 @@ dependants: std.ArrayList(*Step), /// Populated within `make`. Implementation may choose to clear and repopulate, /// retain previous value, or update. inputs: Inputs = .init, -pending_deps: u32, +pending_deps: u32 = undefined, -result_error_msgs: std.ArrayList([]const u8), -result_error_bundle: std.zig.ErrorBundle, -result_stderr: []const u8, -result_cached: bool, -result_duration_ns: ?u64, +result_error_msgs: std.ArrayList([]const u8) = .empty, +result_error_bundle: std.zig.ErrorBundle = .empty, +result_stderr: []const u8 = "", +result_cached: bool = false, +result_duration_ns: ?u64 = null, /// 0 means unavailable or not reported. -result_peak_rss: usize, +result_peak_rss: usize = 0, /// If the step is failed and this field is populated, this is the command which failed. /// This field may be populated even if the step succeeded. -result_failed_command: ?[]const u8, -test_results: TestResults, - +result_failed_command: ?[]const u8 = null, +test_results: TestResults = .{}, pub const State = enum { precheck_unstarted, @@ -172,18 +179,6 @@ pub fn make(s: *Step, options: MakeOptions) error{ MakeFailed, MakeSkipped }!voi } } -fn makeNoOp(step: *Step, options: MakeOptions) anyerror!void { - _ = options; - - var all_cached = true; - - for (step.dependencies.items) |dep| { - all_cached = all_cached and dep.result_cached; - } - - step.result_cached = all_cached; -} - /// Implementation detail of file watching. Prepares the step for being re-evaluated. /// Returns `true` if the step was newly invalidated, `false` if it was already invalidated. pub fn invalidateResult(step: *Step, gpa: Allocator) bool { @@ -233,7 +228,7 @@ pub fn captureChildProcess( s.result_failed_command = try allocPrintCmd(gpa, .inherit, null, argv); try handleChildProcUnsupported(s); - try handleVerbose(s.owner, .inherit, argv); + try handleVerbose(s, .inherit, argv); const result = std.process.run(arena, io, .{ .argv = argv, @@ -340,7 +335,7 @@ pub fn evalZigProcess( assert(argv.len != 0); try handleChildProcUnsupported(s); - try handleVerbose(s.owner, .inherit, argv); + try handleVerbose(s, .inherit, argv); const zp = try gpa.create(ZigProcess); defer if (!watch) gpa.destroy(zp); @@ -399,11 +394,11 @@ pub fn evalZigProcess( } /// Wrapper around `Io.Dir.updateFile` that handles verbose and error output. -pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !Io.Dir.PrevStatus { +pub fn installFile(s: *Step, src_lazy_path: LazyPath, dest_path: []const u8) !Io.Dir.PrevStatus { const b = s.owner; const io = b.graph.io; const src_path = src_lazy_path.getPath3(b, s); - try handleVerbose(b, .inherit, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path }); + try handleVerbose(s, .inherit, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path }); return Io.Dir.updateFile(src_path.root_dir.handle, io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err| return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err }); } @@ -412,7 +407,7 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.CreatePathStatus { const b = s.owner; const io = b.graph.io; - try handleVerbose(b, .inherit, &.{ "install", "-d", dest_path }); + try handleVerbose(s, .inherit, &.{ "install", "-d", dest_path }); return Io.Dir.cwd().createDirPathStatus(io, dest_path, .default_dir) catch |err| return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err }); } @@ -567,29 +562,21 @@ fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { } pub fn handleVerbose( - b: *Build, - cwd: std.process.Child.Cwd, - argv: []const []const u8, -) error{OutOfMemory}!void { - return handleVerbose2(b, cwd, null, argv); -} - -pub fn handleVerbose2( - b: *Build, + s: *Step, + arena: Allocator, cwd: std.process.Child.Cwd, opt_env: ?*const std.process.Environ.Map, argv: []const []const u8, ) error{OutOfMemory}!void { - if (b.verbose) { - const graph = b.graph; - // Intention of verbose is to print all sub-process command lines to - // stderr before spawning them. - const text = try allocPrintCmd(b.allocator, cwd, if (opt_env) |env| .{ - .child = env, - .parent = &graph.environ_map, - } else null, argv); - std.debug.print("{s}\n", .{text}); - } + if (!s.verbose) return; + const graph = s.graph; + // Intention of verbose is to print all sub-process command lines to + // stderr before spawning them. + const text = try allocPrintCmd(arena, cwd, if (opt_env) |env| .{ + .child = env, + .parent = &graph.environ_map, + } else null, argv); + std.log.scoped(.verbose).info("{s}", .{text}); } /// Asserts that the caller has already populated `s.result_failed_command`. @@ -688,7 +675,7 @@ fn setWatchInputsFromManifest(s: *Step, man: *Cache.Manifest) !void { } /// For steps that have a single input that never changes when re-running `make`. -pub fn singleUnchangingWatchInput(step: *Step, lazy_path: Build.LazyPath) Allocator.Error!void { +pub fn singleUnchangingWatchInput(step: *Step, lazy_path: LazyPath) Allocator.Error!void { if (!step.inputs.populated()) try step.addWatchInput(lazy_path); } @@ -698,7 +685,7 @@ pub fn clearWatchInputs(step: *Step) void { } /// Places a *file* dependency on the path. -pub fn addWatchInput(step: *Step, lazy_file: Build.LazyPath) Allocator.Error!void { +pub fn addWatchInput(step: *Step, lazy_file: LazyPath) Allocator.Error!void { switch (lazy_file) { .src_path => |src_path| try addWatchInputFromBuilder(step, src_path.owner, src_path.sub_path), .dependency => |d| try addWatchInputFromBuilder(step, d.dependency.builder, d.sub_path), @@ -723,7 +710,7 @@ pub fn addWatchInput(step: *Step, lazy_file: Build.LazyPath) Allocator.Error!voi /// Paths derived from this directory should also be manually added via /// `addDirectoryWatchInputFromPath` if and only if this function returns /// `true`. -pub fn addDirectoryWatchInput(step: *Step, lazy_directory: Build.LazyPath) Allocator.Error!bool { +pub fn addDirectoryWatchInput(step: *Step, lazy_directory: LazyPath) Allocator.Error!bool { switch (lazy_directory) { .src_path => |src_path| try addDirectoryWatchInputFromBuilder(step, src_path.owner, src_path.sub_path), .dependency => |d| try addDirectoryWatchInputFromBuilder(step, d.dependency.builder, d.sub_path), @@ -744,26 +731,26 @@ pub fn addDirectoryWatchInput(step: *Step, lazy_directory: Build.LazyPath) Alloc /// Any changes inside the directory will trigger invalidation. /// -/// See also `addDirectoryWatchInput` which takes a `Build.LazyPath` instead. +/// See also `addDirectoryWatchInput` which takes a `LazyPath` instead. /// /// This function should only be called when it has been verified that the /// dependency on `path` is not already accounted for by a `Step` dependency. /// In other words, before calling this function, first check that the -/// `Build.LazyPath` which this `path` is derived from is not `generated`. +/// `LazyPath` which this `path` is derived from is not `generated`. pub fn addDirectoryWatchInputFromPath(step: *Step, path: Cache.Path) !void { return addWatchInputFromPath(step, path, "."); } -fn addWatchInputFromBuilder(step: *Step, builder: *Build, sub_path: []const u8) !void { +fn addWatchInputFromBuilder(step: *Step, package: Package, sub_path: []const u8) !void { return addWatchInputFromPath(step, .{ - .root_dir = builder.build_root, + .root_dir = package.build_root, .sub_path = std.fs.path.dirname(sub_path) orelse "", }, std.fs.path.basename(sub_path)); } -fn addDirectoryWatchInputFromBuilder(step: *Step, builder: *Build, sub_path: []const u8) !void { +fn addDirectoryWatchInputFromBuilder(step: *Step, package: Package, sub_path: []const u8) !void { return addDirectoryWatchInputFromPath(step, .{ - .root_dir = builder.build_root, + .root_dir = package.build_root, .sub_path = sub_path, }); } @@ -847,16 +834,3 @@ pub fn allocPrintCmd( } return aw.toOwnedSlice(); } - -pub fn getInstallPath(b: *Build, dir: InstallDir, dest_rel_path: []const u8) []const u8 { - assert(!fs.path.isAbsolute(dest_rel_path)); // Install paths must be relative to the prefix - const base_dir = switch (dir) { - .prefix => b.install_path, - .bin => b.exe_dir, - .lib => b.lib_dir, - .header => b.h_dir, - .custom => |p| b.pathJoin(&.{ b.install_path, p }), - }; - return b.pathResolve(&.{ base_dir, dest_rel_path }); -} - diff --git a/lib/std/Build.zig b/lib/std/Build.zig @@ -112,6 +112,10 @@ pub const Graph = struct { /// respects the '--color' flag. stderr_mode: ?Io.Terminal.Mode = null, release_mode: ReleaseMode = .off, + /// Whether the user passed in "--" arguments. They can be added to a child + /// process via `Step.Run` API but cannot be observed in the configure + /// phase. + have_run_args: bool = false, }; const AvailableDeps = []const struct { []const u8, []const u8 }; diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig @@ -141,6 +141,8 @@ pub const Arg = union(enum) { bytes: []u8, output_file: *Output, output_directory: *Output, + /// The arguments passed after "--" on the "zig build" CLI. + cli_rest_positionals, }; pub const PrefixedArtifact = struct { diff --git a/lib/std/zig/Configuration.zig b/lib/std/zig/Configuration.zig @@ -413,6 +413,7 @@ pub const AvailableOption = extern struct { pub const Step = extern struct { name: String, + owner: Package.Index, deps: Deps, max_rss: MaxRss, /// Points into `extra` for step-specific data. First element has flags @@ -534,6 +535,7 @@ pub const Step = extern struct { bytes, output_file, output_directory, + cli_rest_positionals, }; }; @@ -841,7 +843,7 @@ pub const LazyPath = enum(u32) { pub const SourcePath = struct { flags: Flags, - owner: Package, + owner: Package.Index, sub_path: String, pub const Flags = packed struct(u32) { @@ -877,16 +879,19 @@ pub const LazyPath = enum(u32) { }; }; -/// It's an OptionalString which points to the package hash. -pub const Package = enum(u32) { - root = maxInt(u32), - _, +pub const Package = struct { + dep_prefix: String, + hash: String, - pub fn fromHash(hash: String) Package { - const result: Package = @enumFromInt(@intFromEnum(hash)); - assert(result != .root); - return result; - } + pub const Index = enum(u32) { + root = maxInt(u32), + _, + + pub fn depPrefixSlice(i: Index, c: *const Configuration) [:0]const u8 { + if (i == .root) return ""; + return extraData(c, Package, @intFromEnum(i)).dep_prefix.slice(c); + } + }; }; /// Trailing: @@ -900,7 +905,7 @@ pub const Package = enum(u32) { pub const Module = struct { flags: Flags, flags2: Flags2, - owner: Package, + owner: Package.Index, root_source_file: OptionalLazyPath, import_table: ImportTable, resolved_target: ResolvedTarget.OptionalIndex, @@ -1048,6 +1053,11 @@ pub const ImportTable = enum(u32) { /// elements is `Step.Index` per count. pub const Deps = enum(u32) { _, + + pub fn slice(deps: Deps, c: *const Configuration) []Step.Index { + const len = c.extra[@intFromEnum(deps)]; + return @ptrCast(c.extra[@intFromEnum(deps) + 1 ..][0..len]); + } }; /// Points into `extra`, where the first element is count of strings, following