mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
std.Build: don't force all children to inherit color option
The build runner was previously forcing child processes to have their
stderr colorization match the build runner by setting `CLICOLOR_FORCE`
or `NO_COLOR`. This is a nice idea in some cases---for instance a simple
`Run` step which we just expect to exit with code 0 and whose stderr is
not being programmatically inspected---but is a bad idea in others, for
instance if there is a check on stderr or if stderr is captured, in
which case forcing color on the child could cause checks to fail.
Instead, this commit adds a field to `std.Build.Step.Run` which
specifies a behavior for the build runner to employ in terms of
assigning the `CLICOLOR_FORCE` and `NO_COLOR` environment variables. The
default behavior is to set `CLICOLOR_FORCE` if the build runner's output
is colorized and the step's stderr is not captured, and to set
`NO_COLOR` otherwise. Alternatively, colors can be always enabled,
always disabled, always match the build runner, or the environment
variables can be left untouched so they can be manually controlled
through `env_map`.
Notably, this fixes a failure when running `zig build test-cli` in a
TTY (or with colors explicitly enabled). GitHub CI hadn't caught this
because it does not request color, but Codeberg CI now does, and we were
seeing a failure in the `zig init` test because the actual output had
color escape codes in it due to 6d280dc.
This commit is contained in:
parent
b38fb4bff3
commit
c6b5945356
6 changed files with 76 additions and 19 deletions
|
|
@ -443,11 +443,6 @@ pub fn main() !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ttyconf = color.detectTtyConf();
|
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(.{
|
const main_progress_node = std.Progress.start(.{
|
||||||
.disable_printing = (color == .off),
|
.disable_printing = (color == .off),
|
||||||
|
|
@ -1389,6 +1384,7 @@ fn workerMakeOneStep(
|
||||||
.thread_pool = thread_pool,
|
.thread_pool = thread_pool,
|
||||||
.watch = run.watch,
|
.watch = run.watch,
|
||||||
.web_server = if (run.web_server) |*ws| ws else null,
|
.web_server = if (run.web_server) |*ws| ws else null,
|
||||||
|
.ttyconf = run.ttyconf,
|
||||||
.unit_test_timeout_ns = run.unit_test_timeout_ns,
|
.unit_test_timeout_ns = run.unit_test_timeout_ns,
|
||||||
.gpa = run.gpa,
|
.gpa = run.gpa,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,7 @@ pub const MakeOptions = struct {
|
||||||
// it currently breaks because `std.net.Address` doesn't work there. Work around for now.
|
// it currently breaks because `std.net.Address` doesn't work there. Work around for now.
|
||||||
.wasm32 => void,
|
.wasm32 => void,
|
||||||
},
|
},
|
||||||
|
ttyconf: std.Io.tty.Config,
|
||||||
/// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds.
|
/// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds.
|
||||||
unit_test_timeout_ns: ?u64,
|
unit_test_timeout_ns: ?u64,
|
||||||
/// Not to be confused with `Build.allocator`, which is an alias of `Build.graph.arena`.
|
/// Not to be confused with `Build.allocator`, which is an alias of `Build.graph.arena`.
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,21 @@ cwd: ?Build.LazyPath,
|
||||||
/// Override this field to modify the environment, or use setEnvironmentVariable
|
/// Override this field to modify the environment, or use setEnvironmentVariable
|
||||||
env_map: ?*EnvMap,
|
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
|
/// When `true` prevents `ZIG_PROGRESS` environment variable from being passed
|
||||||
/// to the child process, which otherwise would be used for the child to send
|
/// to the child process, which otherwise would be used for the child to send
|
||||||
/// progress updates to the parent.
|
/// progress updates to the parent.
|
||||||
|
|
@ -525,7 +540,7 @@ pub fn setCwd(run: *Run, cwd: Build.LazyPath) void {
|
||||||
pub fn clearEnvironment(run: *Run) void {
|
pub fn clearEnvironment(run: *Run) void {
|
||||||
const b = run.step.owner;
|
const b = run.step.owner;
|
||||||
const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM");
|
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;
|
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| {
|
for (run.argv.items) |arg| {
|
||||||
switch (arg) {
|
switch (arg) {
|
||||||
.bytes => |bytes| {
|
.bytes => |bytes| {
|
||||||
|
|
@ -1130,6 +1148,7 @@ pub fn rerunInFuzzMode(
|
||||||
.thread_pool = undefined, // not used by `runCommand`
|
.thread_pool = undefined, // not used by `runCommand`
|
||||||
.watch = undefined, // not used by `runCommand`
|
.watch = undefined, // not used by `runCommand`
|
||||||
.web_server = null, // only needed for time reports
|
.web_server = null, // only needed for time reports
|
||||||
|
.ttyconf = fuzz.ttyconf,
|
||||||
.unit_test_timeout_ns = null, // don't time out fuzz tests for now
|
.unit_test_timeout_ns = null, // don't time out fuzz tests for now
|
||||||
.gpa = undefined, // not used by `runCommand`
|
.gpa = undefined, // not used by `runCommand`
|
||||||
}, .{
|
}, .{
|
||||||
|
|
@ -1234,9 +1253,40 @@ fn runCommand(
|
||||||
var interp_argv = std.array_list.Managed([]const u8).init(b.allocator);
|
var interp_argv = std.array_list.Managed([]const u8).init(b.allocator);
|
||||||
defer interp_argv.deinit();
|
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
|
// InvalidExe: cpu arch mismatch
|
||||||
// FileNotFound: can happen with a wrong dynamic linker path
|
// FileNotFound: can happen with a wrong dynamic linker path
|
||||||
if (err == error.InvalidExe or err == error.FileNotFound) interpret: {
|
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
|
// 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.
|
// allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired.
|
||||||
if (env_map.get("WINEDEBUG") == null) {
|
if (env_map.get("WINEDEBUG") == null) {
|
||||||
// We don't own `env_map` at this point, so create a copy in order to modify it.
|
try env_map.put("WINEDEBUG", "-all");
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return failForeign(run, "-fwine", argv[0], exe);
|
return failForeign(run, "-fwine", argv[0], exe);
|
||||||
|
|
@ -1377,7 +1422,7 @@ fn runCommand(
|
||||||
step.result_failed_command = null;
|
step.result_failed_command = null;
|
||||||
try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items);
|
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 (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;
|
||||||
if (e == error.MakeFailed) return error.MakeFailed; // error already reported
|
if (e == error.MakeFailed) return error.MakeFailed; // error already reported
|
||||||
return step.fail("unable to spawn interpreter {s}: {s}", .{
|
return step.fail("unable to spawn interpreter {s}: {s}", .{
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ pub const Color = enum {
|
||||||
pub const Config = union(enum) {
|
pub const Config = union(enum) {
|
||||||
no_color,
|
no_color,
|
||||||
escape_codes,
|
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).
|
/// 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
|
/// 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);
|
try w.writeAll(color_string);
|
||||||
},
|
},
|
||||||
.windows_api => |ctx| if (native_os == .windows) {
|
.windows_api => |ctx| {
|
||||||
const attributes = switch (color) {
|
const attributes = switch (color) {
|
||||||
.black => 0,
|
.black => 0,
|
||||||
.red => windows.FOREGROUND_RED,
|
.red => windows.FOREGROUND_RED,
|
||||||
|
|
@ -130,8 +130,6 @@ pub const Config = union(enum) {
|
||||||
};
|
};
|
||||||
try w.flush();
|
try w.flush();
|
||||||
try windows.SetConsoleTextAttribute(ctx.handle, attributes);
|
try windows.SetConsoleTextAttribute(ctx.handle, attributes);
|
||||||
} else {
|
|
||||||
unreachable;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,22 @@ pub const EnvMap = struct {
|
||||||
return self.hash_map.iterator();
|
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 {
|
fn free(self: EnvMap, value: []const u8) void {
|
||||||
self.hash_map.allocator.free(value);
|
self.hash_map.allocator.free(value);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ pub fn build(b: *std.Build) void {
|
||||||
const run = b.addRunArtifact(main);
|
const run = b.addRunArtifact(main);
|
||||||
run.clearEnvironment();
|
run.clearEnvironment();
|
||||||
run.disable_zig_progress = true;
|
run.disable_zig_progress = true;
|
||||||
|
run.color = .manual;
|
||||||
|
|
||||||
test_step.dependOn(&run.step);
|
test_step.dependOn(&run.step);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue