Merge pull request 'Debug I/O color detection fixes/improvements for Windows/POSIX/WASI/Emscripten + tests' (#31108) from castholm/zig:wasm-env-vars into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31108
Reviewed-by: Andrew Kelley <andrew@ziglang.org>
This commit is contained in:
Andrew Kelley
2026-04-11 21:42:47 +02:00
5 changed files with 123 additions and 14 deletions
+17 -14
View File
@@ -1671,20 +1671,23 @@ pub fn init(
/// When initialized this way:
/// * cancel requests have no effect.
/// * `deinit` is safe, but unnecessary to call.
pub const init_single_threaded: Threaded = .{
.allocator = .failing,
.stack_size = std.Thread.SpawnConfig.default_stack_size,
.async_limit = .nothing,
.cpu_count_error = null,
.concurrent_limit = .nothing,
.old_sig_io = undefined,
.old_sig_pipe = undefined,
.have_signal_handler = false,
.argv0 = .empty,
.environ_initialized = true,
.environ = .empty,
.worker_threads = .init(null),
.disable_memory_mapping = false,
pub const init_single_threaded: Threaded = init: {
const env_block: process.Environ.Block = if (is_windows) .global else .empty;
break :init .{
.allocator = .failing,
.stack_size = std.Thread.SpawnConfig.default_stack_size,
.async_limit = .nothing,
.cpu_count_error = null,
.concurrent_limit = .nothing,
.old_sig_io = undefined,
.old_sig_pipe = undefined,
.have_signal_handler = false,
.argv0 = .empty,
.environ_initialized = env_block.isEmpty(),
.environ = .{ .process_environ = .{ .block = env_block } },
.worker_threads = .init(null),
.disable_memory_mapping = false,
};
};
var global_single_threaded_instance: Threaded = .init_single_threaded;
+1
View File
@@ -631,6 +631,7 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8)
if (std.Options.debug_threaded_io) |t| {
if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
t.environ = .{ .process_environ = .{ .block = env_block } };
t.environ_initialized = env_block.isEmpty();
}
std.Thread.maybeAttachSignalStack();
std.debug.maybeEnableSegfaultHandler();
+3
View File
@@ -199,6 +199,9 @@
.posix = .{
.path = "posix",
},
.debug_io_color = .{
.path = "debug_io_color",
},
},
.paths = .{
"build.zig",
+95
View File
@@ -0,0 +1,95 @@
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;
}
+7
View File
@@ -0,0 +1,7 @@
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");
}