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 83e30b278f..d338aef4fb 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 b807e1de45..be1e741ce6 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 0000000000..4e6396e010 --- /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; + }; +} 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 4e6396e010..635f469b39 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 }, + ); + }, + } +} 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 d338aef4fb..fa778b9eab 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 be1e741ce6..0525bbf034 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 635f469b39..1f36401599 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; 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 1f36401599..9745ebb89f 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 1587def9b8..a5baf255c6 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 1f40b3d8e0..0ecca14aa2 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 708a09dc32..a314fd2201 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 e41fd48e71..d7fd872f77 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 e858d1f4d8..9dbc0e6473 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 c5e867c8d0..39a82bc6a7 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 8da59dcb53..3abf48df7a 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 f1070f3b2b..8c41e0dfd1 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); } 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 | 102 +++++++++++++++++++++--------------------- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/ci/zinc/linux_test.sh b/ci/zinc/linux_test.sh index 611af4adce..2990287bcc 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 afab1852eb..1e75620919 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/entry/build.zig", .{ + .build_modes = true, + }); - cases.addBuildFile("test/link/macho/pagezero/build.zig", .{ - .build_modes = false, - }); + 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/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/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/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/needed_library/build.zig", .{ + .build_modes = true, + }); - cases.addBuildFile("test/link/macho/weak_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/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, - }); + 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/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, - }); + // 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/stack_size/build.zig", .{ + .build_modes = true, + }); - cases.addBuildFile("test/link/macho/search_strategy/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/headerpad/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); } 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 fa778b9eab..ad6742bb04 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 9745ebb89f..0479d3a2f0 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 0b8c233bfa..1e22fd10a3 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 => {}, 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 5b063308b5..dea225dd6f 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); }