llm
Download
902 lines 43.4 KiBCModified 2026-09-28 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902
// This file defines some code for getting info about packages and installing them.

#include <err.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#include "config.h"
#include "naming.h"
#include "packages.h"
#include "sha256.h"
#include "stdlib/datatypes.h"
#include "stdlib/optionals.h"
#include "stdlib/path.h"
#include "stdlib/print.h"
#include "stdlib/simpleparse.h"
#include "stdlib/table.h"
#include "stdlib/text.h"
#include "stdlib/util.h"

typedef struct {
    const char *name;
    Table_t info;
} pkg_info_t;

// Installed packages are content-addressed: each project keeps its own store
// of them in .tomo/store/<digest>/, and the names things call them by are
// symlinks (see create_binding_link() below).

// The --package-sources entry (a <package sources dir>/<digest>/ directory)
// containing the given path, if any:
public
OptionalPath_t package_sources_entry(Path_t path) {
    OptionalPath_t sources = package_sources_dir();
    if (sources == NULL) return NONE_PATH;
    path = Path$resolved(path, Path$current_dir());
    for (int depth = 0; depth < PATH_MAX; depth++) {
        OptionalPath_t parent = Path$parent(path);
        if (parent == NULL || parent[0] == '\0' || streq(parent, path)) return NONE_PATH;
        if (streq(parent, sources)) return path;
        path = parent;
    }
    return NONE_PATH;
}

// The store entry containing the given path, if any: a .tomo/store/<digest>/
// directory, or an entry of the active --package-sources directory:
OptionalPath_t package_store_entry(Path_t path) {
    OptionalPath_t sources_entry = package_sources_entry(path);
    if (sources_entry != NULL) return sources_entry;
    for (int depth = 0; depth < PATH_MAX; depth++) {
        OptionalPath_t parent = Path$parent(path);
        // Stop at the root, or once the walk escapes a relative path's start:
        if (parent == NULL || parent[0] == '\0' || streq(parent, path) || streq(parent, ".")
            || Text$equal_values(Path$base_name(parent), Text("..")))
            return NONE_PATH;
        OptionalPath_t grandparent = Path$parent(parent);
        if (grandparent != NULL && Text$equal_values(Path$base_name(parent), Text("store"))
            && Text$equal_values(Path$base_name(grandparent), Text(".tomo")))
            return path;
        path = parent;
    }
    return NONE_PATH;
}

// A file inside a store entry installs *its* dependencies into the same
// store, so the store root for a consumer is the store containing it (if any)
// or its own directory's .tomo/store:
static Path_t package_store_root(Path_t using_file) {
    OptionalPath_t entry = package_store_entry(using_file);
    if (entry != NULL) {
        OptionalPath_t store = Path$parent(entry);
        assert(store); // A store entry sits inside a store directory
        return store;
    }
    OptionalPath_t using_dir = Path$parent(using_file);
    assert(using_dir); // A source file always has a directory
    return Path$child(tomo_root_for(using_dir), Text("store"));
}

// A literal ":" isn't valid in a filename on Windows, so this gets stripped
// before a digest is used to name a directory:
const char *package_digest_hex(const char *digest) {
    const char *hex = strchr(digest, ':');
    return hex ? hex + 1 : digest;
}

Path_t package_store_path(Path_t store_root, const char *digest) {
    return Path$child(store_root, Text$from_str(package_digest_hex(digest)));
}

// Verified downloaded package artifacts are cached globally, keyed by digest,
// so other projects can materialize their stores without re-fetching:
static Path_t download_cache_dir(Text_t digest) {
    const char *cache_home = getenv("XDG_CACHE_HOME");
    Path_t base = (cache_home && cache_home[0] == '/') ? Path$from_str(String(cache_home, "/tomo"))
                                                       : Path$expand_home(Path$from_str("~/.cache/tomo"));
    Text_t hex = Text$from_str(package_digest_hex(Text$as_c_string(digest)));
    return Path$child(Path$child(base, Text("packages")), hex);
}

// Record which package a `use NAME` resolved to, as a "packages/NAME" symlink
// next to the file that used it. For a consumer inside a store, the link is
// store-relative ("../../<digest>", pointing two levels up from its packages/
// directory into the store); for a program, the link farm lives in its .tomo
// directory, pointing "../store/<digest>" for entries in the project's own
// store. Either way the whole project tree is relocatable, and every level of
// the dependency graph resolves the same way: ./packages/NAME, one level at a
// time. Only dependencies living outside the consumer's store (e.g. local
// directory-source packages) get absolute link targets.
static void create_binding_link(Path_t using_file, const char *name, Path_t installed) {
    if (strchr(name, '/') != NULL) return; // Just in case: never write outside packages/
    OptionalPath_t using_dir = Path$parent(using_file);
    OptionalPath_t installed_parent = Path$parent(installed);
    assert(using_dir && installed_parent); // Both name things inside a directory
    bool consumer_in_store = package_store_entry(using_file) != NULL;
    bool dep_in_this_store = streq(installed_parent, package_store_root(using_file));

    Path_t link_dir = consumer_in_store ? Path$child(using_dir, Text("packages"))
                                        : Path$child(tomo_root_for(using_dir), Text("packages"));
    const char *target = installed;
    if (dep_in_this_store) {
        target = Path$relative_to(installed, link_dir);
    } else if (!consumer_in_store) {
        // Directory-source packages inside the project (e.g. vendored ones)
        // also get relative links, so the project stays relocatable:
        Path_t cwd = Path$current_dir();
        Path_t project = Path$resolved(using_dir, cwd);
        Path_t dep = Path$resolved(installed, cwd);
        if (strncmp(dep, project, strlen(project)) == 0 && dep[strlen(project)] == '/')
            target = Path$relative_to(dep, Path$resolved(link_dir, cwd));
    }

    Result_t result = Path$create_directory(link_dir, 0755, true);
    if (result.Failure.reason.tag != TEXT_NONE) return;
    Path_t link = Path$child(link_dir, Text$from_str(name));
    char existing[PATH_MAX];
    ssize_t len = readlink(link, existing, sizeof(existing) - 1);
    if (len >= 0) {
        existing[len] = '\0';
        if (streq(existing, target)) return; // Already correct
    }
    unlink(link);
    if (symlink(target, link) != 0) fprint(stderr, "Warning: could not create package link ", link, " -> ", target);
}

