Skip to content

Commit 6622bf7

Browse files
committed
build: replace bash/bat for stdout->stderr wrapping
Simpler code and no system dependencies.
1 parent 865a218 commit 6622bf7

File tree

2 files changed

+50
-34
lines changed

2 files changed

+50
-34
lines changed

src/build/std_extras.zig

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -182,45 +182,21 @@ pub fn createCommandWithStdoutToStderr(
182182
target: ?std.Target,
183183
name: []const u8,
184184
) *std.Build.Step.Run {
185-
const run = std.Build.Step.Run.create(b, name);
186-
187-
// IMPROVE: it would probably be more robust to create a small Zig program (like fetch()) that does this
188-
// redirection so we can guarantee cross-platform compatibility and don't have to do the bash vs batch stuff.
189-
190-
if (builtin.os.tag == .windows) {
191-
const batch_wrapper = b.addWriteFiles().add("stdout-to-stderr-wrapper.bat",
192-
\\@echo off
193-
\\%* 1>&2
194-
\\exit /b %errorlevel%
195-
);
196-
run.addFileArg(batch_wrapper);
197-
} else {
198-
const bash_wrapper = b.addWriteFiles().add("stdout-to-stderr-wrapper.sh",
199-
\\#!/usr/bin/env bash
200-
\\exec "$@" >&2
201-
);
202-
if (target != null and target.?.os.tag == .windows) {
203-
if (builtin.os.tag == .linux and b.enable_wine) {
204-
run.addArg("bash");
205-
run.addFileArg(bash_wrapper);
206-
run.addArg("wine64");
207-
} else {
208-
// It's probably not going to work, but we can try natively. At any rate, we are deferring the
209-
// error to later when the actual command is run (which might never happen).
210-
run.addArg("bash");
211-
run.addFileArg(bash_wrapper);
212-
}
213-
} else {
214-
// macOS or Linux - use system pluginval
215-
run.addArg("bash");
216-
run.addFileArg(bash_wrapper);
217-
}
185+
const run = b.addRunArtifact(b.addExecutable(.{
186+
.name = name,
187+
.root_module = b.createModule(.{
188+
.root_source_file = b.path("src/build/wrap_stdout_to_stderr_cmd.zig"),
189+
.target = b.graph.host,
190+
}),
191+
}));
192+
193+
if (builtin.os.tag == .linux and b.enable_wine and target != null and target.?.os.tag == .windows) {
194+
run.addArg("wine64");
218195
}
219196

220197
return run;
221198
}
222199

223-
224200
// A wrapper for turning commands that modify files in-place into commands with input and output files.
225201
// See wrap_inplace_cmd.zig for more information.
226202
// To use it, create the InPlaceCmd, add args as normal to the .run field, but at some point you must call
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2025 Sam Windell
2+
// SPDX-License-Identifier: GPL-3.0-or-later
3+
4+
// Little util that wraps a command and redirects its stdout to stderr.
5+
6+
// Why this exists:
7+
// In the Zig build system when using std.Build.Step.Run, Zig never prints stdout to the console, only stderr.
8+
// If the program doesn't put debug information in stderr, you don't see anything other than the exit code.
9+
// This wrapper redirects stdout to stderr so you see all output, allowing you to debug why a program fails.
10+
11+
const builtin = @import("builtin");
12+
const std = @import("std");
13+
14+
pub fn main() !u8 {
15+
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
16+
const allocator = arena.allocator();
17+
var args = try std.process.argsAlloc(allocator);
18+
19+
std.debug.assert(args.len >= 2);
20+
21+
const result = try std.process.Child.run(.{
22+
.allocator = allocator,
23+
.argv = args[1..],
24+
});
25+
26+
// Redirect stdout to stderr
27+
if (result.stdout.len > 0) std.io.getStdErr().writer().writeAll(result.stdout) catch {};
28+
if (result.stderr.len > 0) std.io.getStdErr().writer().writeAll(result.stderr) catch {};
29+
30+
switch (result.term) {
31+
.Exited => |code| {
32+
return code;
33+
},
34+
.Signal, .Stopped, .Unknown => {
35+
std.io.getStdErr().writer().writeAll("terminated unexpectedly\n") catch {};
36+
return 1;
37+
},
38+
}
39+
}
40+

0 commit comments

Comments
 (0)