Skip to content

Commit bc92cdf

Browse files
committed
release: v0.0.4 — glob exclusion, xlings layout, auto sandbox PATH
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 `<MCPP_HOME>/registry/bin/xlings`, which IS `<XLINGS_HOME>/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 `<XLINGS_HOME>/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.
1 parent 52d6fa4 commit bc92cdf

6 files changed

Lines changed: 92 additions & 52 deletions

File tree

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,34 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [0.0.4] — 2026-05-10
7+
8+
构建 / 环境体验优化三件套。
9+
10+
### 新增
11+
12+
-**Glob 排除模式** —— `[modules].sources` (以及 Form B 的 `sources`)
13+
现在支持 `!` 前缀的排除模式(类似 `.gitignore`):
14+
```toml
15+
sources = ["src/**/*.cpp", "!src/**/*_test.cpp", "!src/**/*_fuzzer.cpp"]
16+
```
17+
正向 glob 先展开、再减去 `!`-prefixed glob 命中的路径。解决了上游库
18+
test/fuzzer 文件与源混放时不得不逐文件列举的问题(典型如 ftxui)。
19+
20+
### 改进
21+
22+
- 🔧 **xlings 布局调整** —— xlings 二进制从 `<MCPP_HOME>/bin/xlings`
23+
(与 mcpp 同目录)移至 `<MCPP_HOME>/registry/bin/xlings`
24+
(= `<XLINGS_HOME>/bin/xlings`)。由于 xlings 的 shim-creation guard
25+
恰好检查 `<XLINGS_HOME>/bin/xlings` 是否存在,新布局下
26+
`ensure_sandbox_xlings_binary` 自然变成 no-op,省去了之前的 hardlink
27+
步骤。
28+
29+
- 🔧 **测试自动继承 sandbox PATH** —— `mcpp test` 在调用测试二进制前,
30+
自动把 sandbox 的 `subos/default/bin`(含 patchelf、ninja 等
31+
一次性自举工具)追加到 `$PATH`,使 test 代码 shell-out 到这些工具时
32+
不再报 "command not found"。
33+
634
## [0.0.3] — 2026-05-10
735