#define xsystem(...)                                                                                                   \
    ({                                                                                                                 \
        const char *cmd = String(__VA_ARGS__);                                                                         \
        int _status = system(cmd);                                                                                     \
        if (!WIFEXITED(_status) || WEXITSTATUS(_status) != 0) {                                                        \
            errx(1, "Failed to run command: %s", String(__VA_ARGS__));                                                 \
        }                                                                                                              \
    })

// The tomo executable to use for nested invocations: the same one that's
// running (recorded in main()), never a possibly-older `tomo` from PATH:
static const char *tomo_exe(void) {
    const char *exe = getenv("TOMO_EXE");
    return exe ? exe : "tomo";
}

// Packages install as source; make sure the compiled package.a exists too
// (e.g. for store entries pre-seeded by `tomo source -o`):
static void ensure_package_built(Path_t install_location) {
    if (!Path$exists(Path$child(install_location, Text("package.a"))))
        xsystem(quoted(tomo_exe()), " package ", install_location);
}

// --package-sources: a directory holding package sources by digest (like the
// packages/ directory in an extracted source archive), consulted after a
// consumer's own store but before the download cache and the network. Carried
// in an environment variable so nested tomo invocations (see tomo_exe())
// resolve the same way.
public
OptionalPath_t package_sources_dir(void) {
    const char *dir = getenv("TOMO_PACKAGE_SOURCES");
    return (dir && dir[0]) ? Path$from_str(dir) : NONE_PATH;
}

public
void set_package_sources_dir(Path_t dir) {
    dir = Path$resolved(Path$expand_home(dir), Path$current_dir());
    if (!Path$is_directory(dir, true)) fail("The --package-sources directory does not exist: ", dir);
    setenv("TOMO_PACKAGE_SOURCES", Path$as_c_string(dir), 1);
}

#define xsystem_cleanup(tmpdir, ...)                                                                                   \
    ({                                                                                                                 \
        const char *cmd = String(__VA_ARGS__);                                                                         \
        int _status = system(cmd);                                                                                     \
        if (!WIFEXITED(_status) || WEXITSTATUS(_status) != 0) {                                                        \
            if (tmpdir) Path$remove(tmpdir, true);                                                                     \
            errx(1, "Failed to run command: %s", String(__VA_ARGS__));                                                 \
        }                                                                                                              \
    })

static OptionalText_t file_digest(Path_t path) {
    FILE *f = fopen(path, "rb");
    if (!f) return NONE_TEXT;

    sha256 ctx;
    sha256_init(&ctx);

    unsigned char buf[65536];
    size_t n;
    while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
        sha256_append(&ctx, buf, n);
    fclose(f);

    char hash[SHA256_HEX_SIZE];
    sha256_finalize_hex(&ctx, hash);

    const char *prefix = "sha256:";
    char *ret = GC_MALLOC_ATOMIC(strlen(prefix) + SHA256_HEX_SIZE + 1);
    char *p = ret;
    p = stpcpy(p, prefix);
    memcpy(p, hash, SHA256_HEX_SIZE);
    p += SHA256_HEX_SIZE;
    *p = '\0';
    return Text$from_str(ret);
}

Text_t get_package_name(Path_t lib_dir) {
    OptionalText_t name = Path$base_name(lib_dir);
    if (name.tag == TEXT_NONE) fail("This package's directory name is not valid UTF-8: ", lib_dir);
    name = Text$without_prefix(name, Text("tomo-"));
    name = Text$without_suffix(name, Text("-tomo"));
    return name;
}

static Text_t package_text(pkg_info_t pkg) {
    Text_t text = Texts("[", pkg.name, "]\n");
    for (int64_t i = 0; i < (int64_t)pkg.info.entries.length; i++) {
        struct {
            const char *key, *value;
        } *entry = pkg.info.entries.data + i * pkg.info.entries.stride;
        text = Texts(text, entry->key, "=", entry->value, "\n");
    }
    return text;
}

static OptionalPath_t try_install_package_from_file(pkg_info_t *pkg, const char *source, Path_t downloaded,
                                                    OptionalPath_t tmpdir, Path_t store_root);

