[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/mcpp-community/mcpp/main/src/cli.cppm [Back]  [Original]

// mcpp.cli  top-level command dispatch (and nothing else).
//
// The cli layer only parses arguments and routes:
//   mcpp.cli.cmd_build / cmd_new / cmd_registry / cmd_cache /
//   mcpp.cli.cmd_toolchain / cmd_publish / cmd_self   (parse + route)
//   mcpp.pm.commands                                  (add / remove / update)
// Domain logic lives in its owning subsystem: mcpp.build.{prepare,execute},
// mcpp.pm.index_management, mcpp.toolchain.lifecycle, mcpp.scaffold.create,
// mcpp.publish.pipeline, mcpp.pack.pipeline, mcpp.bmi_cache.maintenance, mcpp.doctor,
// mcpp.project, mcpp.fetcher.progress.
// See .agents/docs/2026-06-10-cli-modularization.md for the architecture.

module;
#include 
#include 

export module mcpp.cli;

import std;
import mcpplibs.cmdline;
import mcpp.cli.cmd_build;
import mcpp.cli.cmd_cache;
import mcpp.cli.cmd_new;
import mcpp.cli.cmd_publish;
import mcpp.cli.cmd_xpkg;
import mcpp.cli.cmd_registry;
import mcpp.cli.cmd_self;
import mcpp.cli.cmd_toolchain;
import mcpp.pm.commands;
import mcpp.toolchain.fingerprint;   // MCPP_VERSION
import mcpp.wire;
import mcpp.platform.env;            // --offline  MCPP_OFFLINE
import mcpp.platform.runtime_search; // linker-wrapper path-injection opt-out
import mcpp.ui;
import mcpp.log;

export namespace mcpp::cli {

int run(int argc, char** argv);

} // namespace mcpp::cli

namespace mcpp::cli {

// Custom top-level help. cmdline's auto-generated `print_help` is a fine
// default but its layout (`USAGE:`, no command-specific blurbs) doesn't
// match what the e2e tests assert against  they check for `Usage:`
// (mixed case) plus `mcpp new` / `mcpp build` literals. We keep the
// canonical printer here so the docs/CHANGELOG examples don't drift
// every time cmdline tweaks its formatting.
void print_usage() {
    std::println("mcpp v{} - modern C++23 build tool", mcpp::toolchain::MCPP_VERSION);
    std::println("");
    std::println("Usage:");
    std::println("Project commands:");
    std::println("  mcpp new                       Create a new package skeleton");
    std::println("  mcpp build [options]                 Build the current package");
    std::println("  mcpp run [target] [-- args...]       Build + run a binary target");
    std::println("  mcpp test [pattern] [-- args...]     Build + run tests/**/*.cpp (--list, --timeout, --build-timeout, --message-format json)");
    std::println("  mcpp clean [--bmi-cache]             Remove target/ (and optionally the build cache)");
    std::println("  mcpp add [ns.]pkg@ver                Add an exact dependency to mcpp.toml");
    std::println("  mcpp remove [ns.]pkg                 Remove an exact dependency from mcpp.toml");
    std::println("  mcpp update [pkg]                    Re-resolve deps and rewrite mcpp.lock");
    std::println("  mcpp search                 Search packages in registries");
    std::println("  mcpp publish [--dry-run]             Publish package to default registry");
    std::println("  mcpp pack [target]                   Build + package (program: bundle; library: interface + binaries)");
    std::println("  mcpp emit xpkg [-V VER] [-o FILE]    Generate xpkg Lua entry");
    std::println("  mcpp xpkg parse  [--json]  Validate an xpkg descriptor (resolver grammar)");
    std::println("");
    std::println("Resource management:");
    std::println("  mcpp toolchain install|list|default  Manage mcpp's private toolchains");
    std::println("  mcpp cache dir|list|info|gc|...      Inspect/manage the global build cache");
    std::println("  mcpp index list|add|remove|update    Manage package registries");
    std::println("");
    std::println("About mcpp itself:");
    std::println("  mcpp self doctor                     Diagnose mcpp environment health");
    std::println("  mcpp self env                        Print mcpp paths and toolchain");
    std::println("  mcpp self config [--mirror CN|GLOBAL] Show or modify mcpp's xlings config");
    std::println("  mcpp self version                    Show mcpp version");
    std::println("  mcpp self explain              Show extended description for an error code");
    std::println("  mcpp --help / --version              Help / version");
    std::println("");
    std::println("Build options:");
    std::println("  --verbose, -v                        Verbose compiler output");
    std::println("  --quiet, -q                          Suppress status output");
    std::println("  --print-fingerprint                  Show toolchain fingerprint and 11 inputs");
    std::println("  --configure-only                     Generate CDB without compiling or linking");
    std::println("  --cache                        Dependency cache: global (default) | local | off");
    std::println("  --no-cache                           Deprecated alias for --cache=off (clears the build dir)");
    std::println("  --no-color                           Disable colored output");
    std::println("  --offline                            Never touch the network (also: MCPP_OFFLINE=1)");
    std::println("  --jobs N|auto, -j                    Concurrent compiles ('auto' = cores + free RAM)");
    std::println("  --toolchain SPEC                     Use this toolchain for one build (e.g. llvm@22.1.8)");
    std::println("");
    std::println("Docs: https://github.com/mcpp-community/mcpp/tree/main/docs");
}

int run(int argc, char** argv) {
    namespace cl = mcpplibs::cmdline;

    //  --quiet / --no-color: pre-scan 
    // The cmdline lib propagates global options into nested subcommand
    // ParsedArgs, but we set ui:: state up-front so that *every* line
    // emitted from the action lambdas (including the very first
    // "Resolving toolchain" banner) honours the user's intent. This is
    // a side-channel only  the global options are still declared on
    // the App below so they show up in --help and pass schema checks.
    for (int i = 1; i < argc; ++i) {
        std::string_view a = argv[i];
        // Everything after a bare `--` belongs to the program being run or the
        // test binary being invoked, not to mcpp. Without this, `mcpp run -- -j 4`
        // reads the child's flag as mcpp's own concurrency setting  `-j` is a
        // common enough flag that this is a matter of when, not whether.
        if (a == "--") break;
        if      (a == "--quiet" || a == "-q") mcpp::ui::set_quiet(true);
        else if (a == "--no-color")           mcpp::ui::disable_color();
        else if (a == "--verbose" || a == "-v") mcpp::log::set_verbose(true);
        // --offline is published as the env var rather than plumbed through
        // BuildOverrides: its consumers are index refresh, package install and
        // toolchain auto-install, which sit in three subsystems and would each
        // need a parameter threaded down. Same shape as MCPP_VERBOSE above, and
        // it makes `MCPP_OFFLINE=1` and `--offline` literally the same switch.
        else if (a == "--offline") mcpp::platform::env::set("MCPP_OFFLINE", "1");
        // --jobs rides the same side channel as --offline, for the same reason
        // recorded there: its consumer is deep in mcpp.build.execute and
        // threading a parameter down would touch every caller in between.
        // Accepts `--jobs N`, `--jobs=N`, `-j N` and `-jN`.
        else if (a == "--jobs" || a == "-j") {
            if (i + 1 < argc) mcpp::platform::env::set("MCPP_JOBS", argv[++i]);
        }
        else if (a.starts_with("--jobs=")) mcpp::platform::env::set("MCPP_JOBS", std::string(a.substr(7)));
        // --toolchain rides the same channel, for the same reason: its consumer
        // is deep inside prepare's resolution and threading a parameter down
        // would touch every caller in between.
        else if (a == "--toolchain") {
            if (i + 1 < argc) mcpp::platform::env::set("MCPP_TOOLCHAIN", argv[++i]);
        }
        else if (a.starts_with("--toolchain=")) mcpp::platform::env::set("MCPP_TOOLCHAIN", std::string(a.substr(12)));
        else if (a.starts_with("-j") && a.size() > 2)
            mcpp::platform::env::set("MCPP_JOBS", std::string(a.substr(2)));
    }
    // Decline xlings' linker-wrapper path injection, for this process and
    // everything it spawns (openxlings/xlings#540).
    //
    // That wrapper appends `-rpath "$XLINGS_SUBOS_LIB"` to every link it sees.
    // mcpp wants the TAG half of what it does and must refuse the PATH half:
    // `$XLINGS_SUBOS_LIB` names the ACTIVE SHELL's SubOS, which is measurably
    // not the one mcpp resolved  mcpp keeps its own xlings home under
    // `/registry`, so on an ordinary developer machine the variable
    // points at a different farm backed by a DIFFERENT PHYSICAL glibc payload.
    // Inheriting it would put a second libc on the artifact's search path,
    // which is the one thing rule B exists to prevent. mcpp emits its own farm
    // entry, derived from the binding it actually selected.
    //
    // Set here rather than per link command: the link line has a hard 128KiB
    // ceiling that real workspaces already spend 43% of, and children inherit
    // the environment for free. Declared BEFORE the wrapper ships, because
    // "the exit must be declared, not inferred" is the rule that whole
    // negotiation established  today this is a no-op.
    mcpp::platform::env::set(
        std::string(mcpp::platform::search::kLinkerPathInjectionOptOut),
        std::string(mcpp::platform::search::kLinkerPathInjectionOptOutValue));

    // Env override (observability, esp. CI): MCPP_VERBOSE=
    // turns on verbose logging for EVERY mcpp invocation  including the ones
    // nested inside e2e test scripts that call $MCPP without flags. Lets a
    // workflow flip on diagnostics globally with one env var. An explicit
    // --quiet still wins (it is processed above and gates the verbose sinks).
    if (const char* v = std::getenv("MCPP_VERBOSE");
        v && *v && std::string_view(v) != "0")
        mcpp::log::set_verbose(true);

    //  top-level --help / -h / --version intercept 
    // cmdline auto-handles these but its formatter doesn't match the
    // mixed-case "Usage:" + per-command blurbs that documentation +
    // e2e tests pin. Print our canonical screen and `mcpp X.Y.Z`
    // version line up-front so the App never sees these tokens.
    if (argc >= 2) {
        std::string_view a = argv[1];
        if (a == "--help" || a == "-h") { print_usage(); return 0; }
        if (a == "--version" || a == "-V") {
            std::println("mcpp {}", mcpp::toolchain::MCPP_VERSION);
            return 0;
        }
    }

    //  action_rc plumbing 
    // cmdline's action callback returns void. Capture int return codes
    // via a shared local; `wrap_rc` adapts an `int(ParsedArgs&)` lambda
    // into the void-returning shape cmdline expects.
    int action_rc = 0;
    auto wrap_rc = [&action_rc](auto&& fn) {
        return [fn = std::forward(fn), &action_rc]
               (const cl::ParsedArgs& args) {
            action_rc = fn(args);
        };
    };

    //  nested-subcommand dispatcher 
    // cmdline's run() only dispatches one level  when a parent subcommand
    // (e.g. `self`, `cache`, `index`, `emit`) has its own children but no
    // parent action, the matched leaf never gets invoked. We give every
    // such parent an action that switches on the parsed child name and
    // forwards to the right cmd_* directly.
    using cmd_fn = int(*)(const cl::ParsedArgs&);
    auto dispatch_sub = [](std::string_view parent,
                           const cl::ParsedArgs& parsed,
                           std::initializer_list table) -> int {
        if (!parsed.has_subcommand()) {
            std::string usage = std::format("`mcpp {}` requires a subcommand: ", parent);
            bool first = true;
            for (auto& [n, _] : table) {
                if (!first) usage += " / ";
                usage += n;
                first = false;
            }
            mcpp::ui::error(usage);
            return 2;
        }
        auto name    = parsed.subcommand_name();
        auto sub_ref = parsed.subcommand();
        if (!sub_ref) return 2;
        for (auto& [n, fn] : table) {
            if (n == name) return fn(sub_ref->get());
        }
        mcpp::ui::error(std::format("unknown `mcpp {} {}` subcommand", parent, name));
        return 2;
    };

    //  `--` passthrough for `mcpp run` / `mcpp test` 
    // cmdline natively recognises `--` and dumps everything after it
    // into `parsed.positionals` (with the bare `--` token dropped). For
    // `mcpp run [target] -- args...` we need to distinguish the
    // optional `target` (a real positional) from the passthrough
    // tokens (which must reach the executed binary verbatim, even when
    // they look like `-x` / `--foo`). We split ourselves at the first
    // `--` and present cmdline only the pre-`--` slice; post-args go
    // into `passthrough` and are handed to the action helper directly.
    std::vector passthrough;
    std::vector trimmed_argv(argv, argv + argc);
    {
        for (std::size_t i = 1; i < trimmed_argv.size(); ++i) {
            if (std::string_view(trimmed_argv[i]) != "--") continue;
            for (std::size_t j = i + 1; j < trimmed_argv.size(); ++j)
                passthrough.emplace_back(trimmed_argv[j]);
            trimmed_argv.resize(i);  // drop `--` and everything after
            break;
        }
    }
    int      trimmed_argc = static_cast(trimmed_argv.size());
    char**   trimmed_argp = trimmed_argv.empty() ? nullptr : trimmed_argv.data();

    //  Build the top-level App 
    auto app = cl::App("mcpp")
        .version(std::string{mcpp::toolchain::MCPP_VERSION})
        .description("modern C++ build tool")
        .option(cl::Option("quiet").short_name('q')
            .help("Suppress status output").global())
        .option(cl::Option("verbose").short_name('v')
            .help("Show detailed progress on stderr").global())
        .option(cl::Option("no-color")
            .help("Disable colored output").global())
        .option(cl::Option("offline")
            .help("Never touch the network (index refresh, downloads, toolchain install)")
            .global())
        // Answers "what do you speak" without spawning a command that might
        // fail. An optimisation, NOT the client's detection rule: on any mcpp
        // predating it this is itself an unknown option, so a client must
        // still detect the protocol by parsing stdout for schemaVersion+kind.
        .option(cl::Option("protocol-version")
            .help("Print the machine-output protocol this build speaks (JSON)"))

        //  project commands 
        .subcommand(cl::App("new")
            .description("Create a new mcpp package skeleton")
            // not .required(): `--list-templates` runs without a name
            // (cmd_new validates presence for project creation itself).
            .arg(cl::Arg("name").help("Package directory name"))
            .option(cl::Option("template").short_name('t').takes_value().value_name("SPEC")
                .help("bin (default) | [ns.]pkg[@ver][:template]  exact package template"))
            .option(cl::Option("list-templates").takes_value().value_name("PKG")
                .help("List templates from exact [ns.]pkg[@ver]"))
            .action(wrap_rc(cmd_new)))
        .subcommand(cl::App("build")
            .description("Build the current package")
            .option(cl::Option("configure-only")
                .help("Generate compile_commands.json without compiling or linking"))
            .option(cl::Option("print-fingerprint")
                .help("Show toolchain fingerprint and 11 inputs"))
            .option(cl::Option("cache").takes_value().value_name("MODE")
                .help("Global dependency cache: global (default) | local | off"))
            .option(cl::Option("jobs").short_name('j').takes_value().value_name("N")
                .help("Concurrent compiles: a number, or 'auto' to size from cores + free RAM"))
            .option(cl::Option("toolchain").takes_value().value_name("SPEC")
                .help("Build with this toolchain for one build, e.g. llvm@22.1.8"))
            .option(cl::Option("no-cache")
                .help("Deprecated alias for --cache=off (also clears the build dir)"))
            .option(cl::Option("target").takes_value().help(
                "Build for  (e.g. x86_64-linux-musl); looks up [target.] in mcpp.toml"))
            .option(cl::Option("static").help(
                "Force static linking (-static). On Linux, prefer pairing with --target -linux-musl"))
            .option(cl::Option("package").short_name('p').takes_value().value_name("NAME")
                .help("Build only the named workspace member"))
            .option(cl::Option("profile").takes_value().value_name("NAME")
                .help("Build profile: release (default) | dev | dist | "))
            .option(cl::Option("release").help("Shorthand for --profile release"))
            .option(cl::Option("dev").help("Shorthand for --profile dev (-O0 -g)"))
            .option(cl::Option("features").takes_value().value_name("LIST")
                .help("Activate root-package features (comma-separated)"))
            .option(cl::Option("cap").takes_value().value_name("LIST")
                .help("Pin capability providers (e.g. blas=openblas,lapack=mkl)"))
            .option(cl::Option("strict")
                .help("Treat manifest schema warnings (unknown feature/platform) as errors"))
            .option(cl::Option("workspace")
                .help("Build all workspace members"))
            .action(wrap_rc(cmd_build)))
        .subcommand(cl::App("run")
            .description("Build + run a binary target (after `--`, args are passed to it)")
            //  Named `bin`, NOT `target`, and the rename is load-bearing.
            //
            // This positional is a BINARY NAME from [[bin]]/src layout. It was
            // called `target`  the same word as the cross-target axis  and
            // ParsedArgs::value() falls back from an unset option to a
            // positional OF THE SAME NAME, so adding `--target` here made
            // every ordinary invocation read the binary name as a triple:
            //
            //     $ mcpp run q
            //     error: unknown target 'q'
            //
            // The name is never read back (cmd_run takes positional(0) by
            // index); it only labels this slot and shows up in --help, where
            // `bin` is the more accurate word anyway. So renaming it is what
            // lets `run` spell the flag `--target` like every other
            // subcommand, instead of being the one command that cannot.
            .arg(cl::Arg("bin").help("Binary name (optional)"))
            .option(cl::Option("target").takes_value().value_name("TRIPLE")
                .help("Cross target triple (same axis as `mcpp build --target`)"))
            // Kept as an alias: it shipped in 2026.8.19.1 as the only spelling
            // `run` accepted, and scripts written against it must keep working.
            .option(cl::Option("target-triple").takes_value().value_name("TRIPLE")
                .help("Alias for --target"))
            .option(cl::Option("package").short_name('p').takes_value().value_name("NAME")
                .help("Run only the named workspace member (single-member; no --workspace fan-out)"))
            .option(cl::Option("cache").takes_value().value_name("MODE")
                .help("Global dependency cache: global (default) | local | off"))
            .option(cl::Option("no-cache")
                .help("Deprecated alias for --cache=off (also clears the build dir)"))
            .action(wrap_rc([&passthrough](const cl::ParsedArgs& p) {
                return cmd_run(p, std::span(passthrough));
            })))
        .subcommand(cl::App("test")
            .description("Build + run all tests/**/*.cpp (after `--`, args go to each test binary)")
            .arg(cl::Arg("pattern")
                .help("Run only tests whose name contains PATTERN (optional)"))
            .option(cl::Option("target").takes_value().value_name("TRIPLE")
                .help("Cross target triple (same axis as `mcpp build --target`)"))
            .option(cl::Option("message-format").takes_value().value_name("FMT")
                .help("Output format: human (default) | json (NDJSON, one record per test)"))
            .option(cl::Option("list")
                .help("List (filtered) tests without building or running them"))
            .option(cl::Option("timeout").takes_value().value_name("SECS")
                .help("Kill a test still RUNNING after SECS seconds (default 300; 0 = no limit)"))
            .option(cl::Option("build-timeout").takes_value().value_name("SECS")
                .help("Kill a compile/link drive still running after SECS seconds (default 0 = no limit; POSIX only)"))
            .option(cl::Option("workspace-timeout").takes_value().value_name("SECS")
                .help("Stop the --workspace fan-out after SECS seconds and report what did run (default 0 = no limit)"))
            .option(cl::Option("profile").takes_value().value_name("NAME")
                .help("Build profile for the test build: release (default) | dev | dist | "))
            .option(cl::Option("features").takes_value().value_name("LIST")
                .help("Activate root-package features for the test build (comma-separated)"))
            .option(cl::Option("cap").takes_value().value_name("LIST")
                .help("Pin capability providers (e.g. blas=openblas,lapack=mkl)"))
            .option(cl::Option("strict")
                .help("Treat manifest schema warnings (unknown feature/platform) as errors"))
            .option(cl::Option("package").short_name('p').takes_value().value_name("NAME")
                .help("Run tests only for the named workspace member"))
            .option(cl::Option("cache").takes_value().value_name("MODE")
                .help("Global dependency cache: global (default) | local | off"))
            .option(cl::Option("no-cache")
                .help("Deprecated alias for --cache=off (also clears the build dir)"))
            .option(cl::Option("workspace")
                .help("Run tests for all workspace members"))
            .action(wrap_rc([&passthrough](const cl::ParsedArgs& p) {
                return cmd_test(p, std::span(passthrough));
            })))
        .subcommand(cl::App("clean")
            .description("Remove target/ (and optionally the global build cache)")
            .option(cl::Option("bmi-cache").help("Also wipe the global build cache (see `mcpp cache clean`)"))
            .action(wrap_rc(cmd_clean)))
        .subcommand(cl::App("why")
            .description("Explain how the toolchain / runtime / deps were resolved")
            .arg(cl::Arg("topic").help("toolchain | runtime | deps (default: all)"))
            .action(wrap_rc(cmd_why)))
        .subcommand(cl::App("resolve")
            .description("Re-resolve the build plan and explain it")
            .option(cl::Option("explain").help("Print resolved toolchain / runtime / deps"))
            .action(wrap_rc(cmd_why)))
        .subcommand(cl::App("add")
            .description("Add a dependency to mcpp.toml")
            .arg(cl::Arg("pkg").help(
                "Exact package spec, e.g. foo@1.0.0 or compat.gtest@1.15.2")
                .required())
            .option(cl::Option("dev").help(
                "Add to [dev-dependencies] (test-only, e.g. compat.gtest)"))
            .action(wrap_rc(mcpp::pm::commands::cmd_add)))
        .subcommand(cl::App("remove")
            .description("Remove a dependency from mcpp.toml")
            .arg(cl::Arg("pkg").help("Exact package selector [ns.]name").required())
            .action(wrap_rc(mcpp::pm::commands::cmd_remove)))
        .subcommand(cl::App("update")
            .description("Re-resolve dependencies and rewrite mcpp.lock")
            .arg(cl::Arg("pkg").help("If given, update only that package"))
            .action(wrap_rc(mcpp::pm::commands::cmd_update)))
        .subcommand(cl::App("search")
            .description("Search packages in configured registries")
            .arg(cl::Arg("keyword").help("Search keyword (substring match)").required())
            .action(wrap_rc(cmd_search)))
        .subcommand(cl::App("publish")
            .description("Publish package to default registry")
            .option(cl::Option("dry-run").help("Print xpkg.lua without uploading"))
            .option(cl::Option("allow-dirty").help("Allow uncommitted changes"))
            .action(wrap_rc(cmd_publish)))
        .subcommand(cl::App("pack")
            // "archive", not "tarball": a Windows target produces a .zip, and
            // the help said tarball while the code had already stopped
            // agreeing. `--format tar` likewise selects "an archive rather
            // than a plain directory"  WHICH archive follows the artifact,
            // because a .tar.gz full of DLLs is a package most Windows users
            // cannot open without installing something first.
            // Says both shapes, because `[targets.].kind` picks between them
            // and the one-line help is where a reader finds that out. "Bundle
            // into a self-contained archive" described only the program case,
            // which is now half of what this command does.
            .description("Build + package: a program becomes a self-contained "
                         "bundle, a library an interface + prebuilt binaries")
            // NB: a target NAME from [targets.*], not a triple  the same
            // split `mcpp run [target]` has. Its `kind` decides what is
            // packed, so there is no --lib and no --artifact: a program
            // becomes an application bundle, a library becomes a library
            // package. Omit it and mcpp picks the only packable target.
            .arg(cl::Arg("target").help("Target name from [targets.*] (optional)"))
            .option(cl::Option("mode").takes_value()
                .help("system | vendored (default) | self-contained | static"))
            .option(cl::Option("target").takes_value().multiple()
                .help("Triple, e.g. x86_64-linux-musl (repeatable: one leg per triple)"))
            .option(cl::Option("format").takes_value()
                .help("tar (default; .zip for a Windows target) | dir"))
            .option(cl::Option("output").short_name('o').takes_value()
                .help("Override output path"))
            // Packaging builds RELEASE by default  the artifact leaves this
            // machine. `[build] default-profile` still wins when it is set;
            // this only replaces the "dev" fallback every other command uses.
            .option(cl::Option("profile").takes_value()
                .help("Build profile (default: [build] default-profile, else release)"))
            .option(cl::Option("no-strip")
                .help("Ship the artifacts as built (default: strip debug info)"))
            .option(cl::Option("debug-symbols").takes_value().value_name("DIR")
                .help("Write the separated *.debug files here (default: discard)"))
            .action(wrap_rc(cmd_pack)))

        //  emit (one nested subcommand: xpkg) 
        .subcommand(cl::App("emit")
            .description("Generate package descriptor (xpkg)")
            .subcommand(cl::App("xpkg")
                .description("Generate xpkg Lua entry")
                .option(cl::Option("version").short_name('V').takes_value().value_name("VER")
                    .help("Override package version"))
                .option(cl::Option("output").short_name('o').takes_value().value_name("FILE")
                    .help("Write to file instead of stdout"))
                .option(cl::Option("namespace").takes_value().value_name("NS")
                    .help("Package namespace for the emitted descriptor "
                          "(overrides [package] namespace). Emits both "
                          "`namespace` and the fully-qualified `name`")))
            .action(wrap_rc([&dispatch_sub](const cl::ParsedArgs& p) {
                return dispatch_sub("emit", p, {{"xpkg", cmd_emit_xpkg}});
            })))

        //  xpkg (descriptor tooling: parse) 
        .subcommand(cl::App("xpkg")
            .description("Inspect / validate xpkg descriptors")
            .subcommand(cl::App("parse")
                .description("Parse a descriptor's mcpp segment exactly as the resolver would (strict: unknown keys are errors)")
                .option(cl::Option("json")
                    .help("Emit machine-readable JSON (legacy payload, kept for ever)"))
                .option(cl::Option("format").takes_value().value_name("json")
                    .help("Machine-readable output (enveloped; see docs/11-machine-output.md)"))
                .option(cl::Option("allow-unknown")
                    .help("Downgrade unknown mcpp-segment keys from error to warning"))
                .option(cl::Option("all-os")
                    .help("Validate every per-OS section (linux/macosx/windows), "
                          "not just the running host's"))
                .option(cl::Option("allow-split-name")
                    .help("OBSOLETE (kept accepted so 0.0.105-era index CI keeps "
                          "working): skip the package.name form check. Since "
                          "0.0.106 the canonical form IS the short name, so "
                          "xlings-native descriptors pass without this flag")))
            .action(wrap_rc([&dispatch_sub](const cl::ParsedArgs& p) {
                return dispatch_sub("xpkg", p, {{"parse", cmd_xpkg_parse}});
            })))

        //  resource management 
        .subcommand(cl::App("toolchain")
            .description("Install / list / select / remove C++ toolchains")
            .subcommand(cl::App("list").description("List installed toolchains"))
            .subcommand(cl::App("install")
                .description("Install a toolchain via mcpp's xlings")
                // Both `mcpp toolchain install gcc 16.1.0` and `mcpp toolchain
                // install gcc@16.1.0` are accepted, and the version may be
                // partial (`15`, `15.1`)  mcpp resolves to the highest match.
                // With --target the family may be omitted entirely (taken from
                // the target's convention pin):
                //   mcpp toolchain install --target x86_64-windows-gnu
                .arg(cl::Arg("compiler").help("gcc | llvm | msvc (or gcc@16.1.0; legacy aliases accepted)"))
                .arg(cl::Arg("version").help("e.g. 16.1.0, 15, 15.1"))
                .option(cl::Option("target").takes_value().help(
                    "Install the toolchain payload for  (e.g. x86_64-windows-gnu)")))
            .subcommand(cl::App("default")
                .description("Set the default toolchain (and optionally the default target)")
                // Same dual-form as `install`: `gcc@16.1.0` or `gcc 16.1.0`,
                // partial versions allowed.
                .arg(cl::Arg("spec").help("[@] (version may be partial)").required())
                .arg(cl::Arg("version").help("(optional, alternative to @-form)"))
                .option(cl::Option("target").takes_value().help(
                    "Default build target  (omit = host)")))
            .subcommand(cl::App("remove")
                .description("Uninstall a toolchain")
                .arg(cl::Arg("spec").help("@").required())
                .option(cl::Option("target").takes_value().help(
                    "Remove the payload for  instead of the host one")))
            .action(wrap_rc(cmd_toolchain)))
        .subcommand(cl::App("cache")
            .description("Inspect and manage the global build cache")
            .subcommand(cl::App("dir")
                .description("Print the cache root (and any pre-v1 cache)"))
            .subcommand(cl::App("list")
                .description("List cache entries with size + last-use")
                .option(cl::Option("json")
                    .help("Emit machine-readable JSON (legacy payload, kept for ever)"))
                .option(cl::Option("format").takes_value().value_name("json")
                    .help("Machine-readable output (enveloped; see docs/11-machine-output.md)")))
            .subcommand(cl::App("info")
                .description("Show details (incl. key inputs) for a cached package")
                .arg(cl::Arg("pkg").help("@").required()))
            .subcommand(cl::App("prune")
                .description("Drop entries not used within a threshold")
                .option(cl::Option("older-than").takes_value().value_name("N{s|m|h|d}")
                    .help("Age threshold (e.g. 30d)")))
            .subcommand(cl::App("gc")
                .description("LRU-collect package entries to a size and/or age budget")
                .option(cl::Option("max-size").takes_value().value_name("N{MiB|GiB}")
                    .help("Keep the package cache under this size (e.g. 5GiB)"))
                .option(cl::Option("older-than").takes_value().value_name("N{s|m|h|d}")
                    .help("Also drop entries unused for longer than this")))
            .subcommand(cl::App("clean")
                .description("Drop cache entries (default: package entries only)")
                .option(cl::Option("deps").help("Drop package entries (default)"))
                .option(cl::Option("std").help("Drop std module entries"))
                .option(cl::Option("all").help("Drop both"))
                .option(cl::Option("legacy")
                    .help("Remove the unused pre-v1 cache at $MCPP_HOME/bmi")))
            .subcommand(cl::App("verify")
                .description("Check every entry's manifest against the files on disk"))
            .action(wrap_rc([&dispatch_sub](const cl::ParsedArgs& p) {
                return dispatch_sub("cache", p, {
                    {"dir",    cmd_cache_dir},
                    {"list",   cmd_cache_list},
                    {"info",   cmd_cache_info},
                    {"prune",  cmd_cache_prune},
                    {"gc",     cmd_cache_gc},
                    {"clean",  cmd_cache_clean},
                    {"verify", cmd_cache_verify},
                });
            })))
        .subcommand(cl::App("index")
            .description("Manage configured package registries")
            .subcommand(cl::App("list")
                .description("List configured registries"))
            .subcommand(cl::App("add")
                .description("Add a custom registry")
                .arg(cl::Arg("name").help("Registry name").required())
                .arg(cl::Arg("url").help("Registry URL").required()))
            .subcommand(cl::App("remove")
                .description("Remove a registry")
                .arg(cl::Arg("name").help("Registry name").required()))
            .subcommand(cl::App("update")
                .description("Refresh local registry clones")
                .arg(cl::Arg("name").help("If given, update only this index")))
            .subcommand(cl::App("status")
                .description("Show local index presence/freshness (offline)"))
            .subcommand(cl::App("pin")
                .description("Pin a custom index to a commit rev in mcpp.toml")
                .arg(cl::Arg("name").help("Index name").required())
                .arg(cl::Arg("rev").help("Commit sha (defaults to current lock rev)")))
            .subcommand(cl::App("unpin")
                .description("Remove rev pin from a custom index in mcpp.toml")
                .arg(cl::Arg("name").help("Index name").required()))
            .action(wrap_rc([&dispatch_sub](const cl::ParsedArgs& p) {
                return dispatch_sub("index", p, {
                    {"list",   cmd_index_list},
                    {"add",    cmd_index_add},
                    {"remove", cmd_index_remove},
                    {"update", cmd_index_update},
                    {"status", cmd_index_status},
                    {"pin",    cmd_index_pin},
                    {"unpin",  cmd_index_unpin},
                });
            })))

        //  about mcpp itself 
        .subcommand(cl::App("self")
            .description("Inspect and manage mcpp itself")
            .subcommand(cl::App("init")
                .description("Initialize or repair mcpp sandbox")
                .option(cl::Option("force")
                    .help("Delete registry and re-initialize from scratch")))
            .subcommand(cl::App("doctor")
                .description("Diagnose mcpp environment health"))
            .subcommand(cl::App("env")
                .description("Print mcpp paths and configuration")
                .option(cl::Option("format").takes_value().value_name("json")
                    .help("Machine-readable output (enveloped; see docs/11-machine-output.md)")))
            .subcommand(cl::App("config")
                .description("Show or modify mcpp's private xlings configuration")
                .option(cl::Option("mirror").takes_value().value_name("CN|GLOBAL")
                    .help("Set xlings mirror for mcpp's private registry")))
            .subcommand(cl::App("version")
                .description("Show mcpp version"))
            .subcommand(cl::App("explain")
                .description("Show extended description for an error code")
                .arg(cl::Arg("code").help("Error code such as E0001").required()))
            .action(wrap_rc([&dispatch_sub](const cl::ParsedArgs& p) {
                return dispatch_sub("self", p, {
                    {"init",      cmd_self_init},
                    {"doctor",    cmd_doctor},
                    {"env",       cmd_env},
                    {"config",    cmd_self_config},
                    {"version",   cmd_self_version},
                    {"explain",   cmd_explain_action},
                });
            })))

        //  top-level explain alias 
        // Preserves `mcpp explain E0001` as a shortcut for
        // `mcpp self explain E0001`.
        .subcommand(cl::App("explain")
            .description("Show extended description for an error code")
            .arg(cl::Arg("code").help("Error code such as E0001").required())
            .action(wrap_rc(cmd_explain_action)))

        //  bareword `version` alias 
        // cmdline natively handles `--help`/`--version`/`-h`; the bareword
        // `mcpp help` is intercepted by the pre-scan above (prints the
        // canonical usage screen). `mcpp version` is wired through the
        // App so it shows up in the auto-generated subcommand list.
        .subcommand(cl::App("version")
            .description("Show mcpp version")
            .action(wrap_rc(cmd_self_version)))

        //  hidden / internal 
        .subcommand(cl::App("dyndep")
            .description("(internal: invoked by ninja) Emit ninja dyndep file from .ddi inputs")
            .option(cl::Option("output").short_name('o').takes_value().value_name("PATH")
                .help("Path to write dyndep file"))
            .option(cl::Option("single").help("Single-file mode: one .ddi  one .dd"))
            .option(cl::Option("bmi-dir").takes_value().value_name("DIR")
                .help("BMI cache directory name (default: gcm.cache)"))
            .option(cl::Option("bmi-ext").takes_value().value_name("EXT")
                .help("BMI file extension (default: .gcm)"))
            .option(cl::Option("split-module")
                .help("Also emit a record for the provided BMI (two-phase "
                      "schedule: BMI and object are separate edges)"))
            .option(cl::Option("expect-provides").takes_value().value_name("NAME")
                .help("(verification) planned provided module for this TU"))
            .option(cl::Option("expect-imports").takes_value().value_name("CSV")
                .help("(verification) planned imports for this TU, comma-separated"))
            .option(cl::Option("expect-none")
                .help("(verification) planner assumed no provides/imports"))
            .action(wrap_rc(cmd_dyndep)))
        .subcommand(cl::App("stage")
            .description("(internal: invoked by ninja) Stage a cached artifact into the build dir")
            .option(cl::Option("output").short_name('o').takes_value().value_name("PATH")
                .help("Destination path inside the build directory"))
            .option(cl::Option("verify").takes_value().value_name("MODE")
                .help("Already-staged check: size (default) | content"))
            .action(wrap_rc(cmd_stage)))
        .subcommand(cl::App("coff-def")
            .description("(internal: invoked by ninja) Write a .def of every exportable symbol in the given COFF objects")
            .option(cl::Option("output").takes_value().value_name("PATH").help("the .def to write"))
            .option(cl::Option("name").takes_value().value_name("DLL").help("LIBRARY name recorded in the .def"))
            .action(wrap_rc(cmd_coff_def)))
        .subcommand(cl::App("bmi-equal")
            .description("(internal: invoked by ninja) Compare two BMIs ignoring the compiler's embedded timestamp")
            .action(wrap_rc(cmd_bmi_equal)))
        // The three edges of the detach-codegen schedule. Internal, and named as
        // such: they are only ever invoked by a generated build.ninja.
        .subcommand(cl::App("bmi-compile")
            .description("(internal) Compile a module interface and return when its BMI is published")
            .option(cl::Option("bmi").takes_value().value_name("PATH").help("BMI this unit publishes"))
            .option(cl::Option("slot").takes_value().value_name("PATH").help("where .log/.rc are kept"))
            .option(cl::Option("self").takes_value().value_name("PATH").help("path to mcpp, re-invoked as supervisor"))
            .option(cl::Option("sem").takes_value().value_name("DIR").help("concurrency token directory"))
            .option(cl::Option("cap").takes_value().value_name("N").help("max concurrent compilers"))
            .option(cl::Option("command-file").takes_value().value_name("PATH").help("file holding the compiler command line"))
            .option(cl::Option("dep-from").takes_value().value_name("PATH").help("scanner depfile to adopt"))
            .option(cl::Option("dep-to").takes_value().value_name("PATH").help("where ninja expects this edge's depfile"))
            .action(wrap_rc(cmd_bmi_compile)))
        .subcommand(cl::App("bmi-supervise")
            .description("(internal) Run a compiler to completion and record its status")
            .option(cl::Option("slot").takes_value().value_name("PATH"))
            .option(cl::Option("token").takes_value().value_name("PATH"))
            .option(cl::Option("command-file").takes_value().value_name("PATH"))
            .action(wrap_rc(cmd_bmi_supervise)))
        .subcommand(cl::App("bmi-await")
            .description("(internal) Join a detached compiler and replay its diagnostics")
            .option(cl::Option("slot").takes_value().value_name("PATH"))
            .option(cl::Option("object").takes_value().value_name("PATH"))
            .action(wrap_rc(cmd_bmi_await)))
    ;

    // The bareword `mcpp help` and `mcpp` (no args) both print the
    // canonical help screen.
    if (argc 

Web Proxy Viewer  |  New URL  |  Original Page