Skip to content

Commit 877b1ac

Browse files
committed
refactor: extract fallback code into src/fallback/ modules
Move fallback implementations from config.cppm, package_fetcher.cppm, and probe.cppm into dedicated modules under src/fallback/: - xpkg_copy.cppm: copy xpkg from global ~/.xlings/ to sandbox - xlings_binary.cppm: xlings binary acquisition chain - config_migration.cppm: legacy index name migration - sysroot_complete.cppm: sysroot header symlink completion - legacy_dirs.cppm: legacy xpkg directory scan - probe_sysroot.cppm: sysroot detection strategies Also refactors resolve_xpkg_path() priority: Before: sandbox → copy → install After: sandbox → install → copy (fallback)
1 parent 2dfcf48 commit 877b1ac

9 files changed

Lines changed: 447 additions & 258 deletions

File tree

src/config.cppm

Lines changed: 7 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import mcpp.pm.index_spec;
2424
import mcpp.xlings;
2525
import mcpp.platform;
2626
import mcpp.log;
27+
import mcpp.fallback.xlings_binary;
28+
import mcpp.fallback.config_migration;
2729

2830
export namespace mcpp::config {
2931

@@ -265,55 +267,7 @@ bool write_default_xlings_json(const std::filesystem::path& path,
265267
return std::filesystem::exists(path);
266268
}
267269

268-
bool replace_all(std::string& text, std::string_view from, std::string_view to) {
269-
bool changed = false;
270-
for (std::size_t pos = 0;
271-
(pos = text.find(from, pos)) != std::string::npos;) {
272-
text.replace(pos, from.size(), to);
273-
pos += to.size();
274-
changed = true;
275-
}
276-
return changed;
277-
}
278-
279-
bool write_text_if_changed(const std::filesystem::path& path,
280-
const std::string& original,
281-
const std::string& updated) {
282-
if (updated == original) return false;
283-
write_file(path, updated);
284-
return true;
285-
}
286-
287-
bool migrate_legacy_config_toml_index_names(const std::filesystem::path& path) {
288-
std::ifstream is(path);
289-
if (!is) return false;
290-
std::stringstream ss;
291-
ss << is.rdbuf();
292-
auto original = ss.str();
293-
auto updated = original;
294-
295-
replace_all(updated, "default = \"mcpp-index\"", "default = \"mcpplibs\"");
296-
replace_all(updated, "[index.repos.\"mcpp-index\"]", "[index.repos.\"mcpplibs\"]");
297-
298-
return write_text_if_changed(path, original, updated);
299-
}
300-
301-
bool migrate_legacy_xlings_json_index_names(const std::filesystem::path& path) {
302-
std::ifstream is(path);
303-
if (!is) return false;
304-
std::stringstream ss;
305-
ss << is.rdbuf();
306-
auto original = ss.str();
307-
auto updated = original;
308-
309-
// Older mcpp sandboxes seeded the package index under the repository
310-
// name. Keep the URL and all xlings state intact; only rename the index
311-
// key so xlings config/list output matches mcpp's default namespace.
312-
replace_all(updated, "\"name\": \"mcpp-index\"", "\"name\": \"mcpplibs\"");
313-
replace_all(updated, "\"name\":\"mcpp-index\"", "\"name\":\"mcpplibs\"");
314-
315-
return write_text_if_changed(path, original, updated);
316-
}
270+
// Migration helpers delegated to mcpp.fallback.config_migration.
317271

318272
void canonicalize_legacy_index_names(GlobalConfig& cfg) {
319273
if (cfg.defaultIndex == "mcpp-index")
@@ -334,65 +288,7 @@ void canonicalize_legacy_index_names(GlobalConfig& cfg) {
334288
cfg.indexRepos = std::move(normalized);
335289
}
336290

337-
// Try to acquire xlings binary. Returns the path if successful.
338-
std::expected<std::filesystem::path, std::string>
339-
acquire_xlings_binary(const std::filesystem::path& destBin, bool quiet)
340-
{
341-
if (std::filesystem::exists(destBin)) return destBin;
342-
343-
std::error_code ec;
344-
std::filesystem::create_directories(destBin.parent_path(), ec);
345-
346-
// 1. Explicit override
347-
if (auto* e = std::getenv("MCPP_VENDORED_XLINGS"); e && *e) {
348-
std::filesystem::path src{e};
349-
if (std::filesystem::exists(src)) {
350-
std::filesystem::copy_file(src, destBin,
351-
std::filesystem::copy_options::overwrite_existing, ec);
352-
if (!ec) {
353-
std::filesystem::permissions(destBin,
354-
std::filesystem::perms::owner_exec
355-
| std::filesystem::perms::group_exec
356-
| std::filesystem::perms::others_exec,
357-
std::filesystem::perm_options::add, ec);
358-
if (!quiet) print_status("Bundled",
359-
std::format("xlings v{} (from MCPP_VENDORED_XLINGS)", kXlingsPinnedVersion));
360-
return destBin;
361-
}
362-
}
363-
}
364-
365-
// 2. Copy from system (`which xlings`)
366-
auto xlings_name = std::string("xlings") + std::string(mcpp::platform::exe_suffix);
367-
auto sysXlings = mcpp::platform::fs::which(xlings_name);
368-
if (sysXlings) {
369-
std::string p = sysXlings->string();
370-
if (!p.empty() && std::filesystem::exists(p)) {
371-
std::filesystem::copy_file(p, destBin,
372-
std::filesystem::copy_options::overwrite_existing, ec);
373-
if (!ec) {
374-
std::filesystem::permissions(destBin,
375-
std::filesystem::perms::owner_exec
376-
| std::filesystem::perms::group_exec
377-
| std::filesystem::perms::others_exec,
378-
std::filesystem::perm_options::add, ec);
379-
if (!quiet) print_status("Bundled",
380-
std::format("xlings (copied from system: {})", p));
381-
return destBin;
382-
}
383-
}
384-
}
385-
386-
// 3. Download from GitHub Release (placeholder — real impl uses curl)
387-
// We delegate to curl/wget in the bash bootstrap; for in-process robustness
388-
// we just instruct the user.
389-
return std::unexpected(std::format(
390-
"xlings binary not found. Either:\n"
391-
" - install via: curl -fsSL https://raw.githubusercontent.com/d2learn/xlings/refs/heads/main/tools/other/quick_install.sh | bash\n"
392-
" - export MCPP_VENDORED_XLINGS=/abs/path/to/xlings\n"
393-
" - set [xlings].binary = \"system\" in {}",
394-
(destBin.parent_path().parent_path() / "config.toml").string()));
395-
}
291+
// Xlings binary acquisition delegated to mcpp.fallback.xlings_binary.
396292

397293
// Run `xlings self init` against the sandbox to create the standard
398294
// directory layout (subos/default/{bin,lib,usr,generations}, data/, config/,
@@ -471,7 +367,7 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
471367
// 3. Seed config.toml if missing
472368
bool fresh_config = !std::filesystem::exists(cfg.configFile);
473369
if (fresh_config) write_default_config_toml(cfg.configFile);
474-
else migrate_legacy_config_toml_index_names(cfg.configFile);
370+
else mcpp::fallback::migrate_config_toml_index_names(cfg.configFile);
475371

476372
// 4. Load config.toml
477373
auto doc = t::parse_file(cfg.configFile);
@@ -556,12 +452,12 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
556452
if (!std::filesystem::exists(xjson)) {
557453
write_default_xlings_json(xjson, cfg.indexRepos);
558454
} else {
559-
migrate_legacy_xlings_json_index_names(xjson);
455+
mcpp::fallback::migrate_xlings_json_index_names(xjson);
560456
}
561457

562458
// 6. Acquire xlings binary if needed
563459
if (cfg.xlingsBinaryMode == "bundled") {
564-
auto xbin = acquire_xlings_binary(cfg.xlingsBinary, quiet);
460+
auto xbin = mcpp::fallback::acquire_xlings_binary(cfg.xlingsBinary, quiet);
565461
if (!xbin) return std::unexpected(ConfigError{xbin.error()});
566462
} else if (cfg.xlingsBinaryMode == "system") {
567463
auto sysPath = mcpp::platform::fs::which(

src/fallback/config_migration.cppm

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// mcpp.fallback.config_migration — legacy index name migration.
2+
//
3+
// Older mcpp sandboxes used "mcpp-index" as the default index name.
4+
// These helpers rename it to "mcpplibs" in config.toml and .xlings.json
5+
// so xlings config/list output matches mcpp's default namespace.
6+
7+
export module mcpp.fallback.config_migration;
8+
9+
import std;
10+
11+
export namespace mcpp::fallback {
12+
13+
// Migrate config.toml: rename "mcpp-index" to "mcpplibs".
14+
// Returns true if the file was modified.
15+
bool migrate_config_toml_index_names(const std::filesystem::path& path);
16+
17+
// Migrate .xlings.json: rename "mcpp-index" to "mcpplibs".
18+
// Returns true if the file was modified.
19+
bool migrate_xlings_json_index_names(const std::filesystem::path& path);
20+
21+
} // namespace mcpp::fallback
22+
23+
namespace mcpp::fallback {
24+
25+
namespace {
26+
27+
bool replace_all(std::string& text, std::string_view from, std::string_view to) {
28+
bool changed = false;
29+
for (std::size_t pos = 0;
30+
(pos = text.find(from, pos)) != std::string::npos;) {
31+
text.replace(pos, from.size(), to);
32+
pos += to.size();
33+
changed = true;
34+
}
35+
return changed;
36+
}
37+
38+
void write_file(const std::filesystem::path& p, std::string_view content) {
39+
std::error_code ec;
40+
std::filesystem::create_directories(p.parent_path(), ec);
41+
std::ofstream os(p);
42+
os << content;
43+
}
44+
45+
bool write_text_if_changed(const std::filesystem::path& path,
46+
const std::string& original,
47+
const std::string& updated) {
48+
if (updated == original) return false;
49+
write_file(path, updated);
50+
return true;
51+
}
52+
53+
} // namespace
54+
55+
bool migrate_config_toml_index_names(const std::filesystem::path& path) {
56+
std::ifstream is(path);
57+
if (!is) return false;
58+
std::stringstream ss;
59+
ss << is.rdbuf();
60+
auto original = ss.str();
61+
auto updated = original;
62+
63+
replace_all(updated, "default = \"mcpp-index\"", "default = \"mcpplibs\"");
64+
replace_all(updated, "[index.repos.\"mcpp-index\"]", "[index.repos.\"mcpplibs\"]");
65+
66+
return write_text_if_changed(path, original, updated);
67+
}
68+
69+
bool migrate_xlings_json_index_names(const std::filesystem::path& path) {
70+
std::ifstream is(path);
71+
if (!is) return false;
72+
std::stringstream ss;
73+
ss << is.rdbuf();
74+
auto original = ss.str();
75+
auto updated = original;
76+
77+
replace_all(updated, "\"name\": \"mcpp-index\"", "\"name\": \"mcpplibs\"");
78+
replace_all(updated, "\"name\":\"mcpp-index\"", "\"name\":\"mcpplibs\"");
79+
80+
return write_text_if_changed(path, original, updated);
81+
}
82+
83+
} // namespace mcpp::fallback

src/fallback/legacy_dirs.cppm

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// mcpp.fallback.legacy_dirs — legacy xpkg directory scan.
2+
//
3+
// Last-resort fallback scan (COMPAT, remove in 1.0.0): walk xpkgs/
4+
// for any directory ending with -x-<qualifiedName> or -x-<shortName>.
5+
6+
export module mcpp.fallback.legacy_dirs;
7+
8+
import std;
9+
10+
export namespace mcpp::fallback {
11+
12+
// Scan the xpkgs base directory for a legacy install directory whose
13+
// name ends with "-x-<qualifiedName>" or "-x-<shortName>".
14+
// Returns the matching directory name (not the full path) if found.
15+
std::optional<std::string>
16+
scan_legacy_install_dirs(const std::filesystem::path& xpkgsBase,
17+
std::string_view qualifiedName,
18+
std::string_view shortName) {
19+
std::error_code ec;
20+
std::string suffix1 = std::format("-x-{}", qualifiedName);
21+
std::string suffix2 = std::format("-x-{}", shortName);
22+
23+
for (auto& entry : std::filesystem::directory_iterator(xpkgsBase, ec)) {
24+
if (!entry.is_directory()) continue;
25+
auto dirname = entry.path().filename().string();
26+
if (dirname.ends_with(suffix1))
27+
return dirname;
28+
if (suffix2 != suffix1 && dirname.ends_with(suffix2))
29+
return dirname;
30+
}
31+
return std::nullopt;
32+
}
33+
34+
} // namespace mcpp::fallback

src/fallback/probe_sysroot.cppm

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// mcpp.fallback.probe_sysroot — sysroot detection strategies.
2+
//
3+
// Three strategies for discovering the sysroot:
4+
// 1. Remap GCC's baked-in build-time sysroot to the local xpkgs layout
5+
// 2. Parse Clang's .cfg file for --sysroot=
6+
// 3. macOS: use xcrun to discover the SDK path
7+
8+
module;
9+
#include <cstdlib>
10+
11+
export module mcpp.fallback.probe_sysroot;
12+
13+
import std;
14+
import mcpp.xlings;
15+
import mcpp.platform;
16+
import mcpp.log;
17+
18+
export namespace mcpp::fallback {
19+
20+
// When GCC reports a sysroot ending in "subos/default" that doesn't exist
21+
// on the current machine (baked build-time path), remap it to the
22+
// equivalent sysroot relative to the compiler's own xpkgs directory.
23+
std::optional<std::filesystem::path>
24+
remap_xlings_baked_sysroot(std::string_view reportedPath,
25+
const std::filesystem::path& compilerBin) {
26+
if (reportedPath.empty()) return std::nullopt;
27+
if (!reportedPath.ends_with("subos/default")) return std::nullopt;
28+
if (std::filesystem::exists(std::string(reportedPath))) return std::nullopt;
29+
30+
if (auto xpkgs = mcpp::xlings::paths::xpkgs_from_compiler(compilerBin)) {
31+
// xpkgs is <registry>/data/xpkgs -> registry = xpkgs/../..
32+
auto registrySysroot = xpkgs->parent_path().parent_path()
33+
/ "subos" / "default";
34+
if (std::filesystem::exists(registrySysroot / "usr" / "include"))
35+
return registrySysroot;
36+
}
37+
return std::nullopt;
38+
}
39+
40+
// Parse a Clang .cfg file alongside the compiler binary for --sysroot=.
41+
std::optional<std::filesystem::path>
42+
parse_clang_cfg_sysroot(const std::filesystem::path& compilerBin) {
43+
auto stem = compilerBin.stem().string();
44+
auto cfgPath = compilerBin.parent_path() / (stem + ".cfg");
45+
if (!std::filesystem::exists(cfgPath)) return std::nullopt;
46+
47+
std::ifstream ifs(cfgPath);
48+
std::string line;
49+
while (std::getline(ifs, line)) {
50+
constexpr std::string_view prefix = "--sysroot=";
51+
if (line.starts_with(prefix)) {
52+
// Trim whitespace
53+
auto val = std::string(line.substr(prefix.size()));
54+
while (!val.empty() && (val.back() == '\n' || val.back() == '\r' || val.back() == ' '))
55+
val.pop_back();
56+
while (!val.empty() && (val.front() == '\n' || val.front() == '\r' || val.front() == ' '))
57+
val.erase(val.begin());
58+
if (!val.empty() && std::filesystem::exists(val))
59+
return std::filesystem::path(val);
60+
}
61+
}
62+
return std::nullopt;
63+
}
64+
65+
// macOS fallback: use xcrun to discover the SDK path.
66+
std::optional<std::filesystem::path>
67+
probe_macos_sdk_sysroot() {
68+
auto sdk = mcpp::platform::macos::sdk_path();
69+
if (sdk) {
70+
mcpp::log::verbose("probe", std::format("sysroot (macOS SDK): {}", sdk->string()));
71+
}
72+
return sdk;
73+
}
74+
75+
} // namespace mcpp::fallback

0 commit comments

Comments
 (0)