diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index ae9eb53fb2..4d47c95d21 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -443,11 +443,6 @@ pub fn main() !void { } const ttyconf = color.detectTtyConf(); - switch (ttyconf) { - .no_color => try graph.env_map.put("NO_COLOR", "1"), - .escape_codes => try graph.env_map.put("CLICOLOR_FORCE", "1"), - .windows_api => {}, - } const main_progress_node = std.Progress.start(.{ .disable_printing = (color == .off), @@ -1389,6 +1384,7 @@ fn workerMakeOneStep( .thread_pool = thread_pool, .watch = run.watch, .web_server = if (run.web_server) |*ws| ws else null, + .ttyconf = run.ttyconf, .unit_test_timeout_ns = run.unit_test_timeout_ns, .gpa = run.gpa, }); diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index aa922ff37b..f4387a2480 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -118,6 +118,7 @@ pub const MakeOptions = struct { // it currently breaks because `std.net.Address` doesn't work there. Work around for now. .wasm32 => void, }, + ttyconf: std.Io.tty.Config, /// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds. unit_test_timeout_ns: ?u64, /// Not to be confused with `Build.allocator`, which is an alias of `Build.graph.arena`. diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 2143cf9b02..4d1852e2e1 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -24,6 +24,21 @@ cwd: ?Build.LazyPath, /// Override this field to modify the environment, or use setEnvironmentVariable env_map: ?*EnvMap, +/// Controls the `NO_COLOR` and `CLICOLOR_FORCE` environment variables. +color: enum { + /// `CLICOLOR_FORCE` is set, and `NO_COLOR` is unset. + enable, + /// `NO_COLOR` is set, and `CLICOLOR_FORCE` is unset. + disable, + /// If the build runner is using color, equivalent to `.enable`. Otherwise, equivalent to `.disable`. + inherit, + /// If stderr is captured or checked, equivalent to `.disable`. Otherwise, equivalent to `.inherit`. + auto, + /// The build runner does not modify the `CLICOLOR_FORCE` or `NO_COLOR` environment variables. + /// They are treated like normal variables, so can be controlled through `setEnvironmentVariable`. + manual, +} = .auto, + /// When `true` prevents `ZIG_PROGRESS` environment variable from being passed /// to the child process, which otherwise would be used for the child to send /// progress updates to the parent. @@ -525,7 +540,7 @@ pub fn setCwd(run: *Run, cwd: Build.LazyPath) void { pub fn clearEnvironment(run: *Run) void { const b = run.step.owner; const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM"); - new_env_map.* = EnvMap.init(b.allocator); + new_env_map.* = .init(b.allocator); run.env_map = new_env_map; } @@ -806,6 +821,9 @@ fn make(step: *Step, options: Step.MakeOptions) !void { } } + man.hash.add(run.color); + man.hash.add(run.disable_zig_progress); + for (run.argv.items) |arg| { switch (arg) { .bytes => |bytes| { @@ -1130,6 +1148,7 @@ pub fn rerunInFuzzMode( .thread_pool = undefined, // not used by `runCommand` .watch = undefined, // not used by `runCommand` .web_server = null, // only needed for time reports + .ttyconf = fuzz.ttyconf, .unit_test_timeout_ns = null, // don't time out fuzz tests for now .gpa = undefined, // not used by `runCommand` }, .{ @@ -1234,9 +1253,40 @@ fn runCommand( var interp_argv = std.array_list.Managed([]const u8).init(b.allocator); defer interp_argv.deinit(); - var env_map = run.env_map orelse &b.graph.env_map; + var env_map: EnvMap = env: { + const orig = run.env_map orelse &b.graph.env_map; + break :env try orig.clone(gpa); + }; + defer env_map.deinit(); - const opt_generic_result = spawnChildAndCollect(run, argv, env_map, has_side_effects, options, fuzz_context) catch |err| term: { + color: switch (run.color) { + .manual => {}, + .enable => { + try env_map.put("CLICOLOR_FORCE", "1"); + env_map.remove("NO_COLOR"); + }, + .disable => { + try env_map.put("NO_COLOR", "1"); + env_map.remove("CLICOLOR_FORCE"); + }, + .inherit => switch (options.ttyconf) { + .no_color, .windows_api => continue :color .disable, + .escape_codes => continue :color .enable, + }, + .auto => { + const capture_stderr = run.captured_stderr != null or switch (run.stdio) { + .check => |checks| checksContainStderr(checks.items), + .infer_from_args, .inherit, .zig_test => false, + }; + if (capture_stderr) { + continue :color .disable; + } else { + continue :color .inherit; + } + }, + } + + const opt_generic_result = spawnChildAndCollect(run, argv, &env_map, has_side_effects, options, fuzz_context) catch |err| term: { // InvalidExe: cpu arch mismatch // FileNotFound: can happen with a wrong dynamic linker path if (err == error.InvalidExe or err == error.FileNotFound) interpret: { @@ -1273,12 +1323,7 @@ fn runCommand( // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired. if (env_map.get("WINEDEBUG") == null) { - // We don't own `env_map` at this point, so create a copy in order to modify it. - const new_env_map = arena.create(EnvMap) catch @panic("OOM"); - new_env_map.hash_map = try env_map.hash_map.cloneWithAllocator(arena); - try new_env_map.put("WINEDEBUG", "-all"); - - env_map = new_env_map; + try env_map.put("WINEDEBUG", "-all"); } } else { return failForeign(run, "-fwine", argv[0], exe); @@ -1377,7 +1422,7 @@ fn runCommand( step.result_failed_command = null; try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items); - break :term spawnChildAndCollect(run, interp_argv.items, env_map, has_side_effects, options, fuzz_context) catch |e| { + break :term spawnChildAndCollect(run, interp_argv.items, &env_map, has_side_effects, options, fuzz_context) catch |e| { if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; if (e == error.MakeFailed) return error.MakeFailed; // error already reported return step.fail("unable to spawn interpreter {s}: {s}", .{ diff --git a/lib/std/Io/tty.zig b/lib/std/Io/tty.zig index 8bc44c2348..4f5ec8fd2d 100644 --- a/lib/std/Io/tty.zig +++ b/lib/std/Io/tty.zig @@ -37,7 +37,7 @@ pub const Color = enum { pub const Config = union(enum) { no_color, escape_codes, - windows_api: if (native_os == .windows) WindowsContext else void, + windows_api: if (native_os == .windows) WindowsContext else noreturn, /// Detect suitable TTY configuration options for the given file (commonly stdout/stderr). /// This includes feature checks for ANSI escape codes and the Windows console API, as well as @@ -105,7 +105,7 @@ pub const Config = union(enum) { }; try w.writeAll(color_string); }, - .windows_api => |ctx| if (native_os == .windows) { + .windows_api => |ctx| { const attributes = switch (color) { .black => 0, .red => windows.FOREGROUND_RED, @@ -130,8 +130,6 @@ pub const Config = union(enum) { }; try w.flush(); try windows.SetConsoleTextAttribute(ctx.handle, attributes); - } else { - unreachable; }, }; } diff --git a/lib/std/process.zig b/lib/std/process.zig index 4e41d5901e..1f605c87ce 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -206,6 +206,22 @@ pub const EnvMap = struct { return self.hash_map.iterator(); } + /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily + /// the same allocator used to allocate `em`. + pub fn clone(em: *const EnvMap, gpa: Allocator) Allocator.Error!EnvMap { + var new: EnvMap = .init(gpa); + errdefer new.deinit(); + // Since we need to dupe the keys and values, the only way for error handling to not be a + // nightmare is to add keys to an empty map one-by-one. This could be avoided if this + // abstraction were a bit less... OOP-esque. + try new.hash_map.ensureUnusedCapacity(em.hash_map.count()); + var it = em.hash_map.iterator(); + while (it.next()) |entry| { + try new.put(entry.key_ptr.*, entry.value_ptr.*); + } + return new; + } + fn free(self: EnvMap, value: []const u8) void { self.hash_map.allocator.free(value); } diff --git a/test/standalone/empty_env/build.zig b/test/standalone/empty_env/build.zig index 592566d62c..0eb8087ed2 100644 --- a/test/standalone/empty_env/build.zig +++ b/test/standalone/empty_env/build.zig @@ -31,6 +31,7 @@ pub fn build(b: *std.Build) void { const run = b.addRunArtifact(main); run.clearEnvironment(); run.disable_zig_progress = true; + run.color = .manual; test_step.dependOn(&run.step); }