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
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,40 @@
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。

## [0.0.96] — 2026-07-18

### 修复

- **[windows] `mcpp test --workspace` 静默崩溃(裸 exit 127,实为 0xC0000409)**
(#230,修复 #231)。三层根因:
1. 0.0.95 扫描器的 glob walk 新增 `follow_directory_symlink`,而项目本地
`.mcpp/.xlings/data/<index>` 是指回索引根的**符号链接** → walk 逃逸出成员
目录、扫遍整个索引 checkout(CI 里含 vendored xim-pkgindex);
2. `path_matches_glob` 用 `generic_string()` 拼窄串,MSVC 在非 CJK ANSI 代码页
(runner ACP=1252)下遇到中文文件名 `bug-report---问题反馈.md` 抛
`std::system_error`;
3. 异常逃出 `main` 未捕获 → `std::terminate` → `__fastfail`(0xC0000409),
git-bash 显示为无任何输出的 exit 127。
- 修复:`expand_glob`/`expand_dir_glob` **按名剪枝 `.mcpp` 目录**(mcpp 自身
元数据目录永远不是源码目录,从源头切断符号链接逃逸,顺带避免每个成员把
整个索引树白走一遍);`path_matches_glob` 对无法窄化的文件名按"不匹配"
跳过而不是摧毁构建;`main()` 增加最后防线 catch,逃逸异常打印真实错误并
以 70 退出,不再静默 fastfail。
- 取证:runner 开 WER 全内存 dump,崩溃栈+被转换字符串逐帧还原(记录见
mcpplibs/mcpp-index `debug/mcpp230-windows-repro` 分支及 #230)。
- 回归测试:`tests/e2e/113_scanner_mcpp_dir_prune.sh`(`.mcpp` 符号链接逃逸
必须被剪枝;CJK 文件名不得致命)。linux 行为对照:0.0.95 会顺着该符号链接
把项目外源码编进来(链接期 duplicate `main`),修复后构建干净。

## [0.0.95] — 2026-07-17

- 见 GitHub Release v0.0.95:声明式清单能力(features.sources / generated_files /
cfg 条件 sources / per-glob flags,#223)、汇编源一等公民(.S/.s/.asm 进
sources,NASM 按目标推导 `-f`,#220)、build.mcpp 补全(环境契约 + cross 下
运行 + 依赖包执行,#222)。
已知问题:#230(本版 windows workspace 崩溃,0.0.96 修复)、#232(冷环境
`xim:nasm` 自举产出空载荷,待修)。

## [0.0.94] — 2026-07-15

### 修复
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.95"
version = "0.0.96"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
15 changes: 14 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@ import std;
import mcpp.cli;

int main(int argc, char* argv[]) {
int rc = mcpp::cli::run(argc, argv);
int rc;
try {
rc = mcpp::cli::run(argc, argv);
} catch (const std::exception& e) {
// Last-resort boundary: without it an escaped exception is
// std::terminate — on Windows a silent 0xC0000409 that git-bash
// reports as a bare exit 127 (mcpp#230 wore that mask). Name the
// real error and exit with a recognizable internal-error code.
std::println(std::cerr, "error: internal: unhandled exception: {}", e.what());
rc = 70; // EX_SOFTWARE
} catch (...) {
std::println(std::cerr, "error: internal: unhandled non-standard exception");
rc = 70;
}
#ifdef __APPLE__
// With statically linked libc++ (the macOS release linkage since
// 0.0.50), static destruction can SIGABRT on exit — same issue xlings
Expand Down
27 changes: 26 additions & 1 deletion src/modgraph/scanner.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,18 @@ bool path_matches_glob(const std::filesystem::path& candidate,
// through a directory symlink back to its real location and break the
// match (globs are about where a file appears in the tree, not where
// its bits live).
auto rel = candidate.lexically_relative(root).generic_string();
std::string rel;
try {
rel = candidate.lexically_relative(root).generic_string();
} catch (const std::exception&) {
// MSVC's narrow conversion throws std::system_error when the
// native (wide) name has no spelling in the ANSI codepage (e.g. a
// CJK filename on an en-US host — mcpp#230 hit this on an issue
// template inside a walked index tree). Such a name can never be
// spelled in a glob or a compile command either: not a match, and
// never a reason to tear down the whole build.
return false;
}

auto match = [](std::string_view s, std::string_view p) -> bool {
// Simple recursive matcher.
Expand Down Expand Up @@ -242,6 +253,15 @@ std::vector<std::filesystem::path> expand_glob(const std::filesystem::path& root
for (fs::recursive_directory_iterator end; !ec && it != end; it.increment(ec)) {
auto& e = *it;
if (e.is_directory(eec) && !eec) {
// `.mcpp` is mcpp's own project metadata dir. Its xlings data
// tree holds a SYMLINK back to each path-dep index root, so
// following it walks that entire checkout (mcpp#230: the CI
// workspace walked into a vendored xim-pkgindex and died on a
// CJK filename). Never a source dir — prune by name.
if (e.path().filename() == ".mcpp") {
it.disable_recursion_pending();
continue;
}
auto depth = static_cast<std::size_t>(it.depth());
chain.resize(std::min(chain.size(), depth + 1));
auto c = fs::canonical(e.path(), eec);
Expand Down Expand Up @@ -294,6 +314,11 @@ std::vector<std::filesystem::path> expand_dir_glob(const std::filesystem::path&
!ec && it != end; it.increment(ec)) {
auto& e = *it;
if (!e.is_directory(eec) || eec) continue;
// Same `.mcpp` prune as expand_glob (see comment there / mcpp#230).
if (e.path().filename() == ".mcpp") {
it.disable_recursion_pending();
continue;
}
auto depth = static_cast<std::size_t>(it.depth());
chain.resize(std::min(chain.size(), depth + 1));
auto c = std::filesystem::canonical(e.path(), eec);
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.95";
inline constexpr std::string_view MCPP_VERSION = "0.0.96";

struct FingerprintInputs {
Toolchain toolchain;
Expand Down
49 changes: 49 additions & 0 deletions tests/e2e/113_scanner_mcpp_dir_prune.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# requires: gcc
# mcpp#230: the source scanner follows directory symlinks (0.0.95), and the
# project-local `.mcpp/.xlings/data/<index>` entry is a SYMLINK back to the
# index root — following it walks that entire checkout. In CI that tree held
# a CJK-named file, whose wide→narrow spelling throws on MSVC (bare
# 0xC0000409 / exit 127). Assert both fixes:
# 1. `.mcpp` is pruned from glob walks — an out-of-tree file reachable only
# through the symlink must NOT be compiled (it double-defines main).
# 2. A file whose name cannot be narrowed never aborts the build (covered
# on MSVC by path_matches_glob's catch; here it just rides along).
set -e

TMP=$(mktemp -d)
trap "rm -rf $TMP" EXIT

cd "$TMP"
mkdir -p outside/sub proj/src

cat > outside/sub/extra.cpp <<'EOF'
int main() { return 9; } // double-defines main if the walk escapes
EOF
# CJK filename — on MSVC hosts with a non-CJK ANSI codepage this name has no
# narrow spelling; the scanner must skip it, not tear down the build.
cat > "outside/sub/问题反馈.cpp" <<'EOF'
int cjk_never_compiled() { return 2; }
EOF

cat > proj/src/main.cpp <<'EOF'
int main() { return 0; }
EOF
cat > proj/mcpp.toml <<'EOF'
[package]
name = "prunetest"
version = "0.1.0"

[build]
sources = ["**/*.cpp"]
EOF

# Simulate the path-dep index link mcpp creates under the project data dir.
mkdir -p proj/.mcpp/.xlings/data
ln -s "$TMP/outside" proj/.mcpp/.xlings/data/compat

cd proj
"$MCPP" build
"$MCPP" run

echo "PASS: .mcpp symlink escape pruned; build unaffected by unmatchable names"
Loading