llm
Download
94 lines 4 KiBCModified 2026-09-16 raw
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
// `tomo package`: build Tomo packages into static archives

#include <unistd.h>

#include "../stdlib/list.h"
#include "../stdlib/text.h"
#include "commands.h"
#include "common.h"
#include "compilation.h"

static List_t paths = EMPTY_LIST;
static OptionalPath_t output = NULL;

static cli_arg_t package_spec[] = {
    {"paths", &paths, List$info(&Path$info), .positional = true, .metavar = "dir-or-file",
     .description = "the package directories (or .tm files) to build (default: the current directory)"},
    {"output", &output, &Path$info, .short_flag = 'o', .metavar = "libname.a",
     .description = "where to put the archive (single package only; defaults to package.a in the package "
                    "directory)"},
    OPTIMIZATION_FLAG,
    VERBOSE_FLAG,
    QUIET_FLAG,
};

static int cmd_package(cli_command_t *self, List_t extra_args) {
    (void)self, (void)extra_args;
    set_default_logs(LOG_BUILD);
    // A package archive is a persistent artifact, like `build`: default to the
    // highest safe optimization. Set before the per-package forks so children
    // inherit it; -O overrides the level.
    configure_codegen(opt_flag.tag == TEXT_NONE ? Text("3") : opt_flag, /*optimize=*/true);
    Path_t cwd = Path$current_dir();
    // Default: package the current directory:
    if (paths.length == 0) List$insert(&paths, &cwd, I(0), sizeof(Path_t));

    // Positionals are package directories or explicit .tm files (which are
    // grouped into a single archive):
    List_t dirs = EMPTY_LIST, files = EMPTY_LIST;
    for (int64_t i = 0; i < (int64_t)paths.length; i++) {
        Path_t path = Path$resolved(*(Path_t *)(paths.data + i * paths.stride), cwd);
        if (Path$is_directory(path, true)) List$insert(&dirs, &path, I(0), sizeof(Path_t));
        else if (Path$has_extension(path, Text("tm"))) List$insert(&files, &path, I(0), sizeof(Path_t));
        else print_err("Not a package directory or .tm file: ", path);
    }

    if (output != NULL && dirs.length + (files.length > 0 ? 1 : 0) > 1)
        print_err("--output can only be used when building a single package");

    // Fork a child process to build each package to prevent cross-contamination
    // of side effects when building one package from affecting another package.
    // This *could* be done in parallel, but there may be some dependency issues.
    for (int64_t i = 0; i < (int64_t)dirs.length; i++) {
        Path_t dir = *(Path_t *)(dirs.data + i * dirs.stride);
        pid_t child = fork();
        if (child == 0) {
            if (output != NULL) {
                OptionalList_t tm_files = Path$glob(dir, Text("[!._0-9]*.tm"));
                if (tm_files.data == NULL) print_err("Could not read the package directory: ", dir);
                build_package_archive(dir, tm_files, Path$resolved(output, cwd));
            } else {
                build_package(dir);
            }
            fflush(NULL);
            _exit(0);
        }
        wait_for_child_success(child);
    }

    if (files.length > 0) {
        OptionalPath_t pkg_dir = Path$parent(*(Path_t *)files.data);
        assert(pkg_dir); // The list holds .tm files, which have a directory
        Path_t archive = output != NULL ? Path$resolved(output, cwd) : Path$child(pkg_dir, Text("package.a"));
        pid_t child = fork();
        if (child == 0) {
            build_package_archive(pkg_dir, files, archive);
            fflush(NULL);
            _exit(0);
        }
        wait_for_child_success(child);
    }
    return 0;
}

cli_command_t package_command = {
    .name = "package",
    .summary = "Build Tomo packages into static archives",
    .description = "With no arguments, the current directory is built as a package: every\n"
                   ".tm file not starting with an underscore (or dot or digit) is compiled\n"
                   "and archived into package.a. Directory arguments are each built the\n"
                   "same way; .tm file arguments are compiled into a single archive.",
    .spec_len = sizeof(package_spec) / sizeof(package_spec[0]),
    .spec = package_spec,
    .handler = cmd_package,
};