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..d1d03b6 100644 --- a/.github/workflows/ci-linux-e2e.yml +++ b/.github/workflows/ci-linux-e2e.yml @@ -28,6 +28,11 @@ jobs: timeout-minutes: 45 env: MCPP_HOME: /home/runner/.mcpp + # 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-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..07a2de1 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 + # 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 c37ab28..5318e5a 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 + # 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/.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/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/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/config.cppm b/src/config.cppm index 4a7a34c..466e7c3 100644 --- a/src/config.cppm +++ b/src/config.cppm @@ -374,8 +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: 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::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/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; diff --git a/src/xlings.cppm b/src/xlings.cppm index bba2ee5..fa2e0c1 100644 --- a/src/xlings.cppm +++ b/src/xlings.cppm @@ -225,22 +225,17 @@ 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` 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"); + std::string_view mirror = "auto"); // Persist the xlings mirror selection in .xlings.json via xlings itself. int config_show(const Env& env); @@ -1232,11 +1227,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; } 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 }