From c3168cf25a5824eba35d337c56c5f7c784162c5c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 15 Feb 2026 18:47:38 -0800 Subject: [PATCH] configure runner: implement serialization of InstallArtifact --- lib/compiler/configure_runner.zig | 107 +++++++++++++++------ lib/std/Build.zig | 1 - lib/std/Build/Step/InstallArtifact.zig | 4 +- lib/std/Build/Step/Run.zig | 7 +- lib/std/zig/Configuration.zig | 124 +++++++++++++++++++++++-- src/main.zig | 23 ++++- 6 files changed, 219 insertions(+), 47 deletions(-) diff --git a/lib/compiler/configure_runner.zig b/lib/compiler/configure_runner.zig index 78d3dff4c3..07caacc4e2 100644 --- a/lib/compiler/configure_runner.zig +++ b/lib/compiler/configure_runner.zig @@ -140,16 +140,13 @@ pub fn main(init: process.Init.Minimal) !void { if (try builder.addUserInputFlag(option_contents)) fatal(" access the help menu with 'zig build -h'", .{}); } - } else if (mem.startsWith(u8, arg, "-fsys=")) { - const name = arg["-fsys=".len..]; + } else if (mem.cutPrefix(u8, arg, "-fsys=")) |name| { graph.system_library_options.put(arena, name, .user_enabled) catch @panic("OOM"); - } else if (mem.startsWith(u8, arg, "-fno-sys=")) { - const name = arg["-fno-sys=".len..]; + } else if (mem.cutPrefix(u8, arg, "-fno-sys=")) |name| { graph.system_library_options.put(arena, name, .user_disabled) catch @panic("OOM"); } else if (mem.eql(u8, arg, "--release")) { graph.release_mode = .any; - } else if (mem.startsWith(u8, arg, "--release=")) { - const text = arg["--release=".len..]; + } else if (mem.cutPrefix(u8, arg, "--release=")) |text| { graph.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, text) orelse { fatalWithHint("expected [off|any|fast|safe|small] in '{s}', found '{s}'", .{ arg, text, @@ -175,23 +172,11 @@ pub fn main(init: process.Init.Minimal) !void { multiline_errors = std.meta.stringToEnum(MultilineErrors, next_arg) orelse { fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg }); }; - } else if (mem.eql(u8, arg, "--seed")) { - const next_arg = nextArg(args, &arg_idx) orelse - fatalWithHint("expected u32 after '{s}'", .{arg}); - graph.random_seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| { - fatal("unable to parse seed '{s}' as unsigned 32-bit integer: {s}\n", .{ - next_arg, @errorName(err), - }); - }; } else if (mem.eql(u8, arg, "--build-id")) { builder.build_id = .fast; - } else if (mem.startsWith(u8, arg, "--build-id=")) { - const style = arg["--build-id=".len..]; - builder.build_id = std.zig.BuildId.parse(style) catch |err| { - fatal("unable to parse --build-id style '{s}': {s}", .{ - style, @errorName(err), - }); - }; + } else if (mem.cutPrefix(u8, arg, "--build-id=")) |style| { + builder.build_id = std.zig.BuildId.parse(style) catch |err| + fatal("unable to parse --build-id style '{s}': {t}", .{ style, err }); } else if (mem.eql(u8, arg, "--debug-rt")) { graph.debug_compiler_runtime_libs = true; } else if (mem.eql(u8, arg, "--debug-compile-errors")) { @@ -203,11 +188,6 @@ 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.cutPrefix(u8, arg, "-j")) |text| { - const n = std.fmt.parseUnsigned(u32, text, 10) catch |err| - fatal("unable to parse jobs count '{s}': {t}", .{ text, err }); - if (n < 1) fatal("number of jobs must be at least 1", .{}); - threaded.setAsyncLimit(.limited(n)); } else { fatalWithHint("unrecognized argument: '{s}'", .{arg}); } @@ -281,6 +261,7 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { .name = try wc.addString(step.name), .flags = .{ .tag = step.tag }, .deps = deps, + .max_rss = .fromBytes(step.max_rss), .extra_index = switch (step.tag) { .top_level => e: { const top_level: *Step.TopLevel = @fieldParentPtr("step", step); @@ -289,7 +270,21 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { })); }, .compile => @panic("TODO"), - .install_artifact => @panic("TODO"), + .install_artifact => e: { + const ia: *Step.InstallArtifact = @fieldParentPtr("step", step); + break :e try wc.addExtra(@as(Configuration.Step.InstallArtifact, .{ + .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), + .implib_dir = try addInstallDir(wc, ia.implib_dir), + .emitted_implib = try addOptionalLazyPath(wc, ia.emitted_implib), + .pdb_dir = try addInstallDir(wc, ia.pdb_dir), + .emitted_pdb = try addOptionalLazyPath(wc, ia.emitted_pdb), + .h_dir = try addInstallDir(wc, ia.h_dir), + .emitted_h = try addOptionalLazyPath(wc, ia.emitted_h), + .artifact = stepIndex(&step_map, &ia.artifact.step), + })); + }, .install_file => @panic("TODO"), .install_dir => @panic("TODO"), .remove_dir => @panic("TODO"), @@ -315,10 +310,66 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { } try wc.write(writer, .{ - .default_step = @intCast(step_map.getIndex(b.default_step).?), + .default_step = stepIndex(&step_map, b.default_step), }); } +fn addOptionalLazyPath(wc: *Configuration.Wip, lp: ?std.Build.LazyPath) !Configuration.OptionalLazyPath { + return @enumFromInt(switch (lp orelse return .none) { + .src_path => |src_path| i: { + const owner = builderToPackage(src_path.owner); + const sub_path = try wc.addString(src_path.sub_path); + break :i try wc.addExtra(@as(Configuration.OptionalLazyPath.SourcePath, .{ + .flags = .{}, + .owner = owner, + .sub_path = sub_path, + })); + }, + .generated => |generated| i: { + const sub_path = try wc.addString(generated.sub_path); + break :i try wc.addExtra(@as(Configuration.OptionalLazyPath.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.OptionalLazyPath.Relative, .{ + .flags = .{ .base = .cwd }, + .sub_path = sub_path, + })); + }, + .dependency => |dependency| i: { + const owner = builderToPackage(dependency.dependency.builder); + const sub_path = try wc.addString(dependency.sub_path); + break :i try wc.addExtra(@as(Configuration.OptionalLazyPath.SourcePath, .{ + .flags = .{}, + .owner = owner, + .sub_path = sub_path, + })); + }, + }); +} + +fn builderToPackage(b: *std.Build) Configuration.Package { + _ = b; + @panic("TODO"); +} + +fn addInstallDir(wc: *Configuration.Wip, install_dir: ?std.Build.InstallDir) !Configuration.InstallDir { + switch (install_dir orelse return .none) { + .prefix => return .prefix, + .lib => return .lib, + .bin => return .bin, + .header => return .header, + .custom => |sub_path| return .initCustom(try wc.addString(sub_path)), + } +} + +fn stepIndex(step_map: *const std.AutoArrayHashMapUnmanaged(*Step, void), step: *Step) Configuration.Step.Index { + return @enumFromInt(step_map.getIndex(step).?); +} + /// If the given `Step` is a `Step.Compile`, adds any dependencies for that step which /// are implied by the module graph rooted at `step.cast(Step.Compile).?.root_module`. fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void { diff --git a/lib/std/Build.zig b/lib/std/Build.zig index f0edfb7b81..6fe78cb1d6 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -103,7 +103,6 @@ pub const Graph = struct { needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .empty, /// Information about the native target. Computed before build() is invoked. host: ResolvedTarget, - random_seed: u32 = 0, dependency_cache: InitializedDepMap = .empty, allow_so_scripts: ?bool = null, /// Steps should use `io` to limit the number of jobs, however in the case of diff --git a/lib/std/Build/Step/InstallArtifact.zig b/lib/std/Build/Step/InstallArtifact.zig index f4e9b1d818..820de6b142 100644 --- a/lib/std/Build/Step/InstallArtifact.zig +++ b/lib/std/Build/Step/InstallArtifact.zig @@ -1,8 +1,8 @@ +const InstallArtifact = @This(); + const std = @import("std"); const Step = std.Build.Step; const InstallDir = std.Build.InstallDir; -const InstallArtifact = @This(); -const fs = std.fs; const LazyPath = std.Build.LazyPath; step: Step, diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index b7dd256d8e..f1cde9d533 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -87,6 +87,7 @@ captured_stderr: ?*CapturedStdIo, dep_output_file: ?*Output, has_side_effects: bool, +test_runner_mode: bool = false, /// Populated during the fuzz phase if this run step corresponds to a unit test /// executable that contains fuzz tests. @@ -234,13 +235,11 @@ pub fn setName(run: *Run, name: []const u8) void { } pub fn enableTestRunnerMode(run: *Run) void { + if (run.test_runner_mode) return; const b = run.step.owner; run.stdio = .zig_test; run.addPrefixedDirectoryArg("--cache-dir=", .{ .cwd_relative = b.cache_root.path orelse "." }); - run.addArgs(&.{ - b.fmt("--seed=0x{x}", .{b.graph.random_seed}), - "--listen=-", - }); + run.test_runner_mode = true; } pub fn addArtifactArg(run: *Run, artifact: *Step.Compile) void { diff --git a/lib/std/zig/Configuration.zig b/lib/std/zig/Configuration.zig index 589828295d..ae83e63ef4 100644 --- a/lib/std/zig/Configuration.zig +++ b/lib/std/zig/Configuration.zig @@ -4,6 +4,7 @@ const std = @import("../std.zig"); const Io = std.Io; const Allocator = std.mem.Allocator; const assert = std.debug.assert; +const maxInt = std.math.maxInt; string_bytes: []u8, steps: []Step, @@ -21,8 +22,7 @@ pub const Header = extern struct { unlazy_deps_len: u32, extra_len: u32, - /// Index into `steps`. - default_step: u32, + default_step: Step.Index, }; pub const Wip = struct { @@ -97,7 +97,7 @@ pub const Wip = struct { } pub const Static = struct { - default_step: u32, + default_step: Step.Index, }; pub fn write(wip: *Wip, w: *Io.Writer, static: Static) Io.Writer.Error!void { @@ -182,10 +182,12 @@ pub const Wip = struct { const fields = @typeInfo(@TypeOf(extra)).@"struct".fields; var i = index; inline for (fields) |field| { - wip.extra.items[i] = switch (field.type) { - u32 => @field(extra, field.name), - String, Deps => @intFromEnum(@field(extra, field.name)), - else => @compileError("bad field type"), + comptime assert(@sizeOf(field.type) == @sizeOf(u32)); + wip.extra.items[i] = switch (@typeInfo(field.type)) { + .int => @field(extra, field.name), + .@"enum" => @intFromEnum(@field(extra, field.name)), + .@"struct" => @bitCast(@field(extra, field.name)), + else => @compileError("bad field type: " ++ @typeName(field.type)), }; i += 1; } @@ -196,6 +198,7 @@ pub const Step = extern struct { name: String, flags: Flags, deps: Deps, + max_rss: MaxRss, /// Points into `extra` for step-specific data. extra_index: u32, @@ -231,6 +234,98 @@ pub const Step = extern struct { pub const TopLevel = struct { description: String, }; + + pub const InstallArtifact = struct { + dest_dir: InstallDir, + dest_sub_path: String, + emitted_bin: OptionalLazyPath, + + implib_dir: InstallDir, + emitted_implib: OptionalLazyPath, + + pdb_dir: InstallDir, + emitted_pdb: OptionalLazyPath, + + h_dir: InstallDir, + emitted_h: OptionalLazyPath, + + /// Always a compile step. + artifact: Step.Index, + + const Flags = packed struct(u32) { + tag: Tag = .install_artifact, + dylib_symlinks: bool, + _: u23 = 0, + }; + }; +}; + +pub const MaxRss = enum(u32) { + none = 0, + _, + + pub fn toBytes(mr: MaxRss) usize { + const x: usize = @intFromEnum(mr); + return x << 8; + } + + pub fn fromBytes(bytes: usize) MaxRss { + return @enumFromInt(bytes >> 8); + } +}; + +/// An index into `extra`. +pub const OptionalLazyPath = enum(u32) { + none = maxInt(u32), + _, + + pub const Tag = enum(u8) { + /// A source file path relative to build root. + source_path, + generated, + relative, + }; + + pub const SourcePath = struct { + flags: Flags, + owner: Package, + sub_path: String, + + pub const Flags = packed struct(u32) { + tag: Tag = .source_path, + _: u24 = 0, + }; + }; + + pub const Generated = struct { + flags: Flags, + /// Applied after `up`. + sub_path: String, + + pub const Flags = packed struct(u32) { + tag: Tag = .generated, + /// The number of parent directories to go up. + /// 0 means the generated file itself. + /// 1 means the directory of the generated file. + /// 2 means the parent of that directory, and so on. + up: u24, + }; + }; + + pub const Relative = struct { + flags: Flags, + sub_path: String, + + pub const Flags = packed struct(u32) { + tag: Tag = .relative, + base: Path.Base, + _: u16 = 0, + }; + }; +}; + +pub const Package = enum(u32) { + _, }; /// Points into `extra`, where the first element is number of deps, @@ -258,6 +353,21 @@ pub const Path = extern struct { } }; +pub const InstallDir = enum(u32) { + none = maxInt(u32) - 4, + prefix = maxInt(u32) - 3, + lib = maxInt(u32) - 2, + bin = maxInt(u32) - 1, + header = maxInt(u32), + /// A `String` path relative to the prefix. + _, + + pub fn initCustom(sub_path: String) InstallDir { + assert(@intFromEnum(sub_path) < @intFromEnum(InstallDir.none)); + return @enumFromInt(@intFromEnum(sub_path)); + } +}; + /// Points into `string_bytes`, null-terminated. pub const String = enum(u32) { _, diff --git a/src/main.zig b/src/main.zig index 6572f3de77..59048cd5f5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4996,34 +4996,47 @@ fn cmdBuild( const default_seed = try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}); try configure_argv.ensureUnusedCapacity(arena, 16); + try make_argv.ensureUnusedCapacity(arena, 16); const argv_index_exe = configure_argv.items.len; _ = configure_argv.addOneAssumeCapacity(); + _ = make_argv.addOneAssumeCapacity(); configure_argv.appendAssumeCapacity("--zig"); configure_argv.appendAssumeCapacity(self_exe_path); + make_argv.appendAssumeCapacity("--zig"); + make_argv.appendAssumeCapacity(self_exe_path); + configure_argv.appendAssumeCapacity("--zig-lib-dir"); + make_argv.appendAssumeCapacity("--zig-lib-dir"); const argv_index_zig_lib_dir = configure_argv.items.len; _ = configure_argv.addOneAssumeCapacity(); + _ = make_argv.addOneAssumeCapacity(); configure_argv.appendAssumeCapacity("--build-root"); + make_argv.appendAssumeCapacity("--build-root"); const argv_index_build_file = configure_argv.items.len; _ = configure_argv.addOneAssumeCapacity(); + _ = make_argv.addOneAssumeCapacity(); configure_argv.appendAssumeCapacity("--local-cache"); + make_argv.appendAssumeCapacity("--local-cache"); const argv_index_cache_dir = configure_argv.items.len; _ = configure_argv.addOneAssumeCapacity(); + _ = make_argv.addOneAssumeCapacity(); configure_argv.appendAssumeCapacity("--global-cache"); + make_argv.appendAssumeCapacity("--global-cache"); const argv_index_global_cache_dir = configure_argv.items.len; _ = configure_argv.addOneAssumeCapacity(); + _ = make_argv.addOneAssumeCapacity(); - configure_argv.appendSliceAssumeCapacity(&.{ "--seed", default_seed }); - const argv_index_seed = configure_argv.items.len - 1; + make_argv.appendSliceAssumeCapacity(&.{ "--configuration", undefined }); + const argv_index_configuration_file = make_argv.items.len - 1; - const argv_index_configuration_file = make_argv.items.len; - _ = try make_argv.addOne(arena); + make_argv.appendSliceAssumeCapacity(&.{ "--seed", default_seed }); + const argv_index_seed = make_argv.items.len - 1; var color: Color = .auto; var n_jobs: ?u32 = null; @@ -5164,7 +5177,7 @@ fn cmdBuild( } else if (mem.eql(u8, arg, "--seed")) { if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); i += 1; - configure_argv.items[argv_index_seed] = args[i]; + make_argv.items[argv_index_seed] = args[i]; continue; } else if (mem.eql(u8, arg, "--")) { // The rest of the args are supposed to get passed onto