Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 二进制从 `<MCPP_HOME>/bin/xlings`
(与 mcpp 同目录)移至 `<MCPP_HOME>/registry/bin/xlings`
(= `<XLINGS_HOME>/bin/xlings`)。由于 xlings 的 shim-creation guard
恰好检查 `<XLINGS_HOME>/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,
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -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"]
Expand Down
33 changes: 30 additions & 3 deletions src/cli.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::filesystem::path> sourceFiles;
std::set<std::filesystem::path> 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={})",
Expand Down Expand Up @@ -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 <registry>/data/xpkgs/xim-x-*/ver/bin/g++
// Climb to <registry> = .../(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.
Expand Down
61 changes: 16 additions & 45 deletions src/config.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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 (= <XLINGS_HOME>/bin/xlings)
// .xlings.json seeded with index_repos = [mcpp-index]
// bmi/<fp>/ BMI cache (existing)
// cache/ metadata caches
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 = ""
Expand Down Expand Up @@ -324,47 +325,12 @@ void ensure_sandbox_init(const GlobalConfig& cfg, bool quiet) noexcept {
}
}

// Place a copy of the xlings binary at <XLINGS_HOME>/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
// <subos>/.xlings.json but never create the per-tool shims in
// <subos>/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=<empty-dir> 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 <MCPP_HOME>/registry/bin/
// = <XLINGS_HOME>/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 ────────────
Expand Down Expand Up @@ -596,8 +562,13 @@ std::expected<GlobalConfig, ConfigError> 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
// <XLINGS_HOME>/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";
Expand Down
18 changes: 16 additions & 2 deletions src/modgraph/scanner.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::filesystem::path> all_files;
std::set<std::filesystem::path> 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) {
Expand Down
2 changes: 1 addition & 1 deletion src/toolchain/fingerprint.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/10_env_command.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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" || {
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/27_self_contained_home.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading