authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-07-09 16:28:41+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-07-23 10:03:51+02:00
log735b6eefe92b222a0405671517b67e3b6c5cdded
treef01494108dcf5a630add23990a09c2986593125f
parent0dc3a0180b799e2579c82383a4464f2460e57167
signature Commit is signed but in an unrecognized format.

rename:RunCompareStep -> EmulatableRunStep

Renamed to better convery the intention of the step

4 files changed, 400 insertions(+), 396 deletions(-)

lib/std/build.zig+2-2
...@@ -26,7 +26,7 @@ pub const CheckFileStep = @import("build/CheckFileStep.zig");...@@ -26,7 +26,7 @@ pub const CheckFileStep = @import("build/CheckFileStep.zig");
26pub const CheckObjectStep = @import("build/CheckObjectStep.zig");26pub const CheckObjectStep = @import("build/CheckObjectStep.zig");
27pub const InstallRawStep = @import("build/InstallRawStep.zig");27pub const InstallRawStep = @import("build/InstallRawStep.zig");
28pub const OptionsStep = @import("build/OptionsStep.zig");28pub const OptionsStep = @import("build/OptionsStep.zig");
29pub const RunCompareStep = @import("build/RunCompareStep.zig");29pub const EmulatableRunStep = @import("build/EmulatableRunStep.zig");
3030
31pub const Builder = struct {31pub const Builder = struct {
32 install_tls: TopLevelStep,32 install_tls: TopLevelStep,
...@@ -3605,7 +3605,7 @@ pub const Step = struct {...@@ -3605,7 +3605,7 @@ pub const Step = struct {
3605 translate_c,3605 translate_c,
3606 write_file,3606 write_file,
3607 run,3607 run,
3608 run_and_compare,3608 emulatable_run,
3609 check_file,3609 check_file,
3610 check_object,3610 check_object,
3611 install_raw,3611 install_raw,
lib/std/build/CheckObjectStep.zig+3-3
...@@ -12,7 +12,7 @@ const CheckObjectStep = @This();...@@ -12,7 +12,7 @@ const CheckObjectStep = @This();
12const Allocator = mem.Allocator;12const Allocator = mem.Allocator;
13const Builder = build.Builder;13const Builder = build.Builder;
14const Step = build.Step;14const Step = build.Step;
15const RunCompareStep = build.RunCompareStep;15const EmulatableRunStep = build.EmulatableRunStep;
1616
17pub const base_id = .check_obj;17pub const base_id = .check_obj;
1818
...@@ -40,12 +40,12 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe...@@ -40,12 +40,12 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe
4040
41/// Runs and (optionally) compares the output of a binary.41/// Runs and (optionally) compares the output of a binary.
42/// Asserts `self` was generated from an executable step.42/// Asserts `self` was generated from an executable step.
43pub fn runAndCompare(self: *CheckObjectStep) *RunCompareStep {43pub fn runAndCompare(self: *CheckObjectStep) *EmulatableRunStep {
44 const dependencies_len = self.step.dependencies.items.len;44 const dependencies_len = self.step.dependencies.items.len;
45 assert(dependencies_len > 0);45 assert(dependencies_len > 0);
46 const exe_step = self.step.dependencies.items[dependencies_len - 1];46 const exe_step = self.step.dependencies.items[dependencies_len - 1];
47 const exe = exe_step.cast(std.build.LibExeObjStep).?;47 const exe = exe_step.cast(std.build.LibExeObjStep).?;
48 return RunCompareStep.create(self.builder, "RunCompare", exe);48 return EmulatableRunStep.create(self.builder, "EmulatableRun", exe);
49}49}
5050
51/// There two types of actions currently suported:51/// There two types of actions currently suported:
lib/std/build/EmulatableRunStep.zig created+395
...@@ -0,0 +1,395 @@
1//! Unlike `RunStep` this step will provide emulation, when enabled, to run foreign binaries.
2//! When a binary is foreign, but emulation for the target is disabled, the specified binary
3//! will not be run and therefore also not validated against its output.
4//! This step can be useful when wishing to run a built binary on multiple platforms,
5//! without having to verify if it's possible to be ran against.
6
7const std = @import("../std.zig");
8const build = std.build;
9const Step = std.build.Step;
10const Builder = std.build.Builder;
11const LibExeObjStep = std.build.LibExeObjStep;
12
13const fs = std.fs;
14const process = std.process;
15const EnvMap = process.EnvMap;
16
17const EmulatableRunStep = @This();
18
19pub const step_id = .emulatable_run;
20
21const max_stdout_size = 1 * 1024 * 1024; // 1 MiB
22
23step: Step,
24builder: *Builder,
25
26/// The artifact (executable) to be run by this step
27exe: *LibExeObjStep,
28
29/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
30expected_exit_code: ?u8 = 0,
31
32/// Override this field to modify the environment
33env_map: ?*EnvMap,
34
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
52/// Creates a step that will execute the given artifact. This step will allow running the
53/// binary through emulation when any of the emulation options such as `enable_rosetta` are set to true.
54/// When set to false, and the binary is foreign, running the executable is skipped.
55/// Asserts given artifact is an executable.
56pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *EmulatableRunStep {
57 std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe);
58 const self = builder.allocator.create(EmulatableRunStep) catch unreachable;
59 const hide_warnings = builder.option(bool, "hide-foreign-warnings", "Hide the warning when a foreign binary which is incompatible is skipped") orelse false;
60 self.* = .{
61 .builder = builder,
62 .step = Step.init(.emulatable_run, name, builder.allocator, make),
63 .exe = artifact,
64 .env_map = null,
65 .cwd = null,
66 .hide_foreign_binaries_warning = hide_warnings,
67 };
68 self.step.dependOn(&artifact.step);
69
70 return self;
71}
72
73fn make(step: *Step) !void {
74 const self = @fieldParentPtr(EmulatableRunStep, "step", step);
75 const host_info = self.builder.host;
76 const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root;
77
78 var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);
79 defer argv_list.deinit();
80
81 const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc;
82 switch (host_info.getExternalExecutor(self.exe.target_info, .{
83 .qemu_fixes_dl = need_cross_glibc and self.builder.glibc_runtimes_dir != null,
84 .link_libc = self.exe.is_linking_libc,
85 })) {
86 .native => {},
87 .rosetta => if (!self.builder.enable_rosetta) return warnAboutForeignBinaries(self),
88 .wine => |bin_name| if (self.builder.enable_wine) {
89 try argv_list.append(bin_name);
90 } else return,
91 .qemu => |bin_name| if (self.builder.enable_qemu) {
92 const glibc_dir_arg = if (need_cross_glibc)
93 self.builder.glibc_runtimes_dir orelse return
94 else
95 null;
96 try argv_list.append(bin_name);
97 if (glibc_dir_arg) |dir| {
98 // TODO look into making this a call to `linuxTriple`. This
99 // needs the directory to be called "i686" rather than
100 // "i386" which is why we do it manually here.
101 const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
102 const cpu_arch = self.exe.target.getCpuArch();
103 const os_tag = self.exe.target.getOsTag();
104 const abi = self.exe.target.getAbi();
105 const cpu_arch_name: []const u8 = if (cpu_arch == .i386)
106 "i686"
107 else
108 @tagName(cpu_arch);
109 const full_dir = try std.fmt.allocPrint(self.builder.allocator, fmt_str, .{
110 dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
111 });
112
113 try argv_list.append("-L");
114 try argv_list.append(full_dir);
115 }
116 } else return warnAboutForeignBinaries(self),
117 .darling => |bin_name| if (self.builder.enable_darling) {
118 try argv_list.append(bin_name);
119 } else return warnAboutForeignBinaries(self),
120 .wasmtime => |bin_name| if (self.builder.enable_wasmtime) {
121 try argv_list.append(bin_name);
122 try argv_list.append("--dir=.");
123 } else return warnAboutForeignBinaries(self),
124 else => return warnAboutForeignBinaries(self),
125 }
126
127 if (self.exe.target.isWindows()) {
128 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
129 self.addPathForDynLibs(self.exe);
130 }
131
132 const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder);
133 try argv_list.append(executable_path);
134
135 if (!std.process.can_spawn) {
136 const cmd = try std.mem.join(self.builder.allocator, " ", argv_list.items);
137 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 });
138 self.builder.allocator.free(cmd);
139 return error.ExecNotSupported;
140 }
141
142 var child = std.ChildProcess.init(argv_list.items, self.builder.allocator);
143 child.cwd = cwd;
144 child.env_map = self.env_map orelse self.builder.env_map;
145
146 child.stdin_behavior = .Inherit;
147 child.stdout_behavior = stdIoActionToBehavior(self.stdout_action);
148 child.stderr_behavior = stdIoActionToBehavior(self.stderr_action);
149
150 child.spawn() catch |err| {
151 std.debug.print("Unable to spawn {s}: {s}\n", .{ argv_list.items[0], @errorName(err) });
152 return err;
153 };
154
155 var stdout: ?[]const u8 = null;
156 defer if (stdout) |s| self.builder.allocator.free(s);
157
158 switch (self.stdout_action) {
159 .expect_exact, .expect_matches => {
160 stdout = child.stdout.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable;
161 },
162 .inherit, .ignore => {},
163 }
164
165 var stderr: ?[]const u8 = null;
166 defer if (stderr) |s| self.builder.allocator.free(s);
167
168 switch (self.stderr_action) {
169 .expect_exact, .expect_matches => {
170 stderr = child.stderr.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable;
171 },
172 .inherit, .ignore => {},
173 }
174
175 const term = child.wait() catch |err| {
176 std.debug.print("Unable to spawn {s}: {s}\n", .{ argv_list.items[0], @errorName(err) });
177 return err;
178 };
179
180 switch (term) {
181 .Exited => |code| blk: {
182 const expected_exit_code = self.expected_exit_code orelse break :blk;
183
184 if (code != expected_exit_code) {
185 if (self.builder.prominent_compile_errors) {
186 std.debug.print("Run step exited with error code {} (expected {})\n", .{
187 code,
188 expected_exit_code,
189 });
190 } else {
191 std.debug.print("The following command exited with error code {} (expected {}):\n", .{
192 code,
193 expected_exit_code,
194 });
195 printCmd(cwd, argv_list.items);
196 }
197
198 return error.UnexpectedExitCode;
199 }
200 },
201 else => {
202 std.debug.print("The following command terminated unexpectedly:\n", .{});
203 printCmd(cwd, argv_list.items);
204 return error.UncleanExit;
205 },
206 }
207
208 switch (self.stderr_action) {
209 .inherit, .ignore => {},
210 .expect_exact => |expected_bytes| {
211 if (!std.mem.eql(u8, expected_bytes, stderr.?)) {
212 std.debug.print(
213 \\
214 \\========= Expected this stderr: =========
215 \\{s}
216 \\========= But found: ====================
217 \\{s}
218 \\
219 , .{ expected_bytes, stderr.? });
220 printCmd(cwd, argv_list.items);
221 return error.TestFailed;
222 }
223 },
224 .expect_matches => |matches| for (matches) |match| {
225 if (std.mem.indexOf(u8, stderr.?, match) == null) {
226 std.debug.print(
227 \\
228 \\========= Expected to find in stderr: =========
229 \\{s}
230 \\========= But stderr does not contain it: =====
231 \\{s}
232 \\
233 , .{ match, stderr.? });
234 printCmd(cwd, argv_list.items);
235 return error.TestFailed;
236 }
237 },
238 }
239
240 switch (self.stdout_action) {
241 .inherit, .ignore => {},
242 .expect_exact => |expected_bytes| {
243 if (!std.mem.eql(u8, expected_bytes, stdout.?)) {
244 std.debug.print(
245 \\
246 \\========= Expected this stdout: =========
247 \\{s}
248 \\========= But found: ====================
249 \\{s}
250 \\
251 , .{ expected_bytes, stdout.? });
252 printCmd(cwd, argv_list.items);
253 return error.TestFailed;
254 }
255 },
256 .expect_matches => |matches| for (matches) |match| {
257 if (std.mem.indexOf(u8, stdout.?, match) == null) {
258 std.debug.print(
259 \\
260 \\========= Expected to find in stdout: =========
261 \\{s}
262 \\========= But stdout does not contain it: =====
263 \\{s}
264 \\
265 , .{ match, stdout.? });
266 printCmd(cwd, argv_list.items);
267 return error.TestFailed;
268 }
269 },
270 }
271}
272
273fn addPathForDynLibs(self: *EmulatableRunStep, artifact: *LibExeObjStep) void {
274 for (artifact.link_objects.items) |link_object| {
275 switch (link_object) {
276 .other_step => |other| {
277 if (other.target.isWindows() and other.isDynamicLibrary()) {
278 self.addPathDir(fs.path.dirname(other.getOutputSource().getPath(self.builder)).?);
279 self.addPathForDynLibs(other);
280 }
281 },
282 else => {},
283 }
284 }
285}
286
287pub fn addPathDir(self: *EmulatableRunStep, search_path: []const u8) void {
288 const env_map = self.getEnvMap();
289
290 const key = "PATH";
291 var prev_path = env_map.get(key);
292
293 if (prev_path) |pp| {
294 const new_path = self.builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path });
295 env_map.put(key, new_path) catch unreachable;
296 } else {
297 env_map.put(key, self.builder.dupePath(search_path)) catch unreachable;
298 }
299}
300
301pub fn getEnvMap(self: *EmulatableRunStep) *EnvMap {
302 return self.env_map orelse {
303 const env_map = self.builder.allocator.create(EnvMap) catch unreachable;
304 env_map.* = process.getEnvMap(self.builder.allocator) catch unreachable;
305 self.env_map = env_map;
306 return env_map;
307 };
308}
309
310pub fn expectStdErrEqual(self: *EmulatableRunStep, bytes: []const u8) void {
311 self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) };
312}
313
314pub fn expectStdOutEqual(self: *EmulatableRunStep, bytes: []const u8) void {
315 self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) };
316}
317
318fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo {
319 return switch (action) {
320 .ignore => .Ignore,
321 .inherit => .Inherit,
322 .expect_exact, .expect_matches => .Pipe,
323 };
324}
325
326fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void {
327 if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd});
328 for (argv) |arg| {
329 std.debug.print("{s} ", .{arg});
330 }
331 std.debug.print("\n", .{});
332}
333
334fn warnAboutForeignBinaries(step: *EmulatableRunStep) void {
335 if (step.hide_foreign_binaries_warning) return;
336 const builder = step.builder;
337 const artifact = step.exe;
338
339 const host_name = builder.host.target.zigTriple(builder.allocator) catch unreachable;
340 const foreign_name = artifact.target.zigTriple(builder.allocator) catch unreachable;
341 const target_info = std.zig.system.NativeTargetInfo.detect(builder.allocator, artifact.target) catch unreachable;
342 const need_cross_glibc = artifact.target.isGnuLibC() and artifact.is_linking_libc;
343 switch (builder.host.getExternalExecutor(target_info, .{
344 .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null,
345 .link_libc = artifact.is_linking_libc,
346 })) {
347 .native => unreachable,
348 .bad_dl => |foreign_dl| {
349 const host_dl = builder.host.dynamic_linker.get() orelse "(none)";
350 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", .{
351 host_dl, foreign_dl, host_dl,
352 });
353 },
354 .bad_os_or_cpu => {
355 std.debug.print("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}).\n", .{
356 host_name, foreign_name,
357 });
358 },
359 .darling => if (!builder.enable_darling) {
360 std.debug.print(
361 "the host system ({s}) does not appear to be capable of executing binaries " ++
362 "from the target ({s}). Consider enabling darling.\n",
363 .{ host_name, foreign_name },
364 );
365 },
366 .rosetta => if (!builder.enable_rosetta) {
367 std.debug.print(
368 "the host system ({s}) does not appear to be capable of executing binaries " ++
369 "from the target ({s}). Consider enabling rosetta.\n",
370 .{ host_name, foreign_name },
371 );
372 },
373 .wine => if (!builder.enable_wine) {
374 std.debug.print(
375 "the host system ({s}) does not appear to be capable of executing binaries " ++
376 "from the target ({s}). Consider enabling wine.\n",
377 .{ host_name, foreign_name },
378 );
379 },
380 .qemu => if (!builder.enable_qemu) {
381 std.debug.print(
382 "the host system ({s}) does not appear to be capable of executing binaries " ++
383 "from the target ({s}). Consider enabling qemu.\n",
384 .{ host_name, foreign_name },
385 );
386 },
387 .wasmtime => {
388 std.debug.print(
389 "the host system ({s}) does not appear to be capable of executing binaries " ++
390 "from the target ({s}). Consider enabling wasmtime.\n",
391 .{ host_name, foreign_name },
392 );
393 },
394 }
395}
lib/std/build/RunCompareStep.zig deleted-391
...@@ -1,391 +0,0 @@
1//! Unlike `RunStep` this step will provide emulation, when enabled, to run foreign binaries.
2//! When a binary is foreign, but emulation for the target is disabled, the specified binary
3//! will not be run and therefore also not validated against its output.
4//! This step can be useful when wishing to run a built binary on multiple platforms,
5//! without having to verify if it's possible to be ran against.
6
7const std = @import("../std.zig");
8const build = std.build;
9const Step = std.build.Step;
10const Builder = std.build.Builder;
11const LibExeObjStep = std.build.LibExeObjStep;
12
13const fs = std.fs;
14const process = std.process;
15const EnvMap = process.EnvMap;
16
17const RunCompareStep = @This();
18
19pub const step_id = .run_and_compare;
20
21const max_stdout_size = 1 * 1024 * 1024; // 1 MiB
22
23step: Step,
24builder: *Builder,
25
26/// The artifact (executable) to be run by this step
27exe: *LibExeObjStep,
28
29/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
30expected_exit_code: ?u8 = 0,
31
32/// Override this field to modify the environment
33env_map: ?*EnvMap,
34
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
52pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *RunCompareStep {
53 std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe);
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;
56 self.* = .{
57 .builder = builder,
58 .step = Step.init(.run_and_compare, name, builder.allocator, make),
59 .exe = artifact,
60 .env_map = null,
61 .cwd = null,
62 .hide_foreign_binaries_warning = hide_warnings,
63 };
64 self.step.dependOn(&artifact.step);
65
66 return self;
67}
68
69fn make(step: *Step) !void {
70 const self = @fieldParentPtr(RunCompareStep, "step", step);
71 const host_info = self.builder.host;
72 const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root;
73
74 var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);
75 defer argv_list.deinit();
76
77 const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc;
78 switch (host_info.getExternalExecutor(self.exe.target_info, .{
79 .qemu_fixes_dl = need_cross_glibc and self.builder.glibc_runtimes_dir != null,
80 .link_libc = self.exe.is_linking_libc,
81 })) {
82 .native => {},
83 .rosetta => if (!self.builder.enable_rosetta) return warnAboutForeignBinaries(self),
84 .wine => |bin_name| if (self.builder.enable_wine) {
85 try argv_list.append(bin_name);
86 } else return,
87 .qemu => |bin_name| if (self.builder.enable_qemu) {
88 const glibc_dir_arg = if (need_cross_glibc)
89 self.builder.glibc_runtimes_dir orelse return
90 else
91 null;
92 try argv_list.append(bin_name);
93 if (glibc_dir_arg) |dir| {
94 // TODO look into making this a call to `linuxTriple`. This
95 // needs the directory to be called "i686" rather than
96 // "i386" which is why we do it manually here.
97 const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
98 const cpu_arch = self.exe.target.getCpuArch();
99 const os_tag = self.exe.target.getOsTag();
100 const abi = self.exe.target.getAbi();
101 const cpu_arch_name: []const u8 = if (cpu_arch == .i386)
102 "i686"
103 else
104 @tagName(cpu_arch);
105 const full_dir = try std.fmt.allocPrint(self.builder.allocator, fmt_str, .{
106 dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
107 });
108
109 try argv_list.append("-L");
110 try argv_list.append(full_dir);
111 }
112 } else return warnAboutForeignBinaries(self),
113 .darling => |bin_name| if (self.builder.enable_darling) {
114 try argv_list.append(bin_name);
115 } else return warnAboutForeignBinaries(self),
116 .wasmtime => |bin_name| if (self.builder.enable_wasmtime) {
117 try argv_list.append(bin_name);
118 try argv_list.append("--dir=.");
119 } else return warnAboutForeignBinaries(self),
120 else => return warnAboutForeignBinaries(self),
121 }
122
123 if (self.exe.target.isWindows()) {
124 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
125 self.addPathForDynLibs(self.exe);
126 }
127
128 const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder);
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 }
267}
268
269fn addPathForDynLibs(self: *RunCompareStep, artifact: *LibExeObjStep) void {
270 for (artifact.link_objects.items) |link_object| {
271 switch (link_object) {
272 .other_step => |other| {
273 if (other.target.isWindows() and other.isDynamicLibrary()) {
274 self.addPathDir(fs.path.dirname(other.getOutputSource().getPath(self.builder)).?);
275 self.addPathForDynLibs(other);
276 }
277 },
278 else => {},
279 }
280 }
281}
282
283pub fn addPathDir(self: *RunCompareStep, search_path: []const u8) void {
284 const env_map = self.getEnvMap();
285
286 const key = "PATH";
287 var prev_path = env_map.get(key);
288
289 if (prev_path) |pp| {
290 const new_path = self.builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path });
291 env_map.put(key, new_path) catch unreachable;
292 } else {
293 env_map.put(key, self.builder.dupePath(search_path)) catch unreachable;
294 }
295}
296
297pub fn getEnvMap(self: *RunCompareStep) *EnvMap {
298 return self.env_map orelse {
299 const env_map = self.builder.allocator.create(EnvMap) catch unreachable;
300 env_map.* = process.getEnvMap(self.builder.allocator) catch unreachable;
301 self.env_map = env_map;
302 return env_map;
303 };
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}