From fd26c12469a3eda2c99cf58bf951b2223099b9ef Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Fri, 8 Jul 2022 22:57:17 +0200 Subject: [PATCH 1/7] 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. --- lib/std/build.zig | 2 + lib/std/build/CheckObjectStep.zig | 11 +++ lib/std/build/RunCompareStep.zig | 147 ++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 lib/std/build/RunCompareStep.zig diff --git a/lib/std/build.zig b/lib/std/build.zig index 83e30b278f6b73318f07c2feb4031cac5da27003..d338aef4fbf2baed775ea685af22d65018166f57 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -26,6 +26,7 @@ pub const CheckFileStep = @import("build/CheckFileStep.zig"); pub const CheckObjectStep = @import("build/CheckObjectStep.zig"); pub const InstallRawStep = @import("build/InstallRawStep.zig"); pub const OptionsStep = @import("build/OptionsStep.zig"); +pub const RunCompareStep = @import("build/RunCompareStep.zig"); pub const Builder = struct { install_tls: TopLevelStep, @@ -3604,6 +3605,7 @@ pub const Step = struct { translate_c, write_file, run, + run_and_compare, check_file, check_object, install_raw, diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index b807e1de45d07671540719f62740293ced0bfc69..be1e741ce631294c6c746b7f00fab3fe2f06e0ab 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -12,6 +12,7 @@ const CheckObjectStep = @This(); const Allocator = mem.Allocator; const Builder = build.Builder; const Step = build.Step; +const RunCompareStep = build.RunCompareStep; pub const base_id = .check_obj; @@ -37,6 +38,16 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe return self; } +/// Runs and (optionally) compares the output of a binary. +/// Asserts `self` was generated from an executable step. +pub fn runAndCompare(self: *CheckObjectStep) *RunCompareStep { + const dependencies_len = self.step.dependencies.items.len; + assert(dependencies_len > 0); + const exe_step = self.step.dependencies.items[dependencies_len - 1]; + const exe = exe_step.cast(std.build.LibExeObjStep).?; + return RunCompareStep.create(self.builder, "RunCompare", exe); +} + /// There two types of actions currently suported: /// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}` /// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature diff --git a/lib/std/build/RunCompareStep.zig b/lib/std/build/RunCompareStep.zig new file mode 100644 index 0000000000000000000000000000000000000000..4e6396e010d60a537d5dd63892447d02be597674 --- /dev/null +++ b/lib/std/build/RunCompareStep.zig @@ -0,0 +1,147 @@ +//! Unlike `RunStep` this step will provide emulation, when enabled, to run foreign binaries. +//! When a binary is foreign, but emulation for the target is disabled, the specified binary +//! will not be run and therefore also not validated against its output. +//! This step can be useful when wishing to run a built binary on multiple platforms, +//! without having to verify if it's possible to be ran against. + +const std = @import("../std.zig"); +const build = std.build; +const Step = std.build.Step; +const Builder = std.build.Builder; +const LibExeObjStep = std.build.LibExeObjStep; + +const fs = std.fs; +const process = std.process; +const EnvMap = process.EnvMap; + +const RunCompareStep = @This(); + +pub const step_id = .run_and_compare; + +step: Step, +builder: *Builder, + +/// The artifact (executable) to be run by this step +exe: *LibExeObjStep, + +/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution +expected_exit_code: ?u8 = 0, + +/// Override this field to modify the environment +env_map: ?*EnvMap, + +pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *RunCompareStep { + std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe); + const self = builder.allocator.create(RunCompareStep) catch unreachable; + self.* = .{ + .builder = builder, + .step = Step.init(.run_and_compare, name, builder.allocator, make), + .exe = artifact, + .env_map = null, + }; + self.step.dependOn(&artifact.step); + + return self; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(RunCompareStep, "step", step); + const host_info = self.builder.host; + const cwd = self.builder.build_root; + _ = cwd; + std.debug.print("Make called!\n", .{}); + + var argv_list = std.ArrayList([]const u8).init(self.builder.allocator); + _ = argv_list; + + const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc; + switch (host_info.getExternalExecutor(self.exe.target_info, .{ + .qemu_fixes_dl = need_cross_glibc and self.builder.glibc_runtimes_dir != null, + .link_libc = self.exe.is_linking_libc, + })) { + .native => {}, + .rosetta => if (!self.builder.enable_rosetta) return, + .wine => |bin_name| if (self.builder.enable_wine) { + try argv_list.append(bin_name); + } else return, + .qemu => |bin_name| if (self.builder.enable_qemu) { + const glibc_dir_arg = if (need_cross_glibc) + self.builder.glibc_runtimes_dir orelse return + else + null; + try argv_list.append(bin_name); + if (glibc_dir_arg) |dir| { + // TODO look into making this a call to `linuxTriple`. This + // needs the directory to be called "i686" rather than + // "i386" which is why we do it manually here. + const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; + const cpu_arch = self.exe.target.getCpuArch(); + const os_tag = self.exe.target.getOsTag(); + const abi = self.exe.target.getAbi(); + const cpu_arch_name: []const u8 = if (cpu_arch == .i386) + "i686" + else + @tagName(cpu_arch); + const full_dir = try std.fmt.allocPrint(self.builder.allocator, fmt_str, .{ + dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), + }); + + try argv_list.append("-L"); + try argv_list.append(full_dir); + } + } else return, + .darling => |bin_name| if (self.builder.enable_darling) { + try argv_list.append(bin_name); + } else return, + .wasmtime => |bin_name| if (self.builder.enable_wasmtime) { + try argv_list.append(bin_name); + try argv_list.append("--dir=."); + } else return, + else => return, // on any failures we skip + } + + if (self.exe.target.isWindows()) { + // On Windows we don't have rpaths so we have to add .dll search paths to PATH + self.addPathForDynLibs(self.exe); + } + + const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder); + try argv_list.append(executable_path); +} + +fn addPathForDynLibs(self: *RunCompareStep, artifact: *LibExeObjStep) void { + for (artifact.link_objects.items) |link_object| { + switch (link_object) { + .other_step => |other| { + if (other.target.isWindows() and other.isDynamicLibrary()) { + self.addPathDir(fs.path.dirname(other.getOutputSource().getPath(self.builder)).?); + self.addPathForDynLibs(other); + } + }, + else => {}, + } + } +} + +pub fn addPathDir(self: *RunCompareStep, search_path: []const u8) void { + const env_map = self.getEnvMap(); + + const key = "PATH"; + var prev_path = env_map.get(key); + + if (prev_path) |pp| { + const new_path = self.builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); + env_map.put(key, new_path) catch unreachable; + } else { + env_map.put(key, self.builder.dupePath(search_path)) catch unreachable; + } +} + +pub fn getEnvMap(self: *RunCompareStep) *EnvMap { + return self.env_map orelse { + const env_map = self.builder.allocator.create(EnvMap) catch unreachable; + env_map.* = process.getEnvMap(self.builder.allocator) catch unreachable; + self.env_map = env_map; + return env_map; + }; +} -- 2.54.0 From 0dc3a0180b799e2579c82383a4464f2460e57167 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sat, 9 Jul 2022 16:09:47 +0200 Subject: [PATCH 2/7] 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`. --- lib/std/build/RunCompareStep.zig | 262 +++++++++++++++++++++++++++++-- 1 file changed, 253 insertions(+), 9 deletions(-) diff --git a/lib/std/build/RunCompareStep.zig b/lib/std/build/RunCompareStep.zig index 4e6396e010d60a537d5dd63892447d02be597674..635f469b3934f249504f18f71c695f8fd42886b0 100644 --- a/lib/std/build/RunCompareStep.zig +++ b/lib/std/build/RunCompareStep.zig @@ -18,6 +18,8 @@ const RunCompareStep = @This(); pub const step_id = .run_and_compare; +const max_stdout_size = 1 * 1024 * 1024; // 1 MiB + step: Step, builder: *Builder, @@ -30,14 +32,34 @@ expected_exit_code: ?u8 = 0, /// Override this field to modify the environment env_map: ?*EnvMap, +/// Set this to modify the current working directory +cwd: ?[]const u8, + +stdout_action: StdIoAction = .inherit, +stderr_action: StdIoAction = .inherit, + +/// When set to true, hides the warning of skipping a foreign binary which cannot be run on the host +/// or through emulation. +hide_foreign_binaries_warning: bool, + +pub const StdIoAction = union(enum) { + inherit, + ignore, + expect_exact: []const u8, + expect_matches: []const []const u8, +}; + pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *RunCompareStep { std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe); const self = builder.allocator.create(RunCompareStep) catch unreachable; + const hide_warnings = builder.option(bool, "hide-foreign-warnings", "Hide the warning when a foreign binary which is incompatible is skipped") orelse false; self.* = .{ .builder = builder, .step = Step.init(.run_and_compare, name, builder.allocator, make), .exe = artifact, .env_map = null, + .cwd = null, + .hide_foreign_binaries_warning = hide_warnings, }; self.step.dependOn(&artifact.step); @@ -47,12 +69,10 @@ pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *Ru fn make(step: *Step) !void { const self = @fieldParentPtr(RunCompareStep, "step", step); const host_info = self.builder.host; - const cwd = self.builder.build_root; - _ = cwd; - std.debug.print("Make called!\n", .{}); + const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root; var argv_list = std.ArrayList([]const u8).init(self.builder.allocator); - _ = argv_list; + defer argv_list.deinit(); const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc; switch (host_info.getExternalExecutor(self.exe.target_info, .{ @@ -60,7 +80,7 @@ fn make(step: *Step) !void { .link_libc = self.exe.is_linking_libc, })) { .native => {}, - .rosetta => if (!self.builder.enable_rosetta) return, + .rosetta => if (!self.builder.enable_rosetta) return warnAboutForeignBinaries(self), .wine => |bin_name| if (self.builder.enable_wine) { try argv_list.append(bin_name); } else return, @@ -89,15 +109,15 @@ fn make(step: *Step) !void { try argv_list.append("-L"); try argv_list.append(full_dir); } - } else return, + } else return warnAboutForeignBinaries(self), .darling => |bin_name| if (self.builder.enable_darling) { try argv_list.append(bin_name); - } else return, + } else return warnAboutForeignBinaries(self), .wasmtime => |bin_name| if (self.builder.enable_wasmtime) { try argv_list.append(bin_name); try argv_list.append("--dir=."); - } else return, - else => return, // on any failures we skip + } else return warnAboutForeignBinaries(self), + else => return warnAboutForeignBinaries(self), } if (self.exe.target.isWindows()) { @@ -107,6 +127,143 @@ fn make(step: *Step) !void { const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder); try argv_list.append(executable_path); + + if (!std.process.can_spawn) { + const cmd = try std.mem.join(self.builder.allocator, " ", argv_list.items); + 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 }); + self.builder.allocator.free(cmd); + return error.ExecNotSupported; + } + + var child = std.ChildProcess.init(argv_list.items, self.builder.allocator); + child.cwd = cwd; + child.env_map = self.env_map orelse self.builder.env_map; + + child.stdin_behavior = .Inherit; + child.stdout_behavior = stdIoActionToBehavior(self.stdout_action); + child.stderr_behavior = stdIoActionToBehavior(self.stderr_action); + + child.spawn() catch |err| { + std.debug.print("Unable to spawn {s}: {s}\n", .{ argv_list.items[0], @errorName(err) }); + return err; + }; + + var stdout: ?[]const u8 = null; + defer if (stdout) |s| self.builder.allocator.free(s); + + switch (self.stdout_action) { + .expect_exact, .expect_matches => { + stdout = child.stdout.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; + }, + .inherit, .ignore => {}, + } + + var stderr: ?[]const u8 = null; + defer if (stderr) |s| self.builder.allocator.free(s); + + switch (self.stderr_action) { + .expect_exact, .expect_matches => { + stderr = child.stderr.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; + }, + .inherit, .ignore => {}, + } + + const term = child.wait() catch |err| { + std.debug.print("Unable to spawn {s}: {s}\n", .{ argv_list.items[0], @errorName(err) }); + return err; + }; + + switch (term) { + .Exited => |code| blk: { + const expected_exit_code = self.expected_exit_code orelse break :blk; + + if (code != expected_exit_code) { + if (self.builder.prominent_compile_errors) { + std.debug.print("Run step exited with error code {} (expected {})\n", .{ + code, + expected_exit_code, + }); + } else { + std.debug.print("The following command exited with error code {} (expected {}):\n", .{ + code, + expected_exit_code, + }); + printCmd(cwd, argv_list.items); + } + + return error.UnexpectedExitCode; + } + }, + else => { + std.debug.print("The following command terminated unexpectedly:\n", .{}); + printCmd(cwd, argv_list.items); + return error.UncleanExit; + }, + } + + switch (self.stderr_action) { + .inherit, .ignore => {}, + .expect_exact => |expected_bytes| { + if (!std.mem.eql(u8, expected_bytes, stderr.?)) { + std.debug.print( + \\ + \\========= Expected this stderr: ========= + \\{s} + \\========= But found: ==================== + \\{s} + \\ + , .{ expected_bytes, stderr.? }); + printCmd(cwd, argv_list.items); + return error.TestFailed; + } + }, + .expect_matches => |matches| for (matches) |match| { + if (std.mem.indexOf(u8, stderr.?, match) == null) { + std.debug.print( + \\ + \\========= Expected to find in stderr: ========= + \\{s} + \\========= But stderr does not contain it: ===== + \\{s} + \\ + , .{ match, stderr.? }); + printCmd(cwd, argv_list.items); + return error.TestFailed; + } + }, + } + + switch (self.stdout_action) { + .inherit, .ignore => {}, + .expect_exact => |expected_bytes| { + if (!std.mem.eql(u8, expected_bytes, stdout.?)) { + std.debug.print( + \\ + \\========= Expected this stdout: ========= + \\{s} + \\========= But found: ==================== + \\{s} + \\ + , .{ expected_bytes, stdout.? }); + printCmd(cwd, argv_list.items); + return error.TestFailed; + } + }, + .expect_matches => |matches| for (matches) |match| { + if (std.mem.indexOf(u8, stdout.?, match) == null) { + std.debug.print( + \\ + \\========= Expected to find in stdout: ========= + \\{s} + \\========= But stdout does not contain it: ===== + \\{s} + \\ + , .{ match, stdout.? }); + printCmd(cwd, argv_list.items); + return error.TestFailed; + } + }, + } } fn addPathForDynLibs(self: *RunCompareStep, artifact: *LibExeObjStep) void { @@ -145,3 +302,90 @@ pub fn getEnvMap(self: *RunCompareStep) *EnvMap { return env_map; }; } + +pub fn expectStdErrEqual(self: *RunCompareStep, bytes: []const u8) void { + self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) }; +} + +pub fn expectStdOutEqual(self: *RunCompareStep, bytes: []const u8) void { + self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) }; +} + +fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo { + return switch (action) { + .ignore => .Ignore, + .inherit => .Inherit, + .expect_exact, .expect_matches => .Pipe, + }; +} + +fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void { + if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd}); + for (argv) |arg| { + std.debug.print("{s} ", .{arg}); + } + std.debug.print("\n", .{}); +} + +fn warnAboutForeignBinaries(step: *RunCompareStep) void { + if (step.hide_foreign_binaries_warning) return; + const builder = step.builder; + const artifact = step.exe; + + const host_name = builder.host.target.zigTriple(builder.allocator) catch unreachable; + const foreign_name = artifact.target.zigTriple(builder.allocator) catch unreachable; + const target_info = std.zig.system.NativeTargetInfo.detect(builder.allocator, artifact.target) catch unreachable; + const need_cross_glibc = artifact.target.isGnuLibC() and artifact.is_linking_libc; + switch (builder.host.getExternalExecutor(target_info, .{ + .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null, + .link_libc = artifact.is_linking_libc, + })) { + .native => unreachable, + .bad_dl => |foreign_dl| { + const host_dl = builder.host.dynamic_linker.get() orelse "(none)"; + 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", .{ + host_dl, foreign_dl, host_dl, + }); + }, + .bad_os_or_cpu => { + std.debug.print("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}).\n", .{ + host_name, foreign_name, + }); + }, + .darling => if (!builder.enable_darling) { + std.debug.print( + "the host system ({s}) does not appear to be capable of executing binaries " ++ + "from the target ({s}). Consider enabling darling.\n", + .{ host_name, foreign_name }, + ); + }, + .rosetta => if (!builder.enable_rosetta) { + std.debug.print( + "the host system ({s}) does not appear to be capable of executing binaries " ++ + "from the target ({s}). Consider enabling rosetta.\n", + .{ host_name, foreign_name }, + ); + }, + .wine => if (!builder.enable_wine) { + std.debug.print( + "the host system ({s}) does not appear to be capable of executing binaries " ++ + "from the target ({s}). Consider enabling wine.\n", + .{ host_name, foreign_name }, + ); + }, + .qemu => if (!builder.enable_qemu) { + std.debug.print( + "the host system ({s}) does not appear to be capable of executing binaries " ++ + "from the target ({s}). Consider enabling qemu.\n", + .{ host_name, foreign_name }, + ); + }, + .wasmtime => { + std.debug.print( + "the host system ({s}) does not appear to be capable of executing binaries " ++ + "from the target ({s}). Consider enabling wasmtime.\n", + .{ host_name, foreign_name }, + ); + }, + } +} -- 2.54.0 From 735b6eefe92b222a0405671517b67e3b6c5cdded Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sat, 9 Jul 2022 16:28:41 +0200 Subject: [PATCH 3/7] rename:RunCompareStep -> EmulatableRunStep Renamed to better convery the intention of the step --- lib/std/build.zig | 4 +-- lib/std/build/CheckObjectStep.zig | 6 ++-- ...nCompareStep.zig => EmulatableRunStep.zig} | 28 +++++++++++-------- 3 files changed, 21 insertions(+), 17 deletions(-) rename lib/std/build/{RunCompareStep.zig => EmulatableRunStep.zig} (93%) diff --git a/lib/std/build.zig b/lib/std/build.zig index d338aef4fbf2baed775ea685af22d65018166f57..fa778b9eaba50fc249bd928f226c7614dbc7945f 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -26,7 +26,7 @@ pub const CheckFileStep = @import("build/CheckFileStep.zig"); pub const CheckObjectStep = @import("build/CheckObjectStep.zig"); pub const InstallRawStep = @import("build/InstallRawStep.zig"); pub const OptionsStep = @import("build/OptionsStep.zig"); -pub const RunCompareStep = @import("build/RunCompareStep.zig"); +pub const EmulatableRunStep = @import("build/EmulatableRunStep.zig"); pub const Builder = struct { install_tls: TopLevelStep, @@ -3605,7 +3605,7 @@ pub const Step = struct { translate_c, write_file, run, - run_and_compare, + emulatable_run, check_file, check_object, install_raw, diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index be1e741ce631294c6c746b7f00fab3fe2f06e0ab..0525bbf034a94128eae8ac228c6369961d01d571 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -12,7 +12,7 @@ const CheckObjectStep = @This(); const Allocator = mem.Allocator; const Builder = build.Builder; const Step = build.Step; -const RunCompareStep = build.RunCompareStep; +const EmulatableRunStep = build.EmulatableRunStep; pub const base_id = .check_obj; @@ -40,12 +40,12 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe /// Runs and (optionally) compares the output of a binary. /// Asserts `self` was generated from an executable step. -pub fn runAndCompare(self: *CheckObjectStep) *RunCompareStep { +pub fn runAndCompare(self: *CheckObjectStep) *EmulatableRunStep { const dependencies_len = self.step.dependencies.items.len; assert(dependencies_len > 0); const exe_step = self.step.dependencies.items[dependencies_len - 1]; const exe = exe_step.cast(std.build.LibExeObjStep).?; - return RunCompareStep.create(self.builder, "RunCompare", exe); + return EmulatableRunStep.create(self.builder, "EmulatableRun", exe); } /// There two types of actions currently suported: diff --git a/lib/std/build/RunCompareStep.zig b/lib/std/build/EmulatableRunStep.zig similarity index 93% rename from lib/std/build/RunCompareStep.zig rename to lib/std/build/EmulatableRunStep.zig index 635f469b3934f249504f18f71c695f8fd42886b0..1f364015993564edb90fa6eac1714671fa8d61cf 100644 --- a/lib/std/build/RunCompareStep.zig +++ b/lib/std/build/EmulatableRunStep.zig @@ -14,9 +14,9 @@ const fs = std.fs; const process = std.process; const EnvMap = process.EnvMap; -const RunCompareStep = @This(); +const EmulatableRunStep = @This(); -pub const step_id = .run_and_compare; +pub const step_id = .emulatable_run; const max_stdout_size = 1 * 1024 * 1024; // 1 MiB @@ -49,13 +49,17 @@ pub const StdIoAction = union(enum) { expect_matches: []const []const u8, }; -pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *RunCompareStep { +/// Creates a step that will execute the given artifact. This step will allow running the +/// binary through emulation when any of the emulation options such as `enable_rosetta` are set to true. +/// When set to false, and the binary is foreign, running the executable is skipped. +/// Asserts given artifact is an executable. +pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *EmulatableRunStep { std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe); - const self = builder.allocator.create(RunCompareStep) catch unreachable; + const self = builder.allocator.create(EmulatableRunStep) catch unreachable; const hide_warnings = builder.option(bool, "hide-foreign-warnings", "Hide the warning when a foreign binary which is incompatible is skipped") orelse false; self.* = .{ .builder = builder, - .step = Step.init(.run_and_compare, name, builder.allocator, make), + .step = Step.init(.emulatable_run, name, builder.allocator, make), .exe = artifact, .env_map = null, .cwd = null, @@ -67,7 +71,7 @@ pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *Ru } fn make(step: *Step) !void { - const self = @fieldParentPtr(RunCompareStep, "step", step); + const self = @fieldParentPtr(EmulatableRunStep, "step", step); const host_info = self.builder.host; const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root; @@ -266,7 +270,7 @@ fn make(step: *Step) !void { } } -fn addPathForDynLibs(self: *RunCompareStep, artifact: *LibExeObjStep) void { +fn addPathForDynLibs(self: *EmulatableRunStep, artifact: *LibExeObjStep) void { for (artifact.link_objects.items) |link_object| { switch (link_object) { .other_step => |other| { @@ -280,7 +284,7 @@ fn addPathForDynLibs(self: *RunCompareStep, artifact: *LibExeObjStep) void { } } -pub fn addPathDir(self: *RunCompareStep, search_path: []const u8) void { +pub fn addPathDir(self: *EmulatableRunStep, search_path: []const u8) void { const env_map = self.getEnvMap(); const key = "PATH"; @@ -294,7 +298,7 @@ pub fn addPathDir(self: *RunCompareStep, search_path: []const u8) void { } } -pub fn getEnvMap(self: *RunCompareStep) *EnvMap { +pub fn getEnvMap(self: *EmulatableRunStep) *EnvMap { return self.env_map orelse { const env_map = self.builder.allocator.create(EnvMap) catch unreachable; env_map.* = process.getEnvMap(self.builder.allocator) catch unreachable; @@ -303,11 +307,11 @@ pub fn getEnvMap(self: *RunCompareStep) *EnvMap { }; } -pub fn expectStdErrEqual(self: *RunCompareStep, bytes: []const u8) void { +pub fn expectStdErrEqual(self: *EmulatableRunStep, bytes: []const u8) void { self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) }; } -pub fn expectStdOutEqual(self: *RunCompareStep, bytes: []const u8) void { +pub fn expectStdOutEqual(self: *EmulatableRunStep, bytes: []const u8) void { self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) }; } @@ -327,7 +331,7 @@ fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void { std.debug.print("\n", .{}); } -fn warnAboutForeignBinaries(step: *RunCompareStep) void { +fn warnAboutForeignBinaries(step: *EmulatableRunStep) void { if (step.hide_foreign_binaries_warning) return; const builder = step.builder; const artifact = step.exe; -- 2.54.0 From 4776065036f9878c86e54fa83341fe8cdbb175f0 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 10 Jul 2022 16:25:16 +0200 Subject: [PATCH 4/7] Use `EmulatableRunStep` for MachO linker tests --- lib/std/build/EmulatableRunStep.zig | 7 ++++++- test/link/macho/dylib/build.zig | 7 ++++--- test/link/macho/entry/build.zig | 5 ++--- test/link/macho/needed_library/build.zig | 6 ++++-- test/link/macho/objc/build.zig | 3 +-- test/link/macho/pagezero/build.zig | 2 ++ test/link/macho/search_strategy/build.zig | 10 ++++++---- test/link/macho/stack_size/build.zig | 5 ++--- test/link/macho/weak_library/build.zig | 7 ++++--- 9 files changed, 31 insertions(+), 21 deletions(-) diff --git a/lib/std/build/EmulatableRunStep.zig b/lib/std/build/EmulatableRunStep.zig index 1f364015993564edb90fa6eac1714671fa8d61cf..9745ebb89fa85f0b539e52b1414e48f3b6886c6c 100644 --- a/lib/std/build/EmulatableRunStep.zig +++ b/lib/std/build/EmulatableRunStep.zig @@ -56,7 +56,12 @@ pub const StdIoAction = union(enum) { pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *EmulatableRunStep { std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe); const self = builder.allocator.create(EmulatableRunStep) catch unreachable; - const hide_warnings = builder.option(bool, "hide-foreign-warnings", "Hide the warning when a foreign binary which is incompatible is skipped") orelse false; + + const option_name = "hide-foreign-warnings"; + const hide_warnings = if (builder.available_options_map.get(option_name) == null) warn: { + break :warn builder.option(bool, option_name, "Hide the warning when a foreign binary which is incompatible is skipped") orelse false; + } else false; + self.* = .{ .builder = builder, .step = Step.init(.emulatable_run, name, builder.allocator, make), diff --git a/test/link/macho/dylib/build.zig b/test/link/macho/dylib/build.zig index 1587def9b8153c81ed13b469bca4c602f43e30d6..a5baf255c6422d61fa8c76583fb119c4a6ed2cd3 100644 --- a/test/link/macho/dylib/build.zig +++ b/test/link/macho/dylib/build.zig @@ -3,12 +3,14 @@ const Builder = std.build.Builder; pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); dylib.setBuildMode(mode); + dylib.setTarget(target); dylib.addCSourceFile("a.c", &.{}); dylib.linkLibC(); dylib.install(); @@ -23,6 +25,7 @@ pub fn build(b: *Builder) void { test_step.dependOn(&check_dylib.step); const exe = b.addExecutable("main", null); + exe.setTarget(target); exe.setBuildMode(mode); exe.addCSourceFile("main.c", &.{}); exe.linkSystemLibrary("a"); @@ -40,9 +43,7 @@ pub fn build(b: *Builder) void { check_exe.checkStart("cmd RPATH"); check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{b.pathFromRoot("zig-out/lib")}) catch unreachable); - test_step.dependOn(&check_exe.step); - - const run = exe.run(); + const run = check_exe.runAndCompare(); run.cwd = b.pathFromRoot("."); run.expectStdOutEqual("Hello world"); test_step.dependOn(&run.step); diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig index 1f40b3d8e075e9e876b0cda3037425626211b3a2..0ecca14aa292b56201288635a2f791a855ddfae5 100644 --- a/test/link/macho/entry/build.zig +++ b/test/link/macho/entry/build.zig @@ -8,6 +8,7 @@ pub fn build(b: *Builder) void { test_step.dependOn(b.getInstallStep()); const exe = b.addExecutable("main", null); + exe.setTarget(.{ .os_tag = .macos }); exe.setBuildMode(mode); exe.addCSourceFile("main.c", &.{}); exe.linkLibC(); @@ -26,9 +27,7 @@ pub fn build(b: *Builder) void { check_exe.checkComputeCompare("vmaddr entryoff +", .{ .op = .eq, .value = .{ .variable = "n_value" } }); - test_step.dependOn(&check_exe.step); - - const run = exe.run(); + const run = check_exe.runAndCompare(); run.expectStdOutEqual("42"); test_step.dependOn(&run.step); } diff --git a/test/link/macho/needed_library/build.zig b/test/link/macho/needed_library/build.zig index 708a09dc32e20415cd76dde8311f37785142568c..a314fd22019884e335f912b0eca67160e4e46b6b 100644 --- a/test/link/macho/needed_library/build.zig +++ b/test/link/macho/needed_library/build.zig @@ -4,11 +4,13 @@ const LibExeObjectStep = std.build.LibExeObjStep; pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const test_step = b.step("test", "Test the program"); test_step.dependOn(b.getInstallStep()); const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); + dylib.setTarget(target); dylib.setBuildMode(mode); dylib.addCSourceFile("a.c", &.{}); dylib.linkLibC(); @@ -19,6 +21,7 @@ pub fn build(b: *Builder) void { const exe = b.addExecutable("test", null); exe.addCSourceFile("main.c", &[0][]const u8{}); exe.setBuildMode(mode); + exe.setTarget(target); exe.linkLibC(); exe.linkSystemLibraryNeeded("a"); exe.addLibraryPath(b.pathFromRoot("zig-out/lib")); @@ -28,8 +31,7 @@ pub fn build(b: *Builder) void { const check = exe.checkObject(.macho); check.checkStart("cmd LOAD_DYLIB"); check.checkNext("name @rpath/liba.dylib"); - test_step.dependOn(&check.step); - const run_cmd = exe.run(); + const run_cmd = check.runAndCompare(); test_step.dependOn(&run_cmd.step); } diff --git a/test/link/macho/objc/build.zig b/test/link/macho/objc/build.zig index e41fd48e7164bae47d27534b7201140c8262b857..d7fd872f77579f01ccbd986962549d100feecfe3 100644 --- a/test/link/macho/objc/build.zig +++ b/test/link/macho/objc/build.zig @@ -7,7 +7,6 @@ pub fn build(b: *Builder) void { const test_step = b.step("test", "Test the program"); const exe = b.addExecutable("test", null); - b.default_step.dependOn(&exe.step); exe.addIncludePath("."); exe.addCSourceFile("Foo.m", &[0][]const u8{}); exe.addCSourceFile("test.m", &[0][]const u8{}); @@ -17,6 +16,6 @@ pub fn build(b: *Builder) void { // populate paths to the sysroot here. exe.linkFramework("Foundation"); - const run_cmd = exe.run(); + const run_cmd = std.build.EmulatableRunStep.create(b, "run", exe); test_step.dependOn(&run_cmd.step); } diff --git a/test/link/macho/pagezero/build.zig b/test/link/macho/pagezero/build.zig index e858d1f4d884e6472558615099c75bcdaa1c16eb..9dbc0e64733fc272a1be4103406936da301bc4de 100644 --- a/test/link/macho/pagezero/build.zig +++ b/test/link/macho/pagezero/build.zig @@ -9,6 +9,7 @@ pub fn build(b: *Builder) void { { const exe = b.addExecutable("pagezero", null); + exe.setTarget(.{ .os_tag = .macos }); exe.setBuildMode(mode); exe.addCSourceFile("main.c", &.{}); exe.linkLibC(); @@ -28,6 +29,7 @@ pub fn build(b: *Builder) void { { const exe = b.addExecutable("no_pagezero", null); + exe.setTarget(.{ .os_tag = .macos }); exe.setBuildMode(mode); exe.addCSourceFile("main.c", &.{}); exe.linkLibC(); diff --git a/test/link/macho/search_strategy/build.zig b/test/link/macho/search_strategy/build.zig index c5e867c8d006e2928e81b941438343b3c70d198c..39a82bc6a74d7742598e8577adf860698d9b715a 100644 --- a/test/link/macho/search_strategy/build.zig +++ b/test/link/macho/search_strategy/build.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Builder = std.build.Builder; const LibExeObjectStep = std.build.LibExeObjStep; +const target: std.zig.CrossTarget = .{ .os_tag = .macos }; pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); @@ -17,9 +18,7 @@ pub fn build(b: *Builder) void { check.checkStart("cmd LOAD_DYLIB"); check.checkNext("name @rpath/liba.dylib"); - test_step.dependOn(&check.step); - - const run = exe.run(); + const run = check.runAndCompare(); run.cwd = b.pathFromRoot("."); run.expectStdOutEqual("Hello world"); test_step.dependOn(&run.step); @@ -30,7 +29,7 @@ pub fn build(b: *Builder) void { const exe = createScenario(b, mode); exe.search_strategy = .paths_first; - const run = exe.run(); + const run = std.build.EmulatableRunStep.create(b, "run", exe); run.cwd = b.pathFromRoot("."); run.expectStdOutEqual("Hello world"); test_step.dependOn(&run.step); @@ -39,6 +38,7 @@ pub fn build(b: *Builder) void { fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep { const static = b.addStaticLibrary("a", null); + static.setTarget(target); static.setBuildMode(mode); static.addCSourceFile("a.c", &.{}); static.linkLibC(); @@ -48,6 +48,7 @@ fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep { static.install(); const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); + dylib.setTarget(target); dylib.setBuildMode(mode); dylib.addCSourceFile("a.c", &.{}); dylib.linkLibC(); @@ -57,6 +58,7 @@ fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep { dylib.install(); const exe = b.addExecutable("main", null); + exe.setTarget(target); exe.setBuildMode(mode); exe.addCSourceFile("main.c", &.{}); exe.linkSystemLibraryName("a"); diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig index 8da59dcb53282765e7a6d2bb0d8921bdfc82b4ca..3abf48df7af1863b3f0498a8be2659c6d42ca012 100644 --- a/test/link/macho/stack_size/build.zig +++ b/test/link/macho/stack_size/build.zig @@ -8,6 +8,7 @@ pub fn build(b: *Builder) void { test_step.dependOn(b.getInstallStep()); const exe = b.addExecutable("main", null); + exe.setTarget(.{ .os_tag = .macos }); exe.setBuildMode(mode); exe.addCSourceFile("main.c", &.{}); exe.linkLibC(); @@ -17,8 +18,6 @@ pub fn build(b: *Builder) void { check_exe.checkStart("cmd MAIN"); check_exe.checkNext("stacksize 100000000"); - test_step.dependOn(&check_exe.step); - - const run = exe.run(); + const run = check_exe.runAndCompare(); test_step.dependOn(&run.step); } diff --git a/test/link/macho/weak_library/build.zig b/test/link/macho/weak_library/build.zig index f1070f3b2b777b996e2c840613a37b589c238053..8c41e0dfd15726ad7c68cf078f21403a26883112 100644 --- a/test/link/macho/weak_library/build.zig +++ b/test/link/macho/weak_library/build.zig @@ -4,11 +4,13 @@ const LibExeObjectStep = std.build.LibExeObjStep; pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const test_step = b.step("test", "Test the program"); test_step.dependOn(b.getInstallStep()); const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); + dylib.setTarget(target); dylib.setBuildMode(mode); dylib.addCSourceFile("a.c", &.{}); dylib.linkLibC(); @@ -16,6 +18,7 @@ pub fn build(b: *Builder) void { const exe = b.addExecutable("test", null); exe.addCSourceFile("main.c", &[0][]const u8{}); + exe.setTarget(target); exe.setBuildMode(mode); exe.linkLibC(); exe.linkSystemLibraryWeak("a"); @@ -30,9 +33,7 @@ pub fn build(b: *Builder) void { check.checkNext("(undefined) weak external _a (from liba)"); check.checkNext("(undefined) weak external _asStr (from liba)"); - test_step.dependOn(&check.step); - - const run_cmd = exe.run(); + const run_cmd = check.runAndCompare(); run_cmd.expectStdOutEqual("42 42"); test_step.dependOn(&run_cmd.step); } -- 2.54.0 From 34b786fb0f458ff80a030a94d722ffcaef95f7e4 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 10 Jul 2022 18:28:14 +0200 Subject: [PATCH 5/7] enable link-test on linux CI --- ci/zinc/linux_test.sh | 1 + test/link.zig | 128 +++++++++++++++++++++--------------------- 2 files changed, 64 insertions(+), 65 deletions(-) diff --git a/ci/zinc/linux_test.sh b/ci/zinc/linux_test.sh index 611af4adce2e09969cf9fa15b54a51a7a2b00eae..2990287bcc11f8452502a1436aa63f0c957de27a 100755 --- a/ci/zinc/linux_test.sh +++ b/ci/zinc/linux_test.sh @@ -67,6 +67,7 @@ $STAGE1_ZIG build test-cli -fqemu -fwasmtime $STAGE1_ZIG build test-run-translated-c -fqemu -fwasmtime $STAGE1_ZIG build docs -fqemu -fwasmtime $STAGE1_ZIG build test-cases -fqemu -fwasmtime +$STAGE1_ZIG build test-link -fqemu -fwasmtime # Produce the experimental std lib documentation. mkdir -p "$RELEASE_STAGING/docs/std" diff --git a/test/link.zig b/test/link.zig index afab1852eba733a96c885316a4134e8243b32ff1..1e75620919ca2976f45316f29121c97d582ae692 100644 --- a/test/link.zig +++ b/test/link.zig @@ -47,69 +47,67 @@ pub fn addCases(cases: *tests.StandaloneContext) void { .requires_stage2 = true, }); - if (builtin.os.tag == .macos) { - cases.addBuildFile("test/link/macho/entry/build.zig", .{ - .build_modes = true, - }); - - cases.addBuildFile("test/link/macho/pagezero/build.zig", .{ - .build_modes = false, - }); - - cases.addBuildFile("test/link/macho/dylib/build.zig", .{ - .build_modes = true, - }); - - cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{ - .build_modes = false, - }); - - cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); - - cases.addBuildFile("test/link/macho/needed_library/build.zig", .{ - .build_modes = true, - }); - - cases.addBuildFile("test/link/macho/weak_library/build.zig", .{ - .build_modes = true, - }); - - cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); - - cases.addBuildFile("test/link/macho/weak_framework/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); - - // Try to build and run an Objective-C executable. - cases.addBuildFile("test/link/macho/objc/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); - - // Try to build and run an Objective-C++ executable. - cases.addBuildFile("test/link/macho/objcpp/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); - - cases.addBuildFile("test/link/macho/stack_size/build.zig", .{ - .build_modes = true, - }); - - cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{ - .build_modes = true, - }); - - cases.addBuildFile("test/link/macho/headerpad/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); - } + cases.addBuildFile("test/link/macho/entry/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/pagezero/build.zig", .{ + .build_modes = false, + }); + + cases.addBuildFile("test/link/macho/dylib/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{ + .build_modes = false, + }); + + cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + cases.addBuildFile("test/link/macho/needed_library/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/weak_library/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + cases.addBuildFile("test/link/macho/weak_framework/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + // Try to build and run an Objective-C executable. + cases.addBuildFile("test/link/macho/objc/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + // Try to build and run an Objective-C++ executable. + cases.addBuildFile("test/link/macho/objcpp/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + cases.addBuildFile("test/link/macho/stack_size/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/headerpad/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); } -- 2.54.0 From 2429c3d73c0c8fc279a556d65881f7c25c6bb6e9 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Tue, 12 Jul 2022 18:14:32 +0200 Subject: [PATCH 6/7] Share logic between EmulatableRunStep & RunStep --- lib/std/build.zig | 15 ++ lib/std/build/EmulatableRunStep.zig | 217 ++-------------------------- lib/std/build/RunStep.zig | 113 ++++++++++----- 3 files changed, 110 insertions(+), 235 deletions(-) diff --git a/lib/std/build.zig b/lib/std/build.zig index fa778b9eaba50fc249bd928f226c7614dbc7945f..ad6742bb0449f0f4bf3ce737d85f8b0249d8ef8c 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1891,6 +1891,21 @@ pub const LibExeObjStep = struct { return run_step; } + /// Creates an `EmulatableRunStep` with an executable built with `addExecutable`. + /// Allows running foreign binaries through emulation platforms such as Qemu or Rosetta. + /// When a binary cannot be ran through emulation or the option is disabled, a warning + /// will be printed and the binary will *NOT* be ran. + pub fn runEmulatable(exe: *LibExeObjStep) *EmulatableRunStep { + assert(exe.kind == .exe or exe.kind == .text_exe); + + const run_step = EmulatableRunStep.create(exe.builder.fmt("run {s}", .{exe.step.name}), exe); + if (exe.vcpkg_bin_path) |path| { + run_step.addPathDir(path); + } + + return run_step; + } + pub fn checkObject(self: *LibExeObjStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep { return CheckObjectStep.create(self.builder, self.getOutputSource(), obj_format); } diff --git a/lib/std/build/EmulatableRunStep.zig b/lib/std/build/EmulatableRunStep.zig index 9745ebb89fa85f0b539e52b1414e48f3b6886c6c..0479d3a2f0d5f43fd26e1889f1e52f9fb1265403 100644 --- a/lib/std/build/EmulatableRunStep.zig +++ b/lib/std/build/EmulatableRunStep.zig @@ -9,6 +9,7 @@ const build = std.build; const Step = std.build.Step; const Builder = std.build.Builder; const LibExeObjStep = std.build.LibExeObjStep; +const RunStep = std.build.RunStep; const fs = std.fs; const process = std.process; @@ -16,7 +17,7 @@ const EnvMap = process.EnvMap; const EmulatableRunStep = @This(); -pub const step_id = .emulatable_run; +pub const base_id = .emulatable_run; const max_stdout_size = 1 * 1024 * 1024; // 1 MiB @@ -35,20 +36,13 @@ env_map: ?*EnvMap, /// Set this to modify the current working directory cwd: ?[]const u8, -stdout_action: StdIoAction = .inherit, -stderr_action: StdIoAction = .inherit, +stdout_action: RunStep.StdIoAction = .inherit, +stderr_action: RunStep.StdIoAction = .inherit, /// When set to true, hides the warning of skipping a foreign binary which cannot be run on the host /// or through emulation. hide_foreign_binaries_warning: bool, -pub const StdIoAction = union(enum) { - inherit, - ignore, - expect_exact: []const u8, - expect_matches: []const []const u8, -}; - /// Creates a step that will execute the given artifact. This step will allow running the /// binary through emulation when any of the emulation options such as `enable_rosetta` are set to true. /// When set to false, and the binary is foreign, running the executable is skipped. @@ -78,7 +72,6 @@ pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *Em fn make(step: *Step) !void { const self = @fieldParentPtr(EmulatableRunStep, "step", step); const host_info = self.builder.host; - const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root; var argv_list = std.ArrayList([]const u8).init(self.builder.allocator); defer argv_list.deinit(); @@ -131,185 +124,23 @@ fn make(step: *Step) !void { if (self.exe.target.isWindows()) { // On Windows we don't have rpaths so we have to add .dll search paths to PATH - self.addPathForDynLibs(self.exe); + RunStep.addPathForDynLibsInternal(&self.step, self.builder, self.exe); } const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder); try argv_list.append(executable_path); - if (!std.process.can_spawn) { - const cmd = try std.mem.join(self.builder.allocator, " ", argv_list.items); - 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 }); - self.builder.allocator.free(cmd); - return error.ExecNotSupported; - } - - var child = std.ChildProcess.init(argv_list.items, self.builder.allocator); - child.cwd = cwd; - child.env_map = self.env_map orelse self.builder.env_map; - - child.stdin_behavior = .Inherit; - child.stdout_behavior = stdIoActionToBehavior(self.stdout_action); - child.stderr_behavior = stdIoActionToBehavior(self.stderr_action); - - child.spawn() catch |err| { - std.debug.print("Unable to spawn {s}: {s}\n", .{ argv_list.items[0], @errorName(err) }); - return err; - }; - - var stdout: ?[]const u8 = null; - defer if (stdout) |s| self.builder.allocator.free(s); - - switch (self.stdout_action) { - .expect_exact, .expect_matches => { - stdout = child.stdout.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; - }, - .inherit, .ignore => {}, - } - - var stderr: ?[]const u8 = null; - defer if (stderr) |s| self.builder.allocator.free(s); - - switch (self.stderr_action) { - .expect_exact, .expect_matches => { - stderr = child.stderr.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; - }, - .inherit, .ignore => {}, - } - - const term = child.wait() catch |err| { - std.debug.print("Unable to spawn {s}: {s}\n", .{ argv_list.items[0], @errorName(err) }); - return err; - }; - - switch (term) { - .Exited => |code| blk: { - const expected_exit_code = self.expected_exit_code orelse break :blk; - - if (code != expected_exit_code) { - if (self.builder.prominent_compile_errors) { - std.debug.print("Run step exited with error code {} (expected {})\n", .{ - code, - expected_exit_code, - }); - } else { - std.debug.print("The following command exited with error code {} (expected {}):\n", .{ - code, - expected_exit_code, - }); - printCmd(cwd, argv_list.items); - } - - return error.UnexpectedExitCode; - } - }, - else => { - std.debug.print("The following command terminated unexpectedly:\n", .{}); - printCmd(cwd, argv_list.items); - return error.UncleanExit; - }, - } - - switch (self.stderr_action) { - .inherit, .ignore => {}, - .expect_exact => |expected_bytes| { - if (!std.mem.eql(u8, expected_bytes, stderr.?)) { - std.debug.print( - \\ - \\========= Expected this stderr: ========= - \\{s} - \\========= But found: ==================== - \\{s} - \\ - , .{ expected_bytes, stderr.? }); - printCmd(cwd, argv_list.items); - return error.TestFailed; - } - }, - .expect_matches => |matches| for (matches) |match| { - if (std.mem.indexOf(u8, stderr.?, match) == null) { - std.debug.print( - \\ - \\========= Expected to find in stderr: ========= - \\{s} - \\========= But stderr does not contain it: ===== - \\{s} - \\ - , .{ match, stderr.? }); - printCmd(cwd, argv_list.items); - return error.TestFailed; - } - }, - } - - switch (self.stdout_action) { - .inherit, .ignore => {}, - .expect_exact => |expected_bytes| { - if (!std.mem.eql(u8, expected_bytes, stdout.?)) { - std.debug.print( - \\ - \\========= Expected this stdout: ========= - \\{s} - \\========= But found: ==================== - \\{s} - \\ - , .{ expected_bytes, stdout.? }); - printCmd(cwd, argv_list.items); - return error.TestFailed; - } - }, - .expect_matches => |matches| for (matches) |match| { - if (std.mem.indexOf(u8, stdout.?, match) == null) { - std.debug.print( - \\ - \\========= Expected to find in stdout: ========= - \\{s} - \\========= But stdout does not contain it: ===== - \\{s} - \\ - , .{ match, stdout.? }); - printCmd(cwd, argv_list.items); - return error.TestFailed; - } - }, - } -} - -fn addPathForDynLibs(self: *EmulatableRunStep, artifact: *LibExeObjStep) void { - for (artifact.link_objects.items) |link_object| { - switch (link_object) { - .other_step => |other| { - if (other.target.isWindows() and other.isDynamicLibrary()) { - self.addPathDir(fs.path.dirname(other.getOutputSource().getPath(self.builder)).?); - self.addPathForDynLibs(other); - } - }, - else => {}, - } - } -} - -pub fn addPathDir(self: *EmulatableRunStep, search_path: []const u8) void { - const env_map = self.getEnvMap(); - - const key = "PATH"; - var prev_path = env_map.get(key); - - if (prev_path) |pp| { - const new_path = self.builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); - env_map.put(key, new_path) catch unreachable; - } else { - env_map.put(key, self.builder.dupePath(search_path)) catch unreachable; - } -} - -pub fn getEnvMap(self: *EmulatableRunStep) *EnvMap { - return self.env_map orelse { - const env_map = self.builder.allocator.create(EnvMap) catch unreachable; - env_map.* = process.getEnvMap(self.builder.allocator) catch unreachable; - self.env_map = env_map; - return env_map; - }; + try RunStep.runCommand( + argv_list.items, + self.builder, + self.expected_exit_code, + self.stdout_action, + self.stderr_action, + .Inherit, + self.env_map, + self.cwd, + false, + ); } pub fn expectStdErrEqual(self: *EmulatableRunStep, bytes: []const u8) void { @@ -320,22 +151,6 @@ pub fn expectStdOutEqual(self: *EmulatableRunStep, bytes: []const u8) void { self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) }; } -fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo { - return switch (action) { - .ignore => .Ignore, - .inherit => .Inherit, - .expect_exact, .expect_matches => .Pipe, - }; -} - -fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void { - if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd}); - for (argv) |arg| { - std.debug.print("{s} ", .{arg}); - } - std.debug.print("\n", .{}); -} - fn warnAboutForeignBinaries(step: *EmulatableRunStep) void { if (step.hide_foreign_binaries_warning) return; const builder = step.builder; diff --git a/lib/std/build/RunStep.zig b/lib/std/build/RunStep.zig index 0b8c233bfa713e0d23f22dc16d5b22a1360cb46f..1e22fd10a3f668cacf7343d66bf680d011c93ed7 100644 --- a/lib/std/build/RunStep.zig +++ b/lib/std/build/RunStep.zig @@ -97,24 +97,42 @@ pub fn clearEnvironment(self: *RunStep) void { } pub fn addPathDir(self: *RunStep, search_path: []const u8) void { - const env_map = self.getEnvMap(); + addPathDirInternal(&self.step, self.builder, search_path); +} + +/// For internal use only, users of `RunStep` should use `addPathDir` directly. +fn addPathDirInternal(step: *Step, builder: *Builder, search_path: []const u8) void { + const env_map = getEnvMapInternal(step, builder.allocator); const key = "PATH"; var prev_path = env_map.get(key); if (prev_path) |pp| { - const new_path = self.builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); + const new_path = builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); env_map.put(key, new_path) catch unreachable; } else { - env_map.put(key, self.builder.dupePath(search_path)) catch unreachable; + env_map.put(key, builder.dupePath(search_path)) catch unreachable; } } pub fn getEnvMap(self: *RunStep) *EnvMap { - return self.env_map orelse { - const env_map = self.builder.allocator.create(EnvMap) catch unreachable; - env_map.* = process.getEnvMap(self.builder.allocator) catch unreachable; - self.env_map = env_map; + return getEnvMapInternal(&self.step, self.builder.allocator); +} + +fn getEnvMapInternal(step: *Step, allocator: Allocator) *EnvMap { + const maybe_env_map = switch (step.id) { + .run => step.cast(RunStep).?.env_map, + .emulatable_run => step.cast(build.EmulatableRunStep).?.env_map, + else => unreachable, + }; + return maybe_env_map orelse { + const env_map = allocator.create(EnvMap) catch unreachable; + env_map.* = process.getEnvMap(allocator) catch unreachable; + switch (step.id) { + .run => step.cast(RunStep).?.env_map = env_map, + .emulatable_run => step.cast(RunStep).?.env_map = env_map, + else => unreachable, + } return env_map; }; } @@ -146,10 +164,7 @@ fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo { fn make(step: *Step) !void { const self = @fieldParentPtr(RunStep, "step", step); - const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root; - var argv_list = ArrayList([]const u8).init(self.builder.allocator); - for (self.argv.items) |arg| { switch (arg) { .bytes => |bytes| try argv_list.append(bytes), @@ -165,24 +180,48 @@ fn make(step: *Step) !void { } } - const argv = argv_list.items; + try runCommand( + argv_list.items, + self.builder, + self.expected_exit_code, + self.stdout_action, + self.stderr_action, + self.stdin_behavior, + self.env_map, + self.cwd, + self.print, + ); +} + +pub fn runCommand( + argv: []const []const u8, + builder: *Builder, + expected_exit_code: ?u8, + stdout_action: StdIoAction, + stderr_action: StdIoAction, + stdin_behavior: std.ChildProcess.StdIo, + env_map: ?*EnvMap, + maybe_cwd: ?[]const u8, + print: bool, +) !void { + const cwd = if (maybe_cwd) |cwd| builder.pathFromRoot(cwd) else builder.build_root; if (!std.process.can_spawn) { - const cmd = try std.mem.join(self.builder.allocator, " ", argv); + const cmd = try std.mem.join(builder.addInstallDirectory, " ", argv); std.debug.print("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(builtin.os.tag), cmd }); - self.builder.allocator.free(cmd); + builder.allocator.free(cmd); return ExecError.ExecNotSupported; } - var child = std.ChildProcess.init(argv, self.builder.allocator); + var child = std.ChildProcess.init(argv, builder.allocator); child.cwd = cwd; - child.env_map = self.env_map orelse self.builder.env_map; + child.env_map = env_map orelse builder.env_map; - child.stdin_behavior = self.stdin_behavior; - child.stdout_behavior = stdIoActionToBehavior(self.stdout_action); - child.stderr_behavior = stdIoActionToBehavior(self.stderr_action); + child.stdin_behavior = stdin_behavior; + child.stdout_behavior = stdIoActionToBehavior(stdout_action); + child.stderr_behavior = stdIoActionToBehavior(stderr_action); - if (self.print) + if (print) printCmd(cwd, argv); child.spawn() catch |err| { @@ -193,21 +232,21 @@ fn make(step: *Step) !void { // TODO need to poll to read these streams to prevent a deadlock (or rely on evented I/O). var stdout: ?[]const u8 = null; - defer if (stdout) |s| self.builder.allocator.free(s); + defer if (stdout) |s| builder.allocator.free(s); - switch (self.stdout_action) { + switch (stdout_action) { .expect_exact, .expect_matches => { - stdout = child.stdout.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; + stdout = child.stdout.?.reader().readAllAlloc(builder.allocator, max_stdout_size) catch unreachable; }, .inherit, .ignore => {}, } var stderr: ?[]const u8 = null; - defer if (stderr) |s| self.builder.allocator.free(s); + defer if (stderr) |s| builder.allocator.free(s); - switch (self.stderr_action) { + switch (stderr_action) { .expect_exact, .expect_matches => { - stderr = child.stderr.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; + stderr = child.stderr.?.reader().readAllAlloc(builder.allocator, max_stdout_size) catch unreachable; }, .inherit, .ignore => {}, } @@ -219,18 +258,18 @@ fn make(step: *Step) !void { switch (term) { .Exited => |code| blk: { - const expected_exit_code = self.expected_exit_code orelse break :blk; + const expected_code = expected_exit_code orelse break :blk; - if (code != expected_exit_code) { - if (self.builder.prominent_compile_errors) { + if (code != expected_code) { + if (builder.prominent_compile_errors) { std.debug.print("Run step exited with error code {} (expected {})\n", .{ code, - expected_exit_code, + expected_code, }); } else { std.debug.print("The following command exited with error code {} (expected {}):\n", .{ code, - expected_exit_code, + expected_code, }); printCmd(cwd, argv); } @@ -245,7 +284,7 @@ fn make(step: *Step) !void { }, } - switch (self.stderr_action) { + switch (stderr_action) { .inherit, .ignore => {}, .expect_exact => |expected_bytes| { if (!mem.eql(u8, expected_bytes, stderr.?)) { @@ -277,7 +316,7 @@ fn make(step: *Step) !void { }, } - switch (self.stdout_action) { + switch (stdout_action) { .inherit, .ignore => {}, .expect_exact => |expected_bytes| { if (!mem.eql(u8, expected_bytes, stdout.?)) { @@ -319,12 +358,18 @@ fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void { } fn addPathForDynLibs(self: *RunStep, artifact: *LibExeObjStep) void { + addPathForDynLibsInternal(&self.step, self.builder, artifact); +} + +/// This should only be used for internal usage, this is called automatically +/// for the user. +pub fn addPathForDynLibsInternal(step: *Step, builder: *Builder, artifact: *LibExeObjStep) void { for (artifact.link_objects.items) |link_object| { switch (link_object) { .other_step => |other| { if (other.target.isWindows() and other.isDynamicLibrary()) { - self.addPathDir(fs.path.dirname(other.getOutputSource().getPath(self.builder)).?); - self.addPathForDynLibs(other); + addPathDirInternal(step, builder, fs.path.dirname(other.getOutputSource().getPath(builder)).?); + addPathForDynLibsInternal(step, builder, other); } }, else => {}, -- 2.54.0 From 72c0cebe5c5e6954ae9992d36f8164ae9433df9e Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sat, 23 Jul 2022 17:27:47 +0200 Subject: [PATCH 7/7] test/link/macho: use EmulationStep for dead_strip --- test/link/macho/dead_strip/build.zig | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/link/macho/dead_strip/build.zig b/test/link/macho/dead_strip/build.zig index 5b063308b5b0ec08eec2434b09aeb4edf95610a8..dea225dd6fd72715d7364210a31cfcd03e12504c 100644 --- a/test/link/macho/dead_strip/build.zig +++ b/test/link/macho/dead_strip/build.zig @@ -16,9 +16,7 @@ pub fn build(b: *Builder) void { check.checkInSymtab(); check.checkNext("{*} (__TEXT,__text) external _iAmUnused"); - test_step.dependOn(&check.step); - - const run_cmd = exe.run(); + const run_cmd = check.runAndCompare(); run_cmd.expectStdOutEqual("Hello!\n"); test_step.dependOn(&run_cmd.step); } @@ -32,9 +30,7 @@ pub fn build(b: *Builder) void { check.checkInSymtab(); check.checkNotPresent("{*} (__TEXT,__text) external _iAmUnused"); - test_step.dependOn(&check.step); - - const run_cmd = exe.run(); + const run_cmd = check.runAndCompare(); run_cmd.expectStdOutEqual("Hello!\n"); test_step.dependOn(&run_cmd.step); } -- 2.54.0