authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-07-09 16:09:47+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-07-23 10:03:51+02:00
log0dc3a0180b799e2579c82383a4464f2460e57167
tree2adf47dea2c424d0495472c101610435dc36f25b
parentfd26c12469a3eda2c99cf58bf951b2223099b9ef
signature Commit is signed but in an unrecognized format.

show/hide warning for incompatible warnings

Implements running and verifying the expected output when a binary is run. Also adds warnings when a binary is skipped because of incompatibility. This warning can be hidden by either setting the option manually through build.zig, or by providing the option `-Dhide_foreign_warnings`.

1 files changed, 253 insertions(+), 9 deletions(-)

lib/std/build/RunCompareStep.zig+253-9
...@@ -18,6 +18,8 @@ const RunCompareStep = @This();...@@ -18,6 +18,8 @@ const RunCompareStep = @This();
1818
19pub const step_id = .run_and_compare;19pub const step_id = .run_and_compare;
2020
21const max_stdout_size = 1 * 1024 * 1024; // 1 MiB
22
21step: Step,23step: Step,
22builder: *Builder,24builder: *Builder,
2325
...@@ -30,14 +32,34 @@ expected_exit_code: ?u8 = 0,...@@ -30,14 +32,34 @@ expected_exit_code: ?u8 = 0,
30/// Override this field to modify the environment32/// Override this field to modify the environment
31env_map: ?*EnvMap,33env_map: ?*EnvMap,
3234
35/// Set this to modify the current working directory
36cwd: ?[]const u8,
37
38stdout_action: StdIoAction = .inherit,
39stderr_action: StdIoAction = .inherit,
40
41/// When set to true, hides the warning of skipping a foreign binary which cannot be run on the host
42/// or through emulation.
43hide_foreign_binaries_warning: bool,
44
45pub const StdIoAction = union(enum) {
46 inherit,
47 ignore,
48 expect_exact: []const u8,
49 expect_matches: []const []const u8,
50};
51
33pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *RunCompareStep {52pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *RunCompareStep {
34 std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe);53 std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe);
35 const self = builder.allocator.create(RunCompareStep) catch unreachable;54 const self = builder.allocator.create(RunCompareStep) catch unreachable;
55 const hide_warnings = builder.option(bool, "hide-foreign-warnings", "Hide the warning when a foreign binary which is incompatible is skipped") orelse false;
36 self.* = .{56 self.* = .{
37 .builder = builder,57 .builder = builder,
38 .step = Step.init(.run_and_compare, name, builder.allocator, make),58 .step = Step.init(.run_and_compare, name, builder.allocator, make),
39 .exe = artifact,59 .exe = artifact,
40 .env_map = null,60 .env_map = null,
61 .cwd = null,
62 .hide_foreign_binaries_warning = hide_warnings,
41 };63 };
42 self.step.dependOn(&artifact.step);64 self.step.dependOn(&artifact.step);
4365
...@@ -47,12 +69,10 @@ pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *Ru...@@ -47,12 +69,10 @@ pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *Ru
47fn make(step: *Step) !void {69fn make(step: *Step) !void {
48 const self = @fieldParentPtr(RunCompareStep, "step", step);70 const self = @fieldParentPtr(RunCompareStep, "step", step);
49 const host_info = self.builder.host;71 const host_info = self.builder.host;
50 const cwd = self.builder.build_root;72 const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root;
51 _ = cwd;
52 std.debug.print("Make called!\n", .{});
5373
54 var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);74 var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);
55 _ = argv_list;75 defer argv_list.deinit();
5676
57 const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc;77 const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc;
58 switch (host_info.getExternalExecutor(self.exe.target_info, .{78 switch (host_info.getExternalExecutor(self.exe.target_info, .{
...@@ -60,7 +80,7 @@ fn make(step: *Step) !void {...@@ -60,7 +80,7 @@ fn make(step: *Step) !void {
60 .link_libc = self.exe.is_linking_libc,80 .link_libc = self.exe.is_linking_libc,
61 })) {81 })) {
62 .native => {},82 .native => {},
63 .rosetta => if (!self.builder.enable_rosetta) return,83 .rosetta => if (!self.builder.enable_rosetta) return warnAboutForeignBinaries(self),
64 .wine => |bin_name| if (self.builder.enable_wine) {84 .wine => |bin_name| if (self.builder.enable_wine) {
65 try argv_list.append(bin_name);85 try argv_list.append(bin_name);
66 } else return,86 } else return,
...@@ -89,15 +109,15 @@ fn make(step: *Step) !void {...@@ -89,15 +109,15 @@ fn make(step: *Step) !void {
89 try argv_list.append("-L");109 try argv_list.append("-L");
90 try argv_list.append(full_dir);110 try argv_list.append(full_dir);
91 }111 }
92 } else return,112 } else return warnAboutForeignBinaries(self),
93 .darling => |bin_name| if (self.builder.enable_darling) {113 .darling => |bin_name| if (self.builder.enable_darling) {
94 try argv_list.append(bin_name);114 try argv_list.append(bin_name);
95 } else return,115 } else return warnAboutForeignBinaries(self),
96 .wasmtime => |bin_name| if (self.builder.enable_wasmtime) {116 .wasmtime => |bin_name| if (self.builder.enable_wasmtime) {
97 try argv_list.append(bin_name);117 try argv_list.append(bin_name);
98 try argv_list.append("--dir=.");118 try argv_list.append("--dir=.");
99 } else return,119 } else return warnAboutForeignBinaries(self),
100 else => return, // on any failures we skip120 else => return warnAboutForeignBinaries(self),
101 }121 }
102122
103 if (self.exe.target.isWindows()) {123 if (self.exe.target.isWindows()) {
...@@ -107,6 +127,143 @@ fn make(step: *Step) !void {...@@ -107,6 +127,143 @@ fn make(step: *Step) !void {
107127
108 const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder);128 const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder);
109 try argv_list.append(executable_path);129 try argv_list.append(executable_path);
130
131 if (!std.process.can_spawn) {
132 const cmd = try std.mem.join(self.builder.allocator, " ", argv_list.items);
133 std.debug.print("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(@import("builtin").os.tag), cmd });
134 self.builder.allocator.free(cmd);
135 return error.ExecNotSupported;
136 }
137
138 var child = std.ChildProcess.init(argv_list.items, self.builder.allocator);
139 child.cwd = cwd;
140 child.env_map = self.env_map orelse self.builder.env_map;
141
142 child.stdin_behavior = .Inherit;
143 child.stdout_behavior = stdIoActionToBehavior(self.stdout_action);
144 child.stderr_behavior = stdIoActionToBehavior(self.stderr_action);
145
146 child.spawn() catch |err| {
147 std.debug.print("Unable to spawn {s}: {s}\n", .{ argv_list.items[0], @errorName(err) });
148 return err;
149 };
150
151 var stdout: ?[]const u8 = null;
152 defer if (stdout) |s| self.builder.allocator.free(s);
153
154 switch (self.stdout_action) {
155 .expect_exact, .expect_matches => {
156 stdout = child.stdout.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable;
157 },
158 .inherit, .ignore => {},
159 }
160
161 var stderr: ?[]const u8 = null;
162 defer if (stderr) |s| self.builder.allocator.free(s);
163
164 switch (self.stderr_action) {
165 .expect_exact, .expect_matches => {
166 stderr = child.stderr.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable;
167 },
168 .inherit, .ignore => {},
169 }
170
171 const term = child.wait() catch |err| {
172 std.debug.print("Unable to spawn {s}: {s}\n", .{ argv_list.items[0], @errorName(err) });
173 return err;
174 };
175
176 switch (term) {
177 .Exited => |code| blk: {
178 const expected_exit_code = self.expected_exit_code orelse break :blk;
179
180 if (code != expected_exit_code) {
181 if (self.builder.prominent_compile_errors) {
182 std.debug.print("Run step exited with error code {} (expected {})\n", .{
183 code,
184 expected_exit_code,
185 });
186 } else {
187 std.debug.print("The following command exited with error code {} (expected {}):\n", .{
188 code,
189 expected_exit_code,
190 });
191 printCmd(cwd, argv_list.items);
192 }
193
194 return error.UnexpectedExitCode;
195 }
196 },
197 else => {
198 std.debug.print("The following command terminated unexpectedly:\n", .{});
199 printCmd(cwd, argv_list.items);
200 return error.UncleanExit;
201 },
202 }
203
204 switch (self.stderr_action) {
205 .inherit, .ignore => {},
206 .expect_exact => |expected_bytes| {
207 if (!std.mem.eql(u8, expected_bytes, stderr.?)) {
208 std.debug.print(
209 \\
210 \\========= Expected this stderr: =========
211 \\{s}
212 \\========= But found: ====================
213 \\{s}
214 \\
215 , .{ expected_bytes, stderr.? });
216 printCmd(cwd, argv_list.items);
217 return error.TestFailed;
218 }
219 },
220 .expect_matches => |matches| for (matches) |match| {
221 if (std.mem.indexOf(u8, stderr.?, match) == null) {
222 std.debug.print(
223 \\
224 \\========= Expected to find in stderr: =========
225 \\{s}
226 \\========= But stderr does not contain it: =====
227 \\{s}
228 \\
229 , .{ match, stderr.? });
230 printCmd(cwd, argv_list.items);
231 return error.TestFailed;
232 }
233 },
234 }
235
236 switch (self.stdout_action) {
237 .inherit, .ignore => {},
238 .expect_exact => |expected_bytes| {
239 if (!std.mem.eql(u8, expected_bytes, stdout.?)) {
240 std.debug.print(
241 \\
242 \\========= Expected this stdout: =========
243 \\{s}
244 \\========= But found: ====================
245 \\{s}
246 \\
247 , .{ expected_bytes, stdout.? });
248 printCmd(cwd, argv_list.items);
249 return error.TestFailed;
250 }
251 },
252 .expect_matches => |matches| for (matches) |match| {
253 if (std.mem.indexOf(u8, stdout.?, match) == null) {
254 std.debug.print(
255 \\
256 \\========= Expected to find in stdout: =========
257 \\{s}
258 \\========= But stdout does not contain it: =====
259 \\{s}
260 \\
261 , .{ match, stdout.? });
262 printCmd(cwd, argv_list.items);
263 return error.TestFailed;
264 }
265 },
266 }
110}267}
111268
112fn addPathForDynLibs(self: *RunCompareStep, artifact: *LibExeObjStep) void {269fn addPathForDynLibs(self: *RunCompareStep, artifact: *LibExeObjStep) void {
...@@ -145,3 +302,90 @@ pub fn getEnvMap(self: *RunCompareStep) *EnvMap {...@@ -145,3 +302,90 @@ pub fn getEnvMap(self: *RunCompareStep) *EnvMap {
145 return env_map;302 return env_map;
146 };303 };
147}304}
305
306pub fn expectStdErrEqual(self: *RunCompareStep, bytes: []const u8) void {
307 self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) };
308}
309
310pub fn expectStdOutEqual(self: *RunCompareStep, bytes: []const u8) void {
311 self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) };
312}
313
314fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo {
315 return switch (action) {
316 .ignore => .Ignore,
317 .inherit => .Inherit,
318 .expect_exact, .expect_matches => .Pipe,
319 };
320}
321
322fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void {
323 if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd});
324 for (argv) |arg| {
325 std.debug.print("{s} ", .{arg});
326 }
327 std.debug.print("\n", .{});
328}
329
330fn warnAboutForeignBinaries(step: *RunCompareStep) void {
331 if (step.hide_foreign_binaries_warning) return;
332 const builder = step.builder;
333 const artifact = step.exe;
334
335 const host_name = builder.host.target.zigTriple(builder.allocator) catch unreachable;
336 const foreign_name = artifact.target.zigTriple(builder.allocator) catch unreachable;
337 const target_info = std.zig.system.NativeTargetInfo.detect(builder.allocator, artifact.target) catch unreachable;
338 const need_cross_glibc = artifact.target.isGnuLibC() and artifact.is_linking_libc;
339 switch (builder.host.getExternalExecutor(target_info, .{
340 .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null,
341 .link_libc = artifact.is_linking_libc,
342 })) {
343 .native => unreachable,
344 .bad_dl => |foreign_dl| {
345 const host_dl = builder.host.dynamic_linker.get() orelse "(none)";
346 std.debug.print("the host system does not appear to be capable of executing binaries from the target because the host dynamic linker is '{s}', while the target dynamic linker is '{s}'. Consider setting the dynamic linker as '{s}'.\n", .{
347 host_dl, foreign_dl, host_dl,
348 });
349 },
350 .bad_os_or_cpu => {
351 std.debug.print("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}).\n", .{
352 host_name, foreign_name,
353 });
354 },
355 .darling => if (!builder.enable_darling) {
356 std.debug.print(
357 "the host system ({s}) does not appear to be capable of executing binaries " ++
358 "from the target ({s}). Consider enabling darling.\n",
359 .{ host_name, foreign_name },
360 );
361 },
362 .rosetta => if (!builder.enable_rosetta) {
363 std.debug.print(
364 "the host system ({s}) does not appear to be capable of executing binaries " ++
365 "from the target ({s}). Consider enabling rosetta.\n",
366 .{ host_name, foreign_name },
367 );
368 },
369 .wine => if (!builder.enable_wine) {
370 std.debug.print(
371 "the host system ({s}) does not appear to be capable of executing binaries " ++
372 "from the target ({s}). Consider enabling wine.\n",
373 .{ host_name, foreign_name },
374 );
375 },
376 .qemu => if (!builder.enable_qemu) {
377 std.debug.print(
378 "the host system ({s}) does not appear to be capable of executing binaries " ++
379 "from the target ({s}). Consider enabling qemu.\n",
380 .{ host_name, foreign_name },
381 );
382 },
383 .wasmtime => {
384 std.debug.print(
385 "the host system ({s}) does not appear to be capable of executing binaries " ++
386 "from the target ({s}). Consider enabling wasmtime.\n",
387 .{ host_name, foreign_name },
388 );
389 },
390 }
391}