diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a27711e..27882775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ ## [Unreleased] +### 在已激活的 xlings subos 中运行时,registry 不再被重定向(2026.9.14.3) + +`xlings subos use ` 打开的 shell 导出 `XLINGS_ACTIVE_SUBOS`,xlings 解析 subos +时它的优先级高于 home 自身的 `activeSubos`。这个变量指的是 shell 所在 xlings home +的 subos;mcpp 的 registry 是另一个 home,mcpp 的工具路径与视图路径都从 +`subos/default` 推导。mcpp 调用自带的 xlings 时继承了它,于是 registry 把引导安装 +的工具与工程的载荷装进同名的另一个 subos:2026.9.14.2 上实测,全新 home 的 +`ninja`、`patchelf` 落在 `registry/subos//bin`,`mcpp::pkg_config_libdir()` +指向的 pkg-config 视图为空(在 #634 的沙箱验证中发现)。现在 mcpp 对 xlings 的每次 +调用都不带这个变量,POSIX 上由命令前缀 `env -u` 去掉,Windows 上由 +`ScopedInvocationEnv` 在调用期间移除并在结束后恢复。(单测 +`XlingsInvocationEnv.TheShellsActiveSubosIsNeverInherited`,e2e 686) + ### 一个框架的 CMake 对齐清单:#634 的二十一项中引擎的部分(2026.9.14.2) #634 列出 HuxerUI 从 CMake 迁到 mcpp 时仍缺的二十一项。分类与决定见设计记录 diff --git a/docs/91-toolchain-internals.md b/docs/91-toolchain-internals.md index b153c261..f7b615cd 100644 --- a/docs/91-toolchain-internals.md +++ b/docs/91-toolchain-internals.md @@ -262,6 +262,12 @@ must refuse its `-rpath "$XLINGS_SUBOS_LIB"`, because that variable names the different physical glibc payload. mcpp emits the farm entry it derived from the binding it actually selected. +For the same reason mcpp's own xlings invocations do not carry +`XLINGS_ACTIVE_SUBOS` (2026.9.14.3+). A shell that ran `xlings subos use ` +exports it, and xlings ranks it above a home's own `activeSubos`; inherited, it +made the registry install mcpp's tools and a project's payloads into a SubOS of +that name, while mcpp reads `subos/default`. + ## 3. The link model (`modules/toolchain-model/src/linkmodel.cppm`) `ToolchainLinkModel` answers exactly one question — *how does mcpp compile and diff --git a/docs/zh/91-toolchain-internals.md b/docs/zh/91-toolchain-internals.md index e7caef4b..ec0f477f 100644 --- a/docs/zh/91-toolchain-internals.md +++ b/docs/zh/91-toolchain-internals.md @@ -214,6 +214,11 @@ mcpp 还会给它启动的每个进程声明 `XLINGS_SUBOS_LD_PATHS=0` —— (`/registry`),两者通常指向由**不同物理 glibc 载荷**支撑的不同 farm。 mcpp 发的是自己从已选 binding 推导出来的那一条。 +出于同样的原因,mcpp 自己对 xlings 的调用不带 `XLINGS_ACTIVE_SUBOS` +(2026.9.14.3+)。执行过 `xlings subos use ` 的 shell 会导出它,xlings 把它排在 +home 自身的 `activeSubos` 之前;被继承时,registry 会把 mcpp 的工具和工程的载荷装进 +同名的 subos,而 mcpp 读取的是 `subos/default`。 + ## 3. 链接模型(`modules/toolchain-model/src/linkmodel.cppm`) `ToolchainLinkModel` 只回答一个问题——*如何对该工具链的 C 库编译与链接*—— diff --git a/mcpp.toml b/mcpp.toml index f12c17bd..55718edc 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,6 +1,6 @@ [package] name = "mcpp" -version = "2026.9.14.2" +version = "2026.9.14.3" description = "Modern C++ build & package management tool" license = "Apache-2.0" authors = ["mcpp-community"] diff --git a/modules/versioning/src/version.cppm b/modules/versioning/src/version.cppm index bbe75676..c3333d7f 100644 --- a/modules/versioning/src/version.cppm +++ b/modules/versioning/src/version.cppm @@ -31,6 +31,6 @@ import std; export namespace mcpp { -inline constexpr std::string_view MCPP_VERSION = "2026.9.14.2"; +inline constexpr std::string_view MCPP_VERSION = "2026.9.14.3"; } // namespace mcpp diff --git a/src/xlings/xlings.cppm b/src/xlings/xlings.cppm index e1edb836..9afb7f87 100644 --- a/src/xlings/xlings.cppm +++ b/src/xlings/xlings.cppm @@ -275,6 +275,15 @@ std::string build_command_prefix(const Env& env); // absent XLINGS_PROJECT_DIR, because xlings resolves its subos scope from that // variable. POSIX renders the decision into the command prefix (`env -u` and // `K=V`); Windows applies it to the process through ScopedInvocationEnv. +// +// XLINGS_ACTIVE_SUBOS IS ALWAYS ABSENT. A shell that ran `xlings subos use +// ` exports it, and xlings ranks it above the home's own `activeSubos`. +// It names a SubOS of the shell's xlings home; mcpp's registry is a different +// home, whose paths mcpp derives from `subos/default`. Inherited, it made the +// registry install mcpp's tools and a project's payloads into a SubOS of the +// shell's name, where mcpp does not look (measured on 2026.9.14.2: a fresh +// home's `ninja` and `patchelf` landed in `registry/subos//bin`, and the +// pkg-config view `mcpp::pkg_config_libdir()` names stayed empty). struct InvocationVar { std::string name; std::string value; @@ -297,9 +306,15 @@ public: ScopedInvocationEnv& operator=(const ScopedInvocationEnv&) = delete; private: - bool active_ = false; - bool hadPrevious_ = false; - std::string previous_; + // One entry per scope variable the guard applied: its name and the value + // it had before, if any. Plain members, not `std::optional`, + // which clang on the MSVC ABI does not copy inside a vector element. + struct Saved { + std::string name; + bool hadPrevious = false; + std::string previous; + }; + std::vector saved_; }; // Build full xlings interface command. @@ -1182,18 +1197,24 @@ std::vector invocation_env(const Env& env) { return { {"XLINGS_HOME", env.home.string(), true}, {"XLINGS_PROJECT_DIR", env.projectDir.string(), !env.projectDir.empty()}, + {"XLINGS_ACTIVE_SUBOS", "", false}, }; } ScopedInvocationEnv::ScopedInvocationEnv(const Env& env) { if constexpr (mcpp::platform::is_windows) { + // Every variable but XLINGS_HOME is scope: applied for the guard's + // lifetime and restored after it. XLINGS_HOME keeps the process-wide + // lifetime `build_command_prefix` gives it. for (auto const& var : invocation_env(env)) { - if (var.name != "XLINGS_PROJECT_DIR") continue; + if (var.name == "XLINGS_HOME") continue; + Saved s; + s.name = var.name; if (auto prior = mcpp::platform::env::get(var.name)) { - hadPrevious_ = true; - previous_ = *prior; + s.hadPrevious = true; + s.previous = *prior; } - active_ = true; + saved_.push_back(s); if (var.present) mcpp::platform::env::set(var.name, var.value); else mcpp::platform::env::unset(var.name); } @@ -1201,9 +1222,10 @@ ScopedInvocationEnv::ScopedInvocationEnv(const Env& env) { } ScopedInvocationEnv::~ScopedInvocationEnv() { - if (!active_) return; - if (hadPrevious_) mcpp::platform::env::set("XLINGS_PROJECT_DIR", previous_); - else mcpp::platform::env::unset("XLINGS_PROJECT_DIR"); + for (auto const& s : saved_) { + if (s.hadPrevious) mcpp::platform::env::set(s.name, s.previous); + else mcpp::platform::env::unset(s.name); + } } std::string build_command_prefix(const Env& env) { diff --git a/tests/e2e/686_an_activated_xlings_subos_does_not_redirect_the_registry.sh b/tests/e2e/686_an_activated_xlings_subos_does_not_redirect_the_registry.sh new file mode 100755 index 00000000..7af2f967 --- /dev/null +++ b/tests/e2e/686_an_activated_xlings_subos_does_not_redirect_the_registry.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# requires: fresh-sandbox +# 686 -- a shell's `xlings subos use ` does not redirect mcpp's registry. +# That shell exports XLINGS_ACTIVE_SUBOS, which names a SubOS of the shell's +# xlings home, and xlings ranks it above a home's own `activeSubos`. mcpp's +# registry is a different home, and mcpp derives its tool and view paths from +# `subos/default`. Inherited, the variable made a fresh home's bootstrap install +# `ninja` and `patchelf` into `registry/subos//bin` (measured on +# 2026.9.14.2), where mcpp does not look, and left the pkg-config view that +# `mcpp::pkg_config_libdir()` names empty. +# +# Criteria, for a fresh home whose first command runs with the variable set: +# A. `registry/subos/` does not exist. +# B. The bootstrap's tools are in `registry/subos/default/bin`. This is the +# denominator: a bootstrap that installed nothing would meet A vacuously. +set -e + +TMP=$(mktemp -d) +trap "rm -rf $TMP" EXIT + +fail() { echo "FAIL: $1"; shift; for f in "$@"; do echo "--- $f ---"; cat "$f" 2>/dev/null; done; exit 1; } + +export MCPP_HOME="$TMP/mcpp-home" +cd "$TMP" +SHELL_SUBOS=mcpp-e2e-shell-subos +XLINGS_ACTIVE_SUBOS=$SHELL_SUBOS "$MCPP" self env > env.log 2>&1 || true + +SUBOS="$MCPP_HOME/registry/subos" +[ -d "$SUBOS/default" ] || fail "the registry was not initialised" env.log + +# ── A ────────────────────────────────────────────────────────────────────── +if [ -e "$SUBOS/$SHELL_SUBOS" ]; then + fail "A: the registry acted on the shell's SubOS (bin: $(ls "$SUBOS/$SHELL_SUBOS/bin" 2>/dev/null | tr '\n' ' '))" env.log +fi + +# ── B ────────────────────────────────────────────────────────────────────── +tools=$(ls "$SUBOS/default/bin" 2>/dev/null | grep -v -i '^xlings' | tr '\n' ' ') +[ -n "$tools" ] || fail "B: the bootstrap placed no tool in subos/default/bin" env.log +echo "ok: the bootstrap's tools are in subos/default/bin: $tools" + +echo "PASS: 686_an_activated_xlings_subos_does_not_redirect_the_registry" diff --git a/tests/unit/test_xlings.cpp b/tests/unit/test_xlings.cpp index 70a9f9b2..95691533 100644 --- a/tests/unit/test_xlings.cpp +++ b/tests/unit/test_xlings.cpp @@ -629,12 +629,40 @@ TEST(XlingsInvocationEnv, TheProcessEnvironmentIsUnchangedAfterwards) { #if !defined(_WIN32) TEST(XlingsInvocationEnv, ThePosixPrefixRendersTheDecision) { auto global = mcpp::xlings::build_command_prefix(xlings_env("")); - EXPECT_NE(global.find("env -u XLINGS_PROJECT_DIR PATH="), std::string::npos) << global; + EXPECT_NE(global.find("env -u XLINGS_PROJECT_DIR -u XLINGS_ACTIVE_SUBOS PATH="), + std::string::npos) << global; EXPECT_EQ(global.find("XLINGS_PROJECT_DIR="), std::string::npos) << global; auto project = mcpp::xlings::build_command_prefix(xlings_env("/work/proj")); EXPECT_EQ(project.find("-u XLINGS_PROJECT_DIR"), std::string::npos) << project; + EXPECT_NE(project.find("env -u XLINGS_ACTIVE_SUBOS PATH="), std::string::npos) << project; EXPECT_NE(project.find("XLINGS_PROJECT_DIR="), std::string::npos) << project; EXPECT_NE(project.find("/work/proj"), std::string::npos) << project; } #endif + +// A shell that ran `xlings subos use ` exports XLINGS_ACTIVE_SUBOS, which +// names a SubOS of the shell's xlings home and not of mcpp's registry; no +// invocation carries it, in either mode. +TEST(XlingsInvocationEnv, TheShellsActiveSubosIsNeverInherited) { + auto global = decided(xlings_env(""), "XLINGS_ACTIVE_SUBOS"); + ASSERT_EQ(global.name, "XLINGS_ACTIVE_SUBOS"); + EXPECT_FALSE(global.present); + auto project = decided(xlings_env("proj-dir"), "XLINGS_ACTIVE_SUBOS"); + ASSERT_EQ(project.name, "XLINGS_ACTIVE_SUBOS"); + EXPECT_FALSE(project.present); + + namespace env = mcpp::platform::env; + env::ScopedEnv keepPath("PATH", env::get("PATH")); + env::ScopedEnv keepHome("XLINGS_HOME", env::get("XLINGS_HOME")); + env::ScopedEnv prior("XLINGS_ACTIVE_SUBOS", std::string("shell-subos")); + { + auto e = xlings_env(""); + mcpp::xlings::ScopedInvocationEnv scope(e); + (void)mcpp::xlings::build_command_prefix(e); +#if defined(_WIN32) + EXPECT_FALSE(env::get("XLINGS_ACTIVE_SUBOS").has_value()); +#endif + } + EXPECT_EQ(env::get("XLINGS_ACTIVE_SUBOS"), std::optional("shell-subos")); +}