static OptionalPath_t try_install_package_from_source(Path_t ini_file, pkg_info_t *pkg, const char *source,
                                                      bool ask_confirmation, Path_t store_root) {
    Table$str_set(&pkg->info, "source", source);
    if (source[0] == '.' || source[0] == '/' || source[0] == '~') {
        Path_t source_path = Path$from_str(source);
        OptionalPath_t ini_dir = Path$parent(ini_file);
        assert(ini_dir); // packages.ini always sits in a directory
        source_path = Path$resolved(source_path, ini_dir);
        if (!Path$exists(source_path)) {
            print("No such file: ", source_path);
            return NONE_PATH;
        }
        if (Path$is_directory(source_path, true)) {
            if (Table$str_get(pkg->info, "digest") != NULL) {
                // TODO: add support for automatically deleting digest with confirmation
                fail("The package source for ", pkg->name, " is a directory, but the package has a digest.",
                     "\nSource directory packages cannot have a digest, so please delete the digest from this "
                     "package.\nSource: ",
                     source_path);
            }
            xsystem(quoted(tomo_exe()), " package ", source_path);
            return source_path;
        } else {
            OptionalPath_t file_ini_dir = Path$parent(ini_file);
            assert(file_ini_dir); // packages.ini always sits in a directory
            return try_install_package_from_file(pkg, source, Path$resolved(source, file_ini_dir), NULL, store_root);
        }
    }

    if (ask_confirmation) {
        OptionalText_t answer = ask(Texts("The package ", Text$quoted(Text$from_str(pkg->name), false, Text("\"")),
                                          " is not installed.\nDo you want to install it from ",
                                          Text$quoted(Text$from_str(source), false, Text("\"")), "? [Y/n] "),
                                    true, true);
        if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) {
            print("Okay, not installing it!");
            exit(1);
        }
    }

    print("Installing ", Text$quoted(Text$from_str(pkg->name), false, Text("\"")), " from URL...");

    OptionalPath_t tmpdir = Path$unique_directory(Path$from_text(Texts("/tmp/tomo-", pkg->name, "-XXXXXX")));
    if (tmpdir == NULL) print_err("Could not create a temporary directory to download ", pkg->name, " into");

    xsystem_cleanup(tmpdir, "curl --output-dir ", quoted(tmpdir), " -LJO ", quoted(source));

    OptionalList_t children = Path$children(tmpdir, true);
    if (children.data == NULL || children.length != 1) {
        Path$remove(tmpdir, true);
        print("Failed to download file ", pkg->name, " from: ", source);
        return NONE_PATH;
    }

    Path_t downloaded = *(Path_t *)children.data;
    return try_install_package_from_file(pkg, source, downloaded, tmpdir, store_root);
}

OptionalPath_t try_install_package_from_file(pkg_info_t *pkg, const char *source, Path_t downloaded,
                                             OptionalPath_t tmpdir, Path_t store_root) {

    OptionalText_t digest = file_digest(downloaded);
    if (digest.tag == TEXT_NONE) {
        if (tmpdir != NULL) Path$remove(tmpdir, true);
        fail("Failed to compute digest for package ", pkg->name);
    }

    const char *required_digest = Table$str_get(pkg->info, "digest");
    if (required_digest == NULL) {
        Table$str_set(&pkg->info, "digest", Text$as_c_string(digest));
        print("Added digest for ", pkg->name, ": ", digest);
    } else {
        if (!Text$equal_values(Text$from_str(required_digest), digest)) {
            // Digest mismatch
            if (tmpdir != NULL) Path$remove(tmpdir, true);
            fail("Mismatched digest sum for package ", pkg->name, "! Expected ", required_digest, " but got ", digest);
        }
    }

    // Cache the verified artifact so other projects can materialize their
    // stores without re-downloading:
    Path_t cache_dir = download_cache_dir(digest);
    Path_t cached = Path$child(cache_dir, Path$base_name(downloaded));
    if (!Path$exists(cached)) {
        Result_t cache_result = Path$create_directory(cache_dir, 0755, true);
        if (cache_result.Failure.reason.tag == TEXT_NONE) xsystem("cp ", quoted(downloaded), " ", quoted(cached));
    }

    OptionalPath_t install_location = package_store_path(store_root, Text$as_c_string(digest));

    Result_t result = Path$create_directory(install_location, 0755, true);
    if (result.Failure.reason.tag != TEXT_NONE) {
        if (tmpdir != NULL) Path$remove(tmpdir, true);
        fail("Failed to make install directory: ", result.Failure.reason);
    }

    if (Path$has_extension(downloaded, Text(".tar.gz")) || Path$has_extension(downloaded, Text(".tgz"))
        || Path$has_extension(downloaded, Text(".tar.xz")) || Path$has_extension(downloaded, Text(".txz"))
        || Path$has_extension(downloaded, Text(".tar"))) {
        xsystem_cleanup(tmpdir, "tar xf ", downloaded, " -C ", install_location);
    } else if (Path$has_extension(downloaded, Text(".zip"))) {
        xsystem_cleanup(tmpdir, "unzip ", downloaded, " -d ", install_location);
    } else {
        Path$remove(tmpdir, true);
        fail("Unsupported package filetype: ", downloaded);
    }

    OptionalList_t installed_files = Path$children(install_location, true);
    if (installed_files.data == NULL) print_err("Could not read the installed package: ", install_location);
    if (installed_files.length == 1
        && Path$is_directory(*(Path_t *)installed_files.data, false)) { // Single top-level wrapper dir
        Path_t top_level = *(Path_t *)installed_files.data;
        OptionalList_t contents = Path$children(top_level, true);
        if (contents.data == NULL) print_err("Could not read the installed package: ", top_level);
        for (int64_t i = 0; i < (int64_t)contents.length; i++) {
            Path_t p = *(Path_t *)(contents.data + i * contents.stride);
            OptionalText_t p_name = Path$base_name(p);
            if (p_name.tag == TEXT_NONE) fail("This package contains a file whose name is not valid UTF-8: ", p);
            result = Path$move(p, Path$child(install_location, p_name), false);
            if (result.Failure.reason.tag != TEXT_NONE) {
                if (tmpdir != NULL) Path$remove(tmpdir, true);
                fail(result.Failure.reason);
            }
        }
        result = Path$remove(top_level, true);
        if (result.Failure.reason.tag != TEXT_NONE) {
            if (tmpdir != NULL) Path$remove(tmpdir, true);
            fail(result.Failure.reason);
        }
    }

    xsystem_cleanup(tmpdir, quoted(tomo_exe()), " package ", install_location);

    Path_t info = Path$child(install_location, Text("package_info.ini"));
    Path$write(info, Texts("name=", pkg->name, "\nsource=", source, "\ndigest=", digest, "\n"), 0644);

    // Always clean up tmpdir!
    if (tmpdir != NULL) Path$remove(tmpdir, true);

    // Add digest to the package.ini file if it wasn't already there
    if (required_digest == NULL && digest.tag != TEXT_NONE) {
        Table$str_set(&pkg->info, "digest", Text$as_c_string(digest));
        print("Added digest for ", pkg->name, ": ", digest);
    }

    return install_location;
}

