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.

cmdFetch

Maker.cmdFetch
fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void

File

lib/compiler/Maker.zig:1429

Code

fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
    const environ_map = &graph.environ_map;
    const io = graph.io;
    const arena = graph.arena;

    const color: Color = Color.settingFromEnvironment(environ_map);
    var opt_path_or_url: ?[]const u8 = null;
    var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map);
    var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(environ_map);
    var override_pkg_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_PKG_DIR.get(environ_map);
    var debug_hash: bool = false;
    var save: union(enum) {
        no,
        yes: ?[]const u8,
        exact: ?[]const u8,
    } = .no;

    var arg_i: usize = 0;
    while (nextArg(args, &arg_i)) |arg| {
        if (mem.startsWith(u8, arg, "-")) {
            if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
                try Io.File.stdout().writeStreamingAll(io, usage_fetch);
                return process.cleanExit(io);
            } else if (mem.eql(u8, arg, "--global-cache-dir")) {
                override_global_cache_dir = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--cache-dir")) {
                override_local_cache_dir = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--pkg-dir")) {
                override_pkg_dir = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--debug-hash")) {
                debug_hash = true;
            } else if (mem.eql(u8, arg, "--debug-log")) {
                try graph.debug_log_scopes.append(arena, nextArgOrFatal(args, &arg_i));
            } else if (mem.eql(u8, arg, "--save")) {
                save = .{ .yes = null };
            } else if (mem.cutPrefix(u8, arg, "--save=")) |rest| {
                save = .{ .yes = rest };
            } else if (mem.eql(u8, arg, "--save-exact")) {
                save = .{ .exact = null };
            } else if (mem.cutPrefix(u8, arg, "--save-exact=")) |rest| {
                save = .{ .exact = rest };
            } else {
                fatal("unrecognized parameter: {q}", .{arg});
            }
        } else if (opt_path_or_url != null) {
            fatal("unexpected extra parameter: {q}", .{arg});
        } else {
            opt_path_or_url = arg;
        }
    }

    const path_or_url = opt_path_or_url orelse fatal("missing url or path parameter", .{});

    var http_client: std.http.Client = .{ .allocator = gpa, .io = io };
    defer http_client.deinit();

    try http_client.initDefaultProxies(arena, environ_map);

    var root_prog_node = std.Progress.start(io, .{
        .root_name = "Fetch",
    });
    defer root_prog_node.end();

    var local_storage: Fetch.LocalStorage = undefined;
    var build_root: BuildRoot = undefined;
    var build_root_initialized = false;
    defer if (build_root_initialized) build_root.deinit(io);

    const cwd_path = try std.zig.getResolvedCwd(io, arena);

    const local_storage_ptr = switch (save) {
        .no => null,
        .yes, .exact => ls: {
            build_root = try findBuildRoot(arena, io, .{ .cwd_path = cwd_path });
            build_root_initialized = true;

            local_storage = .{
                .cache_root = if (override_local_cache_dir) |p| .initCwd(p) else .{
                    .root_dir = build_root.directory,
                    .sub_path = ".zig-cache",
                },
                .pkg_root = if (override_pkg_dir) |p| .initCwd(p) else .{
                    .root_dir = build_root.directory,
                    .sub_path = "zig-pkg",
                },
            };

            break :ls &local_storage;
        },
    };

    var job_queue: Fetch.JobQueue = .{
        .io = io,
        .http_client = &http_client,
        .global_cache = graph.global_cache_root,
        .local_storage = local_storage_ptr,
        .recursive = false,
        .read_only = false,
        .debug_hash = debug_hash,
        .mode = .all,
        .prog_node = root_prog_node,
    };
    defer job_queue.deinit();

    var fetch: Fetch = .{
        .arena = std.heap.ArenaAllocator.init(gpa),
        .location = .{ .path_or_url = path_or_url },
        .location_tok = 0,
        .hash_tok = .none,
        .name_tok = 0,
        .lazy_status = .eager,
        .remote_package_root = undefined,
        .parent_package_root = undefined,
        .parent_manifest_ast = null,
        .prog_node = root_prog_node,
        .job_queue = &job_queue,
        .omit_missing_hash_error = true,
        .allow_missing_paths_field = false,
        .use_latest_commit = true,

        .package_root = undefined,
        .error_bundle = undefined,
        .manifest = undefined,
        .manifest_ast = undefined,
        .have_manifest = false,
        .computed_hash = undefined,
        .has_build_zig = false,
        .oom_flag = false,
        .latest_commit = null,

        .cli_module = null,
    };
    defer fetch.deinit();

    fetch.run() catch |err| switch (err) {
        error.OutOfMemory, error.Canceled => |e| return e,
        error.FetchFailed => {}, // error bundle checked below
    };

    try job_queue.group.await(io);

    if (fetch.error_bundle.root_list.items.len > 0) {
        var errors = try fetch.error_bundle.toOwnedBundle("");
        errors.renderToStderr(io, .{}, color) catch {};
        process.exit(1);
    }

    const package_hash = fetch.computedPackageHash();
    const package_hash_slice = package_hash.toSlice();

    root_prog_node.end();
    root_prog_node = .{ .index = .none };

    const name = switch (save) {
        .no => {
            var data: [2][]const u8 = .{ package_hash_slice, "\n" };
            const w = initStdoutWriter(io);
            w.writeVecAll(&data) catch return stdout_writer_allocation.err.?;
            try stdout_writer_allocation.flush();
            return process.cleanExit(io);
        },
        .yes, .exact => |name| name: {
            if (name) |n| break :name n;
            if (!fetch.have_manifest)
                fatal("unable to determine name; fetched package has no build.zig.zon file", .{});
            break :name fetch.manifest.name;
        },
    };

    // The name to use in case the manifest file needs to be created now.
    const init_root_name = Dir.path.basename(build_root.directory.path orelse cwd_path);
    var manifest, var ast = try loadManifest(gpa, arena, io, .{
        .root_name = try sanitizeExampleName(arena, init_root_name),
        .dir = build_root.directory.handle,
        .color = color,
    });
    defer {
        manifest.deinit(gpa);
        ast.deinit(gpa);
    }

    var fixups: std.zig.Ast.Render.Fixups = .{};
    defer fixups.deinit(gpa);

    var saved_path_or_url = path_or_url;

    if (fetch.latest_commit) |latest_commit| resolved: {
        const latest_commit_hex = try arena.print("{f}", .{latest_commit});

        var uri = try std.Uri.parse(path_or_url);

        if (uri.fragment) |fragment| {
            const target_ref = try fragment.toRawMaybeAlloc(arena);

            // the refspec may already be fully resolved
            if (std.mem.eql(u8, target_ref, latest_commit_hex)) break :resolved;

            log.info("resolved ref {q} to commit {s}", .{ target_ref, latest_commit_hex });

            // include the original refspec in a query parameter, could be used to check for updates
            uri.query = .{ .percent_encoded = try arena.print("ref={f}", .{
                std.fmt.alt(fragment, .formatEscaped),
            }) };
        } else {
            log.info("resolved to commit {s}", .{latest_commit_hex});
        }

        // replace the refspec with the resolved commit SHA
        uri.fragment = .{ .raw = latest_commit_hex };

        switch (save) {
            .yes => saved_path_or_url = try arena.print("{f}", .{uri}),
            .no, .exact => {}, // keep the original URL
        }
    }

    const new_node_init = try arena.print(
        \\.{{
        \\            .url = "{f}",
        \\            .hash = "{f}",
        \\        }}
    , .{
        std.zig.fmtString(saved_path_or_url),
        std.zig.fmtString(package_hash_slice),
    });

    const new_node_text = try arena.print(".{f} = {s},\n", .{
        std.zig.fmtIdPU(name), new_node_init,
    });

    const dependencies_init = try arena.print(".{{\n        {s}    }}", .{
        new_node_text,
    });

    const dependencies_text = try arena.print(".dependencies = {s},\n", .{
        dependencies_init,
    });

    if (manifest.dependencies.get(name)) |dep| {
        if (dep.hash) |h| {
            switch (dep.location) {
                .url => |u| {
                    if (mem.eql(u8, h, package_hash_slice) and mem.eql(u8, u, saved_path_or_url)) {
                        log.info("existing dependency named {q} is up-to-date", .{name});
                        process.exit(0);
                    }
                },
                .path => {},
            }
        }

        const location_replace = try arena.print("{q}", .{saved_path_or_url});
        const hash_replace = try arena.print("{q}", .{package_hash_slice});

        log.warn("overwriting existing dependency named {q}", .{name});
        try fixups.replace_nodes_with_string.put(gpa, dep.location_node, location_replace);
        if (dep.hash_node.unwrap()) |hash_node| {
            try fixups.replace_nodes_with_string.put(gpa, hash_node, hash_replace);
        } else {
            // https://github.com/ziglang/zig/issues/21690
        }
    } else if (manifest.dependencies.count() > 0) {
        // Add fixup for adding another dependency.
        const deps = manifest.dependencies.values();
        const last_dep_node = deps[deps.len - 1].node;
        try fixups.append_string_after_node.put(gpa, last_dep_node, new_node_text);
    } else if (manifest.dependencies_node.unwrap()) |dependencies_node| {
        // Add fixup for replacing the entire dependencies struct.
        try fixups.replace_nodes_with_string.put(gpa, dependencies_node, dependencies_init);
    } else {
        // Add fixup for adding dependencies struct.
        try fixups.append_string_after_node.put(gpa, manifest.version_node, dependencies_text);
    }

    var aw: Io.Writer.Allocating = .init(gpa);
    defer aw.deinit();
    try ast.render(gpa, &aw.writer, fixups);
    const rendered = aw.written();

    build_root.directory.handle.writeFile(io, .{ .sub_path = Package.Manifest.basename, .data = rendered }) catch |err| {
        fatal("unable to write {s} file: {t}", .{ Package.Manifest.basename, err });
    };

    return process.cleanExit(io);
}