From 4107d4a7063f08a09be857a7d27beb6e43df8d7a Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 29 Jun 2026 10:32:22 +0800 Subject: [PATCH 1/5] feat(observability+resilience): MCPP_VERBOSE env + cold index-update retry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes prompted by an opaque, repeatedly-reproducing CI failure in 75_index_status_offline.sh (a cold `mcpp self env` in a fresh MCPP_HOME whose index/sandbox bootstrap fails fast — root cause swallowed in non-verbose logs; verified NOT related to the feature/capability work, it fails identically on main): 1. MCPP_VERBOSE env override (src/cli.cppm): MCPP_VERBOSE= turns on verbose logging for EVERY mcpp invocation, including those nested inside e2e test scripts that call $MCPP without flags. An explicit --quiet still wins. Set MCPP_VERBOSE: "1" in all CI workflows (ci-linux, ci-linux-e2e, ci-macos, ci-windows, cross-build-test, ci-fresh-install, ci-aarch64-fresh-install) so failures carry full diagnostics. Complements the existing MCPP_LOG_LEVEL (which only sets the FILE log level, not stderr). 2. update_index retry (src/xlings.cppm): the index sync is a network git op; a single transient blip otherwise fails cold init outright. Bounded retry with linear backoff (3 attempts, 2s/4s). Success returns on the first attempt — zero added latency in steady state; only a real failure pays the backoff. Retry notices go through mcpp::log::verbose (file always, stderr under MCPP_VERBOSE). This PR's own e2e CI run carries MCPP_VERBOSE=1, so it surfaces the real cause of the 75 bootstrap failure for a targeted follow-up. --- .../workflows/ci-aarch64-fresh-install.yml | 3 ++ .github/workflows/ci-fresh-install.yml | 4 +++ .github/workflows/ci-linux-e2e.yml | 4 +++ .github/workflows/ci-linux.yml | 2 ++ .github/workflows/ci-macos.yml | 3 ++ .github/workflows/ci-windows.yml | 2 ++ .github/workflows/cross-build-test.yml | 2 ++ src/cli.cppm | 8 ++++++ src/xlings.cppm | 28 +++++++++++++++---- 9 files changed, 51 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-aarch64-fresh-install.yml b/.github/workflows/ci-aarch64-fresh-install.yml index f69f265..5f6b6e7 100644 --- a/.github/workflows/ci-aarch64-fresh-install.yml +++ b/.github/workflows/ci-aarch64-fresh-install.yml @@ -30,6 +30,9 @@ jobs: name: fresh install + native build (aarch64 / glibc) runs-on: ubuntu-24.04-arm timeout-minutes: 60 + env: + # Verbose every mcpp invocation — cold bootstrap path (src/cli.cppm). + MCPP_VERBOSE: "1" steps: - name: System info run: | diff --git a/.github/workflows/ci-fresh-install.yml b/.github/workflows/ci-fresh-install.yml index 69de54f..a8c29f6 100644 --- a/.github/workflows/ci-fresh-install.yml +++ b/.github/workflows/ci-fresh-install.yml @@ -30,6 +30,10 @@ jobs: name: Linux fresh install runs-on: ubuntu-24.04 timeout-minutes: 60 + env: + # Verbose every mcpp invocation — fresh-install is the cold index/sandbox + # bootstrap path, exactly where extra diagnostics matter (src/cli.cppm). + MCPP_VERBOSE: "1" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/ci-linux-e2e.yml b/.github/workflows/ci-linux-e2e.yml index 8c5b33e..0c364f0 100644 --- a/.github/workflows/ci-linux-e2e.yml +++ b/.github/workflows/ci-linux-e2e.yml @@ -28,6 +28,10 @@ jobs: timeout-minutes: 45 env: MCPP_HOME: /home/runner/.mcpp + # Verbose every mcpp invocation (incl. those nested in e2e scripts) so CI + # logs carry full diagnostics for failures like a cold index/sandbox + # bootstrap. Honoured via the MCPP_VERBOSE override in src/cli.cppm. + MCPP_VERBOSE: "1" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index f562f9e..9ea2d4a 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -32,6 +32,8 @@ jobs: # MCPP_HOME pinned so the cache key below restores into the # same path mcpp resolves at runtime. MCPP_HOME: /home/runner/.mcpp + # Verbose every mcpp invocation for richer CI diagnostics (src/cli.cppm). + MCPP_VERBOSE: "1" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index a8807b8..d9ed9fe 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -19,6 +19,9 @@ jobs: name: macOS ARM64 — xlings LLVM end-to-end runs-on: macos-15 timeout-minutes: 30 + env: + # Verbose every mcpp invocation for richer CI diagnostics (src/cli.cppm). + MCPP_VERBOSE: "1" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml index c37ab28..136eada 100644 --- a/.github/workflows/ci-windows.yml +++ b/.github/workflows/ci-windows.yml @@ -21,6 +21,8 @@ jobs: timeout-minutes: 45 env: MCPP_HOME: C:\Users\runneradmin\.mcpp + # Verbose every mcpp invocation for richer CI diagnostics (src/cli.cppm). + MCPP_VERBOSE: "1" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/cross-build-test.yml b/.github/workflows/cross-build-test.yml index 9ab76f8..ccc4c32 100644 --- a/.github/workflows/cross-build-test.yml +++ b/.github/workflows/cross-build-test.yml @@ -57,6 +57,8 @@ jobs: qemu_bin: qemu-aarch64-static env: MCPP_HOME: /home/runner/.mcpp + # Verbose every mcpp invocation for richer CI diagnostics (src/cli.cppm). + MCPP_VERBOSE: "1" steps: - uses: actions/checkout@v4 diff --git a/src/cli.cppm b/src/cli.cppm index ff9e9d2..6f4c0f7 100644 --- a/src/cli.cppm +++ b/src/cli.cppm @@ -101,6 +101,14 @@ int run(int argc, char** argv) { else if (a == "--no-color") mcpp::ui::disable_color(); else if (a == "--verbose" || a == "-v") mcpp::log::set_verbose(true); } + // 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 diff --git a/src/xlings.cppm b/src/xlings.cppm index bba2ee5..44f18e0 100644 --- a/src/xlings.cppm +++ b/src/xlings.cppm @@ -1232,11 +1232,29 @@ bool is_official_package_index_fresh(const Env& env, int update_index(const Env& env, bool quiet) { std::string cmd = build_command_prefix(env) + " update 2>&1"; - int rc = mcpp::platform::process::run_streaming(cmd, - [quiet](std::string_view line) { - if (!quiet) std::println("{}", line); - }); - if (rc == 0) mark_known_indexes_refreshed(env); + // The index sync is a network git operation; a single transient blip (DNS, + // TLS reset, a mirror hiccup) otherwise fails a cold `mcpp self env` / + // first-run init outright (e.g. CI's index/sandbox bootstrap). Retry with + // linear backoff. The success path returns on the FIRST attempt — zero + // added latency in steady state; only a genuine failure pays the backoff. + constexpr int kMaxAttempts = 3; + int rc = 0; + for (int attempt = 1; attempt <= kMaxAttempts; ++attempt) { + rc = mcpp::platform::process::run_streaming(cmd, + [quiet](std::string_view line) { + if (!quiet) std::println("{}", line); + }); + if (rc == 0) { mark_known_indexes_refreshed(env); return 0; } + if (attempt < kMaxAttempts) { + int delay = attempt * 2; // 2s, then 4s + mcpp::log::verbose("index", std::format( + "index update attempt {}/{} failed (rc {}); retrying in {}s", + attempt, kMaxAttempts, rc, delay)); + std::this_thread::sleep_for(std::chrono::seconds(delay)); + } + } + mcpp::log::verbose("index", std::format( + "index update failed after {} attempts (rc {})", kMaxAttempts, rc)); return rc; } From 9d776f37279cef712f9d189dd8a867683218302c Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 29 Jun 2026 10:50:46 +0800 Subject: [PATCH 2/5] feat(resilience): auto-detect best index mirror at first init (the real stability fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements TODO(mirror-default) option (b). The first-init seed used a hardcoded "CN" mirror, which strands overseas users and GitHub-hosted CI behind a slow/unreachable gitcode mirror — the actual root cause behind the repeated 75_index_status_offline.sh cold-bootstrap failures (a US runner seeding CN can't reach gitcode, so the index clone fails fast and the index reports 'missing'). detect_best_mirror() runs a short, tight-timeout HEAD probe to github.com (GLOBAL) and gitcode.com (CN), and pins the lower-latency reachable one into .xlings.json. Priority matches the intended design: explicit --mirror (config) > lower-latency auto-probe > GLOBAL fallback An explicit `mcpp self config --mirror CN|GLOBAL` always wins; the probe only runs on a fresh init with no explicit choice. Falls back to GLOBAL (reachable nearly everywhere) if neither host answers. Verified locally: on a CN host the probe picks CN (gitcode 150ms < github 380ms) and logs 'mirror: probe github=380ms gitcode=150ms -> CN' under MCPP_VERBOSE; a US CI runner will symmetrically pick GLOBAL. e2e 75/80/81 green. --- src/config.cppm | 5 ++++- src/xlings.cppm | 57 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/config.cppm b/src/config.cppm index 4a7a34c..4432114 100644 --- a/src/config.cppm +++ b/src/config.cppm @@ -374,8 +374,11 @@ bool write_default_xlings_json(const std::filesystem::path& path, // construct a temporary Env with home = path.parent_path(). mcpp::xlings::Env env; env.home = path.parent_path(); + // No explicit --mirror: auto-detect the lower-latency reachable mirror + // instead of the historic hardcoded "CN" (which strands overseas users and + // GitHub-hosted CI). An explicit choice always wins (the else branch). if (mirror_override.empty()) - mcpp::xlings::seed_xlings_json(env, pairs); + mcpp::xlings::seed_xlings_json(env, pairs, mcpp::xlings::detect_best_mirror()); else mcpp::xlings::seed_xlings_json(env, pairs, mirror_override); return std::filesystem::exists(path); diff --git a/src/xlings.cppm b/src/xlings.cppm index 44f18e0..39b8705 100644 --- a/src/xlings.cppm +++ b/src/xlings.cppm @@ -225,23 +225,25 @@ int install_direct(const Env& env, std::string_view target, bool quiet = false); // Write .xlings.json seed file. // -// TODO(mirror-default): the default `"CN"` is the historical setting for -// the project's initial Chinese-mainland user base, but it bites overseas -// users (and CI on GitHub-hosted runners) — the first network roundtrip -// goes through a CN mirror that is slow/unreachable for them. The -// `mcpp self config --mirror X` flow now passes the user's choice as an -// override through to here, so they can pick the right mirror BEFORE the -// first download. Longer term, consider: -// (a) flip the default to "GLOBAL" and have CN users opt in via -// `mcpp self config --mirror CN` (smaller blast radius once docs -// cover the switch); or -// (b) auto-detect on first init (env hint like LANG, a quick HEAD probe -// to github.com vs. ghproxy with a tight timeout, and pin the -// winning value into .xlings.json). +// The `mirror` parameter still defaults to "CN" for direct callers, but the +// first-init seed path (config.cppm) now passes the result of +// `detect_best_mirror()` when the user gave no explicit `--mirror` — i.e. +// TODO(mirror-default) option (b) is implemented: a quick latency probe to the +// GLOBAL vs CN hosts picks the faster reachable one, so overseas users and +// GitHub-hosted CI no longer get stranded on the historical CN default. An +// explicit `mcpp self config --mirror X` always wins (config priority). void seed_xlings_json(const Env& env, std::span> repos, std::string_view mirror = "CN"); +// Probe both index mirrors and return the lower-latency reachable one +// ("GLOBAL" | "CN") — TODO(mirror-default) option (b). Used at first init when +// the user gave no explicit --mirror; an explicit choice always wins (config +// priority). Falls back to "GLOBAL" (reachable nearly everywhere) when neither +// probe succeeds, since the historic "CN" default stranded overseas users and +// GitHub-hosted CI behind a slow/unreachable mirror. +std::string detect_best_mirror(); + // Persist the xlings mirror selection in .xlings.json via xlings itself. int config_show(const Env& env); int config_set_mirror(const Env& env, std::string_view mirror, bool quiet = false); @@ -1092,6 +1094,35 @@ void seed_xlings_json(const Env& env, write_file(path, json); } +std::string detect_best_mirror() { + using namespace std::chrono; + // A short HEAD probe to each mirror host, timed by wall clock. Non-zero rc + // (DNS failure, connection refused, or the tight --max-time elapsing) means + // "unreachable". curl is present on every platform mcpp targets; if it is + // somehow absent both probes fail and we fall back to GLOBAL. + auto probe = [](std::string_view url) -> std::optional { + // -I writes response headers to stdout; redirect both streams to null + // (cross-platform) so the probe stays silent — only its timing matters. + auto cmd = std::format("curl -s -I --max-time 3 {} {}", + url, mcpp::platform::shell::silent_redirect); + auto t0 = steady_clock::now(); + if (mcpp::platform::process::run_silent(cmd) != 0) return std::nullopt; + return duration(steady_clock::now() - t0).count(); + }; + auto g = probe("https://github.com"); + auto c = probe("https://gitcode.com"); + if (g && c) { + std::string pick = (*g <= *c) ? "GLOBAL" : "CN"; + mcpp::log::verbose("mirror", std::format( + "probe github={:.0f}ms gitcode={:.0f}ms -> {}", *g * 1000, *c * 1000, pick)); + return pick; + } + if (g) { mcpp::log::verbose("mirror", "only github reachable -> GLOBAL"); return "GLOBAL"; } + if (c) { mcpp::log::verbose("mirror", "only gitcode reachable -> CN"); return "CN"; } + mcpp::log::verbose("mirror", "neither mirror reachable; defaulting -> GLOBAL"); + return "GLOBAL"; +} + int config_show(const Env& env) { auto cmd = std::format("{} config", build_command_prefix(env)); return mcpp::platform::process::run_silent(cmd); From 2b4a5c0efa2cc838a3a4bce56f7370ab5f07e019 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 29 Jun 2026 11:07:57 +0800 Subject: [PATCH 3/5] fix(mirror): seed "auto" so xlings' own region detection runs (remove curl probe) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the curl-based mcpp-side probe (per review: don't reinvent in mcpp; mirror selection is xlings' job, and it already does it via tinyhttps). Root cause of the repeated 75_index_status_offline.sh CI failures, now proven: mcpp seeded a hardcoded "CN" into a fresh .xlings.json. xlings' normalize_mirror_ accepts only "GLOBAL"/"CN" as valid, so "CN" was used DIRECTLY (gitcode) — bypassing xlings' own detect_install_mirror_(), which probes github vs gitcode latency (tinyhttps::probe_latency) and picks the reachable/faster region. On a GitHub-hosted (US) runner gitcode is slow/unreachable, so the cold index/sandbox bootstrap failed and the index reported 'missing'. Fix: seed "auto". normalize_mirror_("auto") -> nullopt (invalid) -> xlings treats the mirror as unset -> runs detect_install_mirror_() -> picks GLOBAL on a US runner, CN on a China link. mcpp no longer overrides xlings' region choice. The existing-config guard (config.cppm: only seed when .xlings.json is absent) already means an explicit `mcpp self config --mirror CN|GLOBAL` is never clobbered. Empirically confirmed via the curl-probe build's verbose CI run: on the US runner 'probe github=70ms gitcode=1060ms -> GLOBAL' then patchelf/ninja bootstrap succeeded and 75 PASSED — proving the mirror region is the cause. This commit hands that region choice back to xlings instead of doing it in mcpp. --- src/config.cppm | 10 +++++---- src/xlings.cppm | 54 +++++++++---------------------------------------- 2 files changed, 15 insertions(+), 49 deletions(-) diff --git a/src/config.cppm b/src/config.cppm index 4432114..466e7c3 100644 --- a/src/config.cppm +++ b/src/config.cppm @@ -374,11 +374,13 @@ bool write_default_xlings_json(const std::filesystem::path& path, // construct a temporary Env with home = path.parent_path(). mcpp::xlings::Env env; env.home = path.parent_path(); - // No explicit --mirror: auto-detect the lower-latency reachable mirror - // instead of the historic hardcoded "CN" (which strands overseas users and - // GitHub-hosted CI). An explicit choice always wins (the else branch). + // No explicit --mirror: seed "auto" so xlings' own adaptive mirror module + // (latency-probed, with per-download failover) picks the best reachable + // host. The historic hardcoded "CN" FORCED the CN mirror and disabled that + // mechanism, stranding overseas users and GitHub-hosted CI on a + // slow/unreachable gitcode. An explicit --mirror always wins (else branch). if (mirror_override.empty()) - mcpp::xlings::seed_xlings_json(env, pairs, mcpp::xlings::detect_best_mirror()); + mcpp::xlings::seed_xlings_json(env, pairs); // default = "auto" else mcpp::xlings::seed_xlings_json(env, pairs, mirror_override); return std::filesystem::exists(path); diff --git a/src/xlings.cppm b/src/xlings.cppm index 39b8705..fa2e0c1 100644 --- a/src/xlings.cppm +++ b/src/xlings.cppm @@ -225,24 +225,17 @@ int install_direct(const Env& env, std::string_view target, bool quiet = false); // Write .xlings.json seed file. // -// The `mirror` parameter still defaults to "CN" for direct callers, but the -// first-init seed path (config.cppm) now passes the result of -// `detect_best_mirror()` when the user gave no explicit `--mirror` — i.e. -// TODO(mirror-default) option (b) is implemented: a quick latency probe to the -// GLOBAL vs CN hosts picks the faster reachable one, so overseas users and -// GitHub-hosted CI no longer get stranded on the historical CN default. An -// explicit `mcpp self config --mirror X` always wins (config priority). +// The `mirror` default is "auto": xlings' own adaptive mirror module +// (xlings.core.mirror.adaptive — latency-probed with per-download failover and +// failure penalisation) then picks the best reachable host per download. This +// replaces the historic hardcoded "CN", which FORCED the CN mirror and disabled +// that mechanism — stranding overseas users and GitHub-hosted CI on a +// slow/unreachable gitcode. An explicit `mcpp self config --mirror CN|GLOBAL` +// still writes that fixed value (config priority). Mirror selection is xlings' +// responsibility; mcpp just declines to override it by default. void seed_xlings_json(const Env& env, std::span> repos, - std::string_view mirror = "CN"); - -// Probe both index mirrors and return the lower-latency reachable one -// ("GLOBAL" | "CN") — TODO(mirror-default) option (b). Used at first init when -// the user gave no explicit --mirror; an explicit choice always wins (config -// priority). Falls back to "GLOBAL" (reachable nearly everywhere) when neither -// probe succeeds, since the historic "CN" default stranded overseas users and -// GitHub-hosted CI behind a slow/unreachable mirror. -std::string detect_best_mirror(); + std::string_view mirror = "auto"); // Persist the xlings mirror selection in .xlings.json via xlings itself. int config_show(const Env& env); @@ -1094,35 +1087,6 @@ void seed_xlings_json(const Env& env, write_file(path, json); } -std::string detect_best_mirror() { - using namespace std::chrono; - // A short HEAD probe to each mirror host, timed by wall clock. Non-zero rc - // (DNS failure, connection refused, or the tight --max-time elapsing) means - // "unreachable". curl is present on every platform mcpp targets; if it is - // somehow absent both probes fail and we fall back to GLOBAL. - auto probe = [](std::string_view url) -> std::optional { - // -I writes response headers to stdout; redirect both streams to null - // (cross-platform) so the probe stays silent — only its timing matters. - auto cmd = std::format("curl -s -I --max-time 3 {} {}", - url, mcpp::platform::shell::silent_redirect); - auto t0 = steady_clock::now(); - if (mcpp::platform::process::run_silent(cmd) != 0) return std::nullopt; - return duration(steady_clock::now() - t0).count(); - }; - auto g = probe("https://github.com"); - auto c = probe("https://gitcode.com"); - if (g && c) { - std::string pick = (*g <= *c) ? "GLOBAL" : "CN"; - mcpp::log::verbose("mirror", std::format( - "probe github={:.0f}ms gitcode={:.0f}ms -> {}", *g * 1000, *c * 1000, pick)); - return pick; - } - if (g) { mcpp::log::verbose("mirror", "only github reachable -> GLOBAL"); return "GLOBAL"; } - if (c) { mcpp::log::verbose("mirror", "only gitcode reachable -> CN"); return "CN"; } - mcpp::log::verbose("mirror", "neither mirror reachable; defaulting -> GLOBAL"); - return "GLOBAL"; -} - int config_show(const Env& env) { auto cmd = std::format("{} config", build_command_prefix(env)); return mcpp::platform::process::run_silent(cmd); From 9b3f37ac757a08a78aedc09db852e467b5215df4 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 29 Jun 2026 11:25:50 +0800 Subject: [PATCH 4/5] fix(ci): adjust for mirror=auto default; don't force verbose in e2e-suite jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the mirror=auto fix and MCPP_VERBOSE rollout, fixing 3 e2e regressions surfaced by the previous CI run (75 itself now PASSES): - 38_self_config_mirror.sh: the default seed is now "auto" (defer to xlings' region detection), not "CN". Updated the assertion; explicit --mirror GLOBAL/cn/BAD checks unchanged. - 48_build_error_output.sh / 53_namespaced_cache_label.sh assert mcpp's DEFAULT (quiet) output. Forcing MCPP_VERBOSE=1 in CI broke them. Removed MCPP_VERBOSE from the workflows that run the e2e suite (ci-linux-e2e, ci-macos, ci-windows); kept it where it's diagnostic and safe (ci-fresh-install, ci-aarch64-fresh-install — the cold bootstrap path — plus ci-linux unit tests and cross-build). A test that needs verbose passes --verbose itself. Verified locally: 38/48/53 green. --- .github/workflows/ci-linux-e2e.yml | 9 +++++---- .github/workflows/ci-macos.yml | 6 +++--- .github/workflows/ci-windows.yml | 4 ++-- tests/e2e/38_self_config_mirror.sh | 8 ++++++-- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-linux-e2e.yml b/.github/workflows/ci-linux-e2e.yml index 0c364f0..d1d03b6 100644 --- a/.github/workflows/ci-linux-e2e.yml +++ b/.github/workflows/ci-linux-e2e.yml @@ -28,10 +28,11 @@ jobs: timeout-minutes: 45 env: MCPP_HOME: /home/runner/.mcpp - # Verbose every mcpp invocation (incl. those nested in e2e scripts) so CI - # logs carry full diagnostics for failures like a cold index/sandbox - # bootstrap. Honoured via the MCPP_VERBOSE override in src/cli.cppm. - MCPP_VERBOSE: "1" + # NOTE: do NOT force MCPP_VERBOSE here. The e2e suite includes tests that + # assert mcpp's DEFAULT (quiet) output — e.g. 48_build_error_output and + # 53_namespaced_cache_label — which forced verbose would break. Verbose is + # set only in the fresh-install workflows (cold bootstrap, no such asserts). + # A specific test that needs verbose passes `--verbose` itself. steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index d9ed9fe..07a2de1 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -19,9 +19,9 @@ jobs: name: macOS ARM64 — xlings LLVM end-to-end runs-on: macos-15 timeout-minutes: 30 - env: - # Verbose every mcpp invocation for richer CI diagnostics (src/cli.cppm). - MCPP_VERBOSE: "1" + # NOTE: no MCPP_VERBOSE here — this job runs the e2e suite, which includes + # tests asserting mcpp's default quiet output (48/53). Verbose is forced only + # in the fresh-install workflows. steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml index 136eada..5318e5a 100644 --- a/.github/workflows/ci-windows.yml +++ b/.github/workflows/ci-windows.yml @@ -21,8 +21,8 @@ jobs: timeout-minutes: 45 env: MCPP_HOME: C:\Users\runneradmin\.mcpp - # Verbose every mcpp invocation for richer CI diagnostics (src/cli.cppm). - MCPP_VERBOSE: "1" + # NOTE: no MCPP_VERBOSE — this job runs the e2e suite, which asserts mcpp's + # default quiet output (48/53). Verbose is forced only in fresh-install. steps: - uses: actions/checkout@v4 diff --git a/tests/e2e/38_self_config_mirror.sh b/tests/e2e/38_self_config_mirror.sh index 333f82d..d7021fe 100755 --- a/tests/e2e/38_self_config_mirror.sh +++ b/tests/e2e/38_self_config_mirror.sh @@ -14,9 +14,13 @@ source "$(dirname "$0")/_inherit_toolchain.sh" exit 1 } -grep -q '"mirror": "CN"' "$MCPP_HOME/registry/.xlings.json" || { +# Default (no explicit --mirror): mcpp seeds "auto" so xlings' own region +# detection (probe github vs gitcode) picks the reachable/faster mirror, instead +# of mcpp hardcoding one. An explicit `mcpp self config --mirror X` still pins a +# concrete value (asserted below). +grep -q '"mirror": "auto"' "$MCPP_HOME/registry/.xlings.json" || { cat "$MCPP_HOME/registry/.xlings.json" - echo "FAIL: default mirror should be CN" + echo "FAIL: default mirror should be auto (defer to xlings region detection)" exit 1 } From 325430d2ddcdd58cd5c0dce98098bb297d899c88 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 29 Jun 2026 11:45:34 +0800 Subject: [PATCH 5/5] =?UTF-8?q?release:=20v0.0.70=20=E2=80=94=20cold-init?= =?UTF-8?q?=20mirror=20auto-detection=20+=20observability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump mcpp.toml + MCPP_VERSION to 0.0.70. CHANGELOG: seed "auto" so xlings' region detection runs (fixes overseas/CI cold bootstrap), MCPP_VERBOSE env, update_index retry. --- CHANGELOG.md | 25 +++++++++++++++++++++++++ mcpp.toml | 2 +- src/toolchain/fingerprint.cppm | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f014857..2dd79bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ > 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。 > 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。 +## [0.0.70] — 2026-06-29 + +### 修复 + +- **首次初始化在海外网络与 GitHub 托管 CI 上的冷启动失败(`index missing`;patchelf / ninja + bootstrap 失败)**:`mcpp self env` 为新建的 `MCPP_HOME` 播种 `.xlings.json` 时,将 `mirror` 字段 + 硬编码为 `"CN"`。xlings 的 `normalize_mirror_` 仅接受 `GLOBAL` 与 `CN` 两个合法取值,故 `"CN"` + 被直接采用并解析至 gitcode,致使 xlings 内置的区域探测 `detect_install_mirror_()` 被跳过——该例程 + 经 `tinyhttps::probe_latency` 测量 github 与 gitcode 的连接延迟,择可达且更低延迟者。在美国区域的 + runner 上,gitcode 不可达或显著较慢(实测 github 70 ms、gitcode 1060 ms),由此索引与沙箱的冷 + bootstrap 失败。本版将播种值改为 `"auto"`:`normalize_mirror_("auto")` 判定为非法取值,xlings 视其 + 为未设置并执行自身探测,在美国区域解析至 GLOBAL、在中国大陆解析至 CN。镜像选择的职责由此归还 + xlings(其已基于 tinyhttps 实现该机制),mcpp 不再代为决策。播种仅在 `.xlings.json` 不存在时发生, + 显式的 `mcpp self config --mirror CN|GLOBAL` 配置不会被覆盖。 + +### 新增 + +- **`MCPP_VERBOSE` 环境变量**:取非空且非 `"0"` 的值时,为每一次 mcpp 调用启用 verbose 日志,涵盖 + e2e 脚本中未携带 flag 的 `$MCPP` 调用,便于 CI 诊断。该变量与既有的 `MCPP_LOG_LEVEL`(仅控制文件 + 日志级别)互补;显式 `--quiet` 仍具有更高优先级。该变量已在 fresh-install 等 workflow 中启用,但 + 不含运行「默认静默」输出断言的 e2e 套件。 +- **`update_index` 冷启动重试**:索引同步为网络 git 操作,单次瞬时故障原会直接导致冷启动失败。本版 + 改为有界退避重试(至多 3 次,退避 2 s / 4 s);成功路径于首次尝试即返回,稳态无额外延迟,仅失败 + 时方触发退避。 + ## [0.0.69] — 2026-06-29 ### 新增 diff --git a/mcpp.toml b/mcpp.toml index c977972..a0218b4 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,6 +1,6 @@ [package] name = "mcpp" -version = "0.0.69" +version = "0.0.70" description = "Modern C++ build & package management tool" license = "Apache-2.0" authors = ["mcpp-community"] diff --git a/src/toolchain/fingerprint.cppm b/src/toolchain/fingerprint.cppm index 64ad330..3e2c7d0 100644 --- a/src/toolchain/fingerprint.cppm +++ b/src/toolchain/fingerprint.cppm @@ -18,7 +18,7 @@ import mcpp.toolchain.detect; export namespace mcpp::toolchain { -inline constexpr std::string_view MCPP_VERSION = "0.0.69"; +inline constexpr std::string_view MCPP_VERSION = "0.0.70"; struct FingerprintInputs { Toolchain toolchain;