Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

runCommand

Run.runCommand
fn runCommand(
    arena: Allocator,
    run: *Run,
    run_index: Configuration.Step.Index,
    maker: *Maker,
    progress_node: std.Progress.Node,
    argv: []const []const u8,
    has_side_effects: bool,
    output_dir_path: []const u8,
    fuzz_context: ?FuzzContext,
) Step.ExtendedMakeError!void

File

Code

fn runCommand(
    arena: Allocator,
    run: *Run,
    run_index: Configuration.Step.Index,
    maker: *Maker,
    progress_node: std.Progress.Node,
    argv: []const []const u8,
    has_side_effects: bool,
    output_dir_path: []const u8,
    fuzz_context: ?FuzzContext,
) Step.ExtendedMakeError!void {
    const graph = maker.graph;
    const gpa = maker.gpa;
    const step = maker.stepByIndex(run_index);
    const io = graph.io;
    const cache_root = graph.local_cache_root;
    const conf = &maker.scanned_config.configuration;
    const conf_step = run_index.ptr(conf);
    const conf_run = conf_step.extended.get(conf.extra).run;

    const cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd|
        .{ .path = try maker.resolveLazyPathIndexAbs(arena, lazy_cwd, run_index) }
    else
        .inherit;

    const allow_skip = switch (conf_run.flags.stdio) {
        .check, .zig_test => conf_run.flags.skip_foreign_checks,
        else => false,
    };

    var interp_argv: std.ArrayList([]const u8) = .empty;

    var environ_map: std.process.Environ.Map = .init(gpa);
    defer environ_map.deinit();

    // In either case we add to this mutatable data structure so that we can
    // tweak the environment below.
    if (conf_run.environ_map.value) |env_map_index| {
        const conf_env_map = env_map_index.get(conf);
        for (conf_env_map.keys.slice(conf), conf_env_map.values.slice(conf)) |k, v| {
            try environ_map.put(k.slice(conf), v.slice(conf));
        }
    } else {
        try environ_map.putAll(&graph.environ_map);
    }

    // Now that we have the environ map, we might need to mutate it to insert
    // .dll search paths because Windows doesn't have rpaths.
    const arg0 = conf_run.args.slice[0].get(conf);
    if (arg0.producer.value) |producer_index| {
        const producer_step = producer_index.ptr(conf);
        const producer = producer_step.extended.get(conf.extra).compile;
        const root_module = producer.root_module.get(conf);
        const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf);
        if (root_module_target.flags.os_tag == .windows) {
            try addPathForDynLibs(maker, arena, producer_index, &environ_map, argv[0]);
        }
    }

    const cwd_string = switch (cwd) {
        .path => |p| p,
        .dir => unreachable,
        .inherit => null,
    };
    try graph.handleVerbose(cwd_string, &environ_map, argv);

    const opt_generic_result = spawnChildAndCollect(
        arena,
        run_index,
        run,
        maker,
        progress_node,
        argv,
        &environ_map,
        has_side_effects,
        fuzz_context,
    ) catch |err| term: {
        switch (err) {
            error.InvalidExe, // cpu arch mismatch
            error.FileNotFound, // can happen with a wrong dynamic linker path
            => interpret: {
                const producer_index = arg0.producer.value orelse break :interpret;
                const producer_step = producer_index.ptr(conf);
                const producer = producer_step.extended.get(conf.extra).compile;
                switch (producer.flags3.kind) {
                    .exe, .@"test" => {},
                    else => break :interpret,
                }
                const root_module = producer.root_module.get(conf);
                const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf);
                const root_target = root_module_target.unwrapTarget(conf);
                const link_libc = maker.stepByIndex(producer_index).extended.compile.is_linking_libc;

                const host: std.Target = std.zig.system.resolveTargetQuery(io, .{}) catch |he| switch (he) {
                    error.Canceled => |e| return e,
                    else => builtin.target,
                };

                const need_cross_libc = link_libc and root_target.os.tag == .linux and
                    switch (producer.flags2.linkage) {
                        .static => false,
                        .dynamic => true,
                        .default => root_target.isGnuLibC(),
                    };
                switch (std.zig.system.getExternalExecutor(io, &root_target, .{
                    .host_cpu_arch = host.cpu.arch,
                    .host_os_tag = host.os.tag,
                    .qemu_fixes_dl = need_cross_libc and graph.libc_runtimes_dir != null,
                    .link_libc = link_libc,
                })) {
                    .native, .rosetta => {
                        if (allow_skip) return error.MakeSkipped;
                        break :interpret;
                    },
                    .wine => |bin_name| {
                        if (graph.enable_wine) {
                            try interp_argv.ensureUnusedCapacity(arena, 1 + argv.len);
                            interp_argv.appendAssumeCapacity(bin_name);
                            interp_argv.appendSliceAssumeCapacity(argv);

                            // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but
                            // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired.
                            if (environ_map.get("WINEDEBUG") == null) {
                                try environ_map.put("WINEDEBUG", "-all");
                            }
                        } else {
                            return failForeign(arena, &conf_run, maker, run_index, "-fwine", argv[0], &root_target, &host);
                        }
                    },
                    .qemu => |bin_name| {
                        if (graph.enable_qemu) {
                            try interp_argv.ensureUnusedCapacity(arena, 3 + argv.len);
                            interp_argv.appendAssumeCapacity(bin_name);

                            if (need_cross_libc) {
                                if (graph.libc_runtimes_dir) |dir| {
                                    interp_argv.appendAssumeCapacity("-L");
                                    interp_argv.appendAssumeCapacity(try Dir.path.join(arena, &.{
                                        dir,
                                        try if (root_target.isGnuLibC()) std.zig.target.glibcRuntimeTriple(
                                            arena,
                                            root_target.cpu.arch,
                                            root_target.os.tag,
                                            root_target.abi,
                                        ) else if (root_target.isMuslLibC()) std.zig.target.muslRuntimeTriple(
                                            arena,
                                            root_target.cpu.arch,
                                            root_target.abi,
                                        ) else unreachable,
                                    }));
                                } else return failForeign(arena, &conf_run, maker, run_index, "--libc-runtimes", argv[0], &root_target, &host);
                            }

                            interp_argv.appendSliceAssumeCapacity(argv);
                        } else return failForeign(arena, &conf_run, maker, run_index, "-fqemu", argv[0], &root_target, &host);
                    },
                    .darling => |bin_name| {
                        if (graph.enable_darling) {
                            try interp_argv.ensureUnusedCapacity(arena, 1 + argv.len);
                            interp_argv.appendAssumeCapacity(bin_name);
                            interp_argv.appendSliceAssumeCapacity(argv);
                        } else {
                            return failForeign(arena, &conf_run, maker, run_index, "-fdarling", argv[0], &root_target, &host);
                        }
                    },
                    .wasmtime => |bin_name| {
                        if (graph.enable_wasmtime) {
                            try interp_argv.ensureUnusedCapacity(arena, 3 + argv.len + conf_run.preopen_names.slice.len);
                            interp_argv.appendAssumeCapacity(bin_name);
                            interp_argv.appendAssumeCapacity("--dir=.");
                            for (conf_run.preopen_names.slice, conf_run.preopen_paths.slice) |name, lazy_path| {
                                const path = try maker.resolveLazyPath(arena, lazy_path.get(conf), run_index);
                                path.root_dir.handle.createDirPath(io, path.subPathOrDot()) catch |e|
                                    return step.fail(maker, "failed creating directory {f}: {t}", .{ path, e });
                                interp_argv.appendAssumeCapacity(try arena.print("--dir={f}::{s}", .{ path, name.slice(conf) }));
                            }
                            // Wasmtime doeesn't inherit environment variables from the parent process
                            // by default. '-S inherit-env' was added in Wasmtime version 20.
                            interp_argv.appendAssumeCapacity("-Sinherit-env");
                            interp_argv.appendSliceAssumeCapacity(argv);

                            // Enable more detailed backtraces by default, but allow the user to override this (e.g.
                            // with `WASMTIME_BACKTRACE_DETAILS=0`) if desired.
                            if (environ_map.get("WASMTIME_BACKTRACE_DETAILS") == null) {
                                try environ_map.put("WASMTIME_BACKTRACE_DETAILS", "1");
                            }
                        } else {
                            return failForeign(arena, &conf_run, maker, run_index, "-fwasmtime", argv[0], &root_target, &host);
                        }
                    },
                    .bad_dl => |foreign_dl| {
                        if (allow_skip) return error.MakeSkipped;

                        const host_dl = host.dynamic_linker.get() orelse "(none)";

                        return step.fail(maker,
                            \\the host system is unable to execute binaries from the target
                            \\  because the host dynamic linker is '{s}',
                            \\  while the target dynamic linker is '{s}'.
                            \\  consider setting the dynamic linker or enabling skip_foreign_checks in the Run step
                        , .{ host_dl, foreign_dl });
                    },
                    .bad_os_or_cpu => {
                        if (allow_skip) return error.MakeSkipped;

                        const host_name = try host.zigTriple(arena);
                        const foreign_name = try root_target.zigTriple(arena);

                        return step.fail(maker, "the host system ({s}) is unable to execute binaries from the target ({s})", .{
                            host_name, foreign_name,
                        });
                    },
                }

                step.clearFailedCommand(gpa);
                try graph.handleVerbose(cwd_string, &environ_map, interp_argv.items);

                break :term spawnChildAndCollect(
                    arena,
                    run_index,
                    run,
                    maker,
                    progress_node,
                    interp_argv.items,
                    &environ_map,
                    has_side_effects,
                    fuzz_context,
                ) catch |e| {
                    if (!conf_run.flags.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;
                    if (e == error.MakeFailed) return error.MakeFailed; // error already reported
                    return step.fail(maker, "unable to spawn interpreter {s}: {t}", .{ interp_argv.items[0], e });
                };
            },
            error.MakeFailed, error.OutOfMemory, error.Canceled => |e| return e,
            else => {},
        }
        return step.fail(maker, "failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err });
    };

    const generic_result = opt_generic_result orelse {
        assert(conf_run.flags.stdio == .zig_test);
        // Specific errors have already been reported, and test results are populated. All we need
        // to do is report step failure if any test failed.
        if (!step.test_results.isSuccess()) return error.MakeFailed;
        return;
    };

    assert(fuzz_context == null);
    assert(conf_run.flags.stdio != .zig_test);

    // Capture stdout and stderr to GeneratedFile objects.
    const Stream = struct {
        captured: ?Configuration.Step.Run.CapturedStream,
        bytes: ?[]const u8,
        trim_whitespace: Configuration.Step.Run.TrimWhitespace,
    };
    for (&[_]Stream{
        .{
            .captured = conf_run.captured_stdout.value,
            .bytes = generic_result.stdout,
            .trim_whitespace = conf_run.flags.stdout_trim_whitespace,
        },
        .{
            .captured = conf_run.captured_stderr.value,
            .bytes = generic_result.stderr,
            .trim_whitespace = conf_run.flags.stderr_trim_whitespace,
        },
    }) |*stream| {
        if (stream.captured) |captured| {
            const output_path: Path = .{
                .root_dir = cache_root,
                .sub_path = try Dir.path.join(graph.arena, &.{
                    output_dir_path, captured.basename.slice(conf),
                }),
            };
            maker.generatedPath(captured.generated_file).* = output_path;

            const sub_path_parent = output_path.dirname().?;
            sub_path_parent.root_dir.handle.createDirPath(io, sub_path_parent.sub_path) catch |err|
                return step.fail(maker, "unable to make path {f}: {t}", .{ sub_path_parent, err });

            const data = switch (stream.trim_whitespace) {
                .none => stream.bytes.?,
                .all => mem.trim(u8, stream.bytes.?, &std.ascii.whitespace),
                .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace),
                .trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace),
            };
            output_path.root_dir.handle.writeFile(io, .{
                .sub_path = output_path.sub_path,
                .data = data,
            }) catch |err| return step.fail(maker, "unable to write file {f}: {t}", .{ output_path, err });
        }
    }

    switch (conf_run.flags.stdio) {
        .zig_test => unreachable,
        .check => {
            if (conf_run.expect_stderr_exact.value) |bytes| {
                const expected_bytes = bytes.slice(conf);
                if (!mem.eql(u8, expected_bytes, generic_result.stderr.?)) {
                    return step.fail(maker,
                        \\========= expected this stderr: =========
                        \\{s}
                        \\========= but found: ====================
                        \\{s}
                    , .{
                        expected_bytes,
                        generic_result.stderr.?,
                    });
                }
            }
            if (conf_run.expect_stdout_exact.value) |bytes| {
                const expected_bytes = bytes.slice(conf);
                if (!mem.eql(u8, expected_bytes, generic_result.stdout.?)) {
                    return step.fail(maker,
                        \\========= expected this stdout: =========
                        \\{s}
                        \\========= but found: ====================
                        \\{s}
                    , .{
                        expected_bytes,
                        generic_result.stdout.?,
                    });
                }
            }
            for (conf_run.expect_stderr_match.slice) |bytes| {
                const match = bytes.slice(conf);
                if (mem.find(u8, generic_result.stderr.?, match) == null) {
                    return step.fail(maker,
                        \\========= expected to find in stderr: =========
                        \\{s}
                        \\========= but stderr does not contain it: =====
                        \\{s}
                    , .{
                        match,
                        generic_result.stderr.?,
                    });
                }
            }
            for (conf_run.expect_stdout_match.slice) |bytes| {
                const match = bytes.slice(conf);
                if (mem.find(u8, generic_result.stdout.?, match) == null) {
                    return step.fail(maker,
                        \\========= expected to find in stdout: =========
                        \\{s}
                        \\========= but stdout does not contain it: =====
                        \\{s}
                    , .{
                        match,
                        generic_result.stdout.?,
                    });
                }
            }
            if (conf_run.expect_term_value.value) |expected_term_value| {
                const expected_term: process.Child.Term = switch (conf_run.flags2.expect_term_status) {
                    .exited => .{ .exited = @intCast(expected_term_value) },
                    .signal => .{ .signal = @fromBackingInt(@intCast(expected_term_value)) },
                    .stopped => .{ .stopped = @fromBackingInt(@intCast(expected_term_value)) },
                    .unknown => .{ .unknown = expected_term_value },
                };
                if (!termMatches(expected_term, generic_result.term)) {
                    return step.fail(maker, "process {f} (expected {f})", .{
                        fmtTerm(generic_result.term),
                        fmtTerm(expected_term),
                    });
                }
            }
            const snapshots: []const ?struct {
                path: Cache.Path,
                result: enum { stderr, stdout },
            } = &.{
                if (conf_run.expect_stderr_snapshot.value) |path| .{
                    .path = try maker.resolveLazyPathIndex(arena, path, run_index),
                    .result = .stderr,
                } else null,
                if (conf_run.expect_stdout_snapshot.value) |path| .{
                    .path = try maker.resolveLazyPathIndex(arena, path, run_index),
                    .result = .stdout,
                } else null,
            };
            for (snapshots) |opt_snapshot| {
                const snapshot = opt_snapshot orelse continue;

                const file = snapshot.path.root_dir.handle.openFile(io, snapshot.path.sub_path, .{}) catch |err|
                    return step.fail(maker, "unable to open snapshot file {f}: {t}", .{ snapshot.path, err });
                defer file.close(io);

                var file_reader = file.reader(io, &.{});
                const snapshot_contents = file_reader.interface.allocRemaining(gpa, .unlimited) catch |err|
                    return step.fail(maker, "unable to read snapshot file {f}: {t}", .{ snapshot.path, err });
                defer gpa.free(snapshot_contents);

                const result = switch (snapshot.result) {
                    .stderr => generic_result.stderr.?,
                    .stdout => generic_result.stdout.?,
                };
                if (std.mem.findDiff(u8, snapshot_contents, result)) |diff_index| {
                    var diff_line_number: usize = 1;

                    for (snapshot_contents[0..diff_index]) |value| {
                        if (value == '\n') diff_line_number += 1;
                    }

                    return step.fail(maker,
                        \\
                        \\========= snapshot file: =========
                        \\{f}
                        \\========= contained: =============
                        \\{s}
                        \\========= {t} output was: ========
                        \\{s}
                        \\==================================
                        \\first difference on line {d}:
                        \\expected:
                        \\{f}
                        \\found:
                        \\{f}
                    , .{
                        snapshot.path,
                        snapshot_contents,
                        snapshot.result,
                        result,
                        diff_line_number,
                        fmtSnapshotIndicatorLine(snapshot_contents, diff_index),
                        fmtSnapshotIndicatorLine(result, diff_index),
                    });
                }
            }
        },
        else => {
            // On failure, report captured stderr like normal standard error output.
            if (!generic_result.term.success()) {
                if (generic_result.stderr) |bytes| {
                    try step.setResultStderr(gpa, bytes);
                }
            }
            try step.handleChildProcessTerm(maker, generic_result.term);
        },
    }
}