From bc92cdf27ce17498a7a7258de756c4785e32bbd1 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sun, 10 May 2026 05:40:21 +0800 Subject: [PATCH 1/2] =?UTF-8?q?release:=20v0.0.4=20=E2=80=94=20glob=20excl?= =?UTF-8?q?usion,=20xlings=20layout,=20auto=20sandbox=20PATH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three quality-of-life improvements: 1. **Glob exclusion (`!` prefix)** in `[modules].sources` / Form B sources. Patterns starting with `!` expand to a set of paths that are subtracted from the include set. This lets upstreams like ftxui declare `["src/**/*.cpp", "!src/**/*_test.cpp"]` instead of listing 73 files individually. Implemented in `scan_one_into` (scanner.cppm) and `stage_with_rewrite` (cli.cppm). 2. **xlings at `registry/bin/`** instead of `bin/`. The bundled xlings binary now sits at `/registry/bin/xlings`, which IS `/bin/xlings`. xlings 0.4.x's shim-creation guard (`if fs::exists(homeDir/"bin"/"xlings")`) is trivially satisfied, making `ensure_sandbox_xlings_binary` a no-op. Eliminates the hardlink dance and the "two binaries in bin/" confusion. 3. **`mcpp test` inherits sandbox PATH**. Before invoking each test binary, mcpp now prepends `/subos/default/bin` to `$PATH`. Tools bootstrapped during sandbox init — patchelf, ninja, and any xim shims — are visible to test binaries that shell out to them, fixing the "patchelf: command not found" failures that libxpkg's elfpatch tests hit in CI. --- CHANGELOG.md | 28 ++++++++++++++++ mcpp.toml | 2 +- src/cli.cppm | 33 ++++++++++++++++-- src/config.cppm | 61 +++++++++------------------------- src/modgraph/scanner.cppm | 18 ++++++++-- src/toolchain/fingerprint.cppm | 2 +- 6 files changed, 92 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3a4a00..b4199ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,34 @@ > 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。 > 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。 +## [0.0.4] — 2026-05-10 + +构建 / 环境体验优化三件套。 + +### 新增 + +- ✅ **Glob 排除模式** —— `[modules].sources` (以及 Form B 的 `sources`) + 现在支持 `!` 前缀的排除模式(类似 `.gitignore`): + ```toml + sources = ["src/**/*.cpp", "!src/**/*_test.cpp", "!src/**/*_fuzzer.cpp"] + ``` + 正向 glob 先展开、再减去 `!`-prefixed glob 命中的路径。解决了上游库 + test/fuzzer 文件与源混放时不得不逐文件列举的问题(典型如 ftxui)。 + +### 改进 + +- 🔧 **xlings 布局调整** —— xlings 二进制从 `/bin/xlings` + (与 mcpp 同目录)移至 `/registry/bin/xlings` + (= `/bin/xlings`)。由于 xlings 的 shim-creation guard + 恰好检查 `/bin/xlings` 是否存在,新布局下 + `ensure_sandbox_xlings_binary` 自然变成 no-op,省去了之前的 hardlink + 步骤。 + +- 🔧 **测试自动继承 sandbox PATH** —— `mcpp test` 在调用测试二进制前, + 自动把 sandbox 的 `subos/default/bin`(含 patchelf、ninja 等 + 一次性自举工具)追加到 `$PATH`,使 test 代码 shell-out 到这些工具时 + 不再报 "command not found"。 + ## [0.0.3] — 2026-05-10 依赖解析体系的三步演进:0.0.2 release tag 之后合入 transitive walker, diff --git a/mcpp.toml b/mcpp.toml index cd4b57d..646dc2a 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,6 +1,6 @@ [package] name = "mcpp" -version = "0.0.3" +version = "0.0.4" 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 2a6fb35..7237d92 100644 --- a/src/cli.cppm +++ b/src/cli.cppm @@ -1243,12 +1243,19 @@ prepare_build(bool print_fingerprint, globs = { "src/**/*.cppm", "src/**/*.cpp", "src/**/*.cc", "src/**/*.c" }; } + // Glob exclusion (same as scan_one_into): `!` prefix removes. std::set sourceFiles; + std::set excluded; for (auto const& g : globs) { - for (auto& p : mcpp::modgraph::expand_glob(srcRoot, g)) { - sourceFiles.insert(p); + if (!g.empty() && g[0] == '!') { + for (auto& p : mcpp::modgraph::expand_glob(srcRoot, g.substr(1))) + excluded.insert(p); + } else { + for (auto& p : mcpp::modgraph::expand_glob(srcRoot, g)) + sourceFiles.insert(p); } } + for (auto& p : excluded) sourceFiles.erase(p); if (sourceFiles.empty()) { return std::unexpected(std::format( "stage: no source files found under '{}' (globs={})", @@ -2201,7 +2208,27 @@ int cmd_test(const mcpplibs::cmdline::ParsedArgs& /*parsed*/, auto exe = ctx->outputDir / lu.output; mcpp::ui::status("Running", std::format("bin/{}", lu.targetName)); - std::string cmd = std::format("'{}'", exe.string()); + // Prepend the sandbox's subos/default/bin to PATH so tools + // bootstrapped during sandbox init (patchelf, ninja, etc.) are + // visible to test binaries that shell out to them. The + // toolchain binary's path encodes the registry root — derive it. + std::string pathPrefix; + { + auto tcBin = ctx->tc.binaryPath; + // tc binary at /data/xpkgs/xim-x-*/ver/bin/g++ + // Climb to = .../(xpkgs)/../.. + for (auto p = tcBin; !p.empty() && p != p.root_path(); p = p.parent_path()) { + if (p.filename() == "xpkgs") { + auto registryDir = p.parent_path().parent_path(); + auto sandboxBin = registryDir / "subos" / "default" / "bin"; + if (std::filesystem::exists(sandboxBin)) + pathPrefix = std::format("PATH='{}':\"$PATH\" ", sandboxBin.string()); + break; + } + } + } + + std::string cmd = std::format("{}'{}'", pathPrefix, exe.string()); for (auto& a : passthrough) cmd += std::format(" '{}'", a); int rc = std::system(cmd.c_str()); // std::system returns wait status — extract exit code. diff --git a/src/config.cppm b/src/config.cppm index b6cc454..9553221 100644 --- a/src/config.cppm +++ b/src/config.cppm @@ -2,8 +2,9 @@ // // Layout (per docs/14-data-layout.md): // $MCPP_HOME/ default ~/.mcpp/ -// bin/xlings vendored xlings binary +// bin/mcpp mcpp binary (self-contained mode) // registry/ XLINGS_HOME for mcpp's xlings +// bin/xlings vendored xlings binary (= /bin/xlings) // .xlings.json seeded with index_repos = [mcpp-index] // bmi// BMI cache (existing) // cache/ metadata caches @@ -34,7 +35,7 @@ struct GlobalConfig { // Resolved paths std::filesystem::path mcppHome; // ~/.mcpp/ std::filesystem::path binDir; // mcppHome/bin - std::filesystem::path xlingsBinary; // mcppHome/bin/xlings + std::filesystem::path xlingsBinary; // mcppHome/registry/bin/xlings std::filesystem::path registryDir; // mcppHome/registry std::filesystem::path bmiCacheDir; // mcppHome/bmi std::filesystem::path metaCacheDir; // mcppHome/cache @@ -194,7 +195,7 @@ bool write_default_config_toml(const std::filesystem::path& path) { constexpr auto tmpl = R"(# mcpp global config — auto-generated; safe to edit. [xlings] -# binary: "bundled" (use $MCPP_HOME/bin/xlings) | "system" | absolute path +# binary: "bundled" (use $MCPP_HOME/registry/bin/xlings) | "system" | absolute path binary = "bundled" # home: empty = use $MCPP_HOME/registry; can override home = "" @@ -324,47 +325,12 @@ void ensure_sandbox_init(const GlobalConfig& cfg, bool quiet) noexcept { } } -// Place a copy of the xlings binary at /bin/xlings so that -// xlings 0.4.10's `process_xvm_operations_` shim creation guard -// (installer.cppm:480 — `if (fs::exists(paths.homeDir / "bin" / "xlings"))`) -// passes. Without this, xim install hooks register version pins in -// /.xlings.json but never create the per-tool shims in -// /bin/, leaving `as`/`ld`/etc. unreachable in the sandbox. -// -// We use a hardlink so disk cost is zero and the binary stays in lockstep -// with whatever mcpp's bin/xlings is. -// -// TODO(xlings-upstream): When xlings learns to self-bootstrap on first -// `XLINGS_HOME= xlings install ...` (auto-hardlink from -// /proc/self/exe), this function becomes unnecessary. -void ensure_sandbox_xlings_binary(const GlobalConfig& cfg, bool quiet) noexcept { - auto src = cfg.xlingsBinary; - auto dst = cfg.xlingsHome() / "bin" / "xlings"; - if (std::filesystem::exists(dst)) return; - if (!std::filesystem::exists(src)) return; // upstream issue, surface elsewhere - - std::error_code ec; - std::filesystem::create_directories(dst.parent_path(), ec); - - // Try hardlink first (cheap); fall back to copy if cross-device. - std::filesystem::create_hard_link(src, dst, ec); - if (ec) { - ec.clear(); - std::filesystem::copy_file(src, dst, - std::filesystem::copy_options::overwrite_existing, ec); - } - if (!ec) { - std::filesystem::permissions(dst, - std::filesystem::perms::owner_exec - | std::filesystem::perms::group_exec - | std::filesystem::perms::others_exec, - std::filesystem::perm_options::add, ec); - } - if (ec && !quiet) { - std::println(stderr, - "warning: failed to mirror xlings binary into sandbox bin: {}", - ec.message()); - } +// With the 0.0.4 layout change (xlings binary at /registry/bin/ +// = /bin/), the bundled xlings IS already at the path xlings's +// shim-creation guard checks (`paths.homeDir / "bin" / "xlings"`). +// No mirroring / hardlinking needed — this function is now a no-op. +void ensure_sandbox_xlings_binary(const GlobalConfig& /*cfg*/, bool /*quiet*/) noexcept { + // Intentional no-op: xlingsBinary == xlingsHome()/bin/xlings. } // ─── Bootstrap install: shared event-stream parser + driver ──────────── @@ -596,8 +562,13 @@ std::expected load_or_init( // 1. Resolve paths cfg.mcppHome = home_dir(); cfg.binDir = cfg.mcppHome / "bin"; - cfg.xlingsBinary = cfg.binDir / "xlings"; cfg.registryDir = cfg.mcppHome / "registry"; + // xlings lives under registry/, not bin/ — it's a registry tool, + // not a user-facing binary. This also places it exactly at + // /bin/xlings, which satisfies xlings's own shim- + // creation guard (`if fs::exists(homeDir/"bin"/"xlings")`), + // making ensure_sandbox_xlings_binary() a no-op. + cfg.xlingsBinary = cfg.registryDir / "bin" / "xlings"; cfg.bmiCacheDir = cfg.mcppHome / "bmi"; cfg.metaCacheDir = cfg.mcppHome / "cache"; cfg.logDir = cfg.mcppHome / "log"; diff --git a/src/modgraph/scanner.cppm b/src/modgraph/scanner.cppm index 37dcfc9..0caac83 100644 --- a/src/modgraph/scanner.cppm +++ b/src/modgraph/scanner.cppm @@ -321,12 +321,26 @@ void scan_one_into(ScanResult& result, const std::filesystem::path& root, const mcpp::manifest::Manifest& manifest) { + // Glob exclusion: patterns starting with `!` remove files from the + // include set (like .gitignore). + // sources = ["src/**/*.cpp", "!src/**/*_test.cpp"] + // All positive patterns are expanded first, then all `!`-prefixed + // patterns are expanded and the resulting paths are removed. std::set all_files; + std::set excluded; for (auto const& g : manifest.modules.sources) { - for (auto& p : expand_glob(root, g)) { - all_files.insert(p); + if (!g.empty() && g[0] == '!') { + for (auto& p : expand_glob(root, g.substr(1))) { + excluded.insert(p); + } + } else { + for (auto& p : expand_glob(root, g)) { + all_files.insert(p); + } } } + for (auto& p : excluded) all_files.erase(p); + for (auto const& f : all_files) { auto r = scan_file(f, manifest.package.name); if (!r) { diff --git a/src/toolchain/fingerprint.cppm b/src/toolchain/fingerprint.cppm index b5d5dac..18fe184 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.3"; +inline constexpr std::string_view MCPP_VERSION = "0.0.4"; struct FingerprintInputs { Toolchain toolchain; From f4ba04cf1bedbc1658b8c39c2c7397e5e9261a65 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sun, 10 May 2026 05:51:34 +0800 Subject: [PATCH 2/2] fix(tests): update xlings path assertions for registry/bin/ layout Tests 10 and 27 checked for xlings at $MCPP_HOME/bin/xlings; the 0.0.4 layout moved it to $MCPP_HOME/registry/bin/xlings. --- tests/e2e/10_env_command.sh | 2 +- tests/e2e/27_self_contained_home.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/10_env_command.sh b/tests/e2e/10_env_command.sh index 23d5c9a..60cdc9f 100755 --- a/tests/e2e/10_env_command.sh +++ b/tests/e2e/10_env_command.sh @@ -17,7 +17,7 @@ out=$("$MCPP" self env 2>&1) [[ -d "$MCPP_HOME/cache" ]] || { echo "missing cache/"; exit 1; } [[ -f "$MCPP_HOME/config.toml" ]] || { echo "missing config.toml"; exit 1; } [[ -f "$MCPP_HOME/registry/.xlings.json" ]] || { echo "missing seeded .xlings.json"; exit 1; } -[[ -x "$MCPP_HOME/bin/xlings" ]] || { echo "xlings binary not acquired"; exit 1; } +[[ -x "$MCPP_HOME/registry/bin/xlings" ]] || { echo "xlings binary not acquired"; exit 1; } # Verify seeded .xlings.json contains mcpp-index and NOT awesome grep -q '"name": "mcpp-index"' "$MCPP_HOME/registry/.xlings.json" || { diff --git a/tests/e2e/27_self_contained_home.sh b/tests/e2e/27_self_contained_home.sh index da3a44f..ff61d33 100755 --- a/tests/e2e/27_self_contained_home.sh +++ b/tests/e2e/27_self_contained_home.sh @@ -26,7 +26,7 @@ echo "$out" | grep -q "MCPP_HOME *= *$ROOT" || { # And it must have actually populated the layout there. [[ -d "$ROOT/registry" ]] || { echo "missing registry/"; exit 1; } [[ -f "$ROOT/config.toml" ]] || { echo "missing config.toml"; exit 1; } -[[ -x "$ROOT/bin/xlings" ]] || { echo "xlings not acquired into $ROOT/bin"; exit 1; } +[[ -x "$ROOT/registry/bin/xlings" ]] || { echo "xlings not acquired into $ROOT/registry/bin"; exit 1; } # Explicit env var must still win when set. ALT="$TMP/explicit-home"