static OptionalPath_t try_install_package(Path_t ini_file, pkg_info_t *pkg, bool ask_confirmation, Path_t store_root) {
    OptionalPath_t install_location = NULL;
    const char *digest = Table$str_get(pkg->info, "digest");
    if (digest) {
        install_location = package_store_path(store_root, digest);
        if (Path$exists(install_location)) {
            ensure_package_built(install_location);
            return install_location;
        }
        // A --package-sources directory holds package sources by digest, so
        // the pinned package can be used from there in place:
        OptionalPath_t sources_dir = package_sources_dir();
        if (sources_dir != NULL) {
            Path_t entry = package_store_path(sources_dir, digest);
            if (Path$is_directory(entry, true)) {
                ensure_package_built(entry);
                return entry;
            }
        }
        // A verified artifact for this digest may already be cached, in which
        // case no download (and no confirmation) is needed:
        Path_t cache_dir = download_cache_dir(Text$from_str(digest));
        if (Path$is_directory(cache_dir, true)) {
            OptionalList_t cached = Path$children(cache_dir, true);
            if (cached.data != NULL && cached.length == 1) {
                const char *source = Table$str_get(pkg->info, "source");
                install_location = try_install_package_from_file(pkg, source ? source : "cache", *(Path_t *)cached.data,
                                                                 NULL, store_root);
                if (install_location != NULL) return install_location;
            }
        }
    }

    const char *source = Table$str_get(pkg->info, "source");
    for (int i = 1; source; i++) {
        install_location = try_install_package_from_source(ini_file, pkg, source, ask_confirmation, store_root);
        if (install_location != NULL) return install_location;
        const char *new_source_key = String("source-", i + 1);
        source = Table$str_get(pkg->info, new_source_key);
    }
    fail("No source for package: ", pkg->name);
    return NULL;
}

static bool ini_is_writable(Path_t ini_file) {
    if (Path$exists(ini_file)) return Path$can_write(ini_file);
    OptionalPath_t ini_dir = Path$parent(ini_file);
    assert(ini_dir); // packages.ini always sits in a directory
    return Path$can_write(ini_dir);
}

// Package pins are just an optimization, so skip the write instead of
// crashing; warn once per ini file:
static void warn_ini_not_writable(Path_t ini_file) {
    static Table_t warned = EMPTY_TABLE;
    if (Table$str_get(warned, ini_file) != NULL) return;
    fprint(stderr, "Warning: ", ini_file, " is not writable, so package pins will not be saved\n");
    Table$str_set(&warned, ini_file, ini_file);
}

