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 @@ -4,6 +4,19 @@
> Each `## [<version>]` section is that release's notes. Entries are written in English
> from 2026.9.28.3 on; earlier entries remain as written.

## [2026.10.22.1] - 2026-10-22

### Fixed

- In a Luban system, the compiler's sysroot `/` is the system's own declared
root (`ID=luban` in `/etc/os-release` and `/usr/lib/xlings-abi`): `mcpp self
doctor` no longer reports it as belonging to neither the mcpp home nor the
project.
- In a Luban system, `[xlings] upstream = ["/xlings"]` reads the store the
system's root record names (`/etc/xlings/usr`) when `/xlings` is not there --
a system made on a host keeps its home at the path of the home that made it.
Its gcc and glibc are reused rather than reported unreachable.

## [2026.10.21.1] - 2026-10-21

### Fixed
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.10.21.1"
version = "2026.10.22.1"
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.10.21.1";
inline constexpr std::string_view MCPP_VERSION = "2026.10.22.1";

} // namespace mcpp
4 changes: 4 additions & 0 deletions src/config.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,10 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
dir = std::filesystem::path(h) / (entry.size() > 2 ? entry.substr(2) : std::string{});
dir = dir.lexically_normal();
std::error_code ec;
// A Luban system's /xlings is its store, wherever its xlings put it
// (the home that made it, for a system made on a host).
if (dir == "/xlings" && !std::filesystem::is_directory(dir, ec))
if (auto store = mcpp::provider::layout().system_home("/"); !store.empty()) dir = store;
if (!dir.is_absolute() || !std::filesystem::is_directory(dir, ec)) {
// Fewer packages reused than asked for: said, not hidden.
mcpp::diag::degraded("xlings/upstream", std::format("[xlings] upstream {} is not a directory", entry),
Expand Down
7 changes: 6 additions & 1 deletion src/fallback/probe_sysroot.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,15 @@ bool sysroot_is_owned(std::string_view reportedPath,
//
// Both anchors are required. Registry alone would flag every legitimate
// project-local tree; project alone would flag every payload sysroot.
// In a Luban system (`systemRoot`, "/" but for a test) the sysroot `/` is the
// system's own declared root (xlings design 2026-10-21 §3.7).
bool sysroot_is_foreign(const std::filesystem::path& sysroot,
const std::filesystem::path& registryRoot,
const std::filesystem::path& projectRoot) {
const std::filesystem::path& projectRoot,
const std::filesystem::path& systemRoot = "/") {
if (sysroot.empty()) return false;
// A Luban system's own root: its compiler's `/` is the system it is.
if (sysroot.lexically_normal() == "/" && mcpp::provider::layout().declared_system_root(systemRoot)) return false;
return !path_is_under(sysroot, registryRoot)
&& !path_is_under(sysroot, projectRoot);
}
Expand Down
7 changes: 7 additions & 0 deletions src/provider/provider.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,13 @@ public:
// Whether `name` is one of the provider's own indexes (its default and
// the ones it adds itself), which stay where the provider keeps them.
virtual bool is_provider_index(std::string_view name) const = 0;
// A system the provider made, as `root` (/, but for a test) says of
// itself (xlings design 2026-10-21 §3.7): its compiler's sysroot `/` is
// then the system's own declared root, not a tree nothing declared.
virtual bool declared_system_root(const std::filesystem::path& root) const = 0;
// The home such a system reads its packages from, by its root record;
// empty: not one, or its record says nothing usable.
virtual std::filesystem::path system_home(const std::filesystem::path& root) const = 0;
};

// What a provider says of a package's payload without installing anything
Expand Down
30 changes: 30 additions & 0 deletions src/provider/xlings/provider.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,36 @@ public:
return {{dir, {}, {"xim"}}};
return {};
}
// A Luban system (xlings design 2026-10-21 §3.7): its os-release says
// ID=luban and /usr/lib/xlings-abi is the runtime xlings bound; its root
// record (/etc/xlings/usr) names its generation, <home>/subos/<name>/root/usr
// -- /xlings for one installed on a machine, the path of the home that
// made it for one made on a host.
bool declared_system_root(const std::filesystem::path& root) const override {
std::error_code ec;
if (!std::filesystem::exists(root / "usr/lib/xlings-abi", ec)) return false;
std::ifstream in(root / "etc/os-release");
for (std::string line; std::getline(in, line);) {
if (!line.starts_with("ID=")) continue;
auto id = line.substr(3);
if (id.size() >= 2 && (id.front() == '"' || id.front() == '\'')) id = id.substr(1, id.size() - 2);
return id == "luban";
}
return false;
}
std::filesystem::path system_home(const std::filesystem::path& root) const override {
if (!declared_system_root(root)) return {};
std::ifstream in(root / "etc/xlings/usr");
std::string line;
if (!std::getline(in, line)) return {};
while (!line.empty() && (line.back() == '\r' || line.back() == ' ')) line.pop_back();
const std::filesystem::path usr = std::filesystem::path(line).lexically_normal();
const auto rootDir = usr.parent_path(), instance = rootDir.parent_path(), subos = instance.parent_path();
if (!usr.is_absolute() || usr.filename() != "usr" || rootDir.filename() != "root" || subos.filename() != "subos") return {};
std::error_code ec;
const auto home = subos.parent_path();
return std::filesystem::is_directory(home, ec) ? home : std::filesystem::path{};
}
bool is_provider_index(std::string_view name) const override {
// xlings' default index and the ones it adds beside it.
return name == "xim" || name == "awesome" || name == "scode" || name == "d2x";
Expand Down
66 changes: 66 additions & 0 deletions tests/unit/test_luban_root.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// A Luban system says what it is (xlings design 2026-10-21 §3.7, J5): its
// root is declared (os-release ID=luban and /usr/lib/xlings-abi), so its
// compiler's sysroot `/` is not foreign there; and its store is the home its
// root record names, which `[xlings] upstream = ["/xlings"]` means for a
// system made on a host (whose home is not at /xlings).

#include <gtest/gtest.h>

import std;
import mcpp.provider;
import mcpp.provider.select;
import mcpp.fallback.probe_sysroot;

namespace fs = std::filesystem;

namespace {

struct Tree {
fs::path dir;
Tree() {
dir = fs::temp_directory_path() / std::format("mcpp-luban-root-{}-{}", ::testing::UnitTest::GetInstance()->random_seed(),
std::chrono::steady_clock::now().time_since_epoch().count());
fs::create_directories(dir);
}
~Tree() { std::error_code ec; fs::remove_all(dir, ec); }
void write(const fs::path& rel, std::string_view text) const {
fs::create_directories((dir / rel).parent_path());
std::ofstream(dir / rel) << text;
}
};

} // namespace

TEST(LubanRoot, ALubanSystemsRootIsDeclared) {
Tree t;
EXPECT_FALSE(mcpp::provider::layout().declared_system_root(t.dir));
t.write("etc/os-release", "NAME=\"Luban\"\nID=luban\nVERSION_ID=2026.10.22.1\n");
EXPECT_FALSE(mcpp::provider::layout().declared_system_root(t.dir)) << "its os-release alone is not enough";
t.write("usr/lib/xlings-abi/README", "");
EXPECT_TRUE(mcpp::provider::layout().declared_system_root(t.dir));
t.write("etc/os-release", "ID=\"debian\"\n");
EXPECT_FALSE(mcpp::provider::layout().declared_system_root(t.dir));
}

TEST(LubanRoot, ItsSysrootSlashIsNotForeign) {
Tree t;
const fs::path registry = "/home/u/.mcpp/registry", project = "/home/u/work/demo";
EXPECT_TRUE(mcpp::fallback::sysroot_is_foreign("/", registry, project, t.dir)) << "anywhere else, / is nothing declared";
t.write("etc/os-release", "ID=luban\n");
t.write("usr/lib/xlings-abi/README", "");
EXPECT_FALSE(mcpp::fallback::sysroot_is_foreign("/", registry, project, t.dir));
EXPECT_TRUE(mcpp::fallback::sysroot_is_foreign("/opt/other/sysroot", registry, project, t.dir)) << "only its own root";
}

TEST(LubanRoot, ItsStoreIsTheHomeItsRootRecordNames) {
Tree t;
t.write("etc/os-release", "ID=luban\n");
t.write("usr/lib/xlings-abi/README", "");
EXPECT_TRUE(mcpp::provider::layout().system_home(t.dir).empty()) << "no record";
const auto home = t.dir / "host-home";
fs::create_directories(home / "subos" / "box" / "root" / "usr");
t.write("etc/xlings/usr", (home / "subos" / "box" / "root" / "usr").string() + "\n");
EXPECT_EQ(mcpp::provider::layout().system_home(t.dir), home.lexically_normal());
t.write("etc/xlings/usr", "/somewhere/else/usr\n");
EXPECT_TRUE(mcpp::provider::layout().system_home(t.dir).empty()) << "a record of another shape";
}
Loading