Commit 311797f686

Lee Cannon <leecannon@leecannon.xyz>
2021-08-27 01:53:23
Rework build system `build_options` API (#9623)
* rework `build_options` to integrate with the FileSource abstraction * support mapping as an arbitrarily named package * support mapping to multiple different artifacts * use hash of contents for options filename
1 parent 04cafd8
Changed files (3)
lib/std/build/OptionsStep.zig
@@ -0,0 +1,257 @@
+const std = @import("../std.zig");
+const build = std.build;
+const fs = std.fs;
+const Step = build.Step;
+const Builder = build.Builder;
+const GeneratedFile = build.GeneratedFile;
+const LibExeObjStep = build.LibExeObjStep;
+const FileSource = build.FileSource;
+
+const OptionsStep = @This();
+
+step: Step,
+generated_file: GeneratedFile,
+builder: *Builder,
+
+contents: std.ArrayList(u8),
+artifact_args: std.ArrayList(OptionArtifactArg),
+file_source_args: std.ArrayList(OptionFileSourceArg),
+
+pub fn create(builder: *Builder) *OptionsStep {
+    const self = builder.allocator.create(OptionsStep) catch unreachable;
+    self.* = .{
+        .builder = builder,
+        .step = Step.init(.options, "options", builder.allocator, make),
+        .generated_file = undefined,
+        .contents = std.ArrayList(u8).init(builder.allocator),
+        .artifact_args = std.ArrayList(OptionArtifactArg).init(builder.allocator),
+        .file_source_args = std.ArrayList(OptionFileSourceArg).init(builder.allocator),
+    };
+    self.generated_file = .{ .step = &self.step };
+
+    return self;
+}
+
+pub fn addOption(self: *OptionsStep, comptime T: type, name: []const u8, value: T) void {
+    const out = self.contents.writer();
+    switch (T) {
+        []const []const u8 => {
+            out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)}) catch unreachable;
+            for (value) |slice| {
+                out.print("    \"{}\",\n", .{std.zig.fmtEscapes(slice)}) catch unreachable;
+            }
+            out.writeAll("};\n") catch unreachable;
+            return;
+        },
+        [:0]const u8 => {
+            out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable;
+            return;
+        },
+        []const u8 => {
+            out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable;
+            return;
+        },
+        ?[:0]const u8 => {
+            out.print("pub const {}: ?[:0]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable;
+            if (value) |payload| {
+                out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable;
+            } else {
+                out.writeAll("null;\n") catch unreachable;
+            }
+            return;
+        },
+        ?[]const u8 => {
+            out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable;
+            if (value) |payload| {
+                out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable;
+            } else {
+                out.writeAll("null;\n") catch unreachable;
+            }
+            return;
+        },
+        std.builtin.Version => {
+            out.print(
+                \\pub const {}: @import("std").builtin.Version = .{{
+                \\    .major = {d},
+                \\    .minor = {d},
+                \\    .patch = {d},
+                \\}};
+                \\
+            , .{
+                std.zig.fmtId(name),
+
+                value.major,
+                value.minor,
+                value.patch,
+            }) catch unreachable;
+        },
+        std.SemanticVersion => {
+            out.print(
+                \\pub const {}: @import("std").SemanticVersion = .{{
+                \\    .major = {d},
+                \\    .minor = {d},
+                \\    .patch = {d},
+                \\
+            , .{
+                std.zig.fmtId(name),
+
+                value.major,
+                value.minor,
+                value.patch,
+            }) catch unreachable;
+            if (value.pre) |some| {
+                out.print("    .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable;
+            }
+            if (value.build) |some| {
+                out.print("    .build = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable;
+            }
+            out.writeAll("};\n") catch unreachable;
+            return;
+        },
+        else => {},
+    }
+    switch (@typeInfo(T)) {
+        .Enum => |enum_info| {
+            out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))}) catch unreachable;
+            inline for (enum_info.fields) |field| {
+                out.print("    {},\n", .{std.zig.fmtId(field.name)}) catch unreachable;
+            }
+            out.writeAll("};\n") catch unreachable;
+        },
+        else => {},
+    }
+    out.print("pub const {}: {s} = {};\n", .{ std.zig.fmtId(name), @typeName(T), value }) catch unreachable;
+}
+
+/// The value is the path in the cache dir.
+/// Adds a dependency automatically.
+pub fn addOptionFileSource(
+    self: *OptionsStep,
+    name: []const u8,
+    source: FileSource,
+) void {
+    self.file_source_args.append(.{
+        .name = name,
+        .source = source.dupe(self.builder),
+    }) catch unreachable;
+    source.addStepDependencies(&self.step);
+}
+
+/// The value is the path in the cache dir.
+/// Adds a dependency automatically.
+pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *LibExeObjStep) void {
+    self.artifact_args.append(.{ .name = self.builder.dupe(name), .artifact = artifact }) catch unreachable;
+    self.step.dependOn(&artifact.step);
+}
+
+pub fn getPackage(self: OptionsStep, package_name: []const u8) build.Pkg {
+    return .{ .name = package_name, .path = self.getSource() };
+}
+
+pub fn getSource(self: OptionsStep) FileSource {
+    return .{ .generated = &self.generated_file };
+}
+
+fn make(step: *Step) !void {
+    const self = @fieldParentPtr(OptionsStep, "step", step);
+
+    for (self.artifact_args.items) |item| {
+        self.addOption(
+            []const u8,
+            item.name,
+            self.builder.pathFromRoot(item.artifact.getOutputSource().getPath(self.builder)),
+        );
+    }
+
+    for (self.file_source_args.items) |item| {
+        self.addOption(
+            []const u8,
+            item.name,
+            item.source.getPath(self.builder),
+        );
+    }
+
+    const options_directory = self.builder.pathFromRoot(
+        try fs.path.join(
+            self.builder.allocator,
+            &[_][]const u8{ self.builder.cache_root, "options" },
+        ),
+    );
+
+    try fs.cwd().makePath(options_directory);
+
+    const options_file = try fs.path.join(
+        self.builder.allocator,
+        &[_][]const u8{ options_directory, &self.hashContentsToFileName() },
+    );
+
+    try fs.cwd().writeFile(options_file, self.contents.items);
+
+    self.generated_file.path = options_file;
+}
+
+fn hashContentsToFileName(self: *OptionsStep) [64]u8 {
+    // This implementation is copied from `WriteFileStep.make`
+
+    var hash = std.crypto.hash.blake2.Blake2b384.init(.{});
+
+    // Random bytes to make OptionsStep unique. Refresh this with
+    // new random bytes when OptionsStep implementation is modified
+    // in a non-backwards-compatible way.
+    hash.update("yL0Ya4KkmcCjBlP8");
+    hash.update(self.contents.items);
+
+    var digest: [48]u8 = undefined;
+    hash.final(&digest);
+    var hash_basename: [64]u8 = undefined;
+    _ = fs.base64_encoder.encode(&hash_basename, &digest);
+    return hash_basename;
+}
+
+const OptionArtifactArg = struct {
+    name: []const u8,
+    artifact: *LibExeObjStep,
+};
+
+const OptionFileSourceArg = struct {
+    name: []const u8,
+    source: FileSource,
+};
+
+test "OptionsStep" {
+    if (std.builtin.os.tag == .wasi) return error.SkipZigTest;
+
+    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+    defer arena.deinit();
+    var builder = try Builder.create(
+        &arena.allocator,
+        "test",
+        "test",
+        "test",
+        "test",
+    );
+    defer builder.destroy();
+
+    const options = builder.addOptions();
+
+    options.addOption(usize, "option1", 1);
+    options.addOption(?usize, "option2", null);
+    options.addOption([]const u8, "string", "zigisthebest");
+    options.addOption(?[]const u8, "optional_string", null);
+    options.addOption(std.SemanticVersion, "semantic_version", try std.SemanticVersion.parse("0.1.2-foo+bar"));
+
+    try std.testing.expectEqualStrings(
+        \\pub const option1: usize = 1;
+        \\pub const option2: ?usize = null;
+        \\pub const string: []const u8 = "zigisthebest";
+        \\pub const optional_string: ?[]const u8 = null;
+        \\pub const semantic_version: @import("std").SemanticVersion = .{
+        \\    .major = 0,
+        \\    .minor = 1,
+        \\    .patch = 2,
+        \\    .pre = "foo",
+        \\    .build = "bar",
+        \\};
+        \\
+    , options.contents.items);
+}
lib/std/build.zig
@@ -23,6 +23,7 @@ pub const WriteFileStep = @import("build/WriteFileStep.zig");
 pub const RunStep = @import("build/RunStep.zig");
 pub const CheckFileStep = @import("build/CheckFileStep.zig");
 pub const InstallRawStep = @import("build/InstallRawStep.zig");
+pub const OptionsStep = @import("build/OptionsStep.zig");
 
 pub const Builder = struct {
     install_tls: TopLevelStep,
@@ -247,6 +248,10 @@ pub const Builder = struct {
         return LibExeObjStep.createExecutable(builder, name, root_src);
     }
 
+    pub fn addOptions(self: *Builder) *OptionsStep {
+        return OptionsStep.create(self);
+    }
+
     pub fn addObject(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep {
         return addObjectSource(self, name, convertOptionalPathToFileSource(root_src));
     }
@@ -1375,16 +1380,6 @@ pub const FileSource = union(enum) {
     }
 };
 
-const BuildOptionArtifactArg = struct {
-    name: []const u8,
-    artifact: *LibExeObjStep,
-};
-
-const BuildOptionFileSourceArg = struct {
-    name: []const u8,
-    source: FileSource,
-};
-
 pub const LibExeObjStep = struct {
     pub const base_id = .lib_exe_obj;
 
@@ -1434,9 +1429,6 @@ pub const LibExeObjStep = struct {
     out_lib_filename: []const u8,
     out_pdb_filename: []const u8,
     packages: ArrayList(Pkg),
-    build_options_contents: std.ArrayList(u8),
-    build_options_artifact_args: std.ArrayList(BuildOptionArtifactArg),
-    build_options_file_source_args: std.ArrayList(BuildOptionFileSourceArg),
 
     object_src: []const u8,
 
@@ -1603,9 +1595,6 @@ pub const LibExeObjStep = struct {
             .rpaths = ArrayList([]const u8).init(builder.allocator),
             .framework_dirs = ArrayList([]const u8).init(builder.allocator),
             .object_src = undefined,
-            .build_options_contents = std.ArrayList(u8).init(builder.allocator),
-            .build_options_artifact_args = std.ArrayList(BuildOptionArtifactArg).init(builder.allocator),
-            .build_options_file_source_args = std.ArrayList(BuildOptionFileSourceArg).init(builder.allocator),
             .c_std = Builder.CStd.C99,
             .override_lib_dir = null,
             .main_pkg_path = null,
@@ -2038,119 +2027,6 @@ pub const LibExeObjStep = struct {
         self.linkLibraryOrObject(obj);
     }
 
-    pub fn addBuildOption(self: *LibExeObjStep, comptime T: type, name: []const u8, value: T) void {
-        const out = self.build_options_contents.writer();
-        switch (T) {
-            []const []const u8 => {
-                out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)}) catch unreachable;
-                for (value) |slice| {
-                    out.print("    \"{}\",\n", .{std.zig.fmtEscapes(slice)}) catch unreachable;
-                }
-                out.writeAll("};\n") catch unreachable;
-                return;
-            },
-            [:0]const u8 => {
-                out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable;
-                return;
-            },
-            []const u8 => {
-                out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable;
-                return;
-            },
-            ?[:0]const u8 => {
-                out.print("pub const {}: ?[:0]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable;
-                if (value) |payload| {
-                    out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable;
-                } else {
-                    out.writeAll("null;\n") catch unreachable;
-                }
-                return;
-            },
-            ?[]const u8 => {
-                out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable;
-                if (value) |payload| {
-                    out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable;
-                } else {
-                    out.writeAll("null;\n") catch unreachable;
-                }
-                return;
-            },
-            std.builtin.Version => {
-                out.print(
-                    \\pub const {}: @import("std").builtin.Version = .{{
-                    \\    .major = {d},
-                    \\    .minor = {d},
-                    \\    .patch = {d},
-                    \\}};
-                    \\
-                , .{
-                    std.zig.fmtId(name),
-
-                    value.major,
-                    value.minor,
-                    value.patch,
-                }) catch unreachable;
-            },
-            std.SemanticVersion => {
-                out.print(
-                    \\pub const {}: @import("std").SemanticVersion = .{{
-                    \\    .major = {d},
-                    \\    .minor = {d},
-                    \\    .patch = {d},
-                    \\
-                , .{
-                    std.zig.fmtId(name),
-
-                    value.major,
-                    value.minor,
-                    value.patch,
-                }) catch unreachable;
-                if (value.pre) |some| {
-                    out.print("    .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable;
-                }
-                if (value.build) |some| {
-                    out.print("    .build = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable;
-                }
-                out.writeAll("};\n") catch unreachable;
-                return;
-            },
-            else => {},
-        }
-        switch (@typeInfo(T)) {
-            .Enum => |enum_info| {
-                out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))}) catch unreachable;
-                inline for (enum_info.fields) |field| {
-                    out.print("    {},\n", .{std.zig.fmtId(field.name)}) catch unreachable;
-                }
-                out.writeAll("};\n") catch unreachable;
-            },
-            else => {},
-        }
-        out.print("pub const {}: {s} = {};\n", .{ std.zig.fmtId(name), @typeName(T), value }) catch unreachable;
-    }
-
-    /// The value is the path in the cache dir.
-    /// Adds a dependency automatically.
-    pub fn addBuildOptionArtifact(self: *LibExeObjStep, name: []const u8, artifact: *LibExeObjStep) void {
-        self.build_options_artifact_args.append(.{ .name = self.builder.dupe(name), .artifact = artifact }) catch unreachable;
-        self.step.dependOn(&artifact.step);
-    }
-
-    /// The value is the path in the cache dir.
-    /// Adds a dependency automatically.
-    /// basename refers to the basename of the WriteFileStep
-    pub fn addBuildOptionFileSource(
-        self: *LibExeObjStep,
-        name: []const u8,
-        source: FileSource,
-    ) void {
-        self.build_options_file_source_args.append(.{
-            .name = name,
-            .source = source.dupe(self.builder),
-        }) catch unreachable;
-        source.addStepDependencies(&self.step);
-    }
-
     pub fn addSystemIncludeDir(self: *LibExeObjStep, path: []const u8) void {
         self.include_dirs.append(IncludeDir{ .raw_path_system = self.builder.dupe(path) }) catch unreachable;
     }
@@ -2176,6 +2052,10 @@ pub const LibExeObjStep = struct {
         self.addRecursiveBuildDeps(package);
     }
 
+    pub fn addOptions(self: *LibExeObjStep, package_name: []const u8, options: *OptionsStep) void {
+        self.addPackage(options.getPackage(package_name));
+    }
+
     fn addRecursiveBuildDeps(self: *LibExeObjStep, package: Pkg) void {
         package.path.addStepDependencies(&self.step);
         if (package.dependencies) |deps| {
@@ -2393,41 +2273,6 @@ pub const LibExeObjStep = struct {
             }
         }
 
-        if (self.build_options_contents.items.len > 0 or
-            self.build_options_artifact_args.items.len > 0 or
-            self.build_options_file_source_args.items.len > 0)
-        {
-            // Render build artifact and write file options at the last minute, now that the path is known.
-            //
-            // Note that pathFromRoot uses resolve path, so this will have
-            // correct behavior even if getOutputPath is already absolute.
-            for (self.build_options_artifact_args.items) |item| {
-                self.addBuildOption(
-                    []const u8,
-                    item.name,
-                    self.builder.pathFromRoot(item.artifact.getOutputSource().getPath(self.builder)),
-                );
-            }
-            for (self.build_options_file_source_args.items) |item| {
-                self.addBuildOption(
-                    []const u8,
-                    item.name,
-                    item.source.getPath(self.builder),
-                );
-            }
-
-            const build_options_file = try fs.path.join(
-                builder.allocator,
-                &[_][]const u8{ builder.cache_root, builder.fmt("{s}_build_options.zig", .{self.name}) },
-            );
-            const path_from_root = builder.pathFromRoot(build_options_file);
-            try fs.cwd().writeFile(path_from_root, self.build_options_contents.items);
-            try zig_args.append("--pkg-begin");
-            try zig_args.append("build_options");
-            try zig_args.append(path_from_root);
-            try zig_args.append("--pkg-end");
-        }
-
         if (self.image_base) |image_base| {
             try zig_args.append("--image-base");
             try zig_args.append(builder.fmt("0x{x}", .{image_base}));
@@ -3141,6 +2986,7 @@ pub const Step = struct {
         run,
         check_file,
         install_raw,
+        options,
         custom,
     };
 
@@ -3312,43 +3158,6 @@ test "Builder.dupePkg()" {
     try std.testing.expect(dupe_deps[0].path.path.ptr != pkg_dep.path.path.ptr);
 }
 
-test "LibExeObjStep.addBuildOption" {
-    if (builtin.os.tag == .wasi) return error.SkipZigTest;
-
-    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
-    defer arena.deinit();
-    var builder = try Builder.create(
-        &arena.allocator,
-        "test",
-        "test",
-        "test",
-        "test",
-    );
-    defer builder.destroy();
-
-    var exe = builder.addExecutable("not_an_executable", "/not/an/executable.zig");
-    exe.addBuildOption(usize, "option1", 1);
-    exe.addBuildOption(?usize, "option2", null);
-    exe.addBuildOption([]const u8, "string", "zigisthebest");
-    exe.addBuildOption(?[]const u8, "optional_string", null);
-    exe.addBuildOption(std.SemanticVersion, "semantic_version", try std.SemanticVersion.parse("0.1.2-foo+bar"));
-
-    try std.testing.expectEqualStrings(
-        \\pub const option1: usize = 1;
-        \\pub const option2: ?usize = null;
-        \\pub const string: []const u8 = "zigisthebest";
-        \\pub const optional_string: ?[]const u8 = null;
-        \\pub const semantic_version: @import("std").SemanticVersion = .{
-        \\    .major = 0,
-        \\    .minor = 1,
-        \\    .patch = 2,
-        \\    .pre = "foo",
-        \\    .build = "bar",
-        \\};
-        \\
-    , exe.build_options_contents.items);
-}
-
 test "LibExeObjStep.addPackage" {
     if (builtin.os.tag == .wasi) return error.SkipZigTest;
 
build.zig
@@ -109,9 +109,13 @@ pub fn build(b: *Builder) !void {
     b.default_step.dependOn(&exe.step);
     exe.single_threaded = single_threaded;
 
-    exe.addBuildOption(u32, "mem_leak_frames", mem_leak_frames);
-    exe.addBuildOption(bool, "skip_non_native", skip_non_native);
-    exe.addBuildOption(bool, "have_llvm", enable_llvm);
+    const exe_options = b.addOptions();
+    exe.addOptions("build_options", exe_options);
+
+    exe_options.addOption(u32, "mem_leak_frames", mem_leak_frames);
+    exe_options.addOption(bool, "skip_non_native", skip_non_native);
+    exe_options.addOption(bool, "have_llvm", enable_llvm);
+
     if (enable_llvm) {
         const cmake_cfg = if (static_llvm) null else findAndParseConfigH(b, config_h_path_option);
 
@@ -218,15 +222,15 @@ pub fn build(b: *Builder) !void {
             },
         }
     };
-    exe.addBuildOption([:0]const u8, "version", try b.allocator.dupeZ(u8, version));
+    exe_options.addOption([:0]const u8, "version", try b.allocator.dupeZ(u8, version));
 
     const semver = try std.SemanticVersion.parse(version);
-    exe.addBuildOption(std.SemanticVersion, "semver", semver);
+    exe_options.addOption(std.SemanticVersion, "semver", semver);
 
-    exe.addBuildOption(bool, "enable_logging", enable_logging);
-    exe.addBuildOption(bool, "enable_tracy", tracy != null);
-    exe.addBuildOption(bool, "is_stage1", is_stage1);
-    exe.addBuildOption(bool, "omit_stage2", omit_stage2);
+    exe_options.addOption(bool, "enable_logging", enable_logging);
+    exe_options.addOption(bool, "enable_tracy", tracy != null);
+    exe_options.addOption(bool, "is_stage1", is_stage1);
+    exe_options.addOption(bool, "omit_stage2", omit_stage2);
     if (tracy) |tracy_path| {
         const client_cpp = fs.path.join(
             b.allocator,
@@ -248,20 +252,23 @@ pub fn build(b: *Builder) !void {
     const is_darling_enabled = b.option(bool, "enable-darling", "[Experimental] Use Darling to run cross compiled macOS tests") orelse false;
     const glibc_multi_dir = b.option([]const u8, "enable-foreign-glibc", "Provide directory with glibc installations to run cross compiled tests that link glibc");
 
-    test_stage2.addBuildOption(bool, "enable_logging", enable_logging);
-    test_stage2.addBuildOption(bool, "skip_non_native", skip_non_native);
-    test_stage2.addBuildOption(bool, "skip_compile_errors", skip_compile_errors);
-    test_stage2.addBuildOption(bool, "is_stage1", is_stage1);
-    test_stage2.addBuildOption(bool, "omit_stage2", omit_stage2);
-    test_stage2.addBuildOption(bool, "have_llvm", enable_llvm);
-    test_stage2.addBuildOption(bool, "enable_qemu", is_qemu_enabled);
-    test_stage2.addBuildOption(bool, "enable_wine", is_wine_enabled);
-    test_stage2.addBuildOption(bool, "enable_wasmtime", is_wasmtime_enabled);
-    test_stage2.addBuildOption(u32, "mem_leak_frames", mem_leak_frames * 2);
-    test_stage2.addBuildOption(bool, "enable_darling", is_darling_enabled);
-    test_stage2.addBuildOption(?[]const u8, "glibc_multi_install_dir", glibc_multi_dir);
-    test_stage2.addBuildOption([:0]const u8, "version", try b.allocator.dupeZ(u8, version));
-    test_stage2.addBuildOption(std.SemanticVersion, "semver", semver);
+    const test_stage2_options = b.addOptions();
+    test_stage2.addOptions("build_options", test_stage2_options);
+
+    test_stage2_options.addOption(bool, "enable_logging", enable_logging);
+    test_stage2_options.addOption(bool, "skip_non_native", skip_non_native);
+    test_stage2_options.addOption(bool, "skip_compile_errors", skip_compile_errors);
+    test_stage2_options.addOption(bool, "is_stage1", is_stage1);
+    test_stage2_options.addOption(bool, "omit_stage2", omit_stage2);
+    test_stage2_options.addOption(bool, "have_llvm", enable_llvm);
+    test_stage2_options.addOption(bool, "enable_qemu", is_qemu_enabled);
+    test_stage2_options.addOption(bool, "enable_wine", is_wine_enabled);
+    test_stage2_options.addOption(bool, "enable_wasmtime", is_wasmtime_enabled);
+    test_stage2_options.addOption(u32, "mem_leak_frames", mem_leak_frames * 2);
+    test_stage2_options.addOption(bool, "enable_darling", is_darling_enabled);
+    test_stage2_options.addOption(?[]const u8, "glibc_multi_install_dir", glibc_multi_dir);
+    test_stage2_options.addOption([:0]const u8, "version", try b.allocator.dupeZ(u8, version));
+    test_stage2_options.addOption(std.SemanticVersion, "semver", semver);
 
     const test_stage2_step = b.step("test-stage2", "Run the stage2 compiler tests");
     test_stage2_step.dependOn(&test_stage2.step);