static OptionalPath_t get_package_install_location(Table_t *build_info, Path_t ini_file, const char *name,
                                                   Path_t store_root) {
    OptionalClosure_t by_line = Path$by_line(ini_file);
    if (by_line.fn == NULL) return NONE_PATH;
    OptionalText_t (*next_line)(void *) = by_line.fn;

    Text_t reformatted = EMPTY_TEXT;
    for (OptionalText_t line; (line = next_line(by_line.userdata)).tag != TEXT_NONE;) {
        if (Text$equal_values(line, Texts("[", name, "]"))) goto found_package;
        reformatted = Texts(reformatted, line, "\n");
    }
    return NONE_PATH;

found_package:;

    pkg_info_t pkg = {.name = name, .info = EMPTY_TABLE};
    for (OptionalText_t line; (line = next_line(by_line.userdata)).tag != TEXT_NONE;) {
        const char *line_str = Text$as_c_string(line);
        const char *key = NULL, *value = NULL;
        if (!strparse(line_str, &key, "=", &value)) {
            Table$str_set(&pkg.info, key, value);
        } else {
            break;
        }
    }
    bool had_digest = Table$str_get(pkg.info, "digest") != NULL;
    OptionalPath_t installed = try_install_package(ini_file, &pkg, true, store_root);
    if (installed == NULL) return NULL;

    if (!had_digest && Table$str_get(pkg.info, "digest") != NULL) {
        reformatted = Texts(reformatted, package_text(pkg), "\n");
        for (OptionalText_t line; (line = next_line(by_line.userdata)).tag != TEXT_NONE;) {
            reformatted = Texts(reformatted, line, "\n");
        }
        reformatted = Texts(Text$trim(reformatted, Text(" \r\n\t"), true, true), "\n");
        if (ini_is_writable(ini_file)) {
            Result_t result = Path$write(ini_file, reformatted, 0644);
            if (result.Failure.reason.tag != TEXT_NONE) {
                fail(result.Failure.reason);
            }
        } else {
            warn_ini_not_writable(ini_file);
        }
    }

    const char *digest = Table$str_get(pkg.info, "digest");
    Text_t digest_key = Texts("Package digest [", name, "]");
    Table$str_set(build_info, Text$as_c_string(digest_key), digest);

    Text_t source_key = Texts("Package source [", name, "]");
    const char *source = Table$str_get(pkg.info, "source");
    Table$str_set(build_info, Text$as_c_string(source_key), source);

    return installed;
}

OptionalPath_t find_installed_package(Table_t *build_info, ast_t *use) {
    const char *name = Match(use, Use)->path;
    Path_t using_file = Path$from_str(use->file->filename);
    Path_t store_root = package_store_root(using_file);
    OptionalPath_t installed = NONE_PATH;

    OptionalPath_t local_package = Path$sibling(using_file, Text("packages.ini"));
    assert(local_package); // A source file always has a directory
    installed = get_package_install_location(build_info, local_package, name, store_root);

    if (installed == NULL) {
        Path_t tomo_default_packages =
            Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@", TOMO_VERSION, "/packages.ini"));
        installed = get_package_install_location(build_info, tomo_default_packages, name, store_root);
    }

    if (installed != NULL) create_binding_link(using_file, name, installed);
    return installed;
}

static bool parse_package_entry(Path_t ini_file, const char *name, pkg_info_t *pkg);

// The pinned digest for `name`, as resolved through the same packages.ini
// chain a `use` in `using_file` would consult. Parse-only, installing
// nothing. NULL if the package isn't pinned by digest (e.g. a
// directory-source package) or isn't found at all:
const char *find_pinned_digest(Path_t using_file, const char *name) {
    OptionalPath_t local_ini = Path$sibling(using_file, Text("packages.ini"));
    assert(local_ini); // A source file always has a directory
    Path_t candidates[] = {
        local_ini,
        Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@", TOMO_VERSION, "/packages.ini")),
    };
    for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++) {
        pkg_info_t pkg = {.name = name, .info = EMPTY_TABLE};
        if (parse_package_entry(candidates[i], name, &pkg)) return Table$str_get(pkg.info, "digest");
    }
    return NULL;
}

// Parse [name]'s key=value entries from an ini file (without installing
// anything). Returns whether the section was found:
static bool parse_package_entry(Path_t ini_file, const char *name, pkg_info_t *pkg) {
    OptionalClosure_t by_line = Path$by_line(ini_file);
    if (by_line.fn == NULL) return false;
    OptionalText_t (*next_line)(void *) = by_line.fn;
    bool found = false;
    for (OptionalText_t line; (line = next_line(by_line.userdata)).tag != TEXT_NONE;) {
        if (!found) {
            if (Text$equal_values(line, Texts("[", name, "]"))) found = true;
            continue;
        }
        const char *key = NULL, *value = NULL;
        if (strparse(Text$as_c_string(line), &key, "=", &value)) break; // End of section
        Table$str_set(&pkg->info, key, value);
    }
    return found;
}

// Replace (or append) [name]'s entry in an ini file with the given info.
// Returns whether the file was actually written (false, with a warning
// printed, if `ini_file` isn't writable):
static bool rewrite_package_entry(Path_t ini_file, const char *name, pkg_info_t pkg) {
    Text_t out = EMPTY_TEXT;
    bool replaced = false;
    OptionalClosure_t by_line = Path$by_line(ini_file);
    if (by_line.fn != NULL) {
        OptionalText_t (*next_line)(void *) = by_line.fn;
        bool in_section = false;
        for (OptionalText_t line; (line = next_line(by_line.userdata)).tag != TEXT_NONE;) {
            if (in_section) {
                const char *key = NULL, *value = NULL;
                if (!strparse(Text$as_c_string(line), &key, "=", &value)) continue; // Drop the old entries
                in_section = false;
            }
            if (Text$equal_values(line, Texts("[", name, "]"))) {
                out = Texts(out, package_text(pkg));
                in_section = true, replaced = true;
                continue;
            }
            out = Texts(out, line, "\n");
        }
    }
    if (!replaced) {
        if (out.length > 0) out = Texts(Text$trim(out, Text(" \r\n\t"), false, true), Text("\n\n"));
        out = Texts(out, package_text(pkg));
    }
    if (!ini_is_writable(ini_file)) {
        warn_ini_not_writable(ini_file);
        return false;
    }
    Result_t result = Path$write(ini_file, out, 0644);
    if (result.Failure.reason.tag != TEXT_NONE) fail(result.Failure.reason);
    return true;
}

