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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@

## [Unreleased]

### 在已激活的 xlings subos 中运行时,registry 不再被重定向(2026.9.14.3)

`xlings subos use <name>` 打开的 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/<name>/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 时仍缺的二十一项。分类与决定见设计记录
Expand Down
6 changes: 6 additions & 0 deletions docs/91-toolchain-internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name>`
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
Expand Down
5 changes: 5 additions & 0 deletions docs/zh/91-toolchain-internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ mcpp 还会给它启动的每个进程声明 `XLINGS_SUBOS_LD_PATHS=0` ——
(`<mcpp home>/registry`),两者通常指向由**不同物理 glibc 载荷**支撑的不同 farm。
mcpp 发的是自己从已选 binding 推导出来的那一条。

出于同样的原因,mcpp 自己对 xlings 的调用不带 `XLINGS_ACTIVE_SUBOS`
(2026.9.14.3+)。执行过 `xlings subos use <name>` 的 shell 会导出它,xlings 把它排在
home 自身的 `activeSubos` 之前;被继承时,registry 会把 mcpp 的工具和工程的载荷装进
同名的 subos,而 mcpp 读取的是 `subos/default`。

## 3. 链接模型(`modules/toolchain-model/src/linkmodel.cppm`)

`ToolchainLinkModel` 只回答一个问题——*如何对该工具链的 C 库编译与链接*——
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 = "2026.9.14.2"
version = "2026.9.14.3"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
2 changes: 1 addition & 1 deletion modules/versioning/src/version.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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
42 changes: 32 additions & 10 deletions src/xlings/xlings.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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
// <name>` 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/<name>/bin`, and the
// pkg-config view `mcpp::pkg_config_libdir()` names stayed empty).
struct InvocationVar {
std::string name;
std::string value;
Expand All @@ -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<std::string>`,
// 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> saved_;
};

// Build full xlings interface command.
Expand Down Expand Up @@ -1182,28 +1197,35 @@ std::vector<InvocationVar> 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);
}
}
}

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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# requires: fresh-sandbox
# 686 -- a shell's `xlings subos use <name>` 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/<name>/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/<name>` 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"
30 changes: 29 additions & 1 deletion tests/unit/test_xlings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name>` 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<std::string>("shell-subos"));
}
Loading