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
signature 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");...@@ -26,6 +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");
2930
30pub const Builder = struct {31pub const Builder = struct {
31 install_tls: TopLevelStep,32 install_tls: TopLevelStep,
...@@ -3604,6 +3605,7 @@ pub const Step = struct {...@@ -3604,6 +3605,7 @@ pub const Step = struct {
3604 translate_c,3605 translate_c,
3605 write_file,3606 write_file,
3606 run,3607 run,
3608 run_and_compare,
3607 check_file,3609 check_file,
3608 check_object,3610 check_object,
3609 install_raw,3611 install_raw,
lib/std/build/CheckObjectStep.zig+11
...@@ -12,6 +12,7 @@ const CheckObjectStep = @This();...@@ -12,6 +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;
1516
16pub const base_id = .check_obj;17pub const base_id = .check_obj;
1718
...@@ -37,6 +38,16 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe...@@ -37,6 +38,16 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe
37 return self;38 return self;
38}39}
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
40/// There two types of actions currently suported:51/// There two types of actions currently suported:
41/// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}`52/// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}`
42/// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature53/// 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}