// Enumerate the [section] names in an ini file:
static List_t ini_section_names(Path_t ini_file) {
    List_t names = EMPTY_LIST;
    OptionalClosure_t by_line = Path$by_line(ini_file);
    if (by_line.fn == NULL) return names;
    OptionalText_t (*next_line)(void *) = by_line.fn;
    for (OptionalText_t line; (line = next_line(by_line.userdata)).tag != TEXT_NONE;) {
        const char *str = Text$as_c_string(line);
        size_t len = strlen(str);
        if (len >= 2 && str[0] == '[' && str[len - 1] == ']') {
            const char *name = Text$as_c_string(Text$from_strn(str + 1, len - 2));
            List$insert(&names, &name, I(0), sizeof(const char *));
        }
    }
    return names;
}

// Keep packages.ini's `unused=true` markers in sync with what the directory's
// code actually uses: entries whose binding name is not in `used_names` get
// marked, and the marker is removed from entries that are used again. The
// marker is informational (resolution ignores unknown keys); the file is only
// rewritten when a marker actually changes, since its modification time
// triggers rebuilds.
void mark_unused_packages(Path_t ini_file, Table_t used_names) {
    List_t sections = ini_section_names(ini_file);
    for (int64_t i = 0; i < (int64_t)sections.length; i++) {
        const char *name = *(const char **)(sections.data + i * sections.stride);
        pkg_info_t pkg = {.name = name, .info = EMPTY_TABLE};
        if (!parse_package_entry(ini_file, name, &pkg)) continue;
        bool used = Table$str_get(used_names, name) != NULL;
        bool marked = Table$str_get(pkg.info, "unused") != NULL;
        if (used == !marked) continue; // Already in the right state

        pkg_info_t updated = {.name = name, .info = EMPTY_TABLE};
        for (int64_t j = 0; j < (int64_t)pkg.info.entries.length; j++) {
            struct {
                const char *key, *value;
            } *entry = pkg.info.entries.data + j * pkg.info.entries.stride;
            if (!streq(entry->key, "unused")) Table$str_set(&updated.info, entry->key, entry->value);
        }
        if (!used) Table$str_set(&updated.info, "unused", "true");
        if (!rewrite_package_entry(ini_file, name, updated)) continue;
        if (used) print("Removed the unused=true marker for ", name, " in ", ini_file);
        else print("Marked the package ", name, " as unused=true in ", ini_file);
    }
}

// Extract a package source archive into dest, flattening a single top-level
// wrapper directory if there is one:
static void extract_package_archive(Path_t archive, Path_t dest) {
    Result_t result = Path$create_directory(dest, 0755, true);
    if (result.Failure.reason.tag != TEXT_NONE) fail("Failed to make directory: ", result.Failure.reason);

    if (Path$has_extension(archive, Text(".tar.gz")) || Path$has_extension(archive, Text(".tgz"))
        || Path$has_extension(archive, Text(".tar.xz")) || Path$has_extension(archive, Text(".txz"))
        || Path$has_extension(archive, Text(".tar"))) {
        xsystem("tar xf ", quoted(archive), " -C ", quoted(dest));
    } else if (Path$has_extension(archive, Text(".zip"))) {
        xsystem("unzip -q ", quoted(archive), " -d ", quoted(dest));
    } else {
        fail("Unsupported package filetype: ", archive);
    }

    OptionalList_t extracted = Path$children(dest, true);
    if (extracted.data == NULL) print_err("Could not read the extracted archive: ", dest);
    if (extracted.length == 1 && Path$is_directory(*(Path_t *)extracted.data, false)) {
        Path_t top_level = *(Path_t *)extracted.data;
        OptionalList_t contents = Path$children(top_level, true);
        if (contents.data == NULL) print_err("Could not read the extracted archive: ", top_level);
        for (int64_t i = 0; i < (int64_t)contents.length; i++) {
            Path_t p = *(Path_t *)(contents.data + i * contents.stride);
            OptionalText_t p_name = Path$base_name(p);
            if (p_name.tag == TEXT_NONE) fail("This archive contains a file whose name is not valid UTF-8: ", p);
            Result_t moved = Path$move(p, Path$child(dest, p_name), false);
            if (moved.Failure.reason.tag != TEXT_NONE) fail(moved.Failure.reason);
        }
        Result_t removed = Path$remove(top_level, true);
        if (removed.Failure.reason.tag != TEXT_NONE) fail(removed.Failure.reason);
    }
}

