| ... | ... | @@ -1,327 +0,0 @@ |
| 1 | | // SPDX-License-Identifier: MIT |
| 2 | | // Copyright (c) 2015-2021 Zig Contributors |
| 3 | | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. |
| 4 | | // The MIT license requires this copyright notice to be included in all copies |
| 5 | | // and substantial portions of the software. |
| 6 | | const std = @import("../std.zig"); |
| 7 | | const builtin = std.builtin; |
| 8 | | const build = std.build; |
| 9 | | const Step = build.Step; |
| 10 | | const Builder = build.Builder; |
| 11 | | const LibExeObjStep = build.LibExeObjStep; |
| 12 | | const WriteFileStep = build.WriteFileStep; |
| 13 | | const fs = std.fs; |
| 14 | | const mem = std.mem; |
| 15 | | const process = std.process; |
| 16 | | const ArrayList = std.ArrayList; |
| 17 | | const BufMap = std.BufMap; |
| 18 | | const warn = std.debug.warn; |
| 19 | | |
| 20 | | const max_stdout_size = 1 * 1024 * 1024; // 1 MiB |
| 21 | | |
| 22 | | pub const RunStep = struct { |
| 23 | | step: Step, |
| 24 | | builder: *Builder, |
| 25 | | |
| 26 | | /// See also addArg and addArgs to modifying this directly |
| 27 | | argv: ArrayList(Arg), |
| 28 | | |
| 29 | | /// Set this to modify the current working directory |
| 30 | | cwd: ?[]const u8, |
| 31 | | |
| 32 | | /// Override this field to modify the environment, or use setEnvironmentVariable |
| 33 | | env_map: ?*BufMap, |
| 34 | | |
| 35 | | stdout_action: StdIoAction = .inherit, |
| 36 | | stderr_action: StdIoAction = .inherit, |
| 37 | | |
| 38 | | stdin_behavior: std.ChildProcess.StdIo = .Inherit, |
| 39 | | |
| 40 | | expected_exit_code: u8 = 0, |
| 41 | | |
| 42 | | pub const StdIoAction = union(enum) { |
| 43 | | inherit, |
| 44 | | ignore, |
| 45 | | expect_exact: []const u8, |
| 46 | | expect_matches: []const []const u8, |
| 47 | | }; |
| 48 | | |
| 49 | | pub const Arg = union(enum) { |
| 50 | | artifact: *LibExeObjStep, |
| 51 | | file_source: build.FileSource, |
| 52 | | bytes: []u8, |
| 53 | | }; |
| 54 | | |
| 55 | | pub fn create(builder: *Builder, name: []const u8) *RunStep { |
| 56 | | const self = builder.allocator.create(RunStep) catch unreachable; |
| 57 | | self.* = RunStep{ |
| 58 | | .builder = builder, |
| 59 | | .step = Step.init(.Run, name, builder.allocator, make), |
| 60 | | .argv = ArrayList(Arg).init(builder.allocator), |
| 61 | | .cwd = null, |
| 62 | | .env_map = null, |
| 63 | | }; |
| 64 | | return self; |
| 65 | | } |
| 66 | | |
| 67 | | pub fn addArtifactArg(self: *RunStep, artifact: *LibExeObjStep) void { |
| 68 | | self.argv.append(Arg{ .artifact = artifact }) catch unreachable; |
| 69 | | self.step.dependOn(&artifact.step); |
| 70 | | } |
| 71 | | |
| 72 | | pub fn addFileSourceArg(self: *RunStep, file_source: build.FileSource) void { |
| 73 | | self.argv.append(Arg{ |
| 74 | | .file_source = file_source.dupe(self.builder), |
| 75 | | }) catch unreachable; |
| 76 | | file_source.addStepDependencies(&self.step); |
| 77 | | } |
| 78 | | |
| 79 | | pub fn addArg(self: *RunStep, arg: []const u8) void { |
| 80 | | self.argv.append(Arg{ .bytes = self.builder.dupe(arg) }) catch unreachable; |
| 81 | | } |
| 82 | | |
| 83 | | pub fn addArgs(self: *RunStep, args: []const []const u8) void { |
| 84 | | for (args) |arg| { |
| 85 | | self.addArg(arg); |
| 86 | | } |
| 87 | | } |
| 88 | | |
| 89 | | pub fn clearEnvironment(self: *RunStep) void { |
| 90 | | const new_env_map = self.builder.allocator.create(BufMap) catch unreachable; |
| 91 | | new_env_map.* = BufMap.init(self.builder.allocator); |
| 92 | | self.env_map = new_env_map; |
| 93 | | } |
| 94 | | |
| 95 | | pub fn addPathDir(self: *RunStep, search_path: []const u8) void { |
| 96 | | const env_map = self.getEnvMap(); |
| 97 | | |
| 98 | | var key: []const u8 = undefined; |
| 99 | | var prev_path: ?[]const u8 = undefined; |
| 100 | | if (builtin.os.tag == .windows) { |
| 101 | | key = "Path"; |
| 102 | | prev_path = env_map.get(key); |
| 103 | | if (prev_path == null) { |
| 104 | | key = "PATH"; |
| 105 | | prev_path = env_map.get(key); |
| 106 | | } |
| 107 | | } else { |
| 108 | | key = "PATH"; |
| 109 | | prev_path = env_map.get(key); |
| 110 | | } |
| 111 | | |
| 112 | | if (prev_path) |pp| { |
| 113 | | const new_path = self.builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); |
| 114 | | env_map.put(key, new_path) catch unreachable; |
| 115 | | } else { |
| 116 | | env_map.put(key, self.builder.dupePath(search_path)) catch unreachable; |
| 117 | | } |
| 118 | | } |
| 119 | | |
| 120 | | pub fn getEnvMap(self: *RunStep) *BufMap { |
| 121 | | return self.env_map orelse { |
| 122 | | const env_map = self.builder.allocator.create(BufMap) catch unreachable; |
| 123 | | env_map.* = process.getEnvMap(self.builder.allocator) catch unreachable; |
| 124 | | self.env_map = env_map; |
| 125 | | return env_map; |
| 126 | | }; |
| 127 | | } |
| 128 | | |
| 129 | | pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void { |
| 130 | | const env_map = self.getEnvMap(); |
| 131 | | // Note: no need to dupe these strings because BufMap does it internally. |
| 132 | | env_map.put(key, value) catch unreachable; |
| 133 | | } |
| 134 | | |
| 135 | | pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void { |
| 136 | | self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) }; |
| 137 | | } |
| 138 | | |
| 139 | | pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void { |
| 140 | | self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) }; |
| 141 | | } |
| 142 | | |
| 143 | | fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo { |
| 144 | | return switch (action) { |
| 145 | | .ignore => .Ignore, |
| 146 | | .inherit => .Inherit, |
| 147 | | .expect_exact, .expect_matches => .Pipe, |
| 148 | | }; |
| 149 | | } |
| 150 | | |
| 151 | | fn make(step: *Step) !void { |
| 152 | | const self = @fieldParentPtr(RunStep, "step", step); |
| 153 | | |
| 154 | | const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root; |
| 155 | | |
| 156 | | var argv_list = ArrayList([]const u8).init(self.builder.allocator); |
| 157 | | for (self.argv.items) |arg| { |
| 158 | | switch (arg) { |
| 159 | | .bytes => |bytes| try argv_list.append(bytes), |
| 160 | | .file_source => |file| try argv_list.append(file.getPath(self.builder)), |
| 161 | | .artifact => |artifact| { |
| 162 | | if (artifact.target.isWindows()) { |
| 163 | | // On Windows we don't have rpaths so we have to add .dll search paths to PATH |
| 164 | | self.addPathForDynLibs(artifact); |
| 165 | | } |
| 166 | | const executable_path = artifact.installed_path orelse artifact.getOutputPath(); |
| 167 | | try argv_list.append(executable_path); |
| 168 | | }, |
| 169 | | } |
| 170 | | } |
| 171 | | |
| 172 | | const argv = argv_list.items; |
| 173 | | |
| 174 | | const child = std.ChildProcess.init(argv, self.builder.allocator) catch unreachable; |
| 175 | | defer child.deinit(); |
| 176 | | |
| 177 | | child.cwd = cwd; |
| 178 | | child.env_map = self.env_map orelse self.builder.env_map; |
| 179 | | |
| 180 | | child.stdin_behavior = self.stdin_behavior; |
| 181 | | child.stdout_behavior = stdIoActionToBehavior(self.stdout_action); |
| 182 | | child.stderr_behavior = stdIoActionToBehavior(self.stderr_action); |
| 183 | | |
| 184 | | if (self.builder.verbose) { |
| 185 | | for (argv) |arg| { |
| 186 | | warn("{s} ", .{arg}); |
| 187 | | } |
| 188 | | warn("\n", .{}); |
| 189 | | } |
| 190 | | |
| 191 | | child.spawn() catch |err| { |
| 192 | | warn("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) }); |
| 193 | | return err; |
| 194 | | }; |
| 195 | | |
| 196 | | // TODO need to poll to read these streams to prevent a deadlock (or rely on evented I/O). |
| 197 | | |
| 198 | | var stdout: ?[]const u8 = null; |
| 199 | | defer if (stdout) |s| self.builder.allocator.free(s); |
| 200 | | |
| 201 | | switch (self.stdout_action) { |
| 202 | | .expect_exact, .expect_matches => { |
| 203 | | stdout = child.stdout.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; |
| 204 | | }, |
| 205 | | .inherit, .ignore => {}, |
| 206 | | } |
| 207 | | |
| 208 | | var stderr: ?[]const u8 = null; |
| 209 | | defer if (stderr) |s| self.builder.allocator.free(s); |
| 210 | | |
| 211 | | switch (self.stderr_action) { |
| 212 | | .expect_exact, .expect_matches => { |
| 213 | | stderr = child.stderr.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; |
| 214 | | }, |
| 215 | | .inherit, .ignore => {}, |
| 216 | | } |
| 217 | | |
| 218 | | const term = child.wait() catch |err| { |
| 219 | | warn("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) }); |
| 220 | | return err; |
| 221 | | }; |
| 222 | | |
| 223 | | switch (term) { |
| 224 | | .Exited => |code| { |
| 225 | | if (code != self.expected_exit_code) { |
| 226 | | warn("The following command exited with error code {} (expected {}):\n", .{ |
| 227 | | code, |
| 228 | | self.expected_exit_code, |
| 229 | | }); |
| 230 | | printCmd(cwd, argv); |
| 231 | | return error.UncleanExit; |
| 232 | | } |
| 233 | | }, |
| 234 | | else => { |
| 235 | | warn("The following command terminated unexpectedly:\n", .{}); |
| 236 | | printCmd(cwd, argv); |
| 237 | | return error.UncleanExit; |
| 238 | | }, |
| 239 | | } |
| 240 | | |
| 241 | | switch (self.stderr_action) { |
| 242 | | .inherit, .ignore => {}, |
| 243 | | .expect_exact => |expected_bytes| { |
| 244 | | if (!mem.eql(u8, expected_bytes, stderr.?)) { |
| 245 | | warn( |
| 246 | | \\ |
| 247 | | \\========= Expected this stderr: ========= |
| 248 | | \\{s} |
| 249 | | \\========= But found: ==================== |
| 250 | | \\{s} |
| 251 | | \\ |
| 252 | | , .{ expected_bytes, stderr.? }); |
| 253 | | printCmd(cwd, argv); |
| 254 | | return error.TestFailed; |
| 255 | | } |
| 256 | | }, |
| 257 | | .expect_matches => |matches| for (matches) |match| { |
| 258 | | if (mem.indexOf(u8, stderr.?, match) == null) { |
| 259 | | warn( |
| 260 | | \\ |
| 261 | | \\========= Expected to find in stderr: ========= |
| 262 | | \\{s} |
| 263 | | \\========= But stderr does not contain it: ===== |
| 264 | | \\{s} |
| 265 | | \\ |
| 266 | | , .{ match, stderr.? }); |
| 267 | | printCmd(cwd, argv); |
| 268 | | return error.TestFailed; |
| 269 | | } |
| 270 | | }, |
| 271 | | } |
| 272 | | |
| 273 | | switch (self.stdout_action) { |
| 274 | | .inherit, .ignore => {}, |
| 275 | | .expect_exact => |expected_bytes| { |
| 276 | | if (!mem.eql(u8, expected_bytes, stdout.?)) { |
| 277 | | warn( |
| 278 | | \\ |
| 279 | | \\========= Expected this stdout: ========= |
| 280 | | \\{s} |
| 281 | | \\========= But found: ==================== |
| 282 | | \\{s} |
| 283 | | \\ |
| 284 | | , .{ expected_bytes, stdout.? }); |
| 285 | | printCmd(cwd, argv); |
| 286 | | return error.TestFailed; |
| 287 | | } |
| 288 | | }, |
| 289 | | .expect_matches => |matches| for (matches) |match| { |
| 290 | | if (mem.indexOf(u8, stdout.?, match) == null) { |
| 291 | | warn( |
| 292 | | \\ |
| 293 | | \\========= Expected to find in stdout: ========= |
| 294 | | \\{s} |
| 295 | | \\========= But stdout does not contain it: ===== |
| 296 | | \\{s} |
| 297 | | \\ |
| 298 | | , .{ match, stdout.? }); |
| 299 | | printCmd(cwd, argv); |
| 300 | | return error.TestFailed; |
| 301 | | } |
| 302 | | }, |
| 303 | | } |
| 304 | | } |
| 305 | | |
| 306 | | fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void { |
| 307 | | if (cwd) |yes_cwd| warn("cd {s} && ", .{yes_cwd}); |
| 308 | | for (argv) |arg| { |
| 309 | | warn("{s} ", .{arg}); |
| 310 | | } |
| 311 | | warn("\n", .{}); |
| 312 | | } |
| 313 | | |
| 314 | | fn addPathForDynLibs(self: *RunStep, artifact: *LibExeObjStep) void { |
| 315 | | for (artifact.link_objects.items) |link_object| { |
| 316 | | switch (link_object) { |
| 317 | | .other_step => |other| { |
| 318 | | if (other.target.isWindows() and other.isDynamicLibrary()) { |
| 319 | | self.addPathDir(fs.path.dirname(other.getOutputPath()).?); |
| 320 | | self.addPathForDynLibs(other); |
| 321 | | } |
| 322 | | }, |
| 323 | | else => {}, |
| 324 | | } |
| 325 | | } |
| 326 | | } |
| 327 | | }; |