DoxigAlpha

make

Function parameters

Parameters

#
step:*Step
options:Step.MakeOptions

Type definitions in this namespace

Types

#

Functions in this namespace

Functions

#
addOutputFileArg
Provides a file path as a command line argument to the command being run.
addPrefixedOutputFileArg
Provides a file path as a command line argument to the command being run.
addFileArg
Appends an input file to the command line arguments.
addPrefixedFileArg
Appends an input file to the command line arguments prepended with a string.
addOutputDirectoryArg
Provides a directory path as a command line argument to the command being run.
addPrefixedOutputDirectoryArg
Provides a directory path as a command line argument to the command being run.
addDepFileOutputArg
Add a path argument to a dep file (.d) for the child process to write its
addPrefixedDepFileOutputArg
Add a prefixed path argument to a dep file (.d) for the child process to
expectStdErrEqual
Adds a check for exact stderr match.
expectStdOutEqual
Adds a check for exact stdout match as well as a check for exit code 0, if
addFileInput
Adds an additional input files that, when modified, indicates that this Run

= .run

Values

#
base_id
= .run

Source

Implementation

#
fn make(step: *Step, options: Step.MakeOptions) !void {
    const prog_node = options.progress_node;
    const b = step.owner;
    const arena = b.allocator;
    const run: *Run = @fieldParentPtr("step", step);
    const has_side_effects = run.hasSideEffects();

    var argv_list = std.array_list.Managed([]const u8).init(arena);
    var output_placeholders = std.array_list.Managed(IndexedOutput).init(arena);

    var man = b.graph.cache.obtain();
    defer man.deinit();

    if (run.env_map) |env_map| {
        const KV = struct { []const u8, []const u8 };
        var kv_pairs = try std.array_list.Managed(KV).initCapacity(arena, env_map.count());
        var iter = env_map.iterator();
        while (iter.next()) |entry| {
            kv_pairs.appendAssumeCapacity(.{ entry.key_ptr.*, entry.value_ptr.* });
        }

        std.mem.sortUnstable(KV, kv_pairs.items, {}, struct {
            fn lessThan(_: void, kv1: KV, kv2: KV) bool {
                const k1 = kv1[0];
                const k2 = kv2[0];

                if (k1.len != k2.len) return k1.len < k2.len;

                for (k1, k2) |c1, c2| {
                    if (c1 == c2) continue;
                    return c1 < c2;
                }
                unreachable; // two keys cannot be equal
            }
        }.lessThan);

        for (kv_pairs.items) |kv| {
            man.hash.addBytes(kv[0]);
            man.hash.addBytes(kv[1]);
        }
    }

    for (run.argv.items) |arg| {
        switch (arg) {
            .bytes => |bytes| {
                try argv_list.append(bytes);
                man.hash.addBytes(bytes);
            },
            .lazy_path => |file| {
                const file_path = file.lazy_path.getPath3(b, step);
                try argv_list.append(b.fmt("{s}{s}", .{ file.prefix, run.convertPathArg(file_path) }));
                man.hash.addBytes(file.prefix);
                _ = try man.addFilePath(file_path, null);
            },
            .decorated_directory => |dd| {
                const file_path = dd.lazy_path.getPath3(b, step);
                const resolved_arg = b.fmt("{s}{s}{s}", .{ dd.prefix, run.convertPathArg(file_path), dd.suffix });
                try argv_list.append(resolved_arg);
                man.hash.addBytes(resolved_arg);
            },
            .artifact => |pa| {
                const artifact = pa.artifact;

                if (artifact.rootModuleTarget().os.tag == .windows) {
                    // On Windows we don't have rpaths so we have to add .dll search paths to PATH
                    run.addPathForDynLibs(artifact);
                }
                const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?;

                try argv_list.append(b.fmt("{s}{s}", .{
                    pa.prefix,
                    run.convertPathArg(.{ .root_dir = .cwd(), .sub_path = file_path }),
                }));

                _ = try man.addFile(file_path, null);
            },
            .output_file, .output_directory => |output| {
                man.hash.addBytes(output.prefix);
                man.hash.addBytes(output.basename);
                // Add a placeholder into the argument list because we need the
                // manifest hash to be updated with all arguments before the
                // object directory is computed.
                try output_placeholders.append(.{
                    .index = argv_list.items.len,
                    .tag = arg,
                    .output = output,
                });
                _ = try argv_list.addOne();
            },
        }
    }

    switch (run.stdin) {
        .bytes => |bytes| {
            man.hash.addBytes(bytes);
        },
        .lazy_path => |lazy_path| {
            const file_path = lazy_path.getPath2(b, step);
            _ = try man.addFile(file_path, null);
        },
        .none => {},
    }

    if (run.captured_stdout) |output| {
        man.hash.addBytes(output.basename);
    }

    if (run.captured_stderr) |output| {
        man.hash.addBytes(output.basename);
    }

    hashStdIo(&man.hash, run.stdio);

    for (run.file_inputs.items) |lazy_path| {
        _ = try man.addFile(lazy_path.getPath2(b, step), null);
    }

    if (run.cwd) |cwd| {
        const cwd_path = cwd.getPath3(b, step);
        _ = man.hash.addBytes(try cwd_path.toString(arena));
    }

    if (!has_side_effects and try step.cacheHitAndWatch(&man)) {
        // cache hit, skip running command
        const digest = man.final();

        try populateGeneratedPaths(
            arena,
            output_placeholders.items,
            run.captured_stdout,
            run.captured_stderr,
            b.cache_root,
            &digest,
        );

        step.result_cached = true;
        return;
    }

    const dep_output_file = run.dep_output_file orelse {
        // We already know the final output paths, use them directly.
        const digest = if (has_side_effects)
            man.hash.final()
        else
            man.final();

        try populateGeneratedPaths(
            arena,
            output_placeholders.items,
            run.captured_stdout,
            run.captured_stderr,
            b.cache_root,
            &digest,
        );

        const output_dir_path = "o" ++ fs.path.sep_str ++ &digest;
        for (output_placeholders.items) |placeholder| {
            const output_sub_path = b.pathJoin(&.{ output_dir_path, placeholder.output.basename });
            const output_sub_dir_path = switch (placeholder.tag) {
                .output_file => fs.path.dirname(output_sub_path).?,
                .output_directory => output_sub_path,
                else => unreachable,
            };
            b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {
                return step.fail("unable to make path '{f}{s}': {s}", .{
                    b.cache_root, output_sub_dir_path, @errorName(err),
                });
            };
            const arg_output_path = run.convertPathArg(.{
                .root_dir = .cwd(),
                .sub_path = placeholder.output.generated_file.getPath(),
            });
            argv_list.items[placeholder.index] = if (placeholder.output.prefix.len == 0)
                arg_output_path
            else
                b.fmt("{s}{s}", .{ placeholder.output.prefix, arg_output_path });
        }

        try runCommand(run, argv_list.items, has_side_effects, output_dir_path, prog_node, null);
        if (!has_side_effects) try step.writeManifestAndWatch(&man);
        return;
    };

    // We do not know the final output paths yet, use temp paths to run the command.
    const rand_int = std.crypto.random.int(u64);
    const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);

    for (output_placeholders.items) |placeholder| {
        const output_components = .{ tmp_dir_path, placeholder.output.basename };
        const output_sub_path = b.pathJoin(&output_components);
        const output_sub_dir_path = switch (placeholder.tag) {
            .output_file => fs.path.dirname(output_sub_path).?,
            .output_directory => output_sub_path,
            else => unreachable,
        };
        b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {
            return step.fail("unable to make path '{f}{s}': {s}", .{
                b.cache_root, output_sub_dir_path, @errorName(err),
            });
        };
        const raw_output_path: Build.Cache.Path = .{
            .root_dir = b.cache_root,
            .sub_path = b.pathJoin(&output_components),
        };
        placeholder.output.generated_file.path = raw_output_path.toString(b.graph.arena) catch @panic("OOM");
        argv_list.items[placeholder.index] = b.fmt("{s}{s}", .{
            placeholder.output.prefix,
            run.convertPathArg(raw_output_path),
        });
    }

    try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node, null);

    const dep_file_dir = std.fs.cwd();
    const dep_file_basename = dep_output_file.generated_file.getPath2(b, step);
    if (has_side_effects)
        try man.addDepFile(dep_file_dir, dep_file_basename)
    else
        try man.addDepFilePost(dep_file_dir, dep_file_basename);

    const digest = if (has_side_effects)
        man.hash.final()
    else
        man.final();

    const any_output = output_placeholders.items.len > 0 or
        run.captured_stdout != null or run.captured_stderr != null;

    // Rename into place
    if (any_output) {
        const o_sub_path = "o" ++ fs.path.sep_str ++ &digest;

        b.cache_root.handle.rename(tmp_dir_path, o_sub_path) catch |err| {
            if (err == error.PathAlreadyExists) {
                b.cache_root.handle.deleteTree(o_sub_path) catch |del_err| {
                    return step.fail("unable to remove dir '{f}'{s}: {s}", .{
                        b.cache_root,
                        tmp_dir_path,
                        @errorName(del_err),
                    });
                };
                b.cache_root.handle.rename(tmp_dir_path, o_sub_path) catch |retry_err| {
                    return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {s}", .{
                        b.cache_root,          tmp_dir_path,
                        b.cache_root,          o_sub_path,
                        @errorName(retry_err),
                    });
                };
            } else {
                return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {s}", .{
                    b.cache_root,    tmp_dir_path,
                    b.cache_root,    o_sub_path,
                    @errorName(err),
                });
            }
        };
    }

    if (!has_side_effects) try step.writeManifestAndWatch(&man);

    try populateGeneratedPaths(
        arena,
        output_placeholders.items,
        run.captured_stdout,
        run.captured_stderr,
        b.cache_root,
        &digest,
    );
}