836
依赖解析体系的三步演进:0.0.2 release tag 之后合入 transitive walker,

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "0.0.3"
3+
version = "0.0.4"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/cli.cppm

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,12 +1243,19 @@ prepare_build(bool print_fingerprint,
12431243
globs = { "src/**/*.cppm", "src/**/*.cpp",
12441244
"src/**/*.cc", "src/**/*.c" };
12451245
}
1246+
// Glob exclusion (same as scan_one_into): `!` prefix removes.
12461247
std::set<std::filesystem::path> sourceFiles;
1248+
std::set<std::filesystem::path> excluded;
12471249
for (auto const& g : globs) {
1248-
for (auto& p : mcpp::modgraph::expand_glob(srcRoot, g)) {
1249-
sourceFiles.insert(p);
1250+
if (!g.empty() && g[0] == '!') {
1251+
for (auto& p : mcpp::modgraph::expand_glob(srcRoot, g.substr(1)))
1252+
excluded.insert(p);
1253+
} else {
1254+
for (auto& p : mcpp::modgraph::expand_glob(srcRoot, g))
1255+
sourceFiles.insert(p);
12501256
}
12511257
}
1258+
for (auto& p : excluded) sourceFiles.erase(p);
12521259
if (sourceFiles.empty()) {
12531260
return std::unexpected(std::format(
12541261
"stage: no source files found under '{}' (globs={})",
@@ -2201,7 +2208,27 @@ int cmd_test(const mcpplibs::cmdline::ParsedArgs& /*parsed*/,
22012208
auto exe = ctx->outputDir / lu.output;
22022209
mcpp::ui::status("Running", std::format("bin/{}", lu.targetName));
22032210

2204-
std::string cmd = std::format("'{}'", exe.string());
2211+
// Prepend the sandbox's subos/default/bin to PATH so tools
2212+
// bootstrapped during sandbox init (patchelf, ninja, etc.) are
2213+
// visible to test binaries that shell out to them. The
2214+
// toolchain binary's path encodes the registry root — derive it.
2215+
std::string pathPrefix;
2216+
{
2217+
auto tcBin = ctx->tc.binaryPath;
2218+
// tc binary at <registry>/data/xpkgs/xim-x-*/ver/bin/g++
2219+
// Climb to <registry> = .../(xpkgs)/../..
2220+
for (auto p = tcBin; !p.empty() && p != p.root_path(); p = p.parent_path()) {
2221+
if (p.filename() == "xpkgs") {
2222+
auto registryDir = p.parent_path().parent_path();
2223+
auto sandboxBin = registryDir / "subos" / "default" / "bin";
2224+
if (std::filesystem::exists(sandboxBin))
2225+
pathPrefix = std::format("PATH='{}':\"$PATH\" ", sandboxBin.string());
2226+
break;
2227+
}
2228+
}
2229+
}
2230+
2231+
std::string cmd = std::format("{}'{}'", pathPrefix, exe.string());
22052232
for (auto& a : passthrough) cmd += std::format(" '{}'", a);
22062233
int rc = std::system(cmd.c_str());
22072234
// std::system returns wait status — extract exit code.

src/config.cppm

Lines changed: 16 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
//
33
// Layout (per docs/14-data-layout.md):
44
// $MCPP_HOME/ default ~/.mcpp/
5-
// bin/xlings vendored xlings binary
5+
// bin/mcpp mcpp binary (self-contained mode)
66
// registry/ XLINGS_HOME for mcpp's xlings
7+
// bin/xlings vendored xlings binary (= <XLINGS_HOME>/bin/xlings)
78
// .xlings.json seeded with index_repos = [mcpp-index]
89
// bmi/<fp>/ BMI cache (existing)
910
// cache/ metadata caches
@@ -34,7 +35,7 @@ struct GlobalConfig {
3435
// Resolved paths
3536
std::filesystem::path mcppHome; // ~/.mcpp/
3637
std::filesystem::path binDir; // mcppHome/bin
37-
std::filesystem::path xlingsBinary; // mcppHome/bin/xlings
38+
std::filesystem::path xlingsBinary; // mcppHome/registry/bin/xlings
3839
std::filesystem::path registryDir; // mcppHome/registry
3940
std::filesystem::path bmiCacheDir; // mcppHome/bmi
4041
std::filesystem::path metaCacheDir; // mcppHome/cache
@@ -194,7 +195,7 @@ bool write_default_config_toml(const std::filesystem::path& path) {
194195
constexpr auto tmpl = R"(# mcpp global config — auto-generated; safe to edit.
195196
196197
[xlings]
197-
# binary: "bundled" (use $MCPP_HOME/bin/xlings) | "system" | absolute path
198+
# binary: "bundled" (use $MCPP_HOME/registry/bin/xlings) | "system" | absolute path
198199
binary = "bundled"
199200
# home: empty = use $MCPP_HOME/registry; can override
200201
home = ""
@@ -324,47 +325,12 @@ void ensure_sandbox_init(const GlobalConfig& cfg, bool quiet) noexcept {
324325
}
325326
}
326327

327-
// Place a copy of the xlings binary at <XLINGS_HOME>/bin/xlings so that
328-
// xlings 0.4.10's `process_xvm_operations_` shim creation guard
329-
// (installer.cppm:480 — `if (fs::exists(paths.homeDir / "bin" / "xlings"))`)
330-
// passes. Without this, xim install hooks register version pins in
331-
// <subos>/.xlings.json but never create the per-tool shims in
332-
// <subos>/bin/, leaving `as`/`ld`/etc. unreachable in the sandbox.
333-
//
334-
// We use a hardlink so disk cost is zero and the binary stays in lockstep
335-
// with whatever mcpp's bin/xlings is.
336-
//
337-
// TODO(xlings-upstream): When xlings learns to self-bootstrap on first
338-
// `XLINGS_HOME=<empty-dir> xlings install ...` (auto-hardlink from
339-
// /proc/self/exe), this function becomes unnecessary.
340-
void ensure_sandbox_xlings_binary(const GlobalConfig& cfg, bool quiet) noexcept {
341-
auto src = cfg.xlingsBinary;
342-
auto dst = cfg.xlingsHome() / "bin" / "xlings";
343-
if (std::filesystem::exists(dst)) return;
344-
if (!std::filesystem::exists(src)) return; // upstream issue, surface elsewhere
345-
346-
std::error_code ec;
347-
std::filesystem::create_directories(dst.parent_path(), ec);
348-
349-
// Try hardlink first (cheap); fall back to copy if cross-device.
350-
std::filesystem::create_hard_link(src, dst, ec);
351-
if (ec) {
352-
ec.clear();
353-
std::filesystem::copy_file(src, dst,
354-
std::filesystem::copy_options::overwrite_existing, ec);
355-
}
356-
if (!ec) {
357-
std::filesystem::permissions(dst,
358-
std::filesystem::perms::owner_exec
359-
| std::filesystem::perms::group_exec
360-
| std::filesystem::perms::others_exec,
361-
std::filesystem::perm_options::add, ec);
362-
}
363-
if (ec && !quiet) {
364-
std::println(stderr,
365-
"warning: failed to mirror xlings binary into sandbox bin: {}",
366-
ec.message());
367-
}
328+
// With the 0.0.4 layout change (xlings binary at <MCPP_HOME>/registry/bin/
329+
// = <XLINGS_HOME>/bin/), the bundled xlings IS already at the path xlings's
330+
// shim-creation guard checks (`paths.homeDir / "bin" / "xlings"`).
331+
// No mirroring / hardlinking needed — this function is now a no-op.
332+
void ensure_sandbox_xlings_binary(const GlobalConfig& /*cfg*/, bool /*quiet*/) noexcept {
333+
// Intentional no-op: xlingsBinary == xlingsHome()/bin/xlings.
368334
}
369335

370336
// ─── Bootstrap install: shared event-stream parser + driver ────────────
@@ -596,8 +562,13 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
596562
// 1. Resolve paths
597563
cfg.mcppHome = home_dir();
598564
cfg.binDir = cfg.mcppHome / "bin";
599-
cfg.xlingsBinary = cfg.binDir / "xlings";
600565
cfg.registryDir = cfg.mcppHome / "registry";
566+
// xlings lives under registry/, not bin/ — it's a registry tool,
567+
// not a user-facing binary. This also places it exactly at
568+
// <XLINGS_HOME>/bin/xlings, which satisfies xlings's own shim-
569+
// creation guard (`if fs::exists(homeDir/"bin"/"xlings")`),
570+
// making ensure_sandbox_xlings_binary() a no-op.
571+
cfg.xlingsBinary = cfg.registryDir / "bin" / "xlings";
601572
cfg.bmiCacheDir = cfg.mcppHome / "bmi";
602573
cfg.metaCacheDir = cfg.mcppHome / "cache";
603574
cfg.logDir = cfg.mcppHome / "log";

src/modgraph/scanner.cppm

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,12 +321,26 @@ void scan_one_into(ScanResult& result,
321321
const std::filesystem::path& root,
322322
const mcpp::manifest::Manifest& manifest)
323323
{
324+
// Glob exclusion: patterns starting with `!` remove files from the
325+
// include set (like .gitignore).
326+
// sources = ["src/**/*.cpp", "!src/**/*_test.cpp"]
327+
// All positive patterns are expanded first, then all `!`-prefixed
328+
// patterns are expanded and the resulting paths are removed.
324329
std::set<std::filesystem::path> all_files;
330+
std::set<std::filesystem::path> excluded;
325331
for (auto const& g : manifest.modules.sources) {
326-
for (auto& p : expand_glob(root, g)) {
327-
all_files.insert(p);
332+
if (!g.empty() && g[0] == '!') {
333+
for (auto& p : expand_glob(root, g.substr(1))) {
334+
excluded.insert(p);
335+
}
336+
} else {
337+
for (auto& p : expand_glob(root, g)) {
338+
all_files.insert(p);
339+
}
328340
}
329341
}
342+
for (auto& p : excluded) all_files.erase(p);
343+
330344
for (auto const& f : all_files) {
331345
auto r = scan_file(f, manifest.package.name);
332346
if (!r) {

src/toolchain/fingerprint.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import mcpp.toolchain.detect;
1818

1919
export namespace mcpp::toolchain {
2020

21-
inline constexpr std::string_view MCPP_VERSION = "0.0.3";
21+
inline constexpr std::string_view MCPP_VERSION = "0.0.4";
2222

2323
struct FingerprintInputs {
2424
Toolchain toolchain;

0 commit comments

Comments
 (0)