diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 937f75a531..f004a56ca9 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -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; diff --git a/lib/std/start.zig b/lib/std/start.zig index 8735042738..29c76fdfad 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -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(); diff --git a/test/standalone/build.zig.zon b/test/standalone/build.zig.zon index fd4a9e2e31..c3ba6f8bb0 100644 --- a/test/standalone/build.zig.zon +++ b/test/standalone/build.zig.zon @@ -199,6 +199,9 @@ .posix = .{ .path = "posix", }, + .debug_io_color = .{ + .path = "debug_io_color", + }, }, .paths = .{ "build.zig", diff --git a/test/standalone/debug_io_color/build.zig b/test/standalone/debug_io_color/build.zig new file mode 100644 index 0000000000..22ce7c8c22 --- /dev/null +++ b/test/standalone/debug_io_color/build.zig @@ -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; +} diff --git a/test/standalone/debug_io_color/main.zig b/test/standalone/debug_io_color/main.zig new file mode 100644 index 0000000000..d9627f6179 --- /dev/null +++ b/test/standalone/debug_io_color/main.zig @@ -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"); +}