Skip to content

Commit 0651cc3

Browse files
committed
feat: fallback registry + install-first resolve + mcpp self fallbacks
1. Fallback registry (src/fallback/registry.cppm): - Compile-time registry of all 17 fallback mechanisms in mcpp - Each entry: id, domain, lifecycle (permanent/compat/workaround), removeBy - Single source of truth for auditing and cleanup planning 2. resolve_xpkg_path priority fix (src/pm/package_fetcher.cppm): - Refactored from: sandbox → copy → install (copy short-circuits install) - Refactored to: sandbox → install → copy (install-first, copy is fallback) - Split into resolve_quick() + copy_from_global() for clarity - Fixes: install path was never exercised when ~/.xlings/ had the package 3. mcpp self fallbacks command (src/cli.cppm): - Lists all registered fallbacks grouped by lifecycle - Shows workarounds (need upstream fix), compat (remove by 1.0), permanent
1 parent 2dfcf48 commit 0651cc3

3 files changed

Lines changed: 296 additions & 79 deletions

File tree

src/cli.cppm

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import mcpp.pm.compat; // 0.0.6: namespace field + dotted-name compat shims
4242
import mcpp.pm.dep_spec;
4343
import mcpp.ui;
4444
import mcpp.log;
45+
import mcpp.fallback.registry;
4546
import mcpp.bmi_cache;
4647
import mcpp.dyndep;
4748
import mcpp.version_req; // SemVer constraint resolution
@@ -4268,6 +4269,40 @@ int cmd_self_version(const mcpplibs::cmdline::ParsedArgs& /*parsed*/) {
42684269
return 0;
42694270
}
42704271