// Vendor the named package into the current project's vendor/ directory,
// updating (or creating) its ./packages.ini entry. The normal mode copies the
// digest-verified archive and keeps the digest pin; editable mode extracts the
// sources into a directory (dropping the digest, since directory sources
// aren't digested) so the vendored copy can be modified freely. Previous
// sources are demoted to fallbacks.
void vendor_package(const char *name, bool editable) {
    Path_t cwd = Path$current_dir();
    Path_t ini = Path$child(cwd, Text("packages.ini"));
    pkg_info_t pkg = {.name = name, .info = EMPTY_TABLE};
    Path_t found_in = ini;
    if (!parse_package_entry(ini, name, &pkg)) {
        // Fall back to the compiler's default pins, copying the entry into
        // the project's own packages.ini:
        found_in = Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@", TOMO_VERSION, "/packages.ini"));
        if (!parse_package_entry(found_in, name, &pkg))
            fail("There is no [", name, "] package entry in ", ini, " or ", found_in);
    }

    const char *source = Table$str_get(pkg.info, "source");
    if (source == NULL) fail("The package ", name, " has no source to vendor");
    if ((source[0] == '.' || source[0] == '/' || source[0] == '~')
        && Path$is_directory(Path$resolved(Path$from_str(source), cwd), true))
        fail("The package ", name, " is already vendored as a directory: ", source);

    // Make sure the package is installed, which computes/verifies the digest
    // and caches the verified archive:
    Path_t store_root = Path$child(Path$child(cwd, Text(".tomo")), Text("store"));
    OptionalPath_t installed = try_install_package(found_in, &pkg, true, store_root);
    if (installed == NULL) fail("Could not install package: ", name);
    const char *digest = Table$str_get(pkg.info, "digest");
    if (digest == NULL) fail("The package ", name, " has no digest to vendor by");

    Path_t cache_dir = download_cache_dir(Text$from_str(digest));
    OptionalList_t cached = Path$is_directory(cache_dir, true) ? Path$children(cache_dir, true) : NONE_LIST;
    if (cached.data == NULL || cached.length != 1)
        fail("There is no cached archive for package ", name, " (digest ", digest, ")");
    Path_t archive = *(Path_t *)cached.data;

    Result_t result = Path$create_directory(Path$child(cwd, Text("vendor")), 0755, true);
    if (result.Failure.reason.tag != TEXT_NONE) fail("Failed to make vendor/ directory: ", result.Failure.reason);

    const char *new_source;
    Path_t link_target = installed;
    if (editable) {
        Path_t vendored = Path$child(Path$child(cwd, Text("vendor")), Text$from_str(name));
        if (Path$exists(vendored)) fail("There is already a vendored copy at ", vendored);
        extract_package_archive(archive, vendored);
        new_source = String("./vendor/", name);
        link_target = vendored;
    } else {
        Path_t vendored = Path$child(Path$child(cwd, Text("vendor")), Path$base_name(archive));
        xsystem("cp ", quoted(archive), " ", quoted(vendored));
        new_source = String("./vendor/", Text$as_c_string(Path$base_name(archive)));
    }

    // Rebuild the entry: digest (unless editable), the vendored source first,
    // then the previous sources demoted to fallbacks, then any other keys:
    pkg_info_t updated = {.name = name, .info = EMPTY_TABLE};
    if (!editable) Table$str_set(&updated.info, "digest", digest);
    Table$str_set(&updated.info, "source", new_source);
    int fallback = 2;
    for (int i = 1;; i++) {
        const char *key = i == 1 ? "source" : String("source-", i);
        const char *old = Table$str_get(pkg.info, key);
        if (old == NULL) break;
        if (!streq(old, new_source)) Table$str_set(&updated.info, String("source-", fallback++), old);
    }
    for (int64_t i = 0; i < (int64_t)pkg.info.entries.length; i++) {
        struct {
            const char *key, *value;
        } *entry = pkg.info.entries.data + i * pkg.info.entries.stride;
        if (streq(entry->key, "digest") || strncmp(entry->key, "source", strlen("source")) == 0) continue;
        Table$str_set(&updated.info, entry->key, entry->value);
    }
    rewrite_package_entry(ini, name, updated);
    // Record the .tomo/packages/<name> binding link right away (it would
    // otherwise only appear on the next build):
    create_binding_link(ini, name, link_target);
    print("Vendored ", name, " to \033[1m", new_source, "\033[m", editable ? " (editable, without a digest pin)" : "");

    // In editable mode the digest pin is gone, so the store entry it pointed
    // at is no longer used; clean it up. (Non-editable vendoring keeps the
    // digest, so its store entry stays in use.)
    if (editable) {
        Path_t old_entry = package_store_path(store_root, digest);
        if (Path$exists(old_entry)) {
            Result_t removed = Path$remove(old_entry, true);
            if (removed.Failure.reason.tag == TEXT_NONE) print("Removed the now-unused store entry: ", old_entry);
        }
    }
}

// Whether a packages.ini source string points into the project's ./vendor/
// directory:
static bool is_vendored_source(const char *source) {
    return source && (starts_with(source, "./vendor/") || starts_with(source, "vendor/"));
}

// Whether any package entry in the ini file (other than `except_name`) still
// references `source` as one of its sources:
static bool source_still_referenced(Path_t ini_file, const char *except_name, const char *source) {
    List_t sections = ini_section_names(ini_file);
    for (int64_t i = 0; i < (int64_t)sections.length; i++) {
        const char *section = *(const char **)(sections.data + i * sections.stride);
        if (streq(section, except_name)) continue;
        pkg_info_t pkg = {.name = section, .info = EMPTY_TABLE};
        if (!parse_package_entry(ini_file, section, &pkg)) continue;
        for (int j = 1;; j++) {
            const char *key = j == 1 ? "source" : String("source-", j);
            const char *value = Table$str_get(pkg.info, key);
            if (value == NULL) break;
            if (streq(value, source)) return true;
        }
    }
    return false;
}

