Skip to content

Commit 4c80028

Browse files
authored
fix: a Luban system's root is declared and its /xlings upstream is its store (2026.10.22.1) (#831)
In a Luban system (xlings design 2026-10-21 §3.7, J5, M1): the compiler's sysroot / is the system's own declared root (os-release ID=luban and /usr/lib/xlings-abi), and [xlings] upstream = ["/xlings"] reads the home the system's root record (/etc/xlings/usr) names when /xlings is not there -- a system made on a host keeps its home at the path of the home that made it. Both facts are the provider's layout (declared_system_root, system_home): mcpp names none of xlings' paths. Verified in a Luban KVM machine: mcpp self doctor without the sysroot or upstream warning; a first mcpp run of a new project in 4 s. Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent 0e79128 commit 4c80028

8 files changed

Lines changed: 128 additions & 3 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@
44
> Each `## [<version>]` section is that release's notes. Entries are written in English
55
> from 2026.9.28.3 on; earlier entries remain as written.
66

7+
## [2026.10.22.1] - 2026-10-22
8+
9+
### Fixed
10+
11+
- In a Luban system, the compiler's sysroot `/` is the system's own declared
12+
root (`ID=luban` in `/etc/os-release` and `/usr/lib/xlings-abi`): `mcpp self
13+
doctor` no longer reports it as belonging to neither the mcpp home nor the
14+
project.
15+
- In a Luban system, `[xlings] upstream = ["/xlings"]` reads the store the
16+
system's root record names (`/etc/xlings/usr`) when `/xlings` is not there --
17+
a system made on a host keeps its home at the path of the home that made it.
18+
Its gcc and glibc are reused rather than reported unreachable.
19+
720
## [2026.10.21.1] - 2026-10-21
821

922
### Fixed

‎mcpp.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "2026.10.21.1"
3+
version = "2026.10.22.1"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

‎modules/versioning/src/version.cppm‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ import std;
3131

3232
export namespace mcpp {
3333

34-
inline constexpr std::string_view MCPP_VERSION = "2026.10.21.1";
34+
inline constexpr std::string_view MCPP_VERSION = "2026.10.22.1";
3535

3636
} // namespace mcpp

