Skip to content

Commit f9b22e0

Browse files
authored
refactor: extract xlings abstraction layer + fix version/paths (#30)
* refactor: extract xlings abstraction layer (mcpp.xlings module) Consolidate all xlings (external package manager) interactions into a single leaf module `mcpp.xlings`. This eliminates: - Duplicate NDJSON pipe implementations in config.cppm and package_fetcher.cppm (existed due to cyclic import workaround) - Triplicated xpkgs directory climb logic in flags.cppm, ninja_backend.cppm, and stdmod.cppm - Scattered hardcoded version strings and magic paths The new module provides: - Env struct + pinned version constants - Pure path helpers (xpkgs_base, xim_tool, find_sibling_binary, etc.) - Unified NDJSON event parser and subprocess call - Bootstrap lifecycle (ensure_init, ensure_patchelf, ensure_ninja) - Shell command builders and run_capture utility Net result: +750 lines (new module), -618 lines (removed duplication) = net -528 lines across 7 refactored files. * fix: update xlings version to 0.4.31, remove hardcoded build host path - kXlingsVersion: "0.4.7" → "0.4.31" - Remove kXlingsBuildHostLib constant — the old "/home/xlings/.xlings_data/..." path no longer exists in modern xlings - fixup_gcc_specs now dynamically detects the baked-in lib dir from the specs file instead of matching a hardcoded path. xim bakes the installing user's XLINGS_HOME at install time, so the path varies per machine.
1 parent a5fbcc0 commit f9b22e0

8 files changed

Lines changed: 877 additions & 644 deletions

File tree

src/build/flags.cppm

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export module mcpp.build.flags;
1010

1111
import std;
1212
import mcpp.build.plan;
13+
import mcpp.xlings;
1314

1415
export namespace mcpp::build {
1516

@@ -101,27 +102,9 @@ CompileFlags compute_flags(const BuildPlan& plan) {
101102
bool isMuslTc = plan.toolchain.targetTriple.find("-musl") != std::string::npos;
102103
std::filesystem::path binutilsBin;
103104
if (!isMuslTc) {
104-
auto bp = plan.toolchain.binaryPath;
105-
std::filesystem::path xpkgsDir;
106-
for (auto p = bp.parent_path(); p.has_parent_path() && p != p.root_path();
107-
p = p.parent_path()) {
108-
if (p.filename() == "xpkgs") {
109-
xpkgsDir = p;
110-
break;
111-
}
112-
}
113-
if (!xpkgsDir.empty()) {
114-
auto root = xpkgsDir / "xim-x-binutils";
115-
std::error_code ec;
116-
if (std::filesystem::exists(root, ec)) {
117-
for (auto& v : std::filesystem::directory_iterator(root, ec)) {
118-
auto candidate = v.path() / "bin";
119-
if (std::filesystem::exists(candidate / "ar", ec)) {
120-
binutilsBin = candidate;
121-
break;
122-
}
123-
}
124-
}
105+
if (auto ar = mcpp::xlings::paths::find_sibling_binary(
106+
plan.toolchain.binaryPath, "binutils", "bin/ar")) {
107+
binutilsBin = ar->parent_path(); // bin/ar → bin/
125108
}
126109
}
127110
std::string b_flag;

src/build/ninja_backend.cppm

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import mcpp.build.plan;
2323
import mcpp.build.flags;
2424
import mcpp.build.compile_commands;
2525
import mcpp.dyndep;
26+
import mcpp.xlings;
2627

2728
export namespace mcpp::build {
2829

@@ -456,30 +457,9 @@ std::expected<BuildResult, BuildError> NinjaBackend::build(const BuildPlan& plan
456457
// -B<binutils-bin> flag we emit into cxxflags/ldflags (see
457458
// emit_ninja_string). No PATH injection needed here.
458459
std::filesystem::path ninjaBin;
459-
{
460-
auto bp = plan.toolchain.binaryPath;
461-
std::filesystem::path xpkgsDir;
462-
for (auto p = bp.parent_path(); p.has_parent_path() && p != p.root_path();
463-
p = p.parent_path()) {
464-
if (p.filename() == "xpkgs") {
465-
xpkgsDir = p;
466-
break;
467-
}
468-
}
469-
if (!xpkgsDir.empty()) {
470-
// xim's ninja xpkg puts the binary at <v>/ninja (no bin/ subdir).
471-
auto root = xpkgsDir / "xim-x-ninja";
472-
std::error_code ec;
473-
if (std::filesystem::exists(root, ec)) {
474-
for (auto& v : std::filesystem::directory_iterator(root, ec)) {
475-
auto candidate = v.path() / "ninja";
476-
if (std::filesystem::exists(candidate, ec)) {
477-
ninjaBin = candidate;
478-
break;
479-
}
480-
}
481-
}
482-
}
460+
if (auto nb = mcpp::xlings::paths::find_sibling_binary(
461+
plan.toolchain.binaryPath, "ninja", "ninja")) {
462+
ninjaBin = *nb;
483463
}
484464

485465
std::string ninjaProgram =

src/cli.cppm

Lines changed: 56 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import mcpp.lockfile;
2929
import mcpp.publish.xpkg_emit;
3030
import mcpp.pack;
3131
import mcpp.config;
32+
import mcpp.xlings;
3233
import mcpp.fetcher;
3334
import mcpp.pm.resolver; // PR-R4: extracted from cli.cppm
3435
import mcpp.pm.commands; // PR-R5: cmd_add / cmd_remove / cmd_update live here now
@@ -646,38 +647,46 @@ void patchelf_walk(const std::filesystem::path& dir,
646647
}
647648
}
648649

649-
// xim's gcc tarball is built with an absolute hardcoded path
650-
// `/home/xlings/.xlings_data/subos/linux/lib/...` baked into the gcc specs
651-
// (both as `--dynamic-linker` and `-rpath`). xim's gcc-specs-config.lua
652-
// tries to override these but only matches `/lib64/ld-linux-x86-64.so.2`,
653-
// not the baked-in `/home/xlings/...`, so the override silently no-ops.
650+
// xim bakes the installing user's XLINGS_HOME into gcc specs at install
651+
// time (as `--dynamic-linker` and `-rpath`). When mcpp uses its own
652+
// isolated sandbox (MCPP_HOME/registry/), the baked-in paths point to
653+
// xlings' home, not mcpp's sandbox glibc — binaries would fail to exec.
654654
//
655-
// TODO(xim-pkgindex-upstream): file an issue against xim-pkgindex's
656-
// gcc-specs-config.lua to also match the `/home/xlings/...` baked-in
657-
// path. Once fixed, this whole post-install fixup can be deleted.
658-
//
659-
// Result: gcc compiles user binaries with PT_INTERP pointing at
660-
// `/home/xlings/.xlings_data/...` AND an rpath pointing at the same
661-
// non-existent directory. `elfpatch.auto` (now functional thanks to the
662-
// patchelf bootstrap) only fixes ELF binaries it scans; it doesn't touch
663-
// the gcc specs text file.
664-
//
665-
// Mcpp does the post-install spec rewrite itself:
666-
// - The dynamic-linker path becomes <glibc_lib64>/ld-linux-x86-64.so.2.
667-
// - The rpath becomes <glibc_lib64>:<gcc_lib64> so user binaries can find
668-
// both libc.so.6 (glibc) and libstdc++.so.6 + libgcc_s.so.1 (gcc).
669-
// Idempotent.
655+
// Mcpp does a post-install spec rewrite:
656+
// - Dynamically detects the baked-in lib dir from the specs file
657+
// - Replaces the dynamic-linker path with <glibc_lib64>/ld-linux-x86-64.so.2
658+
// - Replaces the rpath with <glibc_lib64>:<gcc_lib64>
659+
// Idempotent — skips if already pointing at the correct glibc.
660+
// Extract the baked-in lib directory from a gcc specs file by finding
661+
// the dynamic-linker path that ends with `/ld-linux-x86-64.so.2`.
662+
// xim bakes the installing user's XLINGS_HOME into specs at install
663+
// time, so the path varies per machine — we cannot hardcode it.
664+
std::string detect_baked_lib_dir(const std::string& specsContent) {
665+
constexpr std::string_view kLoader = "/ld-linux-x86-64.so.2";
666+
auto pos = specsContent.find(kLoader);
667+
if (pos == std::string::npos) return "";
668+
// Walk backwards to find start of the absolute path
669+
auto start = pos;
670+
while (start > 0 && specsContent[start - 1] != ' '
671+
&& specsContent[start - 1] != ':'
672+
&& specsContent[start - 1] != ';'
673+
&& specsContent[start - 1] != '\n') {
674+
--start;
675+
}
676+
auto dir = specsContent.substr(start, pos - start);
677+
// Sanity: must be absolute
678+
if (dir.empty() || dir[0] != '/') return "";
679+
// Skip if it already points to the target glibc (no fixup needed)
680+
return dir;
681+
}
682+
670683
void fixup_gcc_specs(const std::filesystem::path& gccPkgRoot,
671684
const std::filesystem::path& glibcLibDir,
672685
const std::filesystem::path& gccLibDir)
673686
{
674687
auto specsParent = gccPkgRoot / "lib" / "gcc" / "x86_64-linux-gnu";
675688
if (!std::filesystem::exists(specsParent)) return;
676689

677-
constexpr std::string_view kBakedDir =
678-
"/home/xlings/.xlings_data/subos/linux/lib";
679-
auto kBakedLoader = std::format("{}/ld-linux-x86-64.so.2", kBakedDir);
680-
681690
auto loaderReplacement = (glibcLibDir / "ld-linux-x86-64.so.2").string();
682691
auto rpathReplacement = std::format("{}:{}",
683692
glibcLibDir.string(),
@@ -701,12 +710,17 @@ void fixup_gcc_specs(const std::filesystem::path& gccPkgRoot,
701710
std::stringstream ss; ss << is.rdbuf();
702711
std::string content = ss.str();
703712

704-
if (content.find(kBakedDir) == std::string::npos) continue;
713+
auto bakedDir = detect_baked_lib_dir(content);
714+
if (bakedDir.empty()) continue;
715+
// Already pointing at the right place — no fixup needed.
716+
if (bakedDir == glibcLibDir.string()) continue;
717+
718+
auto bakedLoader = bakedDir + "/ld-linux-x86-64.so.2";
705719

706720
// Order matters: replace the full loader file path first so the
707721
// shorter dir pattern doesn't eat its prefix.
708-
replace_all(content, kBakedLoader, loaderReplacement);
709-
replace_all(content, kBakedDir, rpathReplacement);
722+
replace_all(content, bakedLoader, loaderReplacement);
723+
replace_all(content, bakedDir, rpathReplacement);
710724

711725
std::ofstream os(specs);
712726
os << content;
@@ -2375,10 +2389,8 @@ int cmd_index_update(const mcpplibs::cmdline::ParsedArgs& /*parsed*/) {
23752389
auto cfg = mcpp::config::load_or_init(/*quiet=*/false, make_bootstrap_progress_callback());
23762390
if (!cfg) { mcpp::ui::error(cfg.error().message); return 4; }
23772391
mcpp::ui::status("Updating", "all index repos");
2378-
std::string cmd = std::format(
2379-
"env -u XLINGS_PROJECT_DIR XLINGS_HOME='{}' '{}' update 2>&1",
2380-
cfg->xlingsHome().string(),
2381-
cfg->xlingsBinary.string());
2392+
auto xlEnv = mcpp::config::make_xlings_env(*cfg);
2393+
std::string cmd = mcpp::xlings::build_command_prefix(xlEnv) + " update 2>&1";
23822394
std::array<char, 4096> buf{};
23832395
std::FILE* fp = ::popen(cmd.c_str(), "r");
23842396
if (!fp) { mcpp::ui::error("failed to spawn index updater"); return 1; }
@@ -2506,19 +2518,12 @@ int cmd_test(const mcpplibs::cmdline::ParsedArgs& /*parsed*/,
25062518
// visible to test binaries that shell out to them. The
25072519
// toolchain binary's path encodes the registry root — derive it.
25082520
std::string pathPrefix;
2509-
{
2510-
auto tcBin = ctx->tc.binaryPath;
2511-
// tc binary at <registry>/data/xpkgs/xim-x-*/ver/bin/g++
2512-
// Climb to <registry> = .../(xpkgs)/../..
2513-
for (auto p = tcBin; !p.empty() && p != p.root_path(); p = p.parent_path()) {
2514-
if (p.filename() == "xpkgs") {
2515-
auto registryDir = p.parent_path().parent_path();
2516-
auto sandboxBin = registryDir / "subos" / "default" / "bin";
2517-
if (std::filesystem::exists(sandboxBin))
2518-
pathPrefix = std::format("PATH='{}':\"$PATH\" ", sandboxBin.string());
2519-
break;
2520-
}
2521-
}
2521+
if (auto xpkgs = mcpp::xlings::paths::xpkgs_from_compiler(ctx->tc.binaryPath)) {
2522+
// xpkgs is <registry>/data/xpkgs → registry = xpkgs/../..
2523+
auto registryDir = xpkgs->parent_path().parent_path();
2524+
auto sandboxBin = registryDir / "subos" / "default" / "bin";
2525+
if (std::filesystem::exists(sandboxBin))
2526+
pathPrefix = std::format("PATH='{}':\"$PATH\" ", sandboxBin.string());
25222527
}
25232528

25242529
std::string cmd = std::format("{}'{}'", pathPrefix, exe.string());
@@ -2874,6 +2879,7 @@ int cmd_toolchain(const mcpplibs::cmdline::ParsedArgs& parsed) {
28742879
if (!cfg) { mcpp::ui::error(cfg.error().message); return 4; }
28752880

28762881
auto pkgsDir = cfg->xlingsHome() / "data" / "xpkgs";
2882+
auto xlEnv = mcpp::config::make_xlings_env(*cfg);
28772883

28782884
if (subname == "list") {
28792885
// `gcc 15.1.0-musl` and `musl-gcc@15.1.0` describe the same xpkg
@@ -3062,7 +3068,7 @@ int cmd_toolchain(const mcpplibs::cmdline::ParsedArgs& parsed) {
30623068
// `<root>/x86_64-linux-musl/{include,lib}` and doesn't link against
30633069
// xim:glibc, so this fixup is both unnecessary and harmful for it.
30643070
if (compiler == "gcc" && !isMusl) {
3065-
auto glibcRoot = pkgsDir / "xim-x-glibc";
3071+
auto glibcRoot = mcpp::xlings::paths::xim_tool_root(xlEnv, "glibc");
30663072
std::filesystem::path glibcLibDir;
30673073
if (std::filesystem::exists(glibcRoot)) {
30683074
for (auto& v : std::filesystem::directory_iterator(glibcRoot)) {
@@ -3074,7 +3080,8 @@ int cmd_toolchain(const mcpplibs::cmdline::ParsedArgs& parsed) {
30743080
}
30753081
}
30763082
auto gccLibDir = payload->root / "lib64";
3077-
auto patchelfBin = pkgsDir / "xim-x-patchelf" / "0.18.0" / "bin" / "patchelf";
3083+
auto patchelfBin = mcpp::xlings::paths::xim_tool(xlEnv, "patchelf",
3084+
mcpp::xlings::pinned::kPatchelfVersion) / "bin" / "patchelf";
30783085

30793086
if (!glibcLibDir.empty() && std::filesystem::exists(gccLibDir)
30803087
&& std::filesystem::exists(patchelfBin))
@@ -3085,7 +3092,7 @@ int cmd_toolchain(const mcpplibs::cmdline::ParsedArgs& parsed) {
30853092

30863093
// (1) patchelf walk: rewrite PT_INTERP + RUNPATH for binutils
30873094
// and gcc xpkgs so they're self-contained in sandbox.
3088-
auto binutilsRoot = pkgsDir / "xim-x-binutils";
3095+
auto binutilsRoot = mcpp::xlings::paths::xim_tool_root(xlEnv, "binutils");
30893096
if (std::filesystem::exists(binutilsRoot)) {
30903097
for (auto& v : std::filesystem::directory_iterator(binutilsRoot))
30913098
patchelf_walk(v.path(), loader, rpath, patchelfBin);
@@ -3150,7 +3157,7 @@ int cmd_toolchain(const mcpplibs::cmdline::ParsedArgs& parsed) {
31503157
compiler == "musl-gcc" ? "musl-gcc" : compiler, ximVersion)
31513158
: std::format("{}@{}", compiler, ximVersion);
31523159

3153-
auto installDir = pkgsDir / std::format("xim-x-{}", ximName) / ximVersion;
3160+
auto installDir = mcpp::xlings::paths::xim_tool(xlEnv, ximName, ximVersion);
31543161
if (!std::filesystem::exists(installDir)) {
31553162
mcpp::ui::error(std::format(
31563163
"{} is not installed. Run `mcpp toolchain install {} {}` first.",
@@ -3177,7 +3184,7 @@ int cmd_toolchain(const mcpplibs::cmdline::ParsedArgs& parsed) {
31773184
}
31783185
std::string compiler = spec.substr(0, at);
31793186
std::string version = spec.substr(at + 1);
3180-
auto installDir = pkgsDir / std::format("xim-x-{}", compiler) / version;
3187+
auto installDir = mcpp::xlings::paths::xim_tool(xlEnv, compiler, version);
31813188
std::error_code ec;
31823189
if (!std::filesystem::exists(installDir, ec)) {
31833190
mcpp::ui::error(std::format("{} is not installed", spec));

0 commit comments

Comments
 (0)