| ... | @@ -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 | }; |