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
signaturelock-open 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();
1818
1919pub const step_id = .run_and_compare;
2020
21const max_stdout_size = 1 * 1024 * 1024; // 1 MiB
22
2123step: Step,
2224builder: *Builder,
2325
......@@ -30,14 +32,34 @@ expected_exit_code: ?u8 = 0,
3032/// Override this field to modify the environment
3133env_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
3352pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *RunCompareStep {
3453 std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe);
3554 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;
3656 self.* = .{
3757 .builder = builder,
3858 .step = Step.init(.run_and_compare, name, builder.allocator, make),
3959 .exe = artifact,
4060 .env_map = null,
61 .cwd = null,
62 .hide_foreign_binaries_warning = hide_warnings,
4163 };
4264 self.step.dependOn(&artifact.step);
4365
......@@ -47,12 +69,10 @@ pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *Ru
4769fn make(step: *Step) !void {
4870 const self = @fieldParentPtr(RunCompareStep, "step", step);
4971 const host_info = self.builder.host;
50 const cwd = self.builder.build_root;
51 _ = cwd;
52 std.debug.print("Make called!\n", .{});
72 const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root;
5373
5474 var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);
55 _ = argv_list;
75 defer argv_list.deinit();
5676
5777 const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc;
5878 switch (host_info.getExternalExecutor(self.exe.target_info, .{
......@@ -60,7 +80,7 @@ fn make(step: *Step) !void {
6080 .link_libc = self.exe.is_linking_libc,
6181 })) {
6282 .native => {},
63 .rosetta => if (!self.builder.enable_rosetta) return,
83 .rosetta => if (!self.builder.enable_rosetta) return warnAboutForeignBinaries(self),
6484 .wine => |bin_name| if (self.builder.enable_wine) {
6585 try argv_list.append(bin_name);
6686 } else return,
......@@ -89,15 +109,15 @@ fn make(step: *Step) !void {
89109 try argv_list.append("-L");
90110 try argv_list.append(full_dir);
91111 }
92 } else return,
112 } else return warnAboutForeignBinaries(self),
93113 .darling => |bin_name| if (self.builder.enable_darling) {
94114 try argv_list.append(bin_name);
95 } else return,
115 } else return warnAboutForeignBinaries(self),
96116 .wasmtime => |bin_name| if (self.builder.enable_wasmtime) {
97117 try argv_list.append(bin_name);
98118 try argv_list.append("--dir=.");
99 } else return,
100 else => return, // on any failures we skip
119 } else return warnAboutForeignBinaries(self),
120 else => return warnAboutForeignBinaries(self),
101121 }
102122
103123 if (self.exe.target.isWindows()) {
......@@ -107,6 +127,143 @@ fn make(step: *Step) !void {
107127
108128 const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder);
109129 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 }
110267}
111268
112269fn addPathForDynLibs(self: *RunCompareStep, artifact: *LibExeObjStep) void {
......@@ -145,3 +302,90 @@ pub fn getEnvMap(self: *RunCompareStep) *EnvMap {
145302 return env_map;
146303 };
147304}
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}