// The inverse of vendor_package(): restore the named package's ./packages.ini
// entry to a non-vendored source (the first fallback source, or the compiler's
// default pin), re-install it into the project's .tomo/store/ (re-pinning the
// digest if editable vendoring dropped it), and delete the vendored copy.
void unvendor_package(const char *name) {
    Path_t cwd = Path$current_dir();
    Path_t ini = Path$child(cwd, Text("packages.ini"));
    pkg_info_t pkg = {.name = name, .info = EMPTY_TABLE};
    if (!parse_package_entry(ini, name, &pkg)) fail("There is no [", name, "] package entry in ", ini);

    const char *vendored_source = Table$str_get(pkg.info, "source");
    if (!is_vendored_source(vendored_source))
        fail("The package ", name, " is not vendored (source is ", vendored_source ? vendored_source : "missing", ")");
    Path_t vendored = Path$resolved(Path$from_str(vendored_source), cwd);
    bool editable = Path$is_directory(vendored, true);

    // Find the source to restore: the first non-vendored fallback source (the
    // rest stay as fallbacks), or failing that, the compiler's default pin:
    const char *digest = editable ? NULL : Table$str_get(pkg.info, "digest");
    const char *restored_source = NULL;
    List_t remaining_fallbacks = EMPTY_LIST;
    for (int i = 2;; i++) {
        const char *old = Table$str_get(pkg.info, String("source-", i));
        if (old == NULL) break;
        if (is_vendored_source(old)) continue; // Never restore to another vendored copy
        if (restored_source == NULL) restored_source = old;
        else List$insert(&remaining_fallbacks, &old, I(0), sizeof(const char *));
    }
    if (restored_source == NULL) {
        Path_t defaults = Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@", TOMO_VERSION, "/packages.ini"));
        pkg_info_t default_pkg = {.name = name, .info = EMPTY_TABLE};
        if (!parse_package_entry(defaults, name, &default_pkg))
            fail("The package ", name, " has no fallback source to restore, and no [", name, "] entry in ", defaults);
        restored_source = Table$str_get(default_pkg.info, "source");
        if (restored_source == NULL) fail("The package ", name, " has no fallback source to restore");
        if (digest == NULL) digest = Table$str_get(default_pkg.info, "digest");
    }

    // Rebuild the entry: digest (if still known), the restored source, the
    // remaining fallbacks, then any other keys:
    pkg_info_t updated = {.name = name, .info = EMPTY_TABLE};
    if (digest) Table$str_set(&updated.info, "digest", digest);
    Table$str_set(&updated.info, "source", restored_source);
    for (int64_t i = 0; i < (int64_t)remaining_fallbacks.length; i++) {
        const char *old = *(const char **)(remaining_fallbacks.data + i * remaining_fallbacks.stride);
        Table$str_set(&updated.info, String("source-", i + 2), old);
    }
    for (int64_t i = 0; i < (int64_t)pkg.info.entries.length; i++) {
        struct {
            const char *key, *value;
        } *entry = pkg.info.entries.data + i * pkg.info.entries.stride;
        if (streq(entry->key, "digest") || strncmp(entry->key, "source", strlen("source")) == 0) continue;
        Table$str_set(&updated.info, entry->key, entry->value);
    }

    // Reinstall from the restored source, which re-verifies (and, if editable
    // vendoring dropped the digest pin, recomputes and re-pins) the digest:
    Path_t store_root = Path$child(Path$child(cwd, Text(".tomo")), Text("store"));
    OptionalPath_t installed = try_install_package(ini, &updated, true, store_root);
    if (installed == NULL) fail("Could not reinstall package ", name, " from ", restored_source);

    // A freshly recomputed digest lands at the end of the table; rebuild the
    // entry with the digest first, matching how vendoring writes it:
    if (digest == NULL && Table$str_get(updated.info, "digest") != NULL) {
        pkg_info_t reordered = {.name = name, .info = EMPTY_TABLE};
        Table$str_set(&reordered.info, "digest", Table$str_get(updated.info, "digest"));
        for (int64_t i = 0; i < (int64_t)updated.info.entries.length; i++) {
            struct {
                const char *key, *value;
            } *entry = updated.info.entries.data + i * updated.info.entries.stride;
            if (!streq(entry->key, "digest")) Table$str_set(&reordered.info, entry->key, entry->value);
        }
        updated = reordered;
    }

    rewrite_package_entry(ini, name, updated);
    // Point the .tomo/packages/<name> binding link back at the store entry:
    create_binding_link(ini, name, installed);

    // Delete the vendored copy (an extracted directory for editable vendoring,
    // otherwise an archive file, kept if another entry still references it):
    if (editable || !source_still_referenced(ini, name, vendored_source)) {
        Result_t removed = Path$remove(vendored, true);
        if (removed.Failure.reason.tag != TEXT_NONE)
            fprint(stderr, "Warning: could not remove the vendored copy at ", vendored);
    }
    // Clean up the vendor/ directory itself if nothing is left in it:
    Path_t vendor_dir = Path$child(cwd, Text("vendor"));
    // Only remove it if it is readable and empty: an unreadable directory
    // also reports zero children, and removing that would be destructive.
    OptionalList_t vendored_files = Path$is_directory(vendor_dir, true) ? Path$children(vendor_dir, true) : NONE_LIST;
    if (vendored_files.data != NULL && vendored_files.length == 0) Path$remove(vendor_dir, false);

    print("Unvendored ", name, ": restored to \033[1m", restored_source, "\033[m");
}