‎src/config.cppm‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,10 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
838838
dir = std::filesystem::path(h) / (entry.size() > 2 ? entry.substr(2) : std::string{});
839839
dir = dir.lexically_normal();
840840
std::error_code ec;
841+
// A Luban system's /xlings is its store, wherever its xlings put it
842+
// (the home that made it, for a system made on a host).
843+
if (dir == "/xlings" && !std::filesystem::is_directory(dir, ec))
844+
if (auto store = mcpp::provider::layout().system_home("/"); !store.empty()) dir = store;
841845
if (!dir.is_absolute() || !std::filesystem::is_directory(dir, ec)) {
842846
// Fewer packages reused than asked for: said, not hidden.
843847
mcpp::diag::degraded("xlings/upstream", std::format("[xlings] upstream {} is not a directory", entry),

‎src/fallback/probe_sysroot.cppm‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,15 @@ bool sysroot_is_owned(std::string_view reportedPath,
9999
//
100100
// Both anchors are required. Registry alone would flag every legitimate
101101
// project-local tree; project alone would flag every payload sysroot.
102+
// In a Luban system (`systemRoot`, "/" but for a test) the sysroot `/` is the
103+
// system's own declared root (xlings design 2026-10-21 §3.7).
102104
bool sysroot_is_foreign(const std::filesystem::path& sysroot,
103105
const std::filesystem::path& registryRoot,
104-
const std::filesystem::path& projectRoot) {
106+
const std::filesystem::path& projectRoot,
107+
const std::filesystem::path& systemRoot = "/") {
105108
if (sysroot.empty()) return false;
109+
// A Luban system's own root: its compiler's `/` is the system it is.
110+
if (sysroot.lexically_normal() == "/" && mcpp::provider::layout().declared_system_root(systemRoot)) return false;
106111
return !path_is_under(sysroot, registryRoot)
107112
&& !path_is_under(sysroot, projectRoot);
108113
}

‎src/provider/provider.cppm‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,13 @@ public:
377377
// Whether `name` is one of the provider's own indexes (its default and
378378
// the ones it adds itself), which stay where the provider keeps them.
379379
virtual bool is_provider_index(std::string_view name) const = 0;
380+
// A system the provider made, as `root` (/, but for a test) says of
381+
// itself (xlings design 2026-10-21 §3.7): its compiler's sysroot `/` is
382+
// then the system's own declared root, not a tree nothing declared.
383+
virtual bool declared_system_root(const std::filesystem::path& root) const = 0;
384+
// The home such a system reads its packages from, by its root record;
385+
// empty: not one, or its record says nothing usable.
386+
virtual std::filesystem::path system_home(const std::filesystem::path& root) const = 0;
380387
};
381388

382389
// What a provider says of a package's payload without installing anything

‎src/provider/xlings/provider.cppm‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,36 @@ public:
196196
return {{dir, {}, {"xim"}}};
197197
return {};
198198
}
199+
// A Luban system (xlings design 2026-10-21 §3.7): its os-release says
200+
// ID=luban and /usr/lib/xlings-abi is the runtime xlings bound; its root
201+
// record (/etc/xlings/usr) names its generation, <home>/subos/<name>/root/usr
202+
// -- /xlings for one installed on a machine, the path of the home that
203+
// made it for one made on a host.
204+
bool declared_system_root(const std::filesystem::path& root) const override {
205+
std::error_code ec;
206+
if (!std::filesystem::exists(root / "usr/lib/xlings-abi", ec)) return false;
207+
std::ifstream in(root / "etc/os-release");
208+
for (std::string line; std::getline(in, line);) {
209+
if (!line.starts_with("ID=")) continue;
210+
auto id = line.substr(3);
211+
if (id.size() >= 2 && (id.front() == '"' || id.front() == '\'')) id = id.substr(1, id.size() - 2);
212+
return id == "luban";
213+
}
214+
return false;
215+
}
216+
std::filesystem::path system_home(const std::filesystem::path& root) const override {
217+
if (!declared_system_root(root)) return {};
218+
std::ifstream in(root / "etc/xlings/usr");
219+
std::string line;
220+
if (!std::getline(in, line)) return {};
221+
while (!line.empty() && (line.back() == '\r' || line.back() == ' ')) line.pop_back();
222+
const std::filesystem::path usr = std::filesystem::path(line).lexically_normal();
223+
const auto rootDir = usr.parent_path(), instance = rootDir.parent_path(), subos = instance.parent_path();
224+
if (!usr.is_absolute() || usr.filename() != "usr" || rootDir.filename() != "root" || subos.filename() != "subos") return {};
225+
std::error_code ec;
226+
const auto home = subos.parent_path();
227+
return std::filesystem::is_directory(home, ec) ? home : std::filesystem::path{};
228+
}
199229
bool is_provider_index(std::string_view name) const override {
200230
// xlings' default index and the ones it adds beside it.
201231
return name == "xim" || name == "awesome" || name == "scode" || name == "d2x";

‎tests/unit/test_luban_root.cpp‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// A Luban system says what it is (xlings design 2026-10-21 §3.7, J5): its
2+
// root is declared (os-release ID=luban and /usr/lib/xlings-abi), so its
3+
// compiler's sysroot `/` is not foreign there; and its store is the home its
4+
// root record names, which `[xlings] upstream = ["/xlings"]` means for a
5+
// system made on a host (whose home is not at /xlings).
6+
7+
#include <gtest/gtest.h>
8+
9+
import std;
10+
import mcpp.provider;
11+
import mcpp.provider.select;
12+
import mcpp.fallback.probe_sysroot;
13+
14+
namespace fs = std::filesystem;
15+
16+
namespace {
17+
18+
struct Tree {
19+
fs::path dir;
20+
Tree() {
21+
dir = fs::temp_directory_path() / std::format("mcpp-luban-root-{}-{}", ::testing::UnitTest::GetInstance()->random_seed(),
22+
std::chrono::steady_clock::now().time_since_epoch().count());
23+
fs::create_directories(dir);
24+
}
25+
~Tree() { std::error_code ec; fs::remove_all(dir, ec); }
26+
void write(const fs::path& rel, std::string_view text) const {
27+
fs::create_directories((dir / rel).parent_path());
28+
std::ofstream(dir / rel) << text;
29+
}
30+
};
31+
32+
} // namespace
33+
34+
TEST(LubanRoot, ALubanSystemsRootIsDeclared) {
35+
Tree t;
36+
EXPECT_FALSE(mcpp::provider::layout().declared_system_root(t.dir));
37+
t.write("etc/os-release", "NAME=\"Luban\"\nID=luban\nVERSION_ID=2026.10.22.1\n");
38+
EXPECT_FALSE(mcpp::provider::layout().declared_system_root(t.dir)) << "its os-release alone is not enough";
39+
t.write("usr/lib/xlings-abi/README", "");
40+
EXPECT_TRUE(mcpp::provider::layout().declared_system_root(t.dir));
41+
t.write("etc/os-release", "ID=\"debian\"\n");
42+
EXPECT_FALSE(mcpp::provider::layout().declared_system_root(t.dir));
43+
}
44+
45+
TEST(LubanRoot, ItsSysrootSlashIsNotForeign) {
46+
Tree t;
47+
const fs::path registry = "/home/u/.mcpp/registry", project = "/home/u/work/demo";
48+
EXPECT_TRUE(mcpp::fallback::sysroot_is_foreign("/", registry, project, t.dir)) << "anywhere else, / is nothing declared";
49+
t.write("etc/os-release", "ID=luban\n");
50+
t.write("usr/lib/xlings-abi/README", "");
51+
EXPECT_FALSE(mcpp::fallback::sysroot_is_foreign("/", registry, project, t.dir));
52+
EXPECT_TRUE(mcpp::fallback::sysroot_is_foreign("/opt/other/sysroot", registry, project, t.dir)) << "only its own root";
53+
}
54+
55+
TEST(LubanRoot, ItsStoreIsTheHomeItsRootRecordNames) {
56+
Tree t;
57+
t.write("etc/os-release", "ID=luban\n");
58+
t.write("usr/lib/xlings-abi/README", "");
59+
EXPECT_TRUE(mcpp::provider::layout().system_home(t.dir).empty()) << "no record";
60+
const auto home = t.dir / "host-home";
61+
fs::create_directories(home / "subos" / "box" / "root" / "usr");
62+
t.write("etc/xlings/usr", (home / "subos" / "box" / "root" / "usr").string() + "\n");
63+
EXPECT_EQ(mcpp::provider::layout().system_home(t.dir), home.lexically_normal());
64+
t.write("etc/xlings/usr", "/somewhere/else/usr\n");
65+
EXPECT_TRUE(mcpp::provider::layout().system_home(t.dir).empty()) << "a record of another shape";
66+
}

0 commit comments

Comments
 (0)