authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-07-08 22:57:17+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-07-23 10:03:46+02:00
logfd26c12469a3eda2c99cf58bf951b2223099b9ef
tree3f0ce9d851c61395056e43564e75d59dfa3d4e9e
parenta8bfddfaeae4f48c044fd134aac1e977e6a161f8
signaturelock-open Commit is signed but in an unrecognized format.

RunCompareStep: implement new step

This creates a new step that can run foreign binaries when emulation is enabled using options such as `enable_qemu`. When an incompatible binary is found, the binary will not be executed. This differs from `RunStep` which will always execute a binary, regardless of the compatibility. This is useful for usecases where the user wishes to allow for running the binary on any supported platform either natively or through emulation, but not generate an error when met with an incompatibility. The above is useful when creating test cases that rely on running the binary and optionally verifying its output. The addition of this Step was generated by the need for our linker tests. For that reason, a handy function was created on `CheckObjectStep` to ease the setup for that.

3 files changed, 160 insertions(+), 0 deletions(-)

lib/std/build.zig+2
......@@ -26,6 +26,7 @@ pub const CheckFileStep = @import("build/CheckFileStep.zig");
2626pub const CheckObjectStep = @import("build/CheckObjectStep.zig");
2727pub const InstallRawStep = @import("build/InstallRawStep.zig");
2828pub const OptionsStep = @import("build/OptionsStep.zig");
29pub const RunCompareStep = @import("build/RunCompareStep.zig");
2930
3031pub const Builder = struct {
3132 install_tls: TopLevelStep,
......@@ -3604,6 +3605,7 @@ pub const Step = struct {
36043605 translate_c,
36053606 write_file,
36063607 run,
3608 run_and_compare,
36073609 check_file,
36083610 check_object,
36093611 install_raw,
lib/std/build/CheckObjectStep.zig+11
......@@ -12,6 +12,7 @@ const CheckObjectStep = @This();
1212const Allocator = mem.Allocator;
1313const Builder = build.Builder;
1414const Step = build.Step;
15const RunCompareStep = build.RunCompareStep;
1516
1617pub const base_id = .check_obj;
1718
......@@ -37,6 +38,16 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe
3738 return self;
3839}
3940
41/// Runs and (optionally) compares the output of a binary.
42/// Asserts `self` was generated from an executable step.
43pub fn runAndCompare(self: *CheckObjectStep) *RunCompareStep {
44 const dependencies_len = self.step.dependencies.items.len;
45 assert(dependencies_len > 0);
46 const exe_step = self.step.dependencies.items[dependencies_len - 1];
47 const exe = exe_step.cast(std.build.LibExeObjStep).?;
48 return RunCompareStep.create(self.builder, "RunCompare", exe);
49}
50
4051/// There two types of actions currently suported:
4152/// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}`
4253/// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature
lib/std/build/RunCompareStep.zig created+147
......@@ -0,0 +1,147 @@
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
21step: Step,
22builder: *Builder,
23
24/// The artifact (executable) to be run by this step
25exe: *LibExeObjStep,
26
27/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
28expected_exit_code: ?u8 = 0,
29
30/// Override this field to modify the environment
31env_map: ?*EnvMap,
32
33pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *RunCompareStep {
34 std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe);
35 const self = builder.allocator.create(RunCompareStep) catch unreachable;
36 self.* = .{
37 .builder = builder,
38 .step = Step.init(.run_and_compare, name, builder.allocator, make),
39 .exe = artifact,
40 .env_map = null,
41 };
42 self.step.dependOn(&artifact.step);
43
44 return self;
45}
46
47fn make(step: *Step) !void {
48 const self = @fieldParentPtr(RunCompareStep, "step", step);
49 const host_info = self.builder.host;
50 const cwd = self.builder.build_root;
51 _ = cwd;
52 std.debug.print("Make called!\n", .{});
53
54 var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);
55 _ = argv_list;
56
57 const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc;
58 switch (host_info.getExternalExecutor(self.exe.target_info, .{
59 .qemu_fixes_dl = need_cross_glibc and self.builder.glibc_runtimes_dir != null,
60 .link_libc = self.exe.is_linking_libc,
61 })) {
62 .native => {},
63 .rosetta => if (!self.builder.enable_rosetta) return,
64 .wine => |bin_name| if (self.builder.enable_wine) {
65 try argv_list.append(bin_name);
66 } else return,
67 .qemu => |bin_name| if (self.builder.enable_qemu) {
68 const glibc_dir_arg = if (need_cross_glibc)
69 self.builder.glibc_runtimes_dir orelse return
70 else
71 null;
72 try argv_list.append(bin_name);
73 if (glibc_dir_arg) |dir| {
74 // TODO look into making this a call to `linuxTriple`. This
75 // needs the directory to be called "i686" rather than
76 // "i386" which is why we do it manually here.
77 const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
78 const cpu_arch = self.exe.target.getCpuArch();
79 const os_tag = self.exe.target.getOsTag();
80 const abi = self.exe.target.getAbi();
81 const cpu_arch_name: []const u8 = if (cpu_arch == .i386)
82 "i686"
83 else
84 @tagName(cpu_arch);
85 const full_dir = try std.fmt.allocPrint(self.builder.allocator, fmt_str, .{
86 dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
87 });
88
89 try argv_list.append("-L");
90 try argv_list.append(full_dir);
91 }
92 } else return,
93 .darling => |bin_name| if (self.builder.enable_darling) {
94 try argv_list.append(bin_name);
95 } else return,
96 .wasmtime => |bin_name| if (self.builder.enable_wasmtime) {
97 try argv_list.append(bin_name);
98 try argv_list.append("--dir=.");
99 } else return,
100 else => return, // on any failures we skip
101 }
102
103 if (self.exe.target.isWindows()) {
104 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
105 self.addPathForDynLibs(self.exe);
106 }
107
108 const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder);
109 try argv_list.append(executable_path);
110}
111
112fn addPathForDynLibs(self: *RunCompareStep, artifact: *LibExeObjStep) void {
113 for (artifact.link_objects.items) |link_object| {
114 switch (link_object) {
115 .other_step => |other| {
116 if (other.target.isWindows() and other.isDynamicLibrary()) {
117 self.addPathDir(fs.path.dirname(other.getOutputSource().getPath(self.builder)).?);
118 self.addPathForDynLibs(other);
119 }
120 },
121 else => {},
122 }
123 }
124}
125
126pub fn addPathDir(self: *RunCompareStep, search_path: []const u8) void {
127 const env_map = self.getEnvMap();
128
129 const key = "PATH";
130 var prev_path = env_map.get(key);
131
132 if (prev_path) |pp| {
133 const new_path = self.builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path });
134 env_map.put(key, new_path) catch unreachable;
135 } else {
136 env_map.put(key, self.builder.dupePath(search_path)) catch unreachable;
137 }
138}
139
140pub fn getEnvMap(self: *RunCompareStep) *EnvMap {
141 return self.env_map orelse {
142 const env_map = self.builder.allocator.create(EnvMap) catch unreachable;
143 env_map.* = process.getEnvMap(self.builder.allocator) catch unreachable;
144 self.env_map = env_map;
145 return env_map;
146 };
147}