From 2429c3d73c0c8fc279a556d65881f7c25c6bb6e9 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Tue, 12 Jul 2022 18:14:32 +0200 Subject: [PATCH] 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 => {},