From 82d1697a256b0f717d82b9e442af16d23706bbcf Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sun, 26 Jul 2026 00:06:14 +0800 Subject: [PATCH] fix: build the SONAME alias under explicit ninja goals (mcpp test), 0.0.107 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A shared library that declares `soname` is written as bin/libX11.so but records SONAME libX11.so.6. Both the linker resolving a transitive NEEDED and the loader starting a binary look for the alias, never for the plain name — so the alias is a prerequisite of anything that links the library, not a by-product of building it. It was modelled as a standalone ninja edge that nothing depends on, reachable only through `default`. The 0.0.104 test batch (#274) made `mcpp test` pass explicit goal targets so it could isolate per-test compiles, and that silently stopped producing the alias. `mcpp build` and `mcpp run` drive `default` and were unaffected, which is why this survived three releases: only a project driven by `mcpp test` hits it. The symptom lands three levels from the cause — a consumer failing to link with `libX11.so: undefined reference to xcb_connect` (alongside `libxcb.so.1 ... not found`), or a test binary exiting 127. mcpp-index CI has been failing this way on gui-stack, imgui-module and imgui-window since 0.0.104; two probe PRs pinning only MCPP_VERSION over unmodified descriptors reproduced it at both 0.0.104 and 0.0.106, which is what ruled out the package-identity migration as the cause. Hang the alias off the consumer's implicit inputs, beside the .so it already lists. Correct under `default` and under explicit goals alike, and it pulls in nothing that was not already being built. e2e 64 covered only `build` + `run` — precisely the two paths that still worked. It now also runs `mcpp test` from a clean target dir and asserts the alias exists; without the fix that test binary fails with exit 127. Verified: unit 37/37; e2e 64 red before / green after; the three real mcpp-index members pass with `mcpp test` alone (gui-stack 5/5, imgui-module 1/1, imgui-window 1/1). Remaining local e2e failures (22, 98, 141) reproduce identically on the released 0.0.106 binary under the same MCPP_HOME — they are this machine's gcc 15.1.0, not this change. --- CHANGELOG.md | 10 +++++++ mcpp.toml | 2 +- src/build/plan.cppm | 13 +++++++++ src/toolchain/fingerprint.cppm | 2 +- tests/e2e/64_shared_soname_runtime_alias.sh | 30 +++++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6111f94..2c0d7a9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ > 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。 > 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。 +## [0.0.107] — 2026-07-25 + +### 修复 + +- **`mcpp test` 不再漏建共享库的 SONAME 别名(0.0.104 起的回归)。** 声明了 `soname` 的共享库产物写作 `bin/libX11.so`,但记录的 SONAME 是 `libX11.so.6`,链接器解析传递 `NEEDED`、加载器启动程序时找的都是后者 —— 别名不是附属产物,而是**任何链接该库的目标的前置条件**。此前别名是一条无人依赖的独立 ninja 边,只能经 `default` 到达;0.0.104 的 test 能力批次([#274](https://github.com/mcpp-community/mcpp/pull/274))让 `mcpp test` 改为显式指定 ninja 目标以隔离逐测试编译,于是这条边被静默跳过。 + + 症状离根因很远,这也是它潜伏三个版本的原因:消费者链接期报 `libX11.so: undefined reference to xcb_connect`(伴 `libxcb.so.1 ... not found`),或测试二进制以 `exit 127` 退出。`mcpp build` / `mcpp run` 走 `default`,一直正常,所以只有以 `mcpp test` 驱动的工程会中招 —— mcpp-index 的 CI 正是如此,`gui-stack` / `imgui-module` / `imgui-window` 三个成员自 0.0.104 起持续失败。 + + 修法是把别名挂到消费者的隐式输入上(`plan.cppm` 的 `append_direct_shared_deps`),与被链接的 `.so` 并列。无论 ninja 的目标是 `default` 还是某个测试二进制都会生成,且不会多编译任何东西。e2e 64 补了 `mcpp test` 一段覆盖此路径 —— 它此前只覆盖 `build` + `run`,正是缺口所在。 + ## [0.0.106] — 2026-07-25 > 落地 **SPEC-001**(`docs/spec/package-identity.md`)—— 包身份的规范形态。0.0.105 为修 #278 引入的「`name` 必须写成完全限定名」是**编码约束而非设计规则**:它的唯一成因是 mcpp 构造安装目标时丢弃了已读到的字面 `name`、改用 `.<短名>` 重新渲染一遍。本版本改为直接使用字面值,规范形态回归 **`namespace` 承载层级、`name` 是单一原子段**。配套 xlings 0.4.69([#381](https://github.com/openxlings/xlings/issues/381),索引改按 `(namespace, name)` 建键)。 diff --git a/mcpp.toml b/mcpp.toml index 42294de8..c1a63707 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,6 +1,6 @@ [package] name = "mcpp" -version = "0.0.106" +version = "0.0.107" description = "Modern C++ build & package management tool" license = "Apache-2.0" authors = ["mcpp-community"] diff --git a/src/build/plan.cppm b/src/build/plan.cppm index 8f1133fc..4003f794 100644 --- a/src/build/plan.cppm +++ b/src/build/plan.cppm @@ -727,6 +727,19 @@ make_plan(const mcpp::manifest::Manifest& manifest, for (auto targetIndex : targetsIt->second) { auto const& dep = sharedDepTargets[targetIndex]; lu.implicitInputs.push_back(dep.output); + // The SONAME alias is a prerequisite too, not a by-product: the + // library is written as bin/libX11.so but records SONAME + // libX11.so.6, so both the linker (resolving a transitive + // NEEDED) and the loader look for the alias, never for the + // plain name. Hanging it off the consumer keeps it correct + // under explicit ninja goals — `mcpp test` names the test + // binaries, and an alias edge that nothing depends on is + // reachable only through `default`, so it was silently skipped + // (0.0.104-0.0.106). The failure surfaced far away, as + // `libX11.so: undefined reference to xcb_connect` or a test + // exiting 127. + for (auto const& alias : runtime_aliases_for_target(dep.target)) + lu.implicitInputs.push_back(alias); auto flags = shared_library_link_flags(dep.target); lu.linkFlags.insert(lu.linkFlags.end(), flags.begin(), flags.end()); } diff --git a/src/toolchain/fingerprint.cppm b/src/toolchain/fingerprint.cppm index 409e2f51..be8b2c73 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.106"; +inline constexpr std::string_view MCPP_VERSION = "0.0.107"; struct FingerprintInputs { Toolchain toolchain; diff --git a/tests/e2e/64_shared_soname_runtime_alias.sh b/tests/e2e/64_shared_soname_runtime_alias.sh index 5a745386..500ee4c7 100755 --- a/tests/e2e/64_shared_soname_runtime_alias.sh +++ b/tests/e2e/64_shared_soname_runtime_alias.sh @@ -95,4 +95,34 @@ readelf -d "$so" | grep -q 'Library soname: \[libdepShared.so.1\]' || { exit 1 } +# ── `mcpp test` must produce the alias too ────────────────────────── +# The alias edge is reachable through ninja's `default`, which is what build +# and run use. `mcpp test` names its goals explicitly (to isolate per-test +# compiles), and from 0.0.104 to 0.0.106 that skipped the alias: a test binary +# linked against libdepShared.so records NEEDED libdepShared.so.1 and then +# exits 127 because the loader cannot find it. Start from a clean target dir so +# only the test path can create it. +mkdir -p tests +cat > tests/linked_test.cpp <<'EOF' +extern "C" int dep_shared_answer(); + +int main() { + return dep_shared_answer() == 42 ? 0 : 1; +} +EOF + +rm -rf target +"$MCPP" test > test.log 2>&1 || { + cat test.log + echo "mcpp test failed — the SONAME alias is a prerequisite of anything that links the library, not a by-product of the default target" + exit 1 +} + +alias_from_test="$(find target -name 'libdepShared.so.1' | head -1)" +[ -n "$alias_from_test" ] || { + cat test.log + echo "mcpp test did not produce the ABI soname alias" + exit 1 +} + echo "OK"