From 8bf3941f6ba1df180ed29636f548538e748b157b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 5 Aug 2026 15:37:58 -0700 Subject: [PATCH 1/9] std.Build: remove dead fields These were originally intended to tell the configure script whether or not those third party integrations were enabled. This is now a problem because we want to produce a configuration file that is indendent of whether such external integrations will be enabled, so that the logic does not need to be re-executed when those flags are changed on the command line. If we wish to make this feature interact with the configure script, let us consider carefully how to add it in a future enhancement, and not leave these dead fields sitting around in the meantime. closes #35607 --- lib/std/Build.zig | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index eed57a0b15b47b23b54b412c4305f12020b9773c..0258ce941c5fbb32bb162095d2ebec28262f6fc6 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -45,17 +45,6 @@ debug_log_scopes: []const []const u8 = &.{}, /// Set to 0 to disable stack collection. debug_stack_frames_count: u8 = 8, -/// Experimental. Use system Darling installation to run cross compiled macOS build artifacts. -enable_darling: bool = false, -/// Use system QEMU installation to run cross compiled foreign architecture build artifacts. -enable_qemu: bool = false, -/// Darwin. Use Rosetta to run x86_64 macOS build artifacts on arm64 macOS. -enable_rosetta: bool = false, -/// Use system Wasmtime installation to run cross compiled wasm/wasi build artifacts. -enable_wasmtime: bool = false, -/// Use system Wine installation to run cross compiled Windows build artifacts. -enable_wine: bool = false, - dep_prefix: []const u8 = "", modules: std.array_hash_map.String(*Module), @@ -388,11 +377,6 @@ fn createChild( .default_step = undefined, .top_level_steps = .{}, .debug_log_scopes = parent.debug_log_scopes, - .enable_darling = parent.enable_darling, - .enable_qemu = parent.enable_qemu, - .enable_rosetta = parent.enable_rosetta, - .enable_wasmtime = parent.enable_wasmtime, - .enable_wine = parent.enable_wine, .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }), .modules = .empty, .named_writefiles = .empty, -- 2.54.0 From 3a8984f254addc3de4423bab367192c77ebba4fc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 5 Aug 2026 15:45:32 -0700 Subject: [PATCH 2/9] delete standalone test: debug_io_color This test has a workaround in it (setting has_side_effects=true) which made it problematic to maintain. Delete the test instead. --- lib/std/Build/Step/Run.zig | 2 + test/standalone/build.zig.zon | 3 - test/standalone/debug_io_color/build.zig | 95 ------------------------ test/standalone/debug_io_color/main.zig | 7 -- 4 files changed, 2 insertions(+), 105 deletions(-) delete mode 100644 test/standalone/debug_io_color/build.zig delete mode 100644 test/standalone/debug_io_color/main.zig diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 9e1f3ca7047585a13fcae9b77de0afb6c45f9eee..564dd2507dca28de63f943054b787c8602cc4f86 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -68,9 +68,11 @@ rename_step_with_output_arg: bool, /// executed binary will not fail the build if the binary cannot be executed /// due to being for a foreign binary to the host system which is running the /// build graph. +/// /// Command-line arguments such as -fqemu and -fwasmtime may affect whether a /// binary is detected as foreign, as well as system configuration such as /// Rosetta (macOS) and binfmt_misc (Linux). +/// /// If this Run step is considered to have side-effects, then this flag does /// nothing. skip_foreign_checks: bool, diff --git a/test/standalone/build.zig.zon b/test/standalone/build.zig.zon index 190394704ae48cd28406511353905a2f8675dd63..e134d00bd7d5977cda3341787a3dfefca86b4b7c 100644 --- a/test/standalone/build.zig.zon +++ b/test/standalone/build.zig.zon @@ -187,9 +187,6 @@ .posix = .{ .path = "posix", }, - .debug_io_color = .{ - .path = "debug_io_color", - }, .elf2 = .{ .path = "elf2", }, diff --git a/test/standalone/debug_io_color/build.zig b/test/standalone/debug_io_color/build.zig deleted file mode 100644 index 22ce7c8c22f9ab3299bf14e07e7c0b7a23f367c6..0000000000000000000000000000000000000000 --- a/test/standalone/debug_io_color/build.zig +++ /dev/null @@ -1,95 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const test_step = b.step("test", "Test"); - b.default_step = test_step; - - // Most targets handle color the same way, regardless of whether libc is linked. - const native_target = b.graph.host; - addTestCases(test_step, native_target, false); - addTestCases(test_step, native_target, true); - - // WASI behaves differently depending on whether libc is linked. - if (b.enable_wasmtime) { - const wasi_target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .wasi }); - addTestCases(test_step, wasi_target, false); - addTestCases(test_step, wasi_target, true); - } -} - -fn addTestCases( - test_step: *std.Build.Step, - target: std.Build.ResolvedTarget, - link_libc: bool, -) void { - const b = test_step.owner; - const exe = b.addExecutable(.{ - .name = b.fmt("{s}{s}", .{ @tagName(target.result.os.tag), if (link_libc) "-libc" else "" }), - .root_module = b.createModule(.{ - .root_source_file = b.path("main.zig"), - .target = target, - .link_libc = link_libc, - }), - }); - - // Should reflect 'std.process.Environ.Block' and 'std.Io.Threaded.init_single_threaded'. - const debug_io_can_read_environ = switch (target.result.os.tag) { - .windows => true, - .wasi, .emscripten => link_libc, - .freestanding, .other => false, - else => true, - }; - - // Don't forget to account for whether the build process's stderr supports color. - const parent_stderr_color_enabled = (std.Io.Terminal.Mode.detect(b.graph.io, .stderr(), false, false) catch unreachable) != .no_color; - - _ = addTestCase(test_step, exe, "neither", .inherit, .manual, parent_stderr_color_enabled); - _ = addTestCase(test_step, exe, "neither", .redirect, .manual, false); - _ = addTestCase(test_step, exe, "no_color", .inherit, .disable, if (debug_io_can_read_environ) false else parent_stderr_color_enabled); - _ = addTestCase(test_step, exe, "no_color", .redirect, .disable, false); - _ = addTestCase(test_step, exe, "clicolor_force", .inherit, .enable, if (debug_io_can_read_environ) true else parent_stderr_color_enabled); - _ = addTestCase(test_step, exe, "clicolor_force", .redirect, .enable, debug_io_can_read_environ); - - const both = addTestCase(test_step, exe, "both", .inherit, .manual, if (debug_io_can_read_environ) false else parent_stderr_color_enabled); - both.setEnvironmentVariable("NO_COLOR", "1"); - both.setEnvironmentVariable("CLICOLOR_FORCE", "1"); - - const both_redirected = addTestCase(test_step, exe, "both", .redirect, .manual, false); - both_redirected.setEnvironmentVariable("NO_COLOR", "1"); - both_redirected.setEnvironmentVariable("CLICOLOR_FORCE", "1"); -} - -fn addTestCase( - test_step: *std.Build.Step, - exe: *std.Build.Step.Compile, - test_case_name: []const u8, - stderr: enum { inherit, redirect }, - run_step_color: std.Build.Step.Run.Color, - expected_color_enabled: bool, -) *std.Build.Step.Run { - const b = test_step.owner; - const step_name = b.fmt("{s} {s}{s}", .{ - exe.name, - test_case_name, - if (stderr == .redirect) "-redirect" else "", - }); - const run_exe = b.addRunArtifact(exe); - run_exe.setName(b.fmt("run {s}", .{step_name})); - - run_exe.failing_to_execute_foreign_is_an_error = false; - if (stderr == .redirect) run_exe.expectStdErrMatch(""); - - run_exe.clearEnvironment(); - run_exe.color = run_step_color; - - // Build system quirk: Currently, Run step stdout checks will also redirect stderr, so as a - // workaround we use a CheckFile step instead. We must also mark the Run step as having side - // effects, to ensure the parent stderr is inherited when not explicitly redirected. - run_exe.has_side_effects = true; - const stdout = run_exe.captureStdOut(.{}); - const check_file = b.addCheckFile(stdout, .{ .expected_exact = if (expected_color_enabled) "true" else "false" }); - check_file.setName(b.fmt("check {s}", .{step_name})); - test_step.dependOn(&check_file.step); - - return run_exe; -} diff --git a/test/standalone/debug_io_color/main.zig b/test/standalone/debug_io_color/main.zig deleted file mode 100644 index d9627f61792dbf38b9acf6b0e54f2ea54a138fe1..0000000000000000000000000000000000000000 --- a/test/standalone/debug_io_color/main.zig +++ /dev/null @@ -1,7 +0,0 @@ -const std = @import("std"); - -pub fn main() !void { - const stderr = std.debug.lockStderr(&.{}); - defer std.debug.unlockStderr(); - try std.Io.File.stdout().writeStreamingAll(std.Options.debug_io, if (stderr.terminal_mode != .no_color) "true" else "false"); -} -- 2.54.0 From 5228c8902fe178834f678cb5bec12a5a32d5df2f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 5 Aug 2026 16:31:10 -0700 Subject: [PATCH 3/9] tests: use skip_foreign_checks rather than dead branch Configuration logic, in general, should not try to guess whether an executable will be able to be run on the host. This can only be determined by trying to, and encountering failure, for example because binfmt_misc might be installed. OS might handle illegal instruction traps and emulate CPU features not available, etc. skip_foreign_checks is the mechanism intended to handle this use case. --- test/src/ErrorTrace.zig | 2 + test/src/StackTrace.zig | 2 + test/tests.zig | 84 +++++++++++++++++++---------------------- 3 files changed, 42 insertions(+), 46 deletions(-) diff --git a/test/src/ErrorTrace.zig b/test/src/ErrorTrace.zig index b0ce8b05bb39687429ffb8eec893f254d4c4813a..d570cf406ae4d248ff106c7b304cc77eb9097eef 100644 --- a/test/src/ErrorTrace.zig +++ b/test/src/ErrorTrace.zig @@ -105,6 +105,7 @@ fn addCaseConfig( exe.bundle_ubsan_rt = false; const run = b.addRunArtifact(exe); + run.skip_foreign_checks = true; run.removeEnvironmentVariable("CLICOLOR_FORCE"); run.setEnvironmentVariable("NO_COLOR", "1"); run.expectExitCode(1); @@ -116,6 +117,7 @@ fn addCaseConfig( }; const check_run = b.addRunArtifact(self.convert_exe); + check_run.skip_foreign_checks = true; check_run.setName(annotated_case_name); check_run.addFileArg(run.captureStdErr(.{})); check_run.expectStdOutEqual(expected_stderr); diff --git a/test/src/StackTrace.zig b/test/src/StackTrace.zig index 23938cbf1ade2733f8220bbb03612c183ad7e27a..ae2abdd21eddebbf2af40382404644de9d71277f 100644 --- a/test/src/StackTrace.zig +++ b/test/src/StackTrace.zig @@ -224,6 +224,7 @@ fn addCaseInstance( exe.bundle_ubsan_rt = false; const run = b.addRunArtifact(exe); + run.skip_foreign_checks = true; run.removeEnvironmentVariable("CLICOLOR_FORCE"); run.setEnvironmentVariable("NO_COLOR", "1"); run.addCheck(.{ .expect_term = term: { @@ -234,6 +235,7 @@ fn addCaseInstance( run.expectStdOutEqual(""); const check_run = b.addRunArtifact(self.convert_exe); + check_run.skip_foreign_checks = true; check_run.setName(annotated_case_name); check_run.addFileArg(run.captureStdErr(.{})); check_run.expectExitCode(0); diff --git a/test/tests.zig b/test/tests.zig index e2f62400d1d0efa4d40966263e64d4d80e0f2306..bf72b15cf3eb42ab4de671277aeb6555bd5a5867 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2455,29 +2455,25 @@ pub fn addStackTraceTests( }; stack_traces.addCases(host_cases, b.graph.host.result.os.tag); - if (b.enable_wine) { - const wine_cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); - wine_cases.* = .{ - .b = b, - .step = step, - .test_filters = test_filters, - .targets = wineAndCompatible32bit(b, skip_non_native), - .convert_exe = convert_exe, - }; - stack_traces.addCases(wine_cases, .windows); - } + const wine_cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); + wine_cases.* = .{ + .b = b, + .step = step, + .test_filters = test_filters, + .targets = wineAndCompatible32bit(b, skip_non_native), + .convert_exe = convert_exe, + }; + stack_traces.addCases(wine_cases, .windows); - if (b.enable_darling) { - const darling_cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); - darling_cases.* = .{ - .b = b, - .step = step, - .test_filters = test_filters, - .targets = darlingTargets(b), - .convert_exe = convert_exe, - }; - stack_traces.addCases(darling_cases, .macos); - } + const darling_cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); + darling_cases.* = .{ + .b = b, + .step = step, + .test_filters = test_filters, + .targets = darlingTargets(b), + .convert_exe = convert_exe, + }; + stack_traces.addCases(darling_cases, .macos); return step; } @@ -2510,31 +2506,27 @@ pub fn addErrorTraceTests( }; error_traces.addCases(host_cases, b.graph.host.result.os.tag); - if (b.enable_wine) { - const wine_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); - wine_cases.* = .{ - .b = b, - .step = step, - .test_filters = test_filters, - .targets = wineAndCompatible32bit(b, skip_non_native), - .optimize_modes = optimize_modes, - .convert_exe = convert_exe, - }; - error_traces.addCases(wine_cases, .windows); - } + const wine_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); + wine_cases.* = .{ + .b = b, + .step = step, + .test_filters = test_filters, + .targets = wineAndCompatible32bit(b, skip_non_native), + .optimize_modes = optimize_modes, + .convert_exe = convert_exe, + }; + error_traces.addCases(wine_cases, .windows); - if (b.enable_darling) { - const darling_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); - darling_cases.* = .{ - .b = b, - .step = step, - .test_filters = test_filters, - .targets = darlingTargets(b), - .optimize_modes = optimize_modes, - .convert_exe = convert_exe, - }; - error_traces.addCases(darling_cases, .macos); - } + const darling_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); + darling_cases.* = .{ + .b = b, + .step = step, + .test_filters = test_filters, + .targets = darlingTargets(b), + .optimize_modes = optimize_modes, + .convert_exe = convert_exe, + }; + error_traces.addCases(darling_cases, .macos); return step; } -- 2.54.0 From 127e2d8088a37b439ce087f2d58f544b9248dc02 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 5 Aug 2026 17:52:47 -0700 Subject: [PATCH 4/9] ability to forward third party integration arguments to child processes This provides a way to forward, e.g. the `-fqemu` argument from `zig build` to a child process during Maker execution without providing the information to the configuration logic. --- lib/compiler/Maker/Step/Run.zig | 33 ++++++++++++ lib/std/Build/Configuration.zig | 7 +++ lib/std/Build/Serialize.zig | 95 +++++++++++++++++++++++++++++++++ lib/std/Build/Step/Run.zig | 53 ++++++++++++++++++ test/tests.zig | 9 ++-- 5 files changed, 193 insertions(+), 4 deletions(-) diff --git a/lib/compiler/Maker/Step/Run.zig b/lib/compiler/Maker/Step/Run.zig index 7f74e76954f44e5b965955d5ff5113d07428b6a7..85087be4d06ff29c71eabebd303e9ba6077dd1f9 100644 --- a/lib/compiler/Maker/Step/Run.zig +++ b/lib/compiler/Maker/Step/Run.zig @@ -187,6 +187,11 @@ pub fn make( man.hash.addListOfBytes(run_args); } }, + .enable_darling => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_darling, arg.prefix.value, arg.suffix.value), + .enable_qemu => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_qemu, arg.prefix.value, arg.suffix.value), + .enable_rosetta => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_rosetta, arg.prefix.value, arg.suffix.value), + .enable_wasmtime => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_wasmtime, arg.prefix.value, arg.suffix.value), + .enable_wine => thirdPartyToggle(&man.hash, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value), } } @@ -351,6 +356,29 @@ pub fn make( step.clearFailedCommand(gpa); } +fn thirdPartyToggle( + man_hash: ?*Cache.HashHelper, + argv_list: *std.ArrayList([]const u8), + conf: *const Configuration, + setting: bool, + enable: ?Configuration.String, + disable: ?Configuration.String, +) void { + if (setting) { + if (enable) |string| { + const slice = string.slice(conf); + if (man_hash) |h| h.addBytesZ(slice); + argv_list.appendAssumeCapacity(slice); + } + } else { + if (disable) |string| { + const slice = string.slice(conf); + if (man_hash) |h| h.addBytesZ(slice); + argv_list.appendAssumeCapacity(slice); + } + } +} + /// Reads stdout of a Zig test process until a termination condition is reached: /// * A write fails, indicating the child unexpectedly closed stdin /// * A test (or a response from the test runner) times out @@ -1535,6 +1563,11 @@ pub fn rerunInFuzzMode( .output_file => unreachable, .output_directory => unreachable, .passthru => unreachable, + .enable_darling => thirdPartyToggle(null, &argv_list, conf, graph.enable_darling, arg.prefix.value, arg.suffix.value), + .enable_qemu => thirdPartyToggle(null, &argv_list, conf, graph.enable_qemu, arg.prefix.value, arg.suffix.value), + .enable_rosetta => thirdPartyToggle(null, &argv_list, conf, graph.enable_rosetta, arg.prefix.value, arg.suffix.value), + .enable_wasmtime => thirdPartyToggle(null, &argv_list, conf, graph.enable_wasmtime, arg.prefix.value, arg.suffix.value), + .enable_wine => thirdPartyToggle(null, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value), } } diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index 8337da6d2f9d44f6f3b0a96241b4478f55afe3b0..fb5b04dc0039b28850dd11817b73c2f2b597da3d 100644 --- a/lib/std/Build/Configuration.zig +++ b/lib/std/Build/Configuration.zig @@ -626,6 +626,13 @@ pub const Step = extern struct { output_file, output_directory, passthru, + /// `prefix` contains the enabled string. + /// `suffix` contains the disabled string. + enable_darling, + enable_qemu, + enable_rosetta, + enable_wasmtime, + enable_wine, }; pub const Index = IndexType(@This()); diff --git a/lib/std/Build/Serialize.zig b/lib/std/Build/Serialize.zig index c3e9443181565196d6d922ea97f6e94f386b0f8a..995a69da87f9a6becfdb35f61683178b5e046c8b 100644 --- a/lib/std/Build/Serialize.zig +++ b/lib/std/Build/Serialize.zig @@ -1017,6 +1017,101 @@ fn initArgsList(s: *Serialize, args: []const Step.Run.Arg) ![]const Configuratio .producer = .{ .value = null }, .generated = .{ .value = null }, }, + .enable_darling => |a| .{ + .flags = .{ + .tag = .enable_darling, + .prefix = a.enabled != null, + .suffix = a.disabled != null, + .basename = false, + .path = false, + .producer = false, + .generated = false, + .dep_file = false, + .make_absolute = false, + }, + .prefix = .{ .value = try s.addOptionalString(a.enabled) }, + .suffix = .{ .value = try s.addOptionalString(a.disabled) }, + .basename = .{ .value = null }, + .path = .{ .value = null }, + .producer = .{ .value = null }, + .generated = .{ .value = null }, + }, + .enable_qemu => |a| .{ + .flags = .{ + .tag = .enable_qemu, + .prefix = a.enabled != null, + .suffix = a.disabled != null, + .basename = false, + .path = false, + .producer = false, + .generated = false, + .dep_file = false, + .make_absolute = false, + }, + .prefix = .{ .value = try s.addOptionalString(a.enabled) }, + .suffix = .{ .value = try s.addOptionalString(a.disabled) }, + .basename = .{ .value = null }, + .path = .{ .value = null }, + .producer = .{ .value = null }, + .generated = .{ .value = null }, + }, + .enable_rosetta => |a| .{ + .flags = .{ + .tag = .enable_rosetta, + .prefix = a.enabled != null, + .suffix = a.disabled != null, + .basename = false, + .path = false, + .producer = false, + .generated = false, + .dep_file = false, + .make_absolute = false, + }, + .prefix = .{ .value = try s.addOptionalString(a.enabled) }, + .suffix = .{ .value = try s.addOptionalString(a.disabled) }, + .basename = .{ .value = null }, + .path = .{ .value = null }, + .producer = .{ .value = null }, + .generated = .{ .value = null }, + }, + .enable_wasmtime => |a| .{ + .flags = .{ + .tag = .enable_wasmtime, + .prefix = a.enabled != null, + .suffix = a.disabled != null, + .basename = false, + .path = false, + .producer = false, + .generated = false, + .dep_file = false, + .make_absolute = false, + }, + .prefix = .{ .value = try s.addOptionalString(a.enabled) }, + .suffix = .{ .value = try s.addOptionalString(a.disabled) }, + .basename = .{ .value = null }, + .path = .{ .value = null }, + .producer = .{ .value = null }, + .generated = .{ .value = null }, + }, + .enable_wine => |a| .{ + .flags = .{ + .tag = .enable_wine, + .prefix = a.enabled != null, + .suffix = a.disabled != null, + .basename = false, + .path = false, + .producer = false, + .generated = false, + .dep_file = false, + .make_absolute = false, + }, + .prefix = .{ .value = try s.addOptionalString(a.enabled) }, + .suffix = .{ .value = try s.addOptionalString(a.disabled) }, + .basename = .{ .value = null }, + .path = .{ .value = null }, + .producer = .{ .value = null }, + .generated = .{ .value = null }, + }, }); } return result; diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 564dd2507dca28de63f943054b787c8602cc4f86..7d271e10e1461606574b96a6966332975b39b706 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -151,6 +151,19 @@ pub const Arg = union(enum) { output_directory: *Output, /// The arguments passed after "--" on the "zig build" CLI. passthru, + + enable_darling: ToggleFlags, + enable_qemu: ToggleFlags, + enable_rosetta: ToggleFlags, + enable_wasmtime: ToggleFlags, + enable_wine: ToggleFlags, +}; + +pub const ToggleFlags = struct { + /// The string to pass when enabled, or null to omit the arg. + enabled: ?[]const u8 = null, + /// The string to pass when disabled, or null to omit the arg. + disabled: ?[]const u8 = null, }; pub const DecoratedArtifact = struct { @@ -578,6 +591,46 @@ pub fn addPassthruArgs(run: *Run) void { run.argv.append(arena, .passthru) catch @panic("OOM"); } +/// Appends a custom string to the command line depending on the `-fdarling` +/// value passed to `zig build`. +pub fn addThirdPartyEnabledArgDarling(run: *Run, toggle_flags: ToggleFlags) void { + const graph = run.step.owner.graph; + const arena = graph.arena; + run.argv.append(arena, .{ .enable_darling = toggle_flags }) catch @panic("OOM"); +} + +/// Appends a custom string to the command line depending on the `-fqemu` +/// value passed to `zig build`. +pub fn addThirdPartyEnabledArgQemu(run: *Run, toggle_flags: ToggleFlags) void { + const graph = run.step.owner.graph; + const arena = graph.arena; + run.argv.append(arena, .{ .enable_qemu = toggle_flags }) catch @panic("OOM"); +} + +/// Appends a custom string to the command line depending on the `-frosetta` +/// value passed to `zig build`. +pub fn addThirdPartyEnabledArgRosetta(run: *Run, toggle_flags: ToggleFlags) void { + const graph = run.step.owner.graph; + const arena = graph.arena; + run.argv.append(arena, .{ .enable_rosetta = toggle_flags }) catch @panic("OOM"); +} + +/// Appends a custom string to the command line depending on the `-fwasmtime` +/// value passed to `zig build`. +pub fn addThirdPartyEnabledArgWasmtime(run: *Run, toggle_flags: ToggleFlags) void { + const graph = run.step.owner.graph; + const arena = graph.arena; + run.argv.append(arena, .{ .enable_wasmtime = toggle_flags }) catch @panic("OOM"); +} + +/// Appends a custom string to the command line depending on the `-fwine` +/// value passed to `zig build`. +pub fn addThirdPartyEnabledArgWine(run: *Run, toggle_flags: ToggleFlags) void { + const graph = run.step.owner.graph; + const arena = graph.arena; + run.argv.append(arena, .{ .enable_wine = toggle_flags }) catch @panic("OOM"); +} + pub fn setStdIn(run: *Run, stdin: StdIn) void { switch (stdin) { .lazy_path => |lazy_path| lazy_path.addStepDependencies(&run.step), diff --git a/test/tests.zig b/test/tests.zig index bf72b15cf3eb42ab4de671277aeb6555bd5a5867..44fefc69357840c351979e7cd6551e43342f0298 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -3409,10 +3409,11 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc - if (b.enable_qemu) run.addArg("-fqemu"); - if (b.enable_wine) run.addArg("-fwine"); - if (b.enable_wasmtime) run.addArg("-fwasmtime"); - if (b.enable_darling) run.addArg("-fdarling"); + run.addThirdPartyEnabledArgDarling(.{ .enabled = "-fdarling" }); + run.addThirdPartyEnabledArgQemu(.{ .enabled = "-fqemu" }); + run.addThirdPartyEnabledArgRosetta(.{ .enabled = "-frosetta" }); + run.addThirdPartyEnabledArgWasmtime(.{ .enabled = "-fwasmtime" }); + run.addThirdPartyEnabledArgWine(.{ .enabled = "-fwine" }); run.addCheck(.{ .expect_term = .{ .exited = 0 } }); test_step.dependOn(&run.step); -- 2.54.0 From d3a7e7528dc424d689e696ce04d2b7ef5f663142 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 6 Aug 2026 18:55:02 -0700 Subject: [PATCH 5/9] stack trace tests: this check runs on host always --- test/src/StackTrace.zig | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/src/StackTrace.zig b/test/src/StackTrace.zig index ae2abdd21eddebbf2af40382404644de9d71277f..7270ef4cbbe51af44efab1f4950d660554889920 100644 --- a/test/src/StackTrace.zig +++ b/test/src/StackTrace.zig @@ -1,3 +1,12 @@ +const StackTrace = @This(); + +const builtin = @import("builtin"); + +const std = @import("std"); +const Step = std.Build.Step; +const OptimizeMode = std.lang.OptimizeMode; +const mem = std.mem; + b: *std.Build, step: *Step, test_filters: []const []const u8, @@ -45,7 +54,7 @@ fn addCaseTarget( triple: ?[]const u8, ) void { const both_backends = b: { - if (comptime builtin.cpu.arch.endian() == .big) break :b false; // https://github.com/ziglang/zig/issues/25961 + if (builtin.cpu.arch.endian() == .big) break :b false; // https://codeberg.org/ziglang/zig/issues/31522 break :b switch (target.result.cpu.arch) { .x86_64 => switch (target.result.ofmt) { .elf => !target.result.os.tag.isBSD() and target.result.os.tag != .illumos, @@ -235,7 +244,6 @@ fn addCaseInstance( run.expectStdOutEqual(""); const check_run = b.addRunArtifact(self.convert_exe); - check_run.skip_foreign_checks = true; check_run.setName(annotated_case_name); check_run.addFileArg(run.captureStdErr(.{})); check_run.expectExitCode(0); @@ -243,10 +251,3 @@ fn addCaseInstance( self.step.dependOn(&check_run.step); } - -const StackTrace = @This(); -const std = @import("std"); -const builtin = @import("builtin"); -const Step = std.Build.Step; -const OptimizeMode = std.builtin.OptimizeMode; -const mem = std.mem; -- 2.54.0 From d9d308f6241e6b3add34429cc2e8698c6d17c635 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 6 Aug 2026 20:21:53 -0700 Subject: [PATCH 6/9] Maker: skipped steps skip dependants too --- lib/compiler/Maker.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 869471d4e921856cee4fd870c299a95aa0fd5965..f949ac591a951a2f7ac5bc8fe5b5a672c85d4413 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -2581,9 +2581,10 @@ fn makeStep( .failure, .dependency_failure, .skipped_oom, + .skipped, => break .dependency_failure, - .success, .skipped => {}, + .success => {}, } } else if (Step.make(step_index, maker, step_prog_node)) state: { break :state .success; -- 2.54.0 From 750294aa1f87815e23bac1f3e6e287cf9fa88705 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 6 Aug 2026 20:27:23 -0700 Subject: [PATCH 7/9] Maker: introduce transitive_skip --- lib/compiler/Maker.zig | 21 ++++++++++++++++----- lib/compiler/Maker/Step.zig | 3 +++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index f949ac591a951a2f7ac5bc8fe5b5a672c85d4413..d0d36f62ce2c0d36aac774392edce182ed621d4a 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -2119,7 +2119,7 @@ fn markFailedStepsDirty(maker: *Maker) void { for (all_steps) |step_index| { const step = maker.stepByIndex(step_index); switch (step.state) { - .dependency_failure, .failure, .skipped => _ = maker.invalidateResult(step), + .dependency_failure, .dependency_skipped, .failure, .skipped => _ = maker.invalidateResult(step), else => continue, } } @@ -2334,7 +2334,7 @@ fn makeSteps( .precheck_unstarted => unreachable, .precheck_started => unreachable, .precheck_done => unreachable, - .dependency_failure => pending_count += 1, + .dependency_failure, .dependency_skipped => pending_count += 1, .success => success_count += 1, .skipped, .skipped_oom => skipped_count += 1, .failure => { @@ -2580,9 +2580,12 @@ fn makeStep( .failure, .dependency_failure, + => break .dependency_failure, + + .dependency_skipped, .skipped_oom, .skipped, - => break .dependency_failure, + => break .dependency_skipped, .success => {}, } @@ -2603,11 +2606,12 @@ fn makeStep( .failure, .dependency_failure, + .dependency_skipped, .skipped_oom, + .skipped, => false, .success, - .skipped, => true, }; @@ -2624,7 +2628,7 @@ fn makeStep( .precheck_done => unreachable, .success => .success, .failure, .dependency_failure => .failure, - .skipped => .skipped, + .dependency_skipped, .skipped => .skipped, .skipped_oom => .skipped_oom, }; serveBuildStepCompleted( @@ -2778,6 +2782,12 @@ fn printStepStatus(maker: *Maker, step_index: Configuration.Step.Index, stderr: try stderr.setColor(.reset); }, + .dependency_skipped => { + try stderr.setColor(.dim); + try writer.writeAll(" transitive skip\n"); + try stderr.setColor(.reset); + }, + .success => { try stderr.setColor(.green); if (s.result_cached) { @@ -3024,6 +3034,7 @@ fn constructGraphAndCheckForDependencyLoop( // These don't happen until we actually run the step graph. .dependency_failure => unreachable, + .dependency_skipped => unreachable, .success => unreachable, .failure => unreachable, .skipped => unreachable, diff --git a/lib/compiler/Maker/Step.zig b/lib/compiler/Maker/Step.zig index b8c5992cce243ed5b7f5fb0cdfc1b6b9d92f7fdb..181cdf2bd75fc678fc45d72cbab16a9e9bbeecd1 100644 --- a/lib/compiler/Maker/Step.zig +++ b/lib/compiler/Maker/Step.zig @@ -163,6 +163,9 @@ pub const State = enum { /// be re-evaluated. precheck_done, dependency_failure, + /// Handled exactly the same as `dependency_failure` except communicates + /// that the dependency didn't fail but rather was skipped. + dependency_skipped, success, failure, /// This state indicates that the step did not complete, however, it also did not fail, -- 2.54.0 From 0f1c478db329c4b91c3a34aed2d48e4cde059eff Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 7 Aug 2026 12:32:58 -0700 Subject: [PATCH 8/9] tests: remove "compiler has package manager" logic This is simplified now that package fetching is moved to Maker executable. --- test/tests.zig | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/test/tests.zig b/test/tests.zig index 44fefc69357840c351979e7cd6551e43342f0298..d165597131204537abc2c494a97885c3866a5e5f 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2531,12 +2531,6 @@ pub fn addErrorTraceTests( return step; } -fn compilerHasPackageManager(b: *std.Build) bool { - // We can only use dependencies if the compiler was built with support for package management. - // (zig2 doesn't support it, but we still need to construct a build graph to build stage3.) - return b.available_deps.len != 0; -} - pub fn addStandaloneTests( b: *std.Build, optimize_modes: []const OptimizeMode, @@ -2545,21 +2539,19 @@ pub fn addStandaloneTests( enable_symlinks_windows: bool, ) *Step { const step = b.step("test-standalone", "Run the standalone tests"); - if (compilerHasPackageManager(b)) { - const test_cases_dep_name = "standalone_test_cases"; - const test_cases_dep = b.dependency(test_cases_dep_name, .{ - .enable_ios_sdk = enable_ios_sdk, - .enable_macos_sdk = enable_macos_sdk, - .enable_symlinks_windows = enable_symlinks_windows, - .simple_skip_debug = mem.findScalar(OptimizeMode, optimize_modes, .debug) == null, - .simple_skip_release_safe = mem.findScalar(OptimizeMode, optimize_modes, .safe) == null, - .simple_skip_release_fast = mem.findScalar(OptimizeMode, optimize_modes, .fast) == null, - .simple_skip_release_small = mem.findScalar(OptimizeMode, optimize_modes, .small) == null, - }); - const test_cases_dep_step = test_cases_dep.builder.default_step; - test_cases_dep_step.name = b.graph.dupeString(test_cases_dep_name); - step.dependOn(test_cases_dep.builder.default_step); - } + const test_cases_dep_name = "standalone_test_cases"; + const test_cases_dep = b.dependency(test_cases_dep_name, .{ + .enable_ios_sdk = enable_ios_sdk, + .enable_macos_sdk = enable_macos_sdk, + .enable_symlinks_windows = enable_symlinks_windows, + .simple_skip_debug = mem.findScalar(OptimizeMode, optimize_modes, .debug) == null, + .simple_skip_release_safe = mem.findScalar(OptimizeMode, optimize_modes, .safe) == null, + .simple_skip_release_fast = mem.findScalar(OptimizeMode, optimize_modes, .fast) == null, + .simple_skip_release_small = mem.findScalar(OptimizeMode, optimize_modes, .small) == null, + }); + const test_cases_dep_step = test_cases_dep.builder.default_step; + test_cases_dep_step.name = b.graph.dupeString(test_cases_dep_name); + step.dependOn(test_cases_dep.builder.default_step); return step; } -- 2.54.0 From 8b2949e372e615886b9087b06187878452421724 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 7 Aug 2026 13:28:16 -0700 Subject: [PATCH 9/9] simplify stack trace and error trace tests by explicitly listing out the combinations of parameters we would like to check. --- test/error_traces.zig | 43 +++++++- test/src/ErrorTrace.zig | 169 ++++++++++++++++++++--------- test/src/StackTrace.zig | 234 +++++++++++++++++++++++++--------------- test/stack_traces.zig | 29 ++++- test/tests.zig | 114 ++------------------ 5 files changed, 338 insertions(+), 251 deletions(-) diff --git a/test/error_traces.zig b/test/error_traces.zig index 071e4e8df53b7f017f988f6759db5adc30cebef0..5f84202fcfa5268094a5b625c10e30cda2bc3725 100644 --- a/test/error_traces.zig +++ b/test/error_traces.zig @@ -1,7 +1,10 @@ const std = @import("std"); +const Context = @import("tests.zig").ErrorTracesContext; -pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.Os.Tag) void { +pub fn addCases(cases: *Context, params: *const Context.CaseParameters, target: *const std.Target) void { cases.addCase(.{ + .params = params, + .target = target, .name = "return", .source = \\pub fn main() !void { @@ -17,6 +20,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "try return", .source = \\fn foo() !void { @@ -44,6 +49,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }, }); cases.addCase(.{ + .params = params, + .target = target, .name = "non-error return pops error trace", .source = \\fn bar() !void { @@ -70,6 +77,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "continue in while loop", .source = \\fn foo() !void { @@ -93,6 +102,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "for loop pops error return trace", .source = \\fn foo() !void { return error.FooError; } @@ -123,6 +134,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "implicit continue in for loop pops stale error return trace", .source = \\fn foo() !void { return error.FooError; } @@ -154,6 +167,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "while loop pops error return trace", .source = \\fn foo() !void { return error.FooError; } @@ -186,6 +201,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "implicit continue in while loop pops stale error return trace", .source = \\fn foo() !void { return error.FooError; } @@ -219,6 +236,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "try return + handled catch/if-else", .source = \\fn foo() !void { @@ -251,6 +270,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "break from inline loop pops error return trace", .source = \\fn foo() !void { return error.FooBar; } @@ -276,6 +297,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "catch and re-throw error", .source = \\fn foo() !void { @@ -304,6 +327,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "errors stored in var do not contribute to error trace", .source = \\fn foo() !void { @@ -328,6 +353,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "error stored in const has trace preserved for duration of block", .source = \\fn foo() !void { return error.TheSkyIsFalling; } @@ -376,6 +403,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "error passed to function has its trace preserved for duration of the call", .source = \\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void { @@ -418,6 +447,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "try return from within catch", .source = \\fn foo() !void { @@ -455,6 +486,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "try return from within if-else", .source = \\fn foo() !void { @@ -492,6 +525,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "try try return return", .source = \\fn foo() !void { @@ -534,6 +569,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "error union switch with call operand", .source = \\pub fn main() !void { @@ -579,6 +616,8 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "trace through inline call", // The main function has two inline calls to ensure // that inlinees in PDBs are properly deduplicated. @@ -595,7 +634,7 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target. \\} , .expect_error = "ThisIsSoSad", - .expect_trace = switch (os) { + .expect_trace = switch (target.os.tag) { // LLVM doesn't emit column info in the binary annotations for inlinee callees in PDBs, // so our expected result is slightly different for Windows than on other operating // systems. diff --git a/test/src/ErrorTrace.zig b/test/src/ErrorTrace.zig index d570cf406ae4d248ff106c7b304cc77eb9097eef..7fda389dc6c295a6c950f95a6551945eab878643 100644 --- a/test/src/ErrorTrace.zig +++ b/test/src/ErrorTrace.zig @@ -1,11 +1,81 @@ +const ErrorTrace = @This(); + +const builtin = @import("builtin"); + +const std = @import("std"); +const Step = std.Build.Step; +const OptimizeMode = std.lang.Optimize; +const mem = std.mem; + +const error_traces_cases = @import("../error_traces.zig"); + b: *std.Build, step: *Step, test_filters: []const []const u8, -targets: []const std.Build.ResolvedTarget, +skip_non_native: bool, optimize_modes: []const OptimizeMode, convert_exe: *std.Build.Step.Compile, +pub const CaseParameters = @import("StackTrace.zig").CaseParameters; + +const param_sets = [_]CaseParameters{ + .{}, + .{ + .link_libc = true, + }, + .{ + .use_llvm = true, + .use_lld = true, + }, + .{ + .pie = true, + }, + .{ + .target = .{ + .cpu_arch = .aarch64, + .os_tag = .windows, + .abi = .msvc, + }, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .gnu, + }, + }, + .{ + .target = .{ + .cpu_arch = .x86, + .os_tag = .windows, + .abi = .msvc, + }, + }, + .{ + .target = .{ + .cpu_arch = .aarch64, + .os_tag = .macos, + }, + }, + .{ + .target = .{ + .cpu_arch = .s390x, + .os_tag = .linux, + .abi = .none, + }, + }, + .{ + .target = .{ + .cpu_arch = .loongarch32, + .os_tag = .linux, + .abi = .none, + }, + }, +}; + pub const Case = struct { + params: *const CaseParameters, + target: *const std.Target, name: []const u8, source: []const u8, expect_error: []const u8, @@ -22,50 +92,47 @@ pub const Case = struct { pub const Backend = enum { llvm, selfhosted }; }; -pub fn addCase(self: *ErrorTrace, case: Case) void { - for (self.targets) |*target| { - const triple: ?[]const u8 = if (target.query.isNative()) null else t: { - break :t target.query.zigTriple(self.b.graph.arena) catch @panic("OOM"); - }; +pub fn addCases(self: *ErrorTrace) void { + const b = self.b; + + for (¶m_sets) |*params| { + const resolved_target = b.resolveTargetQuery(params.target); + + if (self.skip_non_native and !resolved_target.query.isNative()) continue; + + // To avoid redundant testing, skip cross-compilation targets matching the host. + if (resolved_target.result.os.tag == builtin.target.os.tag and + resolved_target.result.cpu.arch == builtin.target.cpu.arch) + { + continue; + } + for (self.optimize_modes) |optimize| { - self.addCaseConfig(case, target, triple, optimize, .llvm); - } - if (shouldTestNonLlvm(&target.result)) { - for (self.optimize_modes) |optimize| { - self.addCaseConfig(case, target, triple, optimize, .selfhosted); - } - } + if (optimize == params.optimize) break; + } else return; + + error_traces_cases.addCases(self, params, &resolved_target.result); } } -fn shouldTestNonLlvm(target: *const std.Target) bool { - if (comptime builtin.cpu.arch.endian() == .big) return false; // https://github.com/ziglang/zig/issues/25961 - return switch (target.cpu.arch) { - .x86_64 => switch (target.ofmt) { - .elf => !target.os.tag.isBSD() and target.os.tag != .illumos, - else => false, - }, - else => false, - }; -} - -fn addCaseConfig( - self: *ErrorTrace, - case: Case, - target: *const std.Build.ResolvedTarget, - triple: ?[]const u8, - optimize: OptimizeMode, - backend: Case.Backend, -) void { +/// Called from test/error_traces.zig +pub fn addCase(self: *ErrorTrace, case: Case) void { const b = self.b; + const params = case.params; + const target = case.target; + const target_query = params.target; + + const triple: ?[]const u8 = if (target_query.isNative()) null else t: { + break :t target_query.zigTriple(self.b.graph.arena) catch @panic("OOM"); + }; const error_tracing: bool = tracing: { - if (optimize == .debug) break :tracing true; - if (backend != .llvm) break :tracing true; - if (optimize == .small) break :tracing false; + if (params.optimize == .debug) break :tracing true; + if (params.use_llvm == false) break :tracing true; + if (params.optimize == .small) break :tracing false; for (case.disable_trace_optimized) |disable| { const d_arch, const d_os = disable; - if (target.result.cpu.arch == d_arch and target.result.os.tag == d_os) { + if (target.cpu.arch == d_arch and target.os.tag == d_os) { // This particular configuration cannot do error tracing in optimized LLVM builds. break :tracing false; } @@ -73,12 +140,19 @@ fn addCaseConfig( break :tracing true; }; - const annotated_case_name = b.fmt("check {s} ({s}{s}{s} {s})", .{ + const backend_string = if (params.use_llvm == true) + "-llvm" + else if (params.use_llvm == false) + "-selfhosted" + else + ""; + + const annotated_case_name = b.fmt("check {s} ({s}{s}{t}{s})", .{ case.name, triple orelse "", if (triple != null) " " else "", - @tagName(optimize), - @tagName(backend), + params.optimize, + backend_string, }); if (self.test_filters.len > 0) { for (self.test_filters) |test_filter| { @@ -92,15 +166,13 @@ fn addCaseConfig( .name = "test", .root_module = b.createModule(.{ .root_source_file = source_zig, - .optimize = optimize, - .target = target.*, + .optimize = params.optimize, + .target = .{ .result = target.*, .query = target_query }, .error_tracing = error_tracing, .strip = false, }), - .use_llvm = switch (backend) { - .llvm => true, - .selfhosted => false, - }, + .use_llvm = params.use_llvm, + .use_lld = params.use_lld, }); exe.bundle_ubsan_rt = false; @@ -124,10 +196,3 @@ fn addCaseConfig( self.step.dependOn(&check_run.step); } - -const ErrorTrace = @This(); -const std = @import("std"); -const builtin = @import("builtin"); -const Step = std.Build.Step; -const OptimizeMode = std.builtin.OptimizeMode; -const mem = std.mem; diff --git a/test/src/StackTrace.zig b/test/src/StackTrace.zig index 7270ef4cbbe51af44efab1f4950d660554889920..c4e4a5fe391a4141fe167681183b761c8fbf806b 100644 --- a/test/src/StackTrace.zig +++ b/test/src/StackTrace.zig @@ -4,16 +4,88 @@ const builtin = @import("builtin"); const std = @import("std"); const Step = std.Build.Step; -const OptimizeMode = std.lang.OptimizeMode; +const OptimizeMode = std.lang.Optimize; const mem = std.mem; +const stack_traces_cases = @import("../stack_traces.zig"); + b: *std.Build, step: *Step, test_filters: []const []const u8, -targets: []const std.Build.ResolvedTarget, +skip_non_native: bool, convert_exe: *std.Build.Step.Compile, +pub const CaseParameters = struct { + target: std.Target.Query = .{}, + optimize: std.builtin.OptimizeMode = .debug, + link_libc: ?bool = null, + use_llvm: ?bool = null, + use_lld: ?bool = null, + pie: ?bool = null, + /// To enable this coverage, one of two things needs to happen: + /// * The compiler needs to gain the ability to strip only debug info (not symbols) + /// * `std.Build.Step.ObjCopy` needs to be un-regressed + strip: ?bool = false, +}; + +const param_sets = [_]CaseParameters{ + .{}, + .{ + .link_libc = true, + }, + .{ + .use_llvm = true, + .use_lld = true, + }, + .{ + .pie = true, + }, + .{ + .target = .{ + .cpu_arch = .aarch64, + .os_tag = .windows, + .abi = .msvc, + }, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .gnu, + }, + }, + .{ + .target = .{ + .cpu_arch = .x86, + .os_tag = .windows, + .abi = .msvc, + }, + }, + .{ + .target = .{ + .cpu_arch = .aarch64, + .os_tag = .macos, + }, + }, + .{ + .target = .{ + .cpu_arch = .s390x, + .os_tag = .linux, + .abi = .none, + }, + }, + .{ + .target = .{ + .cpu_arch = .loongarch32, + .os_tag = .linux, + .abi = .none, + }, + }, +}; + const Config = struct { + params: *const CaseParameters, + target: *const std.Target, name: []const u8, source: []const u8, /// Whether this test case expects to have unwind tables / frame pointers. @@ -35,42 +107,37 @@ const Config = struct { expect_strip: []const u8, }; +pub fn addCases(self: *StackTrace) void { + const b = self.b; + + for (¶m_sets) |*params| { + const resolved_target = b.resolveTargetQuery(params.target); + + if (self.skip_non_native and !resolved_target.query.isNative()) continue; + + // To avoid redundant testing, skip cross-compilation targets matching the host. + if (resolved_target.result.os.tag == builtin.target.os.tag and + resolved_target.result.cpu.arch == builtin.target.cpu.arch) + { + continue; + } + + stack_traces_cases.addCases(self, params, &resolved_target.result); + } +} + +/// Called from test/stack_traces.zig pub fn addCase(self: *StackTrace, config: Config) void { - for (self.targets) |*target| { - addCaseTarget( - self, - config, - target, - if (target.query.isNative()) null else t: { - break :t target.query.zigTriple(self.b.graph.arena) catch @panic("OOM"); - }, - ); - } -} -fn addCaseTarget( - self: *StackTrace, - config: Config, - target: *const std.Build.ResolvedTarget, - triple: ?[]const u8, -) void { - const both_backends = b: { - if (builtin.cpu.arch.endian() == .big) break :b false; // https://codeberg.org/ziglang/zig/issues/31522 - break :b switch (target.result.cpu.arch) { - .x86_64 => switch (target.result.ofmt) { - .elf => !target.result.os.tag.isBSD() and target.result.os.tag != .illumos, - else => false, - }, - else => false, - }; + const params = config.params; + const target = config.target; + const target_query = config.params.target; + + const triple: ?[]const u8 = if (target_query.isNative()) null else t: { + break :t target_query.zigTriple(self.b.graph.arena) catch @panic("OOM"); }; - const both_pie = switch (target.result.os.tag) { - .fuchsia => false, - else => true, - }; - const both_libc = !std.os.targetRequiresLibC(&target.result); // See `std.debug.StackIterator.fp_usability` logic. - const fp_usability: enum { useless, unsafe, safe, ideal } = switch (target.result.cpu.arch) { + const fp_usability: enum { useless, unsafe, safe, ideal } = switch (target.cpu.arch) { .alpha, .csky, .microblaze, @@ -92,20 +159,15 @@ fn addCaseTarget( .sparc, .sparc64, => .ideal, - .aarch64 => if (target.result.os.tag.isDarwin()) .safe else .unsafe, + .aarch64 => if (target.os.tag.isDarwin()) .safe else .unsafe, else => .unsafe, }; - const supports_unwind_tables = switch (target.result.os.tag) { + const supports_unwind_tables = switch (target.os.tag) { // x86-windows just has no way to do stack unwinding other then using frame pointers. - .windows => target.result.cpu.arch != .x86, + .windows => target.cpu.arch != .x86, else => true, }; - const use_llvm_vals: []const bool = if (both_backends) &.{ true, false } else &.{true}; - const pie_vals: []const ?bool = if (both_pie) &.{ true, false } else &.{null}; - const link_libc_vals: []const ?bool = if (both_libc) &.{ true, false } else &.{null}; - const strip_debug_vals: []const bool = &.{ true, false }; - const UnwindInfo = packed struct(u2) { tables: bool, fp: bool, @@ -135,43 +197,33 @@ fn addCaseTarget( }, }; - for (use_llvm_vals) |use_llvm| { - for (pie_vals) |pie| { - for (link_libc_vals) |link_libc| { - for (strip_debug_vals) |strip_debug| { - for (unwind_info_vals) |unwind_info| { - if (unwind_info.tables and !supports_unwind_tables) continue; - self.addCaseInstance( - target, - triple, - config.name, - config.source, - use_llvm, - pie, - link_libc, - strip_debug, - !unwind_info.tables and supports_unwind_tables, - !unwind_info.fp, - config.expect_panic, - if (strip_debug) config.expect_strip else config.expect, - ); - } - } - } - } + for (unwind_info_vals) |unwind_info| { + if (unwind_info.tables and !supports_unwind_tables) continue; + const strip = params.strip orelse switch (params.optimize) { + .debug, .fast, .safe => false, + .small => true, + }; + self.addCaseInstance( + .{ .result = target.*, .query = target_query }, + triple, + config.name, + config.source, + params, + !unwind_info.tables and supports_unwind_tables, + !unwind_info.fp, + config.expect_panic, + if (strip) config.expect_strip else config.expect, + ); } } fn addCaseInstance( self: *StackTrace, - target: *const std.Build.ResolvedTarget, + resolved_target: std.Build.ResolvedTarget, triple: ?[]const u8, name: []const u8, source: []const u8, - use_llvm: bool, - pie: ?bool, - link_libc: ?bool, - strip_debug: bool, + params: *const CaseParameters, strip_unwind: bool, omit_frame_pointer: bool, expect_panic: bool, @@ -179,13 +231,6 @@ fn addCaseInstance( ) void { const b = self.b; - if (strip_debug) { - // To enable this coverage, one of two things needs to happen: - // * The compiler needs to gain the ability to strip only debug info (not symbols) - // * `std.Build.Step.ObjCopy` needs to be un-regressed - return; - } - if (strip_unwind) { // To enable this coverage, `std.Build.Step.ObjCopy` needs to be un-regressed and gain the // ability to remove individual sections. `-fno-unwind-tables` is insufficient because it @@ -196,14 +241,28 @@ fn addCaseInstance( return; } + const backend_string = if (params.use_llvm == true) + " llvm" + else if (params.use_llvm == false) + " selfhosted" + else + ""; + + const strip_string = if (params.strip == true) + " strip" + else if (params.strip == false) + " unstripped" + else + ""; + const annotated_case_name = b.fmt("check {s} ({s}{s}{s}{s}{s}{s}{s}{s})", .{ name, triple orelse "", if (triple != null) " " else "", - if (use_llvm) "llvm" else "selfhosted", - if (pie == true) " pie" else "", - if (link_libc == true) " libc" else "", - if (strip_debug) " strip" else "", + backend_string, + if (params.pie == true) " pie" else "", + if (params.link_libc == true) " libc" else "", + strip_string, if (strip_unwind) " no_unwind" else "", if (omit_frame_pointer) " no_fp" else "", }); @@ -220,16 +279,17 @@ fn addCaseInstance( .root_module = b.createModule(.{ .root_source_file = source_zig, .optimize = .Debug, - .target = target.*, + .target = resolved_target, .omit_frame_pointer = omit_frame_pointer, - .link_libc = link_libc, + .link_libc = params.link_libc, .unwind_tables = if (strip_unwind) .none else null, // make panics single-threaded so that they don't include a thread ID .single_threaded = expect_panic, }), - .use_llvm = use_llvm, + .use_llvm = params.use_llvm, + .use_lld = params.use_lld, }); - exe.pie = pie; + exe.pie = params.pie; exe.bundle_ubsan_rt = false; const run = b.addRunArtifact(exe); @@ -238,7 +298,7 @@ fn addCaseInstance( run.setEnvironmentVariable("NO_COLOR", "1"); run.addCheck(.{ .expect_term = term: { if (!expect_panic) break :term .{ .exited = 0 }; - if (target.result.os.tag == .windows) break :term .{ .exited = 3 }; + if (resolved_target.result.os.tag == .windows) break :term .{ .exited = 3 }; break :term .{ .signal = @fromBackingInt(@intCast(6)) }; } }); run.expectStdOutEqual(""); diff --git a/test/stack_traces.zig b/test/stack_traces.zig index 82d7d67863c209f4a5e3825f0eeb2cb100e50ced..352950af2b307d0fb1d7a8027358c76fe064db56 100644 --- a/test/stack_traces.zig +++ b/test/stack_traces.zig @@ -1,7 +1,10 @@ const std = @import("std"); +const Context = @import("tests.zig").StackTracesContext; -pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.Os.Tag) void { +pub fn addCases(cases: *Context, params: *const Context.CaseParameters, target: *const std.Target) void { cases.addCase(.{ + .params = params, + .target = target, .name = "simple panic", .source = \\pub fn main() void { @@ -33,6 +36,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "simple panic with no unwind strategy", .source = \\pub fn main() void { @@ -50,6 +55,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "dump current trace", .source = \\pub fn main() void { @@ -89,6 +96,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "dump current trace with no unwind strategy", .source = \\pub fn main() void { @@ -114,6 +123,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "dump captured trace", .source = \\pub fn main() void { @@ -155,6 +166,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "dump captured trace with no unwind strategy", .source = \\pub fn main() void { @@ -180,6 +193,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "dump captured trace on thread", .source = \\pub fn main() !void { @@ -225,6 +240,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target. }); cases.addCase(.{ + .params = params, + .target = target, .name = "simple inline panic", // The main function has two inline calls to ensure // that inlinees in PDBs are properly deduplicated. @@ -240,7 +257,7 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target. , .unwind = .any, .expect_panic = true, - .expect = switch (os) { + .expect = switch (target.os.tag) { // LLVM doesn't emit column info in the binary annotations for inlinee callees in PDBs, // so the first location has only a row. .windows => @@ -262,7 +279,7 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target. \\ ^ , }, - .expect_strip = switch (os) { + .expect_strip = switch (target.os.tag) { .windows => \\panic: oh no \\???:?:?: [address] in source.foo @@ -279,6 +296,8 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target. // Make sure all inline calls are resolved and in the right order! cases.addCase(.{ + .params = params, + .target = target, .name = "nested inline panic", .source = \\pub fn main() void { @@ -298,7 +317,7 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target. .unwind = .any, .expect_panic = true, // This switch serves a similar purpose as in "inline panic". - .expect = switch (os) { + .expect = switch (target.os.tag) { .windows => \\panic: oh no \\source.zig:11: [address] in baz @@ -322,7 +341,7 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target. \\ ^ , }, - .expect_strip = switch (os) { + .expect_strip = switch (target.os.tag) { .windows => \\panic: oh no \\???:?:?: [address] in baz diff --git a/test/tests.zig b/test/tests.zig index d165597131204537abc2c494a97885c3866a5e5f..fbb16c5a2fe759dd4840d4670670983898985276 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -6,8 +6,6 @@ const OptimizeMode = std.builtin.OptimizeMode; const Step = std.Build.Step; // Cases -const error_traces = @import("error_traces.zig"); -const stack_traces = @import("stack_traces.zig"); const llvm_ir = @import("llvm_ir.zig"); const libc = @import("libc.zig"); const link = @import("link.zig"); @@ -2381,59 +2379,7 @@ pub fn isNative(actual_target: *const std.Build.ResolvedTarget, host: *const std return true; } -/// For stack trace tests, we only test native by default, because external executors are pretty -/// unreliable at stack tracing. However, if there's a 32-bit equivalent target which the host can -/// trivially run, we may as well at least test that! -fn nativeAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget { - const host = b.graph.host.result; - const only_native = (&b.graph.host)[0..1]; - if (skip_non_native) return only_native; - const arch32 = compatible32bitArch(&b.graph.host.result) orelse return only_native; - return b.graph.arena.dupe(std.Build.ResolvedTarget, &.{ - b.graph.host, - b.resolveTargetQuery(.{ .cpu_arch = arch32, .os_tag = host.os.tag }), - }) catch @panic("OOM"); -} - -fn wineAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget { - var targets: std.ArrayList(std.Build.ResolvedTarget) = .empty; - - const host = b.graph.host.result; - - targets.append(b.graph.arena, b.resolveTargetQuery(.{ - .cpu_arch = host.cpu.arch, - .os_tag = .windows, - })) catch @panic("OOM"); - if (!skip_non_native) { - if (compatible32bitArch(&b.graph.host.result)) |arch| { - targets.append(b.graph.arena, b.resolveTargetQuery(.{ - .cpu_arch = arch, - .os_tag = .windows, - })) catch @panic("OOM"); - } - } - - return targets.toOwnedSlice(b.graph.arena) catch @panic("OOM"); -} - -fn darlingTargets(b: *std.Build) []const std.Build.ResolvedTarget { - var targets: std.ArrayList(std.Build.ResolvedTarget) = .empty; - - const host = b.graph.host.result; - - targets.append(b.graph.arena, b.resolveTargetQuery(.{ - .cpu_arch = host.cpu.arch, - .os_tag = .macos, - })) catch @panic("OOM"); - - return targets.toOwnedSlice(b.graph.arena) catch @panic("OOM"); -} - -pub fn addStackTraceTests( - b: *std.Build, - test_filters: []const []const u8, - skip_non_native: bool, -) *Step { +pub fn addStackTraceTests(b: *std.Build, test_filters: []const []const u8, skip_non_native: bool) *Step { const step = b.step("test-stack-traces", "Run the stack trace tests"); const convert_exe = b.addExecutable(.{ @@ -2445,35 +2391,15 @@ pub fn addStackTraceTests( }), }); - const host_cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); - host_cases.* = .{ + const stack_traces_context = b.allocator.create(StackTracesContext) catch @panic("OOM"); + stack_traces_context.* = .{ .b = b, .step = step, .test_filters = test_filters, - .targets = nativeAndCompatible32bit(b, skip_non_native), + .skip_non_native = skip_non_native, .convert_exe = convert_exe, }; - stack_traces.addCases(host_cases, b.graph.host.result.os.tag); - - const wine_cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); - wine_cases.* = .{ - .b = b, - .step = step, - .test_filters = test_filters, - .targets = wineAndCompatible32bit(b, skip_non_native), - .convert_exe = convert_exe, - }; - stack_traces.addCases(wine_cases, .windows); - - const darling_cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); - darling_cases.* = .{ - .b = b, - .step = step, - .test_filters = test_filters, - .targets = darlingTargets(b), - .convert_exe = convert_exe, - }; - stack_traces.addCases(darling_cases, .macos); + stack_traces_context.addCases(); return step; } @@ -2495,38 +2421,16 @@ pub fn addErrorTraceTests( }), }); - const host_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); - host_cases.* = .{ + const error_traces_context = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); + error_traces_context.* = .{ .b = b, .step = step, .test_filters = test_filters, - .targets = nativeAndCompatible32bit(b, skip_non_native), + .skip_non_native = skip_non_native, .optimize_modes = optimize_modes, .convert_exe = convert_exe, }; - error_traces.addCases(host_cases, b.graph.host.result.os.tag); - - const wine_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); - wine_cases.* = .{ - .b = b, - .step = step, - .test_filters = test_filters, - .targets = wineAndCompatible32bit(b, skip_non_native), - .optimize_modes = optimize_modes, - .convert_exe = convert_exe, - }; - error_traces.addCases(wine_cases, .windows); - - const darling_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); - darling_cases.* = .{ - .b = b, - .step = step, - .test_filters = test_filters, - .targets = darlingTargets(b), - .optimize_modes = optimize_modes, - .convert_exe = convert_exe, - }; - error_traces.addCases(darling_cases, .macos); + error_traces_context.addCases(); return step; } -- 2.54.0