4272+
int cmd_self_fallbacks(const mcpplibs::cmdline::ParsedArgs& /*parsed*/) {
4273+
namespace fb = mcpp::fallback;
4274+
4275+
// Group by lifecycle
4276+
std::vector<const fb::Entry*> workarounds, compats, permanents;
4277+
for (std::size_t i = 0; i < fb::kEntryCount; ++i) {
4278+
auto* e = &fb::kEntries[i];
4279+
switch (e->lifecycle) {
4280+
case fb::Lifecycle::workaround: workarounds.push_back(e); break;
4281+
case fb::Lifecycle::compat: compats.push_back(e); break;
4282+
case fb::Lifecycle::permanent: permanents.push_back(e); break;
4283+
}
4284+
}
4285+
4286+
std::println("Fallback Registry ({} entries)\n", fb::kEntryCount);
4287+
4288+
auto print_group = [](std::string_view title, const std::vector<const fb::Entry*>& entries) {
4289+
if (entries.empty()) return;
4290+
std::println("{}:", title);
4291+
for (auto* e : entries) {
4292+
std::print(" {:<40s} {}", e->id, e->description);
4293+
if (!e->removeBy.empty())
4294+
std::print(" [remove by {}]", e->removeBy);
4295+
std::println("");
4296+
}
4297+
std::println("");
4298+
};
4299+
4300+
print_group("WORKAROUNDS (need upstream fix)", workarounds);
4301+
print_group("COMPAT (backward compatibility)", compats);
4302+
print_group("PERMANENT (architecturally required)", permanents);
4303+
return 0;
4304+
}
4305+
42714306
std::string upper_ascii(std::string s) {
42724307
for (char& ch : s) {
42734308
if (ch >= 'a' && ch <= 'z') ch = static_cast<char>(ch - 'a' + 'A');
@@ -4648,13 +4683,16 @@ int run(int argc, char** argv) {
46484683
.subcommand(cl::App("explain")
46494684
.description("Show extended description for an error code")
46504685
.arg(cl::Arg("code").help("Error code such as E0001").required()))
4686+
.subcommand(cl::App("fallbacks")
4687+
.description("List all registered fallback mechanisms"))
46514688
.action(wrap_rc([&dispatch_sub](const cl::ParsedArgs& p) {
46524689
return dispatch_sub("self", p, {
4653-
{"doctor", cmd_doctor},
4654-
{"env", cmd_env},
4655-
{"config", cmd_self_config},
4656-
{"version", cmd_self_version},
4657-
{"explain", cmd_explain_action},
4690+
{"doctor", cmd_doctor},
4691+
{"env", cmd_env},
4692+
{"config", cmd_self_config},
4693+
{"version", cmd_self_version},
4694+
{"explain", cmd_explain_action},
4695+
{"fallbacks", cmd_self_fallbacks},
46584696
});
46594697
})))
46604698

src/fallback/registry.cppm

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// mcpp.fallback.registry — compile-time fallback metadata registry.
2+
//
3+
// Every fallback/workaround in mcpp is registered here with its lifecycle,
4+
// domain, and removal plan. This provides a single source of truth for
5+
// auditing, logging, and the `mcpp self fallbacks` command.
6+
//
7+
// Usage:
8+
// auto* fb = mcpp::fallback::find("xpkg.copy_from_global");
9+
// mcpp::log::verbose("fetcher", std::format("[fallback:{}] {}", fb->id, fb->description));
10+
11+
export module mcpp.fallback.registry;
12+
13+
import std;
14+
15+
export namespace mcpp::fallback {
16+
17+
enum class Lifecycle {
18+
permanent, // architecturally required (multi-platform, retry logic)
19+
compat, // backward compatibility, remove by specified version
20+
workaround, // works around external bug, remove when upstream fixed
21+
};
22+
23+
struct Entry {
24+
std::string_view id; // unique key, e.g. "xpkg.copy_from_global"
25+
std::string_view domain; // "package" | "config" | "toolchain" | "build" | "dependency" | "manifest"
26+
std::string_view description; // one-line human-readable description
27+
Lifecycle lifecycle;
28+
std::string_view removeBy; // version string "1.0" or "" (permanent/workaround)
29+
std::string_view upstreamIssue; // "xlings#123" or "" or description of upstream issue
30+
};
31+
32+
// ─── Registry entries ────────────────────────────────────────────────
33+
34+
inline constexpr Entry kEntries[] = {
35+
36+
// ─── Package fetch & install ─────────────────────────────────────
37+
38+
{ .id = "xpkg.copy_from_global",
39+
.domain = "package",
40+
.description = "copy xpkg from ~/.xlings/ when sandbox install fails",
41+
.lifecycle = Lifecycle::workaround,
42+
.removeBy = {},
43+
.upstreamIssue = "xlings XLINGS_HOME propagation / NDJSON large-package install",
44+
},
45+
{ .id = "xpkg.install_direct_before_ndjson",
46+
.domain = "package",
47+
.description = "try direct xlings install before NDJSON interface for large packages",
48+
.lifecycle = Lifecycle::workaround,
49+
.removeBy = {},
50+
.upstreamIssue = "xlings NDJSON interface unreliable for large packages",
51+
},
52+
{ .id = "xpkg.install_dir_scan",
53+
.domain = "package",
54+
.description = "last-resort scan xpkgs/ directory for matching package dir",
55+
.lifecycle = Lifecycle::compat,
56+
.removeBy = "1.0",
57+
},
58+
{ .id = "xpkg.lua_candidates",
59+
.domain = "package",
60+
.description = "multi-candidate xpkg .lua file lookup for legacy naming",
61+
.lifecycle = Lifecycle::compat,
62+
.removeBy = "1.0",
63+
},
64+
65+
// ─── xlings binary acquisition ───────────────────────────────────
66+
67+
{ .id = "xlings_binary.vendored_env",
68+
.domain = "config",
69+
.description = "MCPP_VENDORED_XLINGS env override (Windows runtime workaround)",
70+
.lifecycle = Lifecycle::workaround,
71+
.removeBy = {},
72+
.upstreamIssue = "Windows xlings binary may lack runtime after copy",
73+
},
74+
{ .id = "xlings_binary.system_which",
75+
.domain = "config",
76+
.description = "find xlings in PATH when bundled binary unavailable",
77+
.lifecycle = Lifecycle::permanent,
78+
},
79+
80+
// ─── Toolchain probing ───────────────────────────────────────────
81+
82+
{ .id = "probe.sysroot_compiler",
83+
.domain = "toolchain",
84+
.description = "gcc -print-sysroot direct probe",
85+
.lifecycle = Lifecycle::permanent,
86+
},
87+
{ .id = "probe.sysroot_cfg",
88+
.domain = "toolchain",
89+
.description = "parse clang++.cfg for --sysroot line",
90+
.lifecycle = Lifecycle::permanent,
91+
},
92+
{ .id = "probe.sysroot_xcrun",
93+
.domain = "toolchain",
94+
.description = "macOS xcrun --show-sdk-path fallback",
95+
.lifecycle = Lifecycle::permanent,
96+
},
97+
{ .id = "probe.sysroot_xlings_remap",
98+
.domain = "toolchain",
99+
.description = "remap xlings build-time sysroot path to mcpp registry",
100+
.lifecycle = Lifecycle::workaround,
101+
.removeBy = {},
102+
.upstreamIssue = "xlings bakes build-host absolute path into gcc specs",
103+
},
104+
{ .id = "sysroot.symlink_kernel_headers",
105+
.domain = "toolchain",
106+
.description = "symlink linux-headers xpkg into sysroot when missing",
107+
.lifecycle = Lifecycle::workaround,
108+
.removeBy = {},
109+
.upstreamIssue = "xlings sysroot may lack kernel headers after init",
110+
},
111+
{ .id = "sysroot.symlink_glibc_headers",
112+
.domain = "toolchain",
113+
.description = "symlink glibc xpkg headers into sysroot when missing",
114+
.lifecycle = Lifecycle::workaround,
115+
.removeBy = {},
116+
.upstreamIssue = "xlings sysroot may lack glibc headers after init",
117+
},
118+
119+
// ─── Build system ────────────────────────────────────────────────
120+
121+
{ .id = "build.ninja_incremental_retry",
122+
.domain = "build",
123+
.description = "full rebuild when incremental ninja build fails",
124+
.lifecycle = Lifecycle::permanent,
125+
},
126+
{ .id = "build.dyndep_opt_out",
127+
.domain = "build",
128+
.description = "MCPP_NINJA_DYNDEP=0 disables P1689 dynamic deps",
129+
.lifecycle = Lifecycle::permanent,
130+
},
131+
132+
// ─── Dependency resolution ───────────────────────────────────────
133+
134+
{ .id = "deps.multi_version_mangle",
135+
.domain = "dependency",
136+
.description = "cross-major version coexistence via module name mangling",
137+
.lifecycle = Lifecycle::permanent,
138+
},
139+
140+
// ─── Backward compatibility / migration ──────────────────────────
141+
142+
{ .id = "compat.dotted_package_name",
143+
.domain = "manifest",
144+
.description = "split legacy 'ns.name' dotted package names",
145+
.lifecycle = Lifecycle::compat,
146+
.removeBy = "1.0",
147+
},
148+
{ .id = "compat.config_index_migration",
149+
.domain = "config",
150+
.description = "rename mcpp-index to mcpplibs in config files",
151+
.lifecycle = Lifecycle::compat,
152+
.removeBy = "1.0",
153+
},
154+
};
155+
156+
inline constexpr std::size_t kEntryCount = sizeof(kEntries) / sizeof(kEntries[0]);
157+
158+
// ─── Query API ───────────────────────────────────────────────────────
159+
160+
constexpr const Entry* find(std::string_view id) {
161+
for (auto& e : kEntries)
162+
if (e.id == id) return &e;
163+
return nullptr;
164+
}
165+
166+
inline constexpr const char* lifecycle_str(Lifecycle l) {
167+
switch (l) {
168+
case Lifecycle::permanent: return "permanent";
169+
case Lifecycle::compat: return "compat";
170+
case Lifecycle::workaround: return "workaround";
171+
}
172+
return "unknown";
173+
}
174+
175+
} // namespace mcpp::fallback

0 commit comments

Comments
 (0)