master
   1const std = @import("std");
   2const builtin = @import("builtin");
   3const Build = std.Build;
   4const Step = Build.Step;
   5const fs = std.fs;
   6const mem = std.mem;
   7const process = std.process;
   8const EnvMap = process.EnvMap;
   9const assert = std.debug.assert;
  10const Path = Build.Cache.Path;
  11
  12const Run = @This();
  13
  14pub const base_id: Step.Id = .run;
  15
  16step: Step,
  17
  18/// See also addArg and addArgs to modifying this directly
  19argv: std.ArrayList(Arg),
  20
  21/// Use `setCwd` to set the initial current working directory
  22cwd: ?Build.LazyPath,
  23
  24/// Override this field to modify the environment, or use setEnvironmentVariable
  25env_map: ?*EnvMap,
  26
  27/// Controls the `NO_COLOR` and `CLICOLOR_FORCE` environment variables.
  28color: enum {
  29    /// `CLICOLOR_FORCE` is set, and `NO_COLOR` is unset.
  30    enable,
  31    /// `NO_COLOR` is set, and `CLICOLOR_FORCE` is unset.
  32    disable,
  33    /// If the build runner is using color, equivalent to `.enable`. Otherwise, equivalent to `.disable`.
  34    inherit,
  35    /// If stderr is captured or checked, equivalent to `.disable`. Otherwise, equivalent to `.inherit`.
  36    auto,
  37    /// The build runner does not modify the `CLICOLOR_FORCE` or `NO_COLOR` environment variables.
  38    /// They are treated like normal variables, so can be controlled through `setEnvironmentVariable`.
  39    manual,
  40} = .auto,
  41
  42/// When `true` prevents `ZIG_PROGRESS` environment variable from being passed
  43/// to the child process, which otherwise would be used for the child to send
  44/// progress updates to the parent.
  45disable_zig_progress: bool,
  46
  47/// Configures whether the Run step is considered to have side-effects, and also
  48/// whether the Run step will inherit stdio streams, forwarding them to the
  49/// parent process, in which case will require a global lock to prevent other
  50/// steps from interfering with stdio while the subprocess associated with this
  51/// Run step is running.
  52/// If the Run step is determined to not have side-effects, then execution will
  53/// be skipped if all output files are up-to-date and input files are
  54/// unchanged.
  55stdio: StdIo,
  56
  57/// This field must be `.none` if stdio is `inherit`.
  58/// It should be only set using `setStdIn`.
  59stdin: StdIn,
  60
  61/// Additional input files that, when modified, indicate that the Run step
  62/// should be re-executed.
  63/// If the Run step is determined to have side-effects, the Run step is always
  64/// executed when it appears in the build graph, regardless of whether these
  65/// files have been modified.
  66file_inputs: std.ArrayList(std.Build.LazyPath),
  67
  68/// After adding an output argument, this step will by default rename itself
  69/// for a better display name in the build summary.
  70/// This can be disabled by setting this to false.
  71rename_step_with_output_arg: bool,
  72
  73/// If this is true, a Run step which is configured to check the output of the
  74/// executed binary will not fail the build if the binary cannot be executed
  75/// due to being for a foreign binary to the host system which is running the
  76/// build graph.
  77/// Command-line arguments such as -fqemu and -fwasmtime may affect whether a
  78/// binary is detected as foreign, as well as system configuration such as
  79/// Rosetta (macOS) and binfmt_misc (Linux).
  80/// If this Run step is considered to have side-effects, then this flag does
  81/// nothing.
  82skip_foreign_checks: bool,
  83
  84/// If this is true, failing to execute a foreign binary will be considered an
  85/// error. However if this is false, the step will be skipped on failure instead.
  86///
  87/// This allows for a Run step to attempt to execute a foreign binary using an
  88/// external executor (such as qemu) but not fail if the executor is unavailable.
  89failing_to_execute_foreign_is_an_error: bool,
  90
  91/// Deprecated in favor of `stdio_limit`.
  92max_stdio_size: usize,
  93
  94/// If stderr or stdout exceeds this amount, the child process is killed and
  95/// the step fails.
  96stdio_limit: std.Io.Limit,
  97
  98captured_stdout: ?*CapturedStdIo,
  99captured_stderr: ?*CapturedStdIo,
 100
 101dep_output_file: ?*Output,
 102
 103has_side_effects: bool,
 104
 105/// If this is a Zig unit test binary, this tracks the indexes of the unit
 106/// tests that are also fuzz tests.
 107fuzz_tests: std.ArrayList(u32),
 108cached_test_metadata: ?CachedTestMetadata = null,
 109
 110/// Populated during the fuzz phase if this run step corresponds to a unit test
 111/// executable that contains fuzz tests.
 112rebuilt_executable: ?Path,
 113
 114/// If this Run step was produced by a Compile step, it is tracked here.
 115producer: ?*Step.Compile,
 116
 117pub const StdIn = union(enum) {
 118    none,
 119    bytes: []const u8,
 120    lazy_path: std.Build.LazyPath,
 121};
 122
 123pub const StdIo = union(enum) {
 124    /// Whether the Run step has side-effects will be determined by whether or not one
 125    /// of the args is an output file (added with `addOutputFileArg`).
 126    /// If the Run step is determined to have side-effects, this is the same as `inherit`.
 127    /// The step will fail if the subprocess crashes or returns a non-zero exit code.
 128    infer_from_args,
 129    /// Causes the Run step to be considered to have side-effects, and therefore
 130    /// always execute when it appears in the build graph.
 131    /// It also means that this step will obtain a global lock to prevent other
 132    /// steps from running in the meantime.
 133    /// The step will fail if the subprocess crashes or returns a non-zero exit code.
 134    inherit,
 135    /// Causes the Run step to be considered to *not* have side-effects. The
 136    /// process will be re-executed if any of the input dependencies are
 137    /// modified. The exit code and standard I/O streams will be checked for
 138    /// certain conditions, and the step will succeed or fail based on these
 139    /// conditions.
 140    /// Note that an explicit check for exit code 0 needs to be added to this
 141    /// list if such a check is desirable.
 142    check: std.ArrayList(Check),
 143    /// This Run step is running a zig unit test binary and will communicate
 144    /// extra metadata over the IPC protocol.
 145    zig_test,
 146
 147    pub const Check = union(enum) {
 148        expect_stderr_exact: []const u8,
 149        expect_stderr_match: []const u8,
 150        expect_stdout_exact: []const u8,
 151        expect_stdout_match: []const u8,
 152        expect_term: std.process.Child.Term,
 153    };
 154};
 155
 156pub const Arg = union(enum) {
 157    artifact: PrefixedArtifact,
 158    lazy_path: PrefixedLazyPath,
 159    decorated_directory: DecoratedLazyPath,
 160    file_content: PrefixedLazyPath,
 161    bytes: []u8,
 162    output_file: *Output,
 163    output_directory: *Output,
 164};
 165
 166pub const PrefixedArtifact = struct {
 167    prefix: []const u8,
 168    artifact: *Step.Compile,
 169};
 170
 171pub const PrefixedLazyPath = struct {
 172    prefix: []const u8,
 173    lazy_path: std.Build.LazyPath,
 174};
 175
 176pub const DecoratedLazyPath = struct {
 177    prefix: []const u8,
 178    lazy_path: std.Build.LazyPath,
 179    suffix: []const u8,
 180};
 181
 182pub const Output = struct {
 183    generated_file: std.Build.GeneratedFile,
 184    prefix: []const u8,
 185    basename: []const u8,
 186};
 187
 188pub const CapturedStdIo = struct {
 189    output: Output,
 190    trim_whitespace: TrimWhitespace,
 191
 192    pub const Options = struct {
 193        /// `null` means `stdout`/`stderr`.
 194        basename: ?[]const u8 = null,
 195        /// Does not affect `expectStdOutEqual`/`expectStdErrEqual`.
 196        trim_whitespace: TrimWhitespace = .none,
 197    };
 198
 199    pub const TrimWhitespace = enum {
 200        none,
 201        all,
 202        leading,
 203        trailing,
 204    };
 205};
 206
 207pub fn create(owner: *std.Build, name: []const u8) *Run {
 208    const run = owner.allocator.create(Run) catch @panic("OOM");
 209    run.* = .{
 210        .step = .init(.{
 211            .id = base_id,
 212            .name = name,
 213            .owner = owner,
 214            .makeFn = make,
 215        }),
 216        .argv = .{},
 217        .cwd = null,
 218        .env_map = null,
 219        .disable_zig_progress = false,
 220        .stdio = .infer_from_args,
 221        .stdin = .none,
 222        .file_inputs = .{},
 223        .rename_step_with_output_arg = true,
 224        .skip_foreign_checks = false,
 225        .failing_to_execute_foreign_is_an_error = true,
 226        .max_stdio_size = 10 * 1024 * 1024,
 227        .stdio_limit = .unlimited,
 228        .captured_stdout = null,
 229        .captured_stderr = null,
 230        .dep_output_file = null,
 231        .has_side_effects = false,
 232        .fuzz_tests = .{},
 233        .rebuilt_executable = null,
 234        .producer = null,
 235    };
 236    return run;
 237}
 238
 239pub fn setName(run: *Run, name: []const u8) void {
 240    run.step.name = name;
 241    run.rename_step_with_output_arg = false;
 242}
 243
 244pub fn enableTestRunnerMode(run: *Run) void {
 245    const b = run.step.owner;
 246    run.stdio = .zig_test;
 247    run.addPrefixedDirectoryArg("--cache-dir=", .{ .cwd_relative = b.cache_root.path orelse "." });
 248    run.addArgs(&.{
 249        b.fmt("--seed=0x{x}", .{b.graph.random_seed}),
 250        "--listen=-",
 251    });
 252}
 253
 254pub fn addArtifactArg(run: *Run, artifact: *Step.Compile) void {
 255    run.addPrefixedArtifactArg("", artifact);
 256}
 257
 258pub fn addPrefixedArtifactArg(run: *Run, prefix: []const u8, artifact: *Step.Compile) void {
 259    const b = run.step.owner;
 260
 261    const prefixed_artifact: PrefixedArtifact = .{
 262        .prefix = b.dupe(prefix),
 263        .artifact = artifact,
 264    };
 265    run.argv.append(b.allocator, .{ .artifact = prefixed_artifact }) catch @panic("OOM");
 266
 267    const bin_file = artifact.getEmittedBin();
 268    bin_file.addStepDependencies(&run.step);
 269}
 270
 271/// Provides a file path as a command line argument to the command being run.
 272///
 273/// Returns a `std.Build.LazyPath` which can be used as inputs to other APIs
 274/// throughout the build system.
 275///
 276/// Related:
 277/// * `addPrefixedOutputFileArg` - same thing but prepends a string to the argument
 278/// * `addFileArg` - for input files given to the child process
 279pub fn addOutputFileArg(run: *Run, basename: []const u8) std.Build.LazyPath {
 280    return run.addPrefixedOutputFileArg("", basename);
 281}
 282
 283/// Provides a file path as a command line argument to the command being run.
 284/// Asserts `basename` is not empty.
 285///
 286/// For example, a prefix of "-o" and basename of "output.txt" will result in
 287/// the child process seeing something like this: "-ozig-cache/.../output.txt"
 288///
 289/// The child process will see a single argument, regardless of whether the
 290/// prefix or basename have spaces.
 291///
 292/// The returned `std.Build.LazyPath` can be used as inputs to other APIs
 293/// throughout the build system.
 294///
 295/// Related:
 296/// * `addOutputFileArg` - same thing but without the prefix
 297/// * `addFileArg` - for input files given to the child process
 298pub fn addPrefixedOutputFileArg(
 299    run: *Run,
 300    prefix: []const u8,
 301    basename: []const u8,
 302) std.Build.LazyPath {
 303    const b = run.step.owner;
 304    if (basename.len == 0) @panic("basename must not be empty");
 305
 306    const output = b.allocator.create(Output) catch @panic("OOM");
 307    output.* = .{
 308        .prefix = b.dupe(prefix),
 309        .basename = b.dupe(basename),
 310        .generated_file = .{ .step = &run.step },
 311    };
 312    run.argv.append(b.allocator, .{ .output_file = output }) catch @panic("OOM");
 313
 314    if (run.rename_step_with_output_arg) {
 315        run.setName(b.fmt("{s} ({s})", .{ run.step.name, basename }));
 316    }
 317
 318    return .{ .generated = .{ .file = &output.generated_file } };
 319}
 320
 321/// Appends an input file to the command line arguments.
 322///
 323/// The child process will see a file path. Modifications to this file will be
 324/// detected as a cache miss in subsequent builds, causing the child process to
 325/// be re-executed.
 326///
 327/// Related:
 328/// * `addPrefixedFileArg` - same thing but prepends a string to the argument
 329/// * `addOutputFileArg` - for files generated by the child process
 330pub fn addFileArg(run: *Run, lp: std.Build.LazyPath) void {
 331    run.addPrefixedFileArg("", lp);
 332}
 333
 334/// Appends an input file to the command line arguments prepended with a string.
 335///
 336/// For example, a prefix of "-F" will result in the child process seeing something
 337/// like this: "-Fexample.txt"
 338///
 339/// The child process will see a single argument, even if the prefix has
 340/// spaces. Modifications to this file will be detected as a cache miss in
 341/// subsequent builds, causing the child process to be re-executed.
 342///
 343/// Related:
 344/// * `addFileArg` - same thing but without the prefix
 345/// * `addOutputFileArg` - for files generated by the child process
 346pub fn addPrefixedFileArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath) void {
 347    const b = run.step.owner;
 348
 349    const prefixed_file_source: PrefixedLazyPath = .{
 350        .prefix = b.dupe(prefix),
 351        .lazy_path = lp.dupe(b),
 352    };
 353    run.argv.append(b.allocator, .{ .lazy_path = prefixed_file_source }) catch @panic("OOM");
 354    lp.addStepDependencies(&run.step);
 355}
 356
 357/// Appends the content of an input file to the command line arguments.
 358///
 359/// The child process will see a single argument, even if the file contains whitespace.
 360/// This means that the entire file content up to EOF is rendered as one contiguous
 361/// string, including escape sequences. Notably, any (trailing) newlines will show up
 362/// like this: "hello,\nfile world!\n"
 363///
 364/// Modifications to the source file will be detected as a cache miss in subsequent
 365/// builds, causing the child process to be re-executed.
 366///
 367/// This function may not be used to supply the first argument of a `Run` step.
 368///
 369/// Related:
 370/// * `addPrefixedFileContentArg` - same thing but prepends a string to the argument
 371pub fn addFileContentArg(run: *Run, lp: std.Build.LazyPath) void {
 372    run.addPrefixedFileContentArg("", lp);
 373}
 374
 375/// Appends the content of an input file to the command line arguments prepended with a string.
 376///
 377/// For example, a prefix of "-F" will result in the child process seeing something
 378/// like this: "-Fmy file content"
 379///
 380/// The child process will see a single argument, even if the prefix and/or the file
 381/// contain whitespace.
 382/// This means that the entire file content up to EOF is rendered as one contiguous
 383/// string, including escape sequences. Notably, any (trailing) newlines will show up
 384/// like this: "hello,\nfile world!\n"
 385///
 386/// Modifications to the source file will be detected as a cache miss in subsequent
 387/// builds, causing the child process to be re-executed.
 388///
 389/// This function may not be used to supply the first argument of a `Run` step.
 390///
 391/// Related:
 392/// * `addFileContentArg` - same thing but without the prefix
 393pub fn addPrefixedFileContentArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath) void {
 394    const b = run.step.owner;
 395
 396    // Some parts of this step's configure phase API rely on the first argument being somewhat
 397    // transparent/readable, but the content of the file specified by `lp` remains completely
 398    // opaque until its path can be resolved during the make phase.
 399    if (run.argv.items.len == 0) {
 400        @panic("'addFileContentArg'/'addPrefixedFileContentArg' cannot be first argument");
 401    }
 402
 403    const prefixed_file_source: PrefixedLazyPath = .{
 404        .prefix = b.dupe(prefix),
 405        .lazy_path = lp.dupe(b),
 406    };
 407    run.argv.append(b.allocator, .{ .file_content = prefixed_file_source }) catch @panic("OOM");
 408    lp.addStepDependencies(&run.step);
 409}
 410
 411/// Provides a directory path as a command line argument to the command being run.
 412///
 413/// Returns a `std.Build.LazyPath` which can be used as inputs to other APIs
 414/// throughout the build system.
 415///
 416/// Related:
 417/// * `addPrefixedOutputDirectoryArg` - same thing but prepends a string to the argument
 418/// * `addDirectoryArg` - for input directories given to the child process
 419pub fn addOutputDirectoryArg(run: *Run, basename: []const u8) std.Build.LazyPath {
 420    return run.addPrefixedOutputDirectoryArg("", basename);
 421}
 422
 423/// Provides a directory path as a command line argument to the command being run.
 424/// Asserts `basename` is not empty.
 425///
 426/// For example, a prefix of "-o" and basename of "output_dir" will result in
 427/// the child process seeing something like this: "-ozig-cache/.../output_dir"
 428///
 429/// The child process will see a single argument, regardless of whether the
 430/// prefix or basename have spaces.
 431///
 432/// The returned `std.Build.LazyPath` can be used as inputs to other APIs
 433/// throughout the build system.
 434///
 435/// Related:
 436/// * `addOutputDirectoryArg` - same thing but without the prefix
 437/// * `addDirectoryArg` - for input directories given to the child process
 438pub fn addPrefixedOutputDirectoryArg(
 439    run: *Run,
 440    prefix: []const u8,
 441    basename: []const u8,
 442) std.Build.LazyPath {
 443    if (basename.len == 0) @panic("basename must not be empty");
 444    const b = run.step.owner;
 445
 446    const output = b.allocator.create(Output) catch @panic("OOM");
 447    output.* = .{
 448        .prefix = b.dupe(prefix),
 449        .basename = b.dupe(basename),
 450        .generated_file = .{ .step = &run.step },
 451    };
 452    run.argv.append(b.allocator, .{ .output_directory = output }) catch @panic("OOM");
 453
 454    if (run.rename_step_with_output_arg) {
 455        run.setName(b.fmt("{s} ({s})", .{ run.step.name, basename }));
 456    }
 457
 458    return .{ .generated = .{ .file = &output.generated_file } };
 459}
 460
 461pub fn addDirectoryArg(run: *Run, lazy_directory: std.Build.LazyPath) void {
 462    run.addDecoratedDirectoryArg("", lazy_directory, "");
 463}
 464
 465pub fn addPrefixedDirectoryArg(run: *Run, prefix: []const u8, lazy_directory: std.Build.LazyPath) void {
 466    const b = run.step.owner;
 467    run.argv.append(b.allocator, .{ .decorated_directory = .{
 468        .prefix = b.dupe(prefix),
 469        .lazy_path = lazy_directory.dupe(b),
 470        .suffix = "",
 471    } }) catch @panic("OOM");
 472    lazy_directory.addStepDependencies(&run.step);
 473}
 474
 475pub fn addDecoratedDirectoryArg(
 476    run: *Run,
 477    prefix: []const u8,
 478    lazy_directory: std.Build.LazyPath,
 479    suffix: []const u8,
 480) void {
 481    const b = run.step.owner;
 482    run.argv.append(b.allocator, .{ .decorated_directory = .{
 483        .prefix = b.dupe(prefix),
 484        .lazy_path = lazy_directory.dupe(b),
 485        .suffix = b.dupe(suffix),
 486    } }) catch @panic("OOM");
 487    lazy_directory.addStepDependencies(&run.step);
 488}
 489
 490/// Add a path argument to a dep file (.d) for the child process to write its
 491/// discovered additional dependencies.
 492/// Only one dep file argument is allowed by instance.
 493pub fn addDepFileOutputArg(run: *Run, basename: []const u8) std.Build.LazyPath {
 494    return run.addPrefixedDepFileOutputArg("", basename);
 495}
 496
 497/// Add a prefixed path argument to a dep file (.d) for the child process to
 498/// write its discovered additional dependencies.
 499/// Only one dep file argument is allowed by instance.
 500pub fn addPrefixedDepFileOutputArg(run: *Run, prefix: []const u8, basename: []const u8) std.Build.LazyPath {
 501    const b = run.step.owner;
 502    assert(run.dep_output_file == null);
 503
 504    const dep_file = b.allocator.create(Output) catch @panic("OOM");
 505    dep_file.* = .{
 506        .prefix = b.dupe(prefix),
 507        .basename = b.dupe(basename),
 508        .generated_file = .{ .step = &run.step },
 509    };
 510
 511    run.dep_output_file = dep_file;
 512
 513    run.argv.append(b.allocator, .{ .output_file = dep_file }) catch @panic("OOM");
 514
 515    return .{ .generated = .{ .file = &dep_file.generated_file } };
 516}
 517
 518pub fn addArg(run: *Run, arg: []const u8) void {
 519    const b = run.step.owner;
 520    run.argv.append(b.allocator, .{ .bytes = b.dupe(arg) }) catch @panic("OOM");
 521}
 522
 523pub fn addArgs(run: *Run, args: []const []const u8) void {
 524    for (args) |arg| run.addArg(arg);
 525}
 526
 527pub fn setStdIn(run: *Run, stdin: StdIn) void {
 528    switch (stdin) {
 529        .lazy_path => |lazy_path| lazy_path.addStepDependencies(&run.step),
 530        .bytes, .none => {},
 531    }
 532    run.stdin = stdin;
 533}
 534
 535pub fn setCwd(run: *Run, cwd: Build.LazyPath) void {
 536    cwd.addStepDependencies(&run.step);
 537    run.cwd = cwd.dupe(run.step.owner);
 538}
 539
 540pub fn clearEnvironment(run: *Run) void {
 541    const b = run.step.owner;
 542    const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM");
 543    new_env_map.* = .init(b.allocator);
 544    run.env_map = new_env_map;
 545}
 546
 547pub fn addPathDir(run: *Run, search_path: []const u8) void {
 548    const b = run.step.owner;
 549    const env_map = getEnvMapInternal(run);
 550
 551    const use_wine = b.enable_wine and b.graph.host.result.os.tag != .windows and use_wine: switch (run.argv.items[0]) {
 552        .artifact => |p| p.artifact.rootModuleTarget().os.tag == .windows,
 553        .lazy_path => |p| {
 554            switch (p.lazy_path) {
 555                .generated => |g| if (g.file.step.cast(Step.Compile)) |cs| break :use_wine cs.rootModuleTarget().os.tag == .windows,
 556                else => {},
 557            }
 558            break :use_wine std.mem.endsWith(u8, p.lazy_path.basename(b, &run.step), ".exe");
 559        },
 560        .decorated_directory => false,
 561        .file_content => unreachable, // not allowed as first arg
 562        .bytes => |bytes| std.mem.endsWith(u8, bytes, ".exe"),
 563        .output_file, .output_directory => false,
 564    };
 565    const key = if (use_wine) "WINEPATH" else "PATH";
 566    const prev_path = env_map.get(key);
 567
 568    if (prev_path) |pp| {
 569        const new_path = b.fmt("{s}{c}{s}", .{
 570            pp,
 571            if (use_wine) fs.path.delimiter_windows else fs.path.delimiter,
 572            search_path,
 573        });
 574        env_map.put(key, new_path) catch @panic("OOM");
 575    } else {
 576        env_map.put(key, b.dupePath(search_path)) catch @panic("OOM");
 577    }
 578}
 579
 580pub fn getEnvMap(run: *Run) *EnvMap {
 581    return getEnvMapInternal(run);
 582}
 583
 584fn getEnvMapInternal(run: *Run) *EnvMap {
 585    const arena = run.step.owner.allocator;
 586    return run.env_map orelse {
 587        const env_map = arena.create(EnvMap) catch @panic("OOM");
 588        env_map.* = process.getEnvMap(arena) catch @panic("unhandled error");
 589        run.env_map = env_map;
 590        return env_map;
 591    };
 592}
 593
 594pub fn setEnvironmentVariable(run: *Run, key: []const u8, value: []const u8) void {
 595    const b = run.step.owner;
 596    const env_map = run.getEnvMap();
 597    env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error");
 598}
 599
 600pub fn removeEnvironmentVariable(run: *Run, key: []const u8) void {
 601    run.getEnvMap().remove(key);
 602}
 603
 604/// Adds a check for exact stderr match. Does not add any other checks.
 605pub fn expectStdErrEqual(run: *Run, bytes: []const u8) void {
 606    const new_check: StdIo.Check = .{ .expect_stderr_exact = run.step.owner.dupe(bytes) };
 607    run.addCheck(new_check);
 608}
 609
 610/// Adds a check for exact stdout match as well as a check for exit code 0, if
 611/// there is not already an expected termination check.
 612pub fn expectStdOutEqual(run: *Run, bytes: []const u8) void {
 613    const new_check: StdIo.Check = .{ .expect_stdout_exact = run.step.owner.dupe(bytes) };
 614    run.addCheck(new_check);
 615    if (!run.hasTermCheck()) {
 616        run.expectExitCode(0);
 617    }
 618}
 619
 620pub fn expectExitCode(run: *Run, code: u8) void {
 621    const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } };
 622    run.addCheck(new_check);
 623}
 624
 625pub fn hasTermCheck(run: Run) bool {
 626    for (run.stdio.check.items) |check| switch (check) {
 627        .expect_term => return true,
 628        else => continue,
 629    };
 630    return false;
 631}
 632
 633pub fn addCheck(run: *Run, new_check: StdIo.Check) void {
 634    const b = run.step.owner;
 635
 636    switch (run.stdio) {
 637        .infer_from_args => {
 638            run.stdio = .{ .check = .{} };
 639            run.stdio.check.append(b.allocator, new_check) catch @panic("OOM");
 640        },
 641        .check => |*checks| checks.append(b.allocator, new_check) catch @panic("OOM"),
 642        else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of Run instead"),
 643    }
 644}
 645
 646pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
 647    assert(run.stdio != .inherit);
 648    assert(run.stdio != .zig_test);
 649
 650    const b = run.step.owner;
 651
 652    if (run.captured_stderr) |captured| return .{ .generated = .{ .file = &captured.output.generated_file } };
 653
 654    const captured = b.allocator.create(CapturedStdIo) catch @panic("OOM");
 655    captured.* = .{
 656        .output = .{
 657            .prefix = "",
 658            .basename = if (options.basename) |basename| b.dupe(basename) else "stderr",
 659            .generated_file = .{ .step = &run.step },
 660        },
 661        .trim_whitespace = options.trim_whitespace,
 662    };
 663    run.captured_stderr = captured;
 664    return .{ .generated = .{ .file = &captured.output.generated_file } };
 665}
 666
 667pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
 668    assert(run.stdio != .inherit);
 669    assert(run.stdio != .zig_test);
 670
 671    const b = run.step.owner;
 672
 673    if (run.captured_stdout) |captured| return .{ .generated = .{ .file = &captured.output.generated_file } };
 674
 675    const captured = b.allocator.create(CapturedStdIo) catch @panic("OOM");
 676    captured.* = .{
 677        .output = .{
 678            .prefix = "",
 679            .basename = if (options.basename) |basename| b.dupe(basename) else "stdout",
 680            .generated_file = .{ .step = &run.step },
 681        },
 682        .trim_whitespace = options.trim_whitespace,
 683    };
 684    run.captured_stdout = captured;
 685    return .{ .generated = .{ .file = &captured.output.generated_file } };
 686}
 687
 688/// Adds an additional input files that, when modified, indicates that this Run
 689/// step should be re-executed.
 690/// If the Run step is determined to have side-effects, the Run step is always
 691/// executed when it appears in the build graph, regardless of whether this
 692/// file has been modified.
 693pub fn addFileInput(self: *Run, file_input: std.Build.LazyPath) void {
 694    file_input.addStepDependencies(&self.step);
 695    self.file_inputs.append(self.step.owner.allocator, file_input.dupe(self.step.owner)) catch @panic("OOM");
 696}
 697
 698/// Returns whether the Run step has side effects *other than* updating the output arguments.
 699fn hasSideEffects(run: Run) bool {
 700    if (run.has_side_effects) return true;
 701    return switch (run.stdio) {
 702        .infer_from_args => !run.hasAnyOutputArgs(),
 703        .inherit => true,
 704        .check => false,
 705        .zig_test => false,
 706    };
 707}
 708
 709fn hasAnyOutputArgs(run: Run) bool {
 710    if (run.captured_stdout != null) return true;
 711    if (run.captured_stderr != null) return true;
 712    for (run.argv.items) |arg| switch (arg) {
 713        .output_file, .output_directory => return true,
 714        else => continue,
 715    };
 716    return false;
 717}
 718
 719fn checksContainStdout(checks: []const StdIo.Check) bool {
 720    for (checks) |check| switch (check) {
 721        .expect_stderr_exact,
 722        .expect_stderr_match,
 723        .expect_term,
 724        => continue,
 725
 726        .expect_stdout_exact,
 727        .expect_stdout_match,
 728        => return true,
 729    };
 730    return false;
 731}
 732
 733fn checksContainStderr(checks: []const StdIo.Check) bool {
 734    for (checks) |check| switch (check) {
 735        .expect_stdout_exact,
 736        .expect_stdout_match,
 737        .expect_term,
 738        => continue,
 739
 740        .expect_stderr_exact,
 741        .expect_stderr_match,
 742        => return true,
 743    };
 744    return false;
 745}
 746
 747/// If `path` is cwd-relative, make it relative to the cwd of the child instead.
 748///
 749/// Whenever a path is included in the argv of a child, it should be put through this function first
 750/// to make sure the child doesn't see paths relative to a cwd other than its own.
 751fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {
 752    const b = run.step.owner;
 753    const path_str = path.toString(b.graph.arena) catch @panic("OOM");
 754    if (std.fs.path.isAbsolute(path_str)) {
 755        // Absolute paths don't need changing.
 756        return path_str;
 757    }
 758    const child_cwd_rel: []const u8 = rel: {
 759        const child_lazy_cwd = run.cwd orelse break :rel path_str;
 760        const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(b.graph.arena) catch @panic("OOM");
 761        // Convert it from relative to *our* cwd, to relative to the *child's* cwd.
 762        break :rel std.fs.path.relative(b.graph.arena, child_cwd, path_str) catch @panic("OOM");
 763    };
 764    // Not every path can be made relative, e.g. if the path and the child cwd are on different
 765    // disk designators on Windows. In that case, `relative` will return an absolute path which we can
 766    // just return.
 767    if (std.fs.path.isAbsolute(child_cwd_rel)) {
 768        return child_cwd_rel;
 769    }
 770    // We're not done yet. In some cases this path must be prefixed with './':
 771    // * On POSIX, the executable name cannot be a single component like 'foo'
 772    // * Some executables might treat a leading '-' like a flag, which we must avoid
 773    // There's no harm in it, so just *always* apply this prefix.
 774    return std.fs.path.join(b.graph.arena, &.{ ".", child_cwd_rel }) catch @panic("OOM");
 775}
 776
 777const IndexedOutput = struct {
 778    index: usize,
 779    tag: @typeInfo(Arg).@"union".tag_type.?,
 780    output: *Output,
 781};
 782fn make(step: *Step, options: Step.MakeOptions) !void {
 783    const b = step.owner;
 784    const io = b.graph.io;
 785    const arena = b.allocator;
 786    const run: *Run = @fieldParentPtr("step", step);
 787    const has_side_effects = run.hasSideEffects();
 788
 789    var argv_list = std.array_list.Managed([]const u8).init(arena);
 790    var output_placeholders = std.array_list.Managed(IndexedOutput).init(arena);
 791
 792    var man = b.graph.cache.obtain();
 793    defer man.deinit();
 794
 795    if (run.env_map) |env_map| {
 796        const KV = struct { []const u8, []const u8 };
 797        var kv_pairs = try std.array_list.Managed(KV).initCapacity(arena, env_map.count());
 798        var iter = env_map.iterator();
 799        while (iter.next()) |entry| {
 800            kv_pairs.appendAssumeCapacity(.{ entry.key_ptr.*, entry.value_ptr.* });
 801        }
 802
 803        std.mem.sortUnstable(KV, kv_pairs.items, {}, struct {
 804            fn lessThan(_: void, kv1: KV, kv2: KV) bool {
 805                const k1 = kv1[0];
 806                const k2 = kv2[0];
 807
 808                if (k1.len != k2.len) return k1.len < k2.len;
 809
 810                for (k1, k2) |c1, c2| {
 811                    if (c1 == c2) continue;
 812                    return c1 < c2;
 813                }
 814                unreachable; // two keys cannot be equal
 815            }
 816        }.lessThan);
 817
 818        for (kv_pairs.items) |kv| {
 819            man.hash.addBytes(kv[0]);
 820            man.hash.addBytes(kv[1]);
 821        }
 822    }
 823
 824    man.hash.add(run.color);
 825    man.hash.add(run.disable_zig_progress);
 826
 827    for (run.argv.items) |arg| {
 828        switch (arg) {
 829            .bytes => |bytes| {
 830                try argv_list.append(bytes);
 831                man.hash.addBytes(bytes);
 832            },
 833            .lazy_path => |file| {
 834                const file_path = file.lazy_path.getPath3(b, step);
 835                try argv_list.append(b.fmt("{s}{s}", .{ file.prefix, run.convertPathArg(file_path) }));
 836                man.hash.addBytes(file.prefix);
 837                _ = try man.addFilePath(file_path, null);
 838            },
 839            .decorated_directory => |dd| {
 840                const file_path = dd.lazy_path.getPath3(b, step);
 841                const resolved_arg = b.fmt("{s}{s}{s}", .{ dd.prefix, run.convertPathArg(file_path), dd.suffix });
 842                try argv_list.append(resolved_arg);
 843                man.hash.addBytes(resolved_arg);
 844            },
 845            .file_content => |file_plp| {
 846                const file_path = file_plp.lazy_path.getPath3(b, step);
 847
 848                var result: std.Io.Writer.Allocating = .init(arena);
 849                errdefer result.deinit();
 850                result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory;
 851
 852                const file = file_path.root_dir.handle.openFile(file_path.subPathOrDot(), .{}) catch |err| {
 853                    return step.fail(
 854                        "unable to open input file '{f}': {t}",
 855                        .{ file_path, err },
 856                    );
 857                };
 858                defer file.close();
 859
 860                var buf: [1024]u8 = undefined;
 861                var file_reader = file.reader(io, &buf);
 862                _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
 863                    error.ReadFailed => return step.fail(
 864                        "failed to read from '{f}': {t}",
 865                        .{ file_path, file_reader.err.? },
 866                    ),
 867                    error.WriteFailed => return error.OutOfMemory,
 868                };
 869
 870                try argv_list.append(result.written());
 871                man.hash.addBytes(file_plp.prefix);
 872                _ = try man.addFilePath(file_path, null);
 873            },
 874            .artifact => |pa| {
 875                const artifact = pa.artifact;
 876
 877                if (artifact.rootModuleTarget().os.tag == .windows) {
 878                    // On Windows we don't have rpaths so we have to add .dll search paths to PATH
 879                    run.addPathForDynLibs(artifact);
 880                }
 881                const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?;
 882
 883                try argv_list.append(b.fmt("{s}{s}", .{
 884                    pa.prefix,
 885                    run.convertPathArg(.{ .root_dir = .cwd(), .sub_path = file_path }),
 886                }));
 887
 888                _ = try man.addFile(file_path, null);
 889            },
 890            .output_file, .output_directory => |output| {
 891                man.hash.addBytes(output.prefix);
 892                man.hash.addBytes(output.basename);
 893                // Add a placeholder into the argument list because we need the
 894                // manifest hash to be updated with all arguments before the
 895                // object directory is computed.
 896                try output_placeholders.append(.{
 897                    .index = argv_list.items.len,
 898                    .tag = arg,
 899                    .output = output,
 900                });
 901                _ = try argv_list.addOne();
 902            },
 903        }
 904    }
 905
 906    switch (run.stdin) {
 907        .bytes => |bytes| {
 908            man.hash.addBytes(bytes);
 909        },
 910        .lazy_path => |lazy_path| {
 911            const file_path = lazy_path.getPath2(b, step);
 912            _ = try man.addFile(file_path, null);
 913        },
 914        .none => {},
 915    }
 916
 917    if (run.captured_stdout) |captured| {
 918        man.hash.addBytes(captured.output.basename);
 919        man.hash.add(captured.trim_whitespace);
 920    }
 921
 922    if (run.captured_stderr) |captured| {
 923        man.hash.addBytes(captured.output.basename);
 924        man.hash.add(captured.trim_whitespace);
 925    }
 926
 927    hashStdIo(&man.hash, run.stdio);
 928
 929    for (run.file_inputs.items) |lazy_path| {
 930        _ = try man.addFile(lazy_path.getPath2(b, step), null);
 931    }
 932
 933    if (run.cwd) |cwd| {
 934        const cwd_path = cwd.getPath3(b, step);
 935        _ = man.hash.addBytes(try cwd_path.toString(arena));
 936    }
 937
 938    if (!has_side_effects and try step.cacheHitAndWatch(&man)) {
 939        // cache hit, skip running command
 940        const digest = man.final();
 941
 942        try populateGeneratedPaths(
 943            arena,
 944            output_placeholders.items,
 945            run.captured_stdout,
 946            run.captured_stderr,
 947            b.cache_root,
 948            &digest,
 949        );
 950
 951        step.result_cached = true;
 952        return;
 953    }
 954
 955    const dep_output_file = run.dep_output_file orelse {
 956        // We already know the final output paths, use them directly.
 957        const digest = if (has_side_effects)
 958            man.hash.final()
 959        else
 960            man.final();
 961
 962        try populateGeneratedPaths(
 963            arena,
 964            output_placeholders.items,
 965            run.captured_stdout,
 966            run.captured_stderr,
 967            b.cache_root,
 968            &digest,
 969        );
 970
 971        const output_dir_path = "o" ++ fs.path.sep_str ++ &digest;
 972        for (output_placeholders.items) |placeholder| {
 973            const output_sub_path = b.pathJoin(&.{ output_dir_path, placeholder.output.basename });
 974            const output_sub_dir_path = switch (placeholder.tag) {
 975                .output_file => fs.path.dirname(output_sub_path).?,
 976                .output_directory => output_sub_path,
 977                else => unreachable,
 978            };
 979            b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {
 980                return step.fail("unable to make path '{f}{s}': {s}", .{
 981                    b.cache_root, output_sub_dir_path, @errorName(err),
 982                });
 983            };
 984            const arg_output_path = run.convertPathArg(.{
 985                .root_dir = .cwd(),
 986                .sub_path = placeholder.output.generated_file.getPath(),
 987            });
 988            argv_list.items[placeholder.index] = if (placeholder.output.prefix.len == 0)
 989                arg_output_path
 990            else
 991                b.fmt("{s}{s}", .{ placeholder.output.prefix, arg_output_path });
 992        }
 993
 994        try runCommand(run, argv_list.items, has_side_effects, output_dir_path, options, null);
 995        if (!has_side_effects) try step.writeManifestAndWatch(&man);
 996        return;
 997    };
 998
 999    // We do not know the final output paths yet, use temp paths to run the command.
1000    const rand_int = std.crypto.random.int(u64);
1001    const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
1002
1003    for (output_placeholders.items) |placeholder| {
1004        const output_components = .{ tmp_dir_path, placeholder.output.basename };
1005        const output_sub_path = b.pathJoin(&output_components);
1006        const output_sub_dir_path = switch (placeholder.tag) {
1007            .output_file => fs.path.dirname(output_sub_path).?,
1008            .output_directory => output_sub_path,
1009            else => unreachable,
1010        };
1011        b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {
1012            return step.fail("unable to make path '{f}{s}': {s}", .{
1013                b.cache_root, output_sub_dir_path, @errorName(err),
1014            });
1015        };
1016        const raw_output_path: Build.Cache.Path = .{
1017            .root_dir = b.cache_root,
1018            .sub_path = b.pathJoin(&output_components),
1019        };
1020        placeholder.output.generated_file.path = raw_output_path.toString(b.graph.arena) catch @panic("OOM");
1021        argv_list.items[placeholder.index] = b.fmt("{s}{s}", .{
1022            placeholder.output.prefix,
1023            run.convertPathArg(raw_output_path),
1024        });
1025    }
1026
1027    try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, options, null);
1028
1029    const dep_file_dir = std.fs.cwd();
1030    const dep_file_basename = dep_output_file.generated_file.getPath2(b, step);
1031    if (has_side_effects)
1032        try man.addDepFile(dep_file_dir, dep_file_basename)
1033    else
1034        try man.addDepFilePost(dep_file_dir, dep_file_basename);
1035
1036    const digest = if (has_side_effects)
1037        man.hash.final()
1038    else
1039        man.final();
1040
1041    const any_output = output_placeholders.items.len > 0 or
1042        run.captured_stdout != null or run.captured_stderr != null;
1043
1044    // Rename into place
1045    if (any_output) {
1046        const o_sub_path = "o" ++ fs.path.sep_str ++ &digest;
1047
1048        b.cache_root.handle.rename(tmp_dir_path, o_sub_path) catch |err| {
1049            if (err == error.PathAlreadyExists) {
1050                b.cache_root.handle.deleteTree(o_sub_path) catch |del_err| {
1051                    return step.fail("unable to remove dir '{f}'{s}: {s}", .{
1052                        b.cache_root,
1053                        tmp_dir_path,
1054                        @errorName(del_err),
1055                    });
1056                };
1057                b.cache_root.handle.rename(tmp_dir_path, o_sub_path) catch |retry_err| {
1058                    return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {s}", .{
1059                        b.cache_root,          tmp_dir_path,
1060                        b.cache_root,          o_sub_path,
1061                        @errorName(retry_err),
1062                    });
1063                };
1064            } else {
1065                return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {s}", .{
1066                    b.cache_root,    tmp_dir_path,
1067                    b.cache_root,    o_sub_path,
1068                    @errorName(err),
1069                });
1070            }
1071        };
1072    }
1073
1074    if (!has_side_effects) try step.writeManifestAndWatch(&man);
1075
1076    try populateGeneratedPaths(
1077        arena,
1078        output_placeholders.items,
1079        run.captured_stdout,
1080        run.captured_stderr,
1081        b.cache_root,
1082        &digest,
1083    );
1084}
1085
1086pub fn rerunInFuzzMode(
1087    run: *Run,
1088    fuzz: *std.Build.Fuzz,
1089    unit_test_index: u32,
1090    prog_node: std.Progress.Node,
1091) !void {
1092    const step = &run.step;
1093    const b = step.owner;
1094    const io = b.graph.io;
1095    const arena = b.allocator;
1096    var argv_list: std.ArrayList([]const u8) = .empty;
1097    for (run.argv.items) |arg| {
1098        switch (arg) {
1099            .bytes => |bytes| {
1100                try argv_list.append(arena, bytes);
1101            },
1102            .lazy_path => |file| {
1103                const file_path = file.lazy_path.getPath3(b, step);
1104                try argv_list.append(arena, b.fmt("{s}{s}", .{ file.prefix, run.convertPathArg(file_path) }));
1105            },
1106            .decorated_directory => |dd| {
1107                const file_path = dd.lazy_path.getPath3(b, step);
1108                try argv_list.append(arena, b.fmt("{s}{s}{s}", .{ dd.prefix, run.convertPathArg(file_path), dd.suffix }));
1109            },
1110            .file_content => |file_plp| {
1111                const file_path = file_plp.lazy_path.getPath3(b, step);
1112
1113                var result: std.Io.Writer.Allocating = .init(arena);
1114                errdefer result.deinit();
1115                result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory;
1116
1117                const file = try file_path.root_dir.handle.openFile(file_path.subPathOrDot(), .{});
1118                defer file.close();
1119
1120                var buf: [1024]u8 = undefined;
1121                var file_reader = file.reader(io, &buf);
1122                _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
1123                    error.ReadFailed => return file_reader.err.?,
1124                    error.WriteFailed => return error.OutOfMemory,
1125                };
1126
1127                try argv_list.append(arena, result.written());
1128            },
1129            .artifact => |pa| {
1130                const artifact = pa.artifact;
1131                const file_path: []const u8 = p: {
1132                    if (artifact == run.producer.?) break :p b.fmt("{f}", .{run.rebuilt_executable.?});
1133                    break :p artifact.installed_path orelse artifact.generated_bin.?.path.?;
1134                };
1135                try argv_list.append(arena, b.fmt("{s}{s}", .{
1136                    pa.prefix,
1137                    run.convertPathArg(.{ .root_dir = .cwd(), .sub_path = file_path }),
1138                }));
1139            },
1140            .output_file, .output_directory => unreachable,
1141        }
1142    }
1143
1144    if (run.step.result_failed_command) |cmd| {
1145        fuzz.gpa.free(cmd);
1146        run.step.result_failed_command = null;
1147    }
1148
1149    const has_side_effects = false;
1150    const rand_int = std.crypto.random.int(u64);
1151    const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
1152    try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{
1153        .progress_node = prog_node,
1154        .watch = undefined, // not used by `runCommand`
1155        .web_server = null, // only needed for time reports
1156        .ttyconf = fuzz.ttyconf,
1157        .unit_test_timeout_ns = null, // don't time out fuzz tests for now
1158        .gpa = fuzz.gpa,
1159    }, .{
1160        .unit_test_index = unit_test_index,
1161        .fuzz = fuzz,
1162    });
1163}
1164
1165fn populateGeneratedPaths(
1166    arena: std.mem.Allocator,
1167    output_placeholders: []const IndexedOutput,
1168    captured_stdout: ?*CapturedStdIo,
1169    captured_stderr: ?*CapturedStdIo,
1170    cache_root: Build.Cache.Directory,
1171    digest: *const Build.Cache.HexDigest,
1172) !void {
1173    for (output_placeholders) |placeholder| {
1174        placeholder.output.generated_file.path = try cache_root.join(arena, &.{
1175            "o", digest, placeholder.output.basename,
1176        });
1177    }
1178
1179    if (captured_stdout) |captured| {
1180        captured.output.generated_file.path = try cache_root.join(arena, &.{
1181            "o", digest, captured.output.basename,
1182        });
1183    }
1184
1185    if (captured_stderr) |captured| {
1186        captured.output.generated_file.path = try cache_root.join(arena, &.{
1187            "o", digest, captured.output.basename,
1188        });
1189    }
1190}
1191
1192fn formatTerm(term: ?std.process.Child.Term, w: *std.Io.Writer) std.Io.Writer.Error!void {
1193    if (term) |t| switch (t) {
1194        .Exited => |code| try w.print("exited with code {d}", .{code}),
1195        .Signal => |sig| try w.print("terminated with signal {d}", .{sig}),
1196        .Stopped => |sig| try w.print("stopped with signal {d}", .{sig}),
1197        .Unknown => |code| try w.print("terminated for unknown reason with code {d}", .{code}),
1198    } else {
1199        try w.writeAll("exited with any code");
1200    }
1201}
1202fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Alt(?std.process.Child.Term, formatTerm) {
1203    return .{ .data = term };
1204}
1205
1206fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term) bool {
1207    return if (expected) |e| switch (e) {
1208        .Exited => |expected_code| switch (actual) {
1209            .Exited => |actual_code| expected_code == actual_code,
1210            else => false,
1211        },
1212        .Signal => |expected_sig| switch (actual) {
1213            .Signal => |actual_sig| expected_sig == actual_sig,
1214            else => false,
1215        },
1216        .Stopped => |expected_sig| switch (actual) {
1217            .Stopped => |actual_sig| expected_sig == actual_sig,
1218            else => false,
1219        },
1220        .Unknown => |expected_code| switch (actual) {
1221            .Unknown => |actual_code| expected_code == actual_code,
1222            else => false,
1223        },
1224    } else switch (actual) {
1225        .Exited => true,
1226        else => false,
1227    };
1228}
1229
1230const FuzzContext = struct {
1231    fuzz: *std.Build.Fuzz,
1232    unit_test_index: u32,
1233};
1234
1235fn runCommand(
1236    run: *Run,
1237    argv: []const []const u8,
1238    has_side_effects: bool,
1239    output_dir_path: []const u8,
1240    options: Step.MakeOptions,
1241    fuzz_context: ?FuzzContext,
1242) !void {
1243    const step = &run.step;
1244    const b = step.owner;
1245    const arena = b.allocator;
1246    const gpa = options.gpa;
1247
1248    const cwd: ?[]const u8 = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, step) else null;
1249
1250    try step.handleChildProcUnsupported();
1251    try Step.handleVerbose2(step.owner, cwd, run.env_map, argv);
1252
1253    const allow_skip = switch (run.stdio) {
1254        .check, .zig_test => run.skip_foreign_checks,
1255        else => false,
1256    };
1257
1258    var interp_argv = std.array_list.Managed([]const u8).init(b.allocator);
1259    defer interp_argv.deinit();
1260
1261    var env_map: EnvMap = env: {
1262        const orig = run.env_map orelse &b.graph.env_map;
1263        break :env try orig.clone(gpa);
1264    };
1265    defer env_map.deinit();
1266
1267    color: switch (run.color) {
1268        .manual => {},
1269        .enable => {
1270            try env_map.put("CLICOLOR_FORCE", "1");
1271            env_map.remove("NO_COLOR");
1272        },
1273        .disable => {
1274            try env_map.put("NO_COLOR", "1");
1275            env_map.remove("CLICOLOR_FORCE");
1276        },
1277        .inherit => switch (options.ttyconf) {
1278            .no_color, .windows_api => continue :color .disable,
1279            .escape_codes => continue :color .enable,
1280        },
1281        .auto => {
1282            const capture_stderr = run.captured_stderr != null or switch (run.stdio) {
1283                .check => |checks| checksContainStderr(checks.items),
1284                .infer_from_args, .inherit, .zig_test => false,
1285            };
1286            if (capture_stderr) {
1287                continue :color .disable;
1288            } else {
1289                continue :color .inherit;
1290            }
1291        },
1292    }
1293
1294    const opt_generic_result = spawnChildAndCollect(run, argv, &env_map, has_side_effects, options, fuzz_context) catch |err| term: {
1295        // InvalidExe: cpu arch mismatch
1296        // FileNotFound: can happen with a wrong dynamic linker path
1297        if (err == error.InvalidExe or err == error.FileNotFound) interpret: {
1298            // TODO: learn the target from the binary directly rather than from
1299            // relying on it being a Compile step. This will make this logic
1300            // work even for the edge case that the binary was produced by a
1301            // third party.
1302            const exe = switch (run.argv.items[0]) {
1303                .artifact => |exe| exe.artifact,
1304                else => break :interpret,
1305            };
1306            switch (exe.kind) {
1307                .exe, .@"test" => {},
1308                else => break :interpret,
1309            }
1310
1311            const root_target = exe.rootModuleTarget();
1312            const need_cross_libc = exe.is_linking_libc and
1313                (root_target.isGnuLibC() or (root_target.isMuslLibC() and exe.linkage == .dynamic));
1314            const other_target = exe.root_module.resolved_target.?.result;
1315            switch (std.zig.system.getExternalExecutor(&b.graph.host.result, &other_target, .{
1316                .qemu_fixes_dl = need_cross_libc and b.libc_runtimes_dir != null,
1317                .link_libc = exe.is_linking_libc,
1318            })) {
1319                .native, .rosetta => {
1320                    if (allow_skip) return error.MakeSkipped;
1321                    break :interpret;
1322                },
1323                .wine => |bin_name| {
1324                    if (b.enable_wine) {
1325                        try interp_argv.append(bin_name);
1326                        try interp_argv.appendSlice(argv);
1327
1328                        // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but
1329                        // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired.
1330                        if (env_map.get("WINEDEBUG") == null) {
1331                            try env_map.put("WINEDEBUG", "-all");
1332                        }
1333                    } else {
1334                        return failForeign(run, "-fwine", argv[0], exe);
1335                    }
1336                },
1337                .qemu => |bin_name| {
1338                    if (b.enable_qemu) {
1339                        try interp_argv.append(bin_name);
1340
1341                        if (need_cross_libc) {
1342                            if (b.libc_runtimes_dir) |dir| {
1343                                try interp_argv.append("-L");
1344                                try interp_argv.append(b.pathJoin(&.{
1345                                    dir,
1346                                    try if (root_target.isGnuLibC()) std.zig.target.glibcRuntimeTriple(
1347                                        b.allocator,
1348                                        root_target.cpu.arch,
1349                                        root_target.os.tag,
1350                                        root_target.abi,
1351                                    ) else if (root_target.isMuslLibC()) std.zig.target.muslRuntimeTriple(
1352                                        b.allocator,
1353                                        root_target.cpu.arch,
1354                                        root_target.abi,
1355                                    ) else unreachable,
1356                                }));
1357                            } else return failForeign(run, "--libc-runtimes", argv[0], exe);
1358                        }
1359
1360                        try interp_argv.appendSlice(argv);
1361                    } else return failForeign(run, "-fqemu", argv[0], exe);
1362                },
1363                .darling => |bin_name| {
1364                    if (b.enable_darling) {
1365                        try interp_argv.append(bin_name);
1366                        try interp_argv.appendSlice(argv);
1367                    } else {
1368                        return failForeign(run, "-fdarling", argv[0], exe);
1369                    }
1370                },
1371                .wasmtime => |bin_name| {
1372                    if (b.enable_wasmtime) {
1373                        // https://github.com/bytecodealliance/wasmtime/issues/7384
1374                        //
1375                        // In Wasmtime versions prior to 14, options passed after the module name
1376                        // could be interpreted by Wasmtime if it recognized them. As with many CLI
1377                        // tools, the `--` token is used to stop that behavior and indicate that the
1378                        // remaining arguments are for the WASM program being executed. Historically,
1379                        // we passed `--` after the module name here.
1380                        //
1381                        // After version 14, the `--` can no longer be passed after the module name,
1382                        // but is also not necessary as Wasmtime will no longer try to interpret
1383                        // options after the module name. So, we could just simply omit `--` for
1384                        // newer Wasmtime versions. But to maintain compatibility for older versions
1385                        // that still try to interpret options after the module name, we have moved
1386                        // the `--` before the module name. This appears to work for both old and
1387                        // new Wasmtime versions.
1388                        try interp_argv.append(bin_name);
1389                        try interp_argv.append("--dir=.");
1390                        try interp_argv.append("--");
1391                        try interp_argv.append(argv[0]);
1392                        try interp_argv.appendSlice(argv[1..]);
1393                    } else {
1394                        return failForeign(run, "-fwasmtime", argv[0], exe);
1395                    }
1396                },
1397                .bad_dl => |foreign_dl| {
1398                    if (allow_skip) return error.MakeSkipped;
1399
1400                    const host_dl = b.graph.host.result.dynamic_linker.get() orelse "(none)";
1401
1402                    return step.fail(
1403                        \\the host system is unable to execute binaries from the target
1404                        \\  because the host dynamic linker is '{s}',
1405                        \\  while the target dynamic linker is '{s}'.
1406                        \\  consider setting the dynamic linker or enabling skip_foreign_checks in the Run step
1407                    , .{ host_dl, foreign_dl });
1408                },
1409                .bad_os_or_cpu => {
1410                    if (allow_skip) return error.MakeSkipped;
1411
1412                    const host_name = try b.graph.host.result.zigTriple(b.allocator);
1413                    const foreign_name = try root_target.zigTriple(b.allocator);
1414
1415                    return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{
1416                        host_name, foreign_name,
1417                    });
1418                },
1419            }
1420
1421            if (root_target.os.tag == .windows) {
1422                // On Windows we don't have rpaths so we have to add .dll search paths to PATH
1423                run.addPathForDynLibs(exe);
1424            }
1425
1426            gpa.free(step.result_failed_command.?);
1427            step.result_failed_command = null;
1428            try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items);
1429
1430            break :term spawnChildAndCollect(run, interp_argv.items, &env_map, has_side_effects, options, fuzz_context) catch |e| {
1431                if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;
1432                if (e == error.MakeFailed) return error.MakeFailed; // error already reported
1433                return step.fail("unable to spawn interpreter {s}: {s}", .{
1434                    interp_argv.items[0], @errorName(e),
1435                });
1436            };
1437        }
1438        if (err == error.MakeFailed) return error.MakeFailed; // error already reported
1439
1440        return step.fail("failed to spawn and capture stdio from {s}: {s}", .{ argv[0], @errorName(err) });
1441    };
1442
1443    const generic_result = opt_generic_result orelse {
1444        assert(run.stdio == .zig_test);
1445        // Specific errors have already been reported, and test results are populated. All we need
1446        // to do is report step failure if any test failed.
1447        if (!step.test_results.isSuccess()) return error.MakeFailed;
1448        return;
1449    };
1450
1451    assert(fuzz_context == null);
1452    assert(run.stdio != .zig_test);
1453
1454    // Capture stdout and stderr to GeneratedFile objects.
1455    const Stream = struct {
1456        captured: ?*CapturedStdIo,
1457        bytes: ?[]const u8,
1458    };
1459    for ([_]Stream{
1460        .{
1461            .captured = run.captured_stdout,
1462            .bytes = generic_result.stdout,
1463        },
1464        .{
1465            .captured = run.captured_stderr,
1466            .bytes = generic_result.stderr,
1467        },
1468    }) |stream| {
1469        if (stream.captured) |captured| {
1470            const output_components = .{ output_dir_path, captured.output.basename };
1471            const output_path = try b.cache_root.join(arena, &output_components);
1472            captured.output.generated_file.path = output_path;
1473
1474            const sub_path = b.pathJoin(&output_components);
1475            const sub_path_dirname = fs.path.dirname(sub_path).?;
1476            b.cache_root.handle.makePath(sub_path_dirname) catch |err| {
1477                return step.fail("unable to make path '{f}{s}': {s}", .{
1478                    b.cache_root, sub_path_dirname, @errorName(err),
1479                });
1480            };
1481            const data = switch (captured.trim_whitespace) {
1482                .none => stream.bytes.?,
1483                .all => mem.trim(u8, stream.bytes.?, &std.ascii.whitespace),
1484                .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace),
1485                .trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace),
1486            };
1487            b.cache_root.handle.writeFile(.{ .sub_path = sub_path, .data = data }) catch |err| {
1488                return step.fail("unable to write file '{f}{s}': {s}", .{
1489                    b.cache_root, sub_path, @errorName(err),
1490                });
1491            };
1492        }
1493    }
1494
1495    switch (run.stdio) {
1496        .zig_test => unreachable,
1497        .check => |checks| for (checks.items) |check| switch (check) {
1498            .expect_stderr_exact => |expected_bytes| {
1499                if (!mem.eql(u8, expected_bytes, generic_result.stderr.?)) {
1500                    return step.fail(
1501                        \\========= expected this stderr: =========
1502                        \\{s}
1503                        \\========= but found: ====================
1504                        \\{s}
1505                    , .{
1506                        expected_bytes,
1507                        generic_result.stderr.?,
1508                    });
1509                }
1510            },
1511            .expect_stderr_match => |match| {
1512                if (mem.indexOf(u8, generic_result.stderr.?, match) == null) {
1513                    return step.fail(
1514                        \\========= expected to find in stderr: =========
1515                        \\{s}
1516                        \\========= but stderr does not contain it: =====
1517                        \\{s}
1518                    , .{
1519                        match,
1520                        generic_result.stderr.?,
1521                    });
1522                }
1523            },
1524            .expect_stdout_exact => |expected_bytes| {
1525                if (!mem.eql(u8, expected_bytes, generic_result.stdout.?)) {
1526                    return step.fail(
1527                        \\========= expected this stdout: =========
1528                        \\{s}
1529                        \\========= but found: ====================
1530                        \\{s}
1531                    , .{
1532                        expected_bytes,
1533                        generic_result.stdout.?,
1534                    });
1535                }
1536            },
1537            .expect_stdout_match => |match| {
1538                if (mem.indexOf(u8, generic_result.stdout.?, match) == null) {
1539                    return step.fail(
1540                        \\========= expected to find in stdout: =========
1541                        \\{s}
1542                        \\========= but stdout does not contain it: =====
1543                        \\{s}
1544                    , .{
1545                        match,
1546                        generic_result.stdout.?,
1547                    });
1548                }
1549            },
1550            .expect_term => |expected_term| {
1551                if (!termMatches(expected_term, generic_result.term)) {
1552                    return step.fail("process {f} (expected {f})", .{
1553                        fmtTerm(generic_result.term),
1554                        fmtTerm(expected_term),
1555                    });
1556                }
1557            },
1558        },
1559        else => {
1560            // On failure, report captured stderr like normal standard error output.
1561            const bad_exit = switch (generic_result.term) {
1562                .Exited => |code| code != 0,
1563                .Signal, .Stopped, .Unknown => true,
1564            };
1565            if (bad_exit) {
1566                if (generic_result.stderr) |bytes| {
1567                    run.step.result_stderr = bytes;
1568                }
1569            }
1570
1571            try step.handleChildProcessTerm(generic_result.term);
1572        },
1573    }
1574}
1575
1576const EvalZigTestResult = struct {
1577    test_results: Step.TestResults,
1578    test_metadata: ?TestMetadata,
1579};
1580const EvalGenericResult = struct {
1581    term: std.process.Child.Term,
1582    stdout: ?[]const u8,
1583    stderr: ?[]const u8,
1584};
1585
1586fn spawnChildAndCollect(
1587    run: *Run,
1588    argv: []const []const u8,
1589    env_map: *EnvMap,
1590    has_side_effects: bool,
1591    options: Step.MakeOptions,
1592    fuzz_context: ?FuzzContext,
1593) !?EvalGenericResult {
1594    const b = run.step.owner;
1595    const arena = b.allocator;
1596
1597    if (fuzz_context != null) {
1598        assert(!has_side_effects);
1599        assert(run.stdio == .zig_test);
1600    }
1601
1602    var child = std.process.Child.init(argv, arena);
1603    if (run.cwd) |lazy_cwd| {
1604        child.cwd = lazy_cwd.getPath2(b, &run.step);
1605    }
1606    child.env_map = env_map;
1607    child.request_resource_usage_statistics = true;
1608
1609    child.stdin_behavior = switch (run.stdio) {
1610        .infer_from_args => if (has_side_effects) .Inherit else .Ignore,
1611        .inherit => .Inherit,
1612        .check => .Ignore,
1613        .zig_test => .Pipe,
1614    };
1615    child.stdout_behavior = switch (run.stdio) {
1616        .infer_from_args => if (has_side_effects) .Inherit else .Ignore,
1617        .inherit => .Inherit,
1618        .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore,
1619        .zig_test => .Pipe,
1620    };
1621    child.stderr_behavior = switch (run.stdio) {
1622        .infer_from_args => if (has_side_effects) .Inherit else .Pipe,
1623        .inherit => .Inherit,
1624        .check => .Pipe,
1625        .zig_test => .Pipe,
1626    };
1627    if (run.captured_stdout != null) child.stdout_behavior = .Pipe;
1628    if (run.captured_stderr != null) child.stderr_behavior = .Pipe;
1629    if (run.stdin != .none) {
1630        assert(run.stdio != .inherit);
1631        child.stdin_behavior = .Pipe;
1632    }
1633
1634    // If an error occurs, it's caused by this command:
1635    assert(run.step.result_failed_command == null);
1636    run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child.cwd, argv);
1637
1638    if (run.stdio == .zig_test) {
1639        var timer = try std.time.Timer.start();
1640        const res = try evalZigTest(run, &child, options, fuzz_context);
1641        run.step.result_duration_ns = timer.read();
1642        run.step.test_results = res.test_results;
1643        if (res.test_metadata) |tm| {
1644            run.cached_test_metadata = tm.toCachedTestMetadata();
1645            if (options.web_server) |ws| {
1646                if (b.graph.time_report) {
1647                    ws.updateTimeReportRunTest(
1648                        run,
1649                        &run.cached_test_metadata.?,
1650                        tm.ns_per_test,
1651                    );
1652                }
1653            }
1654        }
1655        return null;
1656    } else {
1657        const inherit = child.stdout_behavior == .Inherit or child.stderr_behavior == .Inherit;
1658        if (!run.disable_zig_progress and !inherit) {
1659            child.progress_node = options.progress_node;
1660        }
1661        if (inherit) std.debug.lockStdErr();
1662        defer if (inherit) std.debug.unlockStdErr();
1663        var timer = try std.time.Timer.start();
1664        const res = try evalGeneric(run, &child);
1665        run.step.result_duration_ns = timer.read();
1666        return .{ .term = res.term, .stdout = res.stdout, .stderr = res.stderr };
1667    }
1668}
1669
1670const StdioPollEnum = enum { stdout, stderr };
1671
1672fn evalZigTest(
1673    run: *Run,
1674    child: *std.process.Child,
1675    options: Step.MakeOptions,
1676    fuzz_context: ?FuzzContext,
1677) !EvalZigTestResult {
1678    const gpa = run.step.owner.allocator;
1679    const arena = run.step.owner.allocator;
1680
1681    // We will update this every time a child runs.
1682    run.step.result_peak_rss = 0;
1683
1684    var result: EvalZigTestResult = .{
1685        .test_results = .{
1686            .test_count = 0,
1687            .skip_count = 0,
1688            .fail_count = 0,
1689            .crash_count = 0,
1690            .timeout_count = 0,
1691            .leak_count = 0,
1692            .log_err_count = 0,
1693        },
1694        .test_metadata = null,
1695    };
1696
1697    while (true) {
1698        try child.spawn();
1699        var poller = std.Io.poll(gpa, StdioPollEnum, .{
1700            .stdout = child.stdout.?,
1701            .stderr = child.stderr.?,
1702        });
1703        var child_killed = false;
1704        defer if (!child_killed) {
1705            _ = child.kill() catch {};
1706            poller.deinit();
1707            run.step.result_peak_rss = @max(
1708                run.step.result_peak_rss,
1709                child.resource_usage_statistics.getMaxRss() orelse 0,
1710            );
1711        };
1712
1713        try child.waitForSpawn();
1714
1715        switch (try pollZigTest(
1716            run,
1717            child,
1718            options,
1719            fuzz_context,
1720            &poller,
1721            &result.test_metadata,
1722            &result.test_results,
1723        )) {
1724            .write_failed => |err| {
1725                // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured
1726                // all available stderr to make our error output as useful as possible.
1727                while (try poller.poll()) {}
1728                run.step.result_stderr = try arena.dupe(u8, poller.reader(.stderr).buffered());
1729
1730                // Clean up everything and wait for the child to exit.
1731                child.stdin.?.close();
1732                child.stdin = null;
1733                poller.deinit();
1734                child_killed = true;
1735                const term = try child.wait();
1736                run.step.result_peak_rss = @max(
1737                    run.step.result_peak_rss,
1738                    child.resource_usage_statistics.getMaxRss() orelse 0,
1739                );
1740
1741                try run.step.addError("unable to write stdin ({t}); test process unexpectedly {f}", .{ err, fmtTerm(term) });
1742                return result;
1743            },
1744            .no_poll => |no_poll| {
1745                // This might be a success (we requested exit and the child dutifully closed stdout) or
1746                // a crash of some kind. Either way, the child will terminate by itself -- wait for it.
1747                const stderr_owned = try arena.dupe(u8, poller.reader(.stderr).buffered());
1748                poller.reader(.stderr).tossBuffered();
1749
1750                // Clean up everything and wait for the child to exit.
1751                child.stdin.?.close();
1752                child.stdin = null;
1753                poller.deinit();
1754                child_killed = true;
1755                const term = try child.wait();
1756                run.step.result_peak_rss = @max(
1757                    run.step.result_peak_rss,
1758                    child.resource_usage_statistics.getMaxRss() orelse 0,
1759                );
1760
1761                if (no_poll.active_test_index) |test_index| {
1762                    // A test was running, so this is definitely a crash. Report it against that
1763                    // test, and continue to the next test.
1764                    result.test_metadata.?.ns_per_test[test_index] = no_poll.ns_elapsed;
1765                    result.test_results.crash_count += 1;
1766                    try run.step.addError("'{s}' {f}{s}{s}", .{
1767                        result.test_metadata.?.testName(test_index),
1768                        fmtTerm(term),
1769                        if (stderr_owned.len != 0) " with stderr:\n" else "",
1770                        std.mem.trim(u8, stderr_owned, "\n"),
1771                    });
1772                    continue;
1773                }
1774
1775                // Report an error if the child terminated uncleanly or if we were still trying to run more tests.
1776                run.step.result_stderr = stderr_owned;
1777                const tests_done = result.test_metadata != null and result.test_metadata.?.next_index == std.math.maxInt(u32);
1778                if (!tests_done or !termMatches(.{ .Exited = 0 }, term)) {
1779                    try run.step.addError("test process unexpectedly {f}", .{fmtTerm(term)});
1780                }
1781                return result;
1782            },
1783            .timeout => |timeout| {
1784                const stderr = poller.reader(.stderr).buffered();
1785                poller.reader(.stderr).tossBuffered();
1786                if (timeout.active_test_index) |test_index| {
1787                    // A test was running. Report the timeout against that test, and continue on to
1788                    // the next test.
1789                    result.test_metadata.?.ns_per_test[test_index] = timeout.ns_elapsed;
1790                    result.test_results.timeout_count += 1;
1791                    try run.step.addError("'{s}' timed out after {D}{s}{s}", .{
1792                        result.test_metadata.?.testName(test_index),
1793                        timeout.ns_elapsed,
1794                        if (stderr.len != 0) " with stderr:\n" else "",
1795                        std.mem.trim(u8, stderr, "\n"),
1796                    });
1797                    continue;
1798                }
1799                // Just log an error and let the child be killed.
1800                run.step.result_stderr = try arena.dupe(u8, stderr);
1801                return run.step.fail("test runner failed to respond for {D}", .{timeout.ns_elapsed});
1802            },
1803        }
1804        comptime unreachable;
1805    }
1806}
1807
1808/// Polls stdout of a Zig test process until a termination condition is reached:
1809/// * A write fails, indicating the child unexpectedly closed stdin
1810/// * A test (or a response from the test runner) times out
1811/// * `poll` fails, indicating the child closed stdout and stderr
1812fn pollZigTest(
1813    run: *Run,
1814    child: *std.process.Child,
1815    options: Step.MakeOptions,
1816    fuzz_context: ?FuzzContext,
1817    poller: *std.Io.Poller(StdioPollEnum),
1818    opt_metadata: *?TestMetadata,
1819    results: *Step.TestResults,
1820) !union(enum) {
1821    write_failed: anyerror,
1822    no_poll: struct {
1823        active_test_index: ?u32,
1824        ns_elapsed: u64,
1825    },
1826    timeout: struct {
1827        active_test_index: ?u32,
1828        ns_elapsed: u64,
1829    },
1830} {
1831    const gpa = run.step.owner.allocator;
1832    const arena = run.step.owner.allocator;
1833    const io = run.step.owner.graph.io;
1834
1835    var sub_prog_node: ?std.Progress.Node = null;
1836    defer if (sub_prog_node) |n| n.end();
1837
1838    if (fuzz_context) |ctx| {
1839        assert(opt_metadata.* == null); // fuzz processes are never restarted
1840        switch (ctx.fuzz.mode) {
1841            .forever => {
1842                sendRunFuzzTestMessage(
1843                    child.stdin.?,
1844                    ctx.unit_test_index,
1845                    .forever,
1846                    0, // instance ID; will be used by multiprocess forever fuzzing in the future
1847                ) catch |err| return .{ .write_failed = err };
1848            },
1849            .limit => |limit| {
1850                sendRunFuzzTestMessage(
1851                    child.stdin.?,
1852                    ctx.unit_test_index,
1853                    .iterations,
1854                    limit.amount,
1855                ) catch |err| return .{ .write_failed = err };
1856            },
1857        }
1858    } else if (opt_metadata.*) |*md| {
1859        // Previous unit test process died or was killed; we're continuing where it left off
1860        requestNextTest(child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
1861    } else {
1862        // Running unit tests normally
1863        run.fuzz_tests.clearRetainingCapacity();
1864        sendMessage(child.stdin.?, .query_test_metadata) catch |err| return .{ .write_failed = err };
1865    }
1866
1867    var active_test_index: ?u32 = null;
1868
1869    // `null` means this host does not support `std.time.Timer`. This timer is `reset()` whenever we
1870    // change `active_test_index`, i.e. whenever a test starts or finishes.
1871    var timer: ?std.time.Timer = std.time.Timer.start() catch null;
1872
1873    var coverage_id: ?u64 = null;
1874
1875    // This timeout is used when we're waiting on the test runner itself rather than a user-specified
1876    // test. For instance, if the test runner leaves this much time between us requesting a test to
1877    // start and it acknowledging the test starting, we terminate the child and raise an error. This
1878    // *should* never happen, but could in theory be caused by some very unlucky IB in a test.
1879    const response_timeout_ns: ?u64 = ns: {
1880        if (fuzz_context != null) break :ns null; // don't timeout fuzz tests
1881        break :ns @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s);
1882    };
1883
1884    const stdout = poller.reader(.stdout);
1885    const stderr = poller.reader(.stderr);
1886
1887    while (true) {
1888        const Header = std.zig.Server.Message.Header;
1889
1890        // This block is exited when `stdout` contains enough bytes for a `Header`.
1891        header_ready: {
1892            if (stdout.buffered().len >= @sizeOf(Header)) {
1893                // We already have one, no need to poll!
1894                break :header_ready;
1895            }
1896
1897            // Always `null` if `timer` is `null`.
1898            const opt_timeout_ns: ?u64 = ns: {
1899                if (timer == null) break :ns null;
1900                if (active_test_index == null) break :ns response_timeout_ns;
1901                break :ns options.unit_test_timeout_ns;
1902            };
1903
1904            if (opt_timeout_ns) |timeout_ns| {
1905                const remaining_ns = timeout_ns -| timer.?.read();
1906                if (!try poller.pollTimeout(remaining_ns)) return .{ .no_poll = .{
1907                    .active_test_index = active_test_index,
1908                    .ns_elapsed = if (timer) |*t| t.read() else 0,
1909                } };
1910            } else {
1911                if (!try poller.poll()) return .{ .no_poll = .{
1912                    .active_test_index = active_test_index,
1913                    .ns_elapsed = if (timer) |*t| t.read() else 0,
1914                } };
1915            }
1916
1917            if (stdout.buffered().len >= @sizeOf(Header)) {
1918                // There wasn't a header before, but there is one after the `poll`.
1919                break :header_ready;
1920            }
1921
1922            if (opt_timeout_ns) |timeout_ns| {
1923                const cur_ns = timer.?.read();
1924                if (cur_ns >= timeout_ns) return .{ .timeout = .{
1925                    .active_test_index = active_test_index,
1926                    .ns_elapsed = cur_ns,
1927                } };
1928            }
1929            continue;
1930        }
1931        // There is definitely a header available now -- read it.
1932        const header = stdout.takeStruct(Header, .little) catch unreachable;
1933
1934        while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) return .{ .no_poll = .{
1935            .active_test_index = active_test_index,
1936            .ns_elapsed = if (timer) |*t| t.read() else 0,
1937        } };
1938        const body = stdout.take(header.bytes_len) catch unreachable;
1939        var body_r: std.Io.Reader = .fixed(body);
1940        switch (header.tag) {
1941            .zig_version => {
1942                if (!std.mem.eql(u8, builtin.zig_version_string, body)) return run.step.fail(
1943                    "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
1944                    .{ builtin.zig_version_string, body },
1945                );
1946            },
1947            .test_metadata => {
1948                assert(fuzz_context == null);
1949
1950                // `metadata` would only be populated if we'd already seen a `test_metadata`, but we
1951                // only request it once (and importantly, we don't re-request it if we kill and
1952                // restart the test runner).
1953                assert(opt_metadata.* == null);
1954
1955                const tm_hdr = body_r.takeStruct(std.zig.Server.Message.TestMetadata, .little) catch unreachable;
1956                results.test_count = tm_hdr.tests_len;
1957
1958                const names = try arena.alloc(u32, results.test_count);
1959                for (names) |*dest| dest.* = body_r.takeInt(u32, .little) catch unreachable;
1960
1961                const expected_panic_msgs = try arena.alloc(u32, results.test_count);
1962                for (expected_panic_msgs) |*dest| dest.* = body_r.takeInt(u32, .little) catch unreachable;
1963
1964                const string_bytes = body_r.take(tm_hdr.string_bytes_len) catch unreachable;
1965
1966                options.progress_node.setEstimatedTotalItems(names.len);
1967                opt_metadata.* = .{
1968                    .string_bytes = try arena.dupe(u8, string_bytes),
1969                    .ns_per_test = try arena.alloc(u64, results.test_count),
1970                    .names = names,
1971                    .expected_panic_msgs = expected_panic_msgs,
1972                    .next_index = 0,
1973                    .prog_node = options.progress_node,
1974                };
1975                @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64));
1976
1977                active_test_index = null;
1978                if (timer) |*t| t.reset();
1979
1980                requestNextTest(child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };
1981            },
1982            .test_started => {
1983                active_test_index = opt_metadata.*.?.next_index - 1;
1984                if (timer) |*t| t.reset();
1985            },
1986            .test_results => {
1987                assert(fuzz_context == null);
1988                const md = &opt_metadata.*.?;
1989
1990                const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable;
1991                assert(tr_hdr.index == active_test_index);
1992
1993                switch (tr_hdr.flags.status) {
1994                    .pass => {},
1995                    .skip => results.skip_count +|= 1,
1996                    .fail => results.fail_count +|= 1,
1997                }
1998                const leak_count = tr_hdr.flags.leak_count;
1999                const log_err_count = tr_hdr.flags.log_err_count;
2000                results.leak_count +|= leak_count;
2001                results.log_err_count +|= log_err_count;
2002
2003                if (tr_hdr.flags.fuzz) try run.fuzz_tests.append(gpa, tr_hdr.index);
2004
2005                if (tr_hdr.flags.status == .fail) {
2006                    const name = std.mem.sliceTo(md.testName(tr_hdr.index), 0);
2007                    const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
2008                    stderr.tossBuffered();
2009                    if (stderr_bytes.len == 0) {
2010                        try run.step.addError("'{s}' failed without output", .{name});
2011                    } else {
2012                        try run.step.addError("'{s}' failed:\n{s}", .{ name, stderr_bytes });
2013                    }
2014                } else if (leak_count > 0) {
2015                    const name = std.mem.sliceTo(md.testName(tr_hdr.index), 0);
2016                    const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
2017                    stderr.tossBuffered();
2018                    try run.step.addError("'{s}' leaked {d} allocations:\n{s}", .{ name, leak_count, stderr_bytes });
2019                } else if (log_err_count > 0) {
2020                    const name = std.mem.sliceTo(md.testName(tr_hdr.index), 0);
2021                    const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
2022                    stderr.tossBuffered();
2023                    try run.step.addError("'{s}' logged {d} errors:\n{s}", .{ name, log_err_count, stderr_bytes });
2024                }
2025
2026                active_test_index = null;
2027                if (timer) |*t| md.ns_per_test[tr_hdr.index] = t.lap();
2028
2029                requestNextTest(child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
2030            },
2031            .coverage_id => {
2032                coverage_id = body_r.takeInt(u64, .little) catch unreachable;
2033                const cumulative_runs = body_r.takeInt(u64, .little) catch unreachable;
2034                const cumulative_unique = body_r.takeInt(u64, .little) catch unreachable;
2035                const cumulative_coverage = body_r.takeInt(u64, .little) catch unreachable;
2036
2037                {
2038                    const fuzz = fuzz_context.?.fuzz;
2039                    fuzz.queue_mutex.lockUncancelable(io);
2040                    defer fuzz.queue_mutex.unlock(io);
2041                    try fuzz.msg_queue.append(fuzz.gpa, .{ .coverage = .{
2042                        .id = coverage_id.?,
2043                        .cumulative = .{
2044                            .runs = cumulative_runs,
2045                            .unique = cumulative_unique,
2046                            .coverage = cumulative_coverage,
2047                        },
2048                        .run = run,
2049                    } });
2050                    fuzz.queue_cond.signal(io);
2051                }
2052            },
2053            .fuzz_start_addr => {
2054                const fuzz = fuzz_context.?.fuzz;
2055                const addr = body_r.takeInt(u64, .little) catch unreachable;
2056                {
2057                    fuzz.queue_mutex.lockUncancelable(io);
2058                    defer fuzz.queue_mutex.unlock(io);
2059                    try fuzz.msg_queue.append(fuzz.gpa, .{ .entry_point = .{
2060                        .addr = addr,
2061                        .coverage_id = coverage_id.?,
2062                    } });
2063                    fuzz.queue_cond.signal(io);
2064                }
2065            },
2066            else => {}, // ignore other messages
2067        }
2068    }
2069}
2070
2071const TestMetadata = struct {
2072    names: []const u32,
2073    ns_per_test: []u64,
2074    expected_panic_msgs: []const u32,
2075    string_bytes: []const u8,
2076    next_index: u32,
2077    prog_node: std.Progress.Node,
2078
2079    fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata {
2080        return .{
2081            .names = tm.names,
2082            .string_bytes = tm.string_bytes,
2083        };
2084    }
2085
2086    fn testName(tm: TestMetadata, index: u32) []const u8 {
2087        return tm.toCachedTestMetadata().testName(index);
2088    }
2089};
2090
2091pub const CachedTestMetadata = struct {
2092    names: []const u32,
2093    string_bytes: []const u8,
2094
2095    pub fn testName(tm: CachedTestMetadata, index: u32) []const u8 {
2096        return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0);
2097    }
2098};
2099
2100fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void {
2101    while (metadata.next_index < metadata.names.len) {
2102        const i = metadata.next_index;
2103        metadata.next_index += 1;
2104
2105        if (metadata.expected_panic_msgs[i] != 0) continue;
2106
2107        const name = metadata.testName(i);
2108        if (sub_prog_node.*) |n| n.end();
2109        sub_prog_node.* = metadata.prog_node.start(name, 0);
2110
2111        try sendRunTestMessage(in, .run_test, i);
2112        return;
2113    } else {
2114        metadata.next_index = std.math.maxInt(u32); // indicate that all tests are done
2115        try sendMessage(in, .exit);
2116    }
2117}
2118
2119fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
2120    const header: std.zig.Client.Message.Header = .{
2121        .tag = tag,
2122        .bytes_len = 0,
2123    };
2124    var w = file.writer(&.{});
2125    w.interface.writeStruct(header, .little) catch |err| switch (err) {
2126        error.WriteFailed => return w.err.?,
2127    };
2128}
2129
2130fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index: u32) !void {
2131    const header: std.zig.Client.Message.Header = .{
2132        .tag = tag,
2133        .bytes_len = 4,
2134    };
2135    var w = file.writer(&.{});
2136    w.interface.writeStruct(header, .little) catch |err| switch (err) {
2137        error.WriteFailed => return w.err.?,
2138    };
2139    w.interface.writeInt(u32, index, .little) catch |err| switch (err) {
2140        error.WriteFailed => return w.err.?,
2141    };
2142}
2143
2144fn sendRunFuzzTestMessage(
2145    file: std.fs.File,
2146    index: u32,
2147    kind: std.Build.abi.fuzz.LimitKind,
2148    amount_or_instance: u64,
2149) !void {
2150    const header: std.zig.Client.Message.Header = .{
2151        .tag = .start_fuzzing,
2152        .bytes_len = 4 + 1 + 8,
2153    };
2154    var w = file.writer(&.{});
2155    w.interface.writeStruct(header, .little) catch |err| switch (err) {
2156        error.WriteFailed => return w.err.?,
2157    };
2158    w.interface.writeInt(u32, index, .little) catch |err| switch (err) {
2159        error.WriteFailed => return w.err.?,
2160    };
2161    w.interface.writeByte(@intFromEnum(kind)) catch |err| switch (err) {
2162        error.WriteFailed => return w.err.?,
2163    };
2164    w.interface.writeInt(u64, amount_or_instance, .little) catch |err| switch (err) {
2165        error.WriteFailed => return w.err.?,
2166    };
2167}
2168
2169fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
2170    const b = run.step.owner;
2171    const io = b.graph.io;
2172    const arena = b.allocator;
2173
2174    try child.spawn();
2175    errdefer _ = child.kill() catch {};
2176
2177    try child.waitForSpawn();
2178
2179    switch (run.stdin) {
2180        .bytes => |bytes| {
2181            child.stdin.?.writeAll(bytes) catch |err| {
2182                return run.step.fail("unable to write stdin: {s}", .{@errorName(err)});
2183            };
2184            child.stdin.?.close();
2185            child.stdin = null;
2186        },
2187        .lazy_path => |lazy_path| {
2188            const path = lazy_path.getPath3(b, &run.step);
2189            const file = path.root_dir.handle.openFile(path.subPathOrDot(), .{}) catch |err| {
2190                return run.step.fail("unable to open stdin file: {s}", .{@errorName(err)});
2191            };
2192            defer file.close();
2193            // TODO https://github.com/ziglang/zig/issues/23955
2194            var read_buffer: [1024]u8 = undefined;
2195            var file_reader = file.reader(io, &read_buffer);
2196            var write_buffer: [1024]u8 = undefined;
2197            var stdin_writer = child.stdin.?.writer(&write_buffer);
2198            _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
2199                error.ReadFailed => return run.step.fail("failed to read from {f}: {t}", .{
2200                    path, file_reader.err.?,
2201                }),
2202                error.WriteFailed => return run.step.fail("failed to write to stdin: {t}", .{
2203                    stdin_writer.err.?,
2204                }),
2205            };
2206            stdin_writer.interface.flush() catch |err| switch (err) {
2207                error.WriteFailed => return run.step.fail("failed to write to stdin: {t}", .{
2208                    stdin_writer.err.?,
2209                }),
2210            };
2211            child.stdin.?.close();
2212            child.stdin = null;
2213        },
2214        .none => {},
2215    }
2216
2217    var stdout_bytes: ?[]const u8 = null;
2218    var stderr_bytes: ?[]const u8 = null;
2219
2220    run.stdio_limit = run.stdio_limit.min(.limited(run.max_stdio_size));
2221    if (child.stdout) |stdout| {
2222        if (child.stderr) |stderr| {
2223            var poller = std.Io.poll(arena, enum { stdout, stderr }, .{
2224                .stdout = stdout,
2225                .stderr = stderr,
2226            });
2227            defer poller.deinit();
2228
2229            while (try poller.poll()) {
2230                if (run.stdio_limit.toInt()) |limit| {
2231                    if (poller.reader(.stderr).buffered().len > limit)
2232                        return error.StdoutStreamTooLong;
2233                    if (poller.reader(.stderr).buffered().len > limit)
2234                        return error.StderrStreamTooLong;
2235                }
2236            }
2237
2238            stdout_bytes = try poller.toOwnedSlice(.stdout);
2239            stderr_bytes = try poller.toOwnedSlice(.stderr);
2240        } else {
2241            var stdout_reader = stdout.readerStreaming(io, &.{});
2242            stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
2243                error.OutOfMemory => return error.OutOfMemory,
2244                error.ReadFailed => return stdout_reader.err.?,
2245                error.StreamTooLong => return error.StdoutStreamTooLong,
2246            };
2247        }
2248    } else if (child.stderr) |stderr| {
2249        var stderr_reader = stderr.readerStreaming(io, &.{});
2250        stderr_bytes = stderr_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
2251            error.OutOfMemory => return error.OutOfMemory,
2252            error.ReadFailed => return stderr_reader.err.?,
2253            error.StreamTooLong => return error.StderrStreamTooLong,
2254        };
2255    }
2256
2257    if (stderr_bytes) |bytes| if (bytes.len > 0) {
2258        // Treat stderr as an error message.
2259        const stderr_is_diagnostic = run.captured_stderr == null and switch (run.stdio) {
2260            .check => |checks| !checksContainStderr(checks.items),
2261            else => true,
2262        };
2263        if (stderr_is_diagnostic) {
2264            run.step.result_stderr = bytes;
2265        }
2266    };
2267
2268    run.step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0;
2269
2270    return .{
2271        .term = try child.wait(),
2272        .stdout = stdout_bytes,
2273        .stderr = stderr_bytes,
2274    };
2275}
2276
2277fn addPathForDynLibs(run: *Run, artifact: *Step.Compile) void {
2278    const b = run.step.owner;
2279    const compiles = artifact.getCompileDependencies(true);
2280    for (compiles) |compile| {
2281        if (compile.root_module.resolved_target.?.result.os.tag == .windows and
2282            compile.isDynamicLibrary())
2283        {
2284            addPathDir(run, fs.path.dirname(compile.getEmittedBin().getPath2(b, &run.step)).?);
2285        }
2286    }
2287}
2288
2289fn failForeign(
2290    run: *Run,
2291    suggested_flag: []const u8,
2292    argv0: []const u8,
2293    exe: *Step.Compile,
2294) error{ MakeFailed, MakeSkipped, OutOfMemory } {
2295    switch (run.stdio) {
2296        .check, .zig_test => {
2297            if (run.skip_foreign_checks)
2298                return error.MakeSkipped;
2299
2300            const b = run.step.owner;
2301            const host_name = try b.graph.host.result.zigTriple(b.allocator);
2302            const foreign_name = try exe.rootModuleTarget().zigTriple(b.allocator);
2303
2304            return run.step.fail(
2305                \\unable to spawn foreign binary '{s}' ({s}) on host system ({s})
2306                \\  consider using {s} or enabling skip_foreign_checks in the Run step
2307            , .{ argv0, foreign_name, host_name, suggested_flag });
2308        },
2309        else => {
2310            return run.step.fail("unable to spawn foreign binary '{s}'", .{argv0});
2311        },
2312    }
2313}
2314
2315fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void {
2316    switch (stdio) {
2317        .infer_from_args, .inherit, .zig_test => {},
2318        .check => |checks| for (checks.items) |check| {
2319            hh.add(@as(std.meta.Tag(StdIo.Check), check));
2320            switch (check) {
2321                .expect_stderr_exact,
2322                .expect_stderr_match,
2323                .expect_stdout_exact,
2324                .expect_stdout_match,
2325                => |s| hh.addBytes(s),
2326
2327                .expect_term => |term| {
2328                    hh.add(@as(std.meta.Tag(std.process.Child.Term), term));
2329                    switch (term) {
2330                        .Exited => |x| hh.add(x),
2331                        .Signal, .Stopped, .Unknown => |x| hh.add(x),
2332                    }
2333                },
2334            }
2335        },
2336    }
2337}