aboutsummaryrefslogtreecommitdiff
path: root/lib/std/Build/RunStep.zig
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std/Build/RunStep.zig')
-rw-r--r--lib/std/Build/RunStep.zig376
1 files changed, 376 insertions, 0 deletions
diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig
new file mode 100644
index 0000000000..07f2363623
--- /dev/null
+++ b/lib/std/Build/RunStep.zig
@@ -0,0 +1,376 @@
+const std = @import("../std.zig");
+const builtin = @import("builtin");
+const Step = std.Build.Step;
+const CompileStep = std.Build.CompileStep;
+const WriteFileStep = std.Build.WriteFileStep;
+const fs = std.fs;
+const mem = std.mem;
+const process = std.process;
+const ArrayList = std.ArrayList;
+const EnvMap = process.EnvMap;
+const Allocator = mem.Allocator;
+const ExecError = std.Build.ExecError;
+
+const max_stdout_size = 1 * 1024 * 1024; // 1 MiB
+
+const RunStep = @This();
+
+pub const base_id: Step.Id = .run;
+
+step: Step,
+builder: *std.Build,
+
+/// See also addArg and addArgs to modifying this directly
+argv: ArrayList(Arg),
+
+/// Set this to modify the current working directory
+cwd: ?[]const u8,
+
+/// Override this field to modify the environment, or use setEnvironmentVariable
+env_map: ?*EnvMap,
+
+stdout_action: StdIoAction = .inherit,
+stderr_action: StdIoAction = .inherit,
+
+stdin_behavior: std.ChildProcess.StdIo = .Inherit,
+
+/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
+expected_exit_code: ?u8 = 0,
+
+/// Print the command before running it
+print: bool,
+
+pub const StdIoAction = union(enum) {
+ inherit,
+ ignore,
+ expect_exact: []const u8,
+ expect_matches: []const []const u8,
+};
+
+pub const Arg = union(enum) {
+ artifact: *CompileStep,
+ file_source: std.Build.FileSource,
+ bytes: []u8,
+};
+
+pub fn create(builder: *std.Build, name: []const u8) *RunStep {
+ const self = builder.allocator.create(RunStep) catch @panic("OOM");
+ self.* = RunStep{
+ .builder = builder,
+ .step = Step.init(base_id, name, builder.allocator, make),
+ .argv = ArrayList(Arg).init(builder.allocator),
+ .cwd = null,
+ .env_map = null,
+ .print = builder.verbose,
+ };
+ return self;
+}
+
+pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void {
+ self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM");
+ self.step.dependOn(&artifact.step);
+}
+
+pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void {
+ self.argv.append(Arg{
+ .file_source = file_source.dupe(self.builder),
+ }) catch @panic("OOM");
+ file_source.addStepDependencies(&self.step);
+}
+
+pub fn addArg(self: *RunStep, arg: []const u8) void {
+ self.argv.append(Arg{ .bytes = self.builder.dupe(arg) }) catch @panic("OOM");
+}
+
+pub fn addArgs(self: *RunStep, args: []const []const u8) void {
+ for (args) |arg| {
+ self.addArg(arg);
+ }
+}
+
+pub fn clearEnvironment(self: *RunStep) void {
+ const new_env_map = self.builder.allocator.create(EnvMap) catch @panic("OOM");
+ new_env_map.* = EnvMap.init(self.builder.allocator);
+ self.env_map = new_env_map;
+}
+
+pub fn addPathDir(self: *RunStep, search_path: []const u8) void {
+ addPathDirInternal(&self.step, self.builder, search_path);
+}
+
+/// For internal use only, users of `RunStep` should use `addPathDir` directly.
+pub fn addPathDirInternal(step: *Step, builder: *std.Build, 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 = builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path });
+ env_map.put(key, new_path) catch @panic("OOM");
+ } else {
+ env_map.put(key, builder.dupePath(search_path)) catch @panic("OOM");
+ }
+}
+
+pub fn getEnvMap(self: *RunStep) *EnvMap {
+ 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(std.Build.EmulatableRunStep).?.env_map,
+ else => unreachable,
+ };
+ return maybe_env_map orelse {
+ const env_map = allocator.create(EnvMap) catch @panic("OOM");
+ env_map.* = process.getEnvMap(allocator) catch @panic("unhandled error");
+ 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;
+ };
+}
+
+pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void {
+ const env_map = self.getEnvMap();
+ env_map.put(
+ self.builder.dupe(key),
+ self.builder.dupe(value),
+ ) catch @panic("unhandled error");
+}
+
+pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void {
+ self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) };
+}
+
+pub fn expectStdOutEqual(self: *RunStep, 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 make(step: *Step) !void {
+ const self = @fieldParentPtr(RunStep, "step", step);
+
+ var argv_list = ArrayList([]const u8).init(self.builder.allocator);
+ for (self.argv.items) |arg| {
+ switch (arg) {
+ .bytes => |bytes| try argv_list.append(bytes),
+ .file_source => |file| try argv_list.append(file.getPath(self.builder)),
+ .artifact => |artifact| {
+ if (artifact.target.isWindows()) {
+ // On Windows we don't have rpaths so we have to add .dll search paths to PATH
+ self.addPathForDynLibs(artifact);
+ }
+ const executable_path = artifact.installed_path orelse artifact.getOutputSource().getPath(self.builder);
+ try argv_list.append(executable_path);
+ },
+ }
+ }
+
+ 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: *std.Build,
+ 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(builder.allocator, " ", 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 });
+ builder.allocator.free(cmd);
+ return ExecError.ExecNotSupported;
+ }
+
+ var child = std.ChildProcess.init(argv, builder.allocator);
+ child.cwd = cwd;
+ child.env_map = env_map orelse builder.env_map;
+
+ child.stdin_behavior = stdin_behavior;
+ child.stdout_behavior = stdIoActionToBehavior(stdout_action);
+ child.stderr_behavior = stdIoActionToBehavior(stderr_action);
+
+ if (print)
+ printCmd(cwd, argv);
+
+ child.spawn() catch |err| {
+ std.debug.print("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) });
+ return err;
+ };
+
+ // 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| builder.allocator.free(s);
+
+ switch (stdout_action) {
+ .expect_exact, .expect_matches => {
+ stdout = try child.stdout.?.reader().readAllAlloc(builder.allocator, max_stdout_size);
+ },
+ .inherit, .ignore => {},
+ }
+
+ var stderr: ?[]const u8 = null;
+ defer if (stderr) |s| builder.allocator.free(s);
+
+ switch (stderr_action) {
+ .expect_exact, .expect_matches => {
+ stderr = try child.stderr.?.reader().readAllAlloc(builder.allocator, max_stdout_size);
+ },
+ .inherit, .ignore => {},
+ }
+
+ const term = child.wait() catch |err| {
+ std.debug.print("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) });
+ return err;
+ };
+
+ switch (term) {
+ .Exited => |code| blk: {
+ const expected_code = expected_exit_code orelse break :blk;
+
+ if (code != expected_code) {
+ if (builder.prominent_compile_errors) {
+ std.debug.print("Run step exited with error code {} (expected {})\n", .{
+ code,
+ expected_code,
+ });
+ } else {
+ std.debug.print("The following command exited with error code {} (expected {}):\n", .{
+ code,
+ expected_code,
+ });
+ printCmd(cwd, argv);
+ }
+
+ return error.UnexpectedExitCode;
+ }
+ },
+ else => {
+ std.debug.print("The following command terminated unexpectedly:\n", .{});
+ printCmd(cwd, argv);
+ return error.UncleanExit;
+ },
+ }
+
+ switch (stderr_action) {
+ .inherit, .ignore => {},
+ .expect_exact => |expected_bytes| {
+ if (!mem.eql(u8, expected_bytes, stderr.?)) {
+ std.debug.print(
+ \\
+ \\========= Expected this stderr: =========
+ \\{s}
+ \\========= But found: ====================
+ \\{s}
+ \\
+ , .{ expected_bytes, stderr.? });
+ printCmd(cwd, argv);
+ return error.TestFailed;
+ }
+ },
+ .expect_matches => |matches| for (matches) |match| {
+ if (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);
+ return error.TestFailed;
+ }
+ },
+ }
+
+ switch (stdout_action) {
+ .inherit, .ignore => {},
+ .expect_exact => |expected_bytes| {
+ if (!mem.eql(u8, expected_bytes, stdout.?)) {
+ std.debug.print(
+ \\
+ \\========= Expected this stdout: =========
+ \\{s}
+ \\========= But found: ====================
+ \\{s}
+ \\
+ , .{ expected_bytes, stdout.? });
+ printCmd(cwd, argv);
+ return error.TestFailed;
+ }
+ },
+ .expect_matches => |matches| for (matches) |match| {
+ if (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);
+ return error.TestFailed;
+ }
+ },
+ }
+}
+
+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 addPathForDynLibs(self: *RunStep, artifact: *CompileStep) 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: *std.Build, artifact: *CompileStep) void {
+ for (artifact.link_objects.items) |link_object| {
+ switch (link_object) {
+ .other_step => |other| {
+ if (other.target.isWindows() and other.isDynamicLibrary()) {
+ addPathDirInternal(step, builder, fs.path.dirname(other.getOutputSource().getPath(builder)).?);
+ addPathForDynLibsInternal(step, builder, other);
+ }
+ },
+ else => {},
+ }
+ }
+}