Skip to content

Commit dcbb06a

Browse files
authored
feat: mcpp self init + ensure_base_init_ok + bootstrap residue cleanup (#72)
1. `mcpp self init` — repair mode: re-runs bootstrap, fixes incomplete state 2. `mcpp self init --force` — reset registry + caches, re-bootstrap from scratch (preserves bin/mcpp, config.toml, log/) 3. ensure_base_init_ok() at end of load_or_init(): - Verifies xlings binary, sandbox marker, patchelf, ninja all present - 4 stat() calls, <0.1ms overhead - On failure: clear error + hint to run `mcpp self init --force` 4. Bootstrap residue cleanup in ensure_patchelf/ensure_ninja: - Detects half-extracted xpkg dirs (exist but binary missing) - Removes residue before re-installing (fixes Ctrl+C interrupt issue)
1 parent 624ce0e commit dcbb06a

3 files changed

Lines changed: 138 additions & 9 deletions

File tree

src/cli.cppm

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4268,6 +4268,53 @@ int cmd_self_version(const mcpplibs::cmdline::ParsedArgs& /*parsed*/) {
42684268
return 0;
42694269
}
42704270

4271+
int cmd_self_init(const mcpplibs::cmdline::ParsedArgs& parsed) {
4272+
bool force = parsed.is_flag_set("force");
4273+
4274+
if (force) {
4275+
// --force: delete registry (sandbox) + caches and re-bootstrap.
4276+
// Preserves: bin/mcpp (self-contained mode), config.toml, log/.
4277+
mcpp::ui::info("Resetting", "mcpp sandbox (registry, caches)");
4278+
4279+
// Resolve MCPP_HOME without running bootstrap (which may fail).
4280+
std::filesystem::path home;
4281+
if (auto* e = std::getenv("MCPP_HOME"); e && *e)
4282+
home = e;
4283+
else {
4284+
const char* userHome = nullptr;
4285+
#if defined(_WIN32)
4286+
userHome = std::getenv("USERPROFILE");
4287+
#endif
4288+
if (!userHome) userHome = std::getenv("HOME");
4289+
if (userHome) home = std::filesystem::path(userHome) / ".mcpp";
4290+
}
4291+
if (!home.empty()) {
4292+
std::error_code ec;
4293+
std::filesystem::remove_all(home / "registry", ec);
4294+
std::filesystem::remove_all(home / "bmi", ec);
4295+
std::filesystem::remove_all(home / "cache", ec);
4296+
}
4297+
}
4298+
4299+
// (Re-)run the full load_or_init, which does bootstrap.
4300+
mcpp::ui::info("Initializing", "mcpp sandbox");
4301+
auto cfg = mcpp::config::load_or_init();
4302+
if (!cfg) {
4303+
mcpp::ui::error(cfg.error().message);
4304+
return 1;
4305+
}
4306+
4307+
// Verify result.
4308+
auto problem = mcpp::config::check_base_init(*cfg);
4309+
if (!problem.empty()) {
4310+
mcpp::ui::error(std::format("init incomplete: {}", problem));
4311+
return 1;
4312+
}
4313+
4314+
mcpp::ui::status("Ready", "sandbox initialized");
4315+
return 0;
4316+
}
4317+
42714318
std::string upper_ascii(std::string s) {
42724319
for (char& ch : s) {
42734320
if (ch >= 'a' && ch <= 'z') ch = static_cast<char>(ch - 'a' + 'A');
@@ -4635,6 +4682,10 @@ int run(int argc, char** argv) {
46354682
// ─── about mcpp itself ─────────────────────────────────────────
46364683
.subcommand(cl::App("self")
46374684
.description("Inspect and manage mcpp itself")
4685+
.subcommand(cl::App("init")
4686+
.description("Initialize or repair mcpp sandbox")
4687+
.option(cl::Option("force")
4688+
.help("Delete registry and re-initialize from scratch")))
46384689
.subcommand(cl::App("doctor")
46394690
.description("Diagnose mcpp environment health"))
46404691
.subcommand(cl::App("env")
@@ -4650,11 +4701,12 @@ int run(int argc, char** argv) {
46504701
.arg(cl::Arg("code").help("Error code such as E0001").required()))
46514702
.action(wrap_rc([&dispatch_sub](const cl::ParsedArgs& p) {
46524703
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},
4704+
{"init", cmd_self_init},
4705+
{"doctor", cmd_doctor},
4706+
{"env", cmd_env},
4707+
{"config", cmd_self_config},
4708+
{"version", cmd_self_version},
4709+
{"explain", cmd_explain_action},
46584710
});
46594711
})))
46604712

src/config.cppm

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,61 @@ mcpp::xlings::Env make_project_xlings_env(const GlobalConfig& cfg,
102102
return { cfg.xlingsBinary, cfg.xlingsHome(), projectDir / ".mcpp" };
103103
}
104104

105+
// Check that the sandbox bootstrap completed successfully.
106+
// Returns empty string if ok, or a description of what's missing.
107+
std::string check_base_init(const GlobalConfig& cfg) {
108+
auto xlEnv = make_xlings_env(cfg);
109+
struct Check { std::string_view name; std::filesystem::path path; };
110+
111+
auto patchelfBin = mcpp::xlings::paths::xim_tool(xlEnv, "patchelf",
112+
mcpp::xlings::pinned::kPatchelfVersion) / "bin" / "patchelf";
113+
auto ninjaRoot = mcpp::xlings::paths::xim_tool_root(xlEnv, "ninja");
114+
auto ninja_name = std::string("ninja") + std::string(mcpp::platform::exe_suffix);
115+
116+
// Check xlings binary
117+
if (!std::filesystem::exists(cfg.xlingsBinary))
118+
return std::format("xlings binary missing: {}", cfg.xlingsBinary.string());
119+
120+
// Check sandbox init marker
121+
auto marker = xlEnv.home / "subos" / "default" / ".xlings.json";
122+
if (!std::filesystem::exists(marker))
123+
return "sandbox not initialized (missing subos/default/.xlings.json)";
124+
125+
// Check patchelf (Linux only)
126+
#if !defined(__APPLE__) && !defined(_WIN32)
127+
if (!std::filesystem::exists(patchelfBin))
128+
return std::format("patchelf missing: {}", patchelfBin.string());
129+
#endif
130+
131+
// Check ninja
132+
bool ninjaOk = false;
133+
if (std::filesystem::exists(ninjaRoot)) {
134+
std::error_code ec;
135+
for (auto& v : std::filesystem::directory_iterator(ninjaRoot, ec)) {
136+
if (std::filesystem::exists(v.path() / ninja_name)) { ninjaOk = true; break; }
137+
}
138+
}
139+
if (!ninjaOk)
140+
return "ninja missing from sandbox";
141+
142+
return {}; // all ok
143+
}
144+
145+
// Delete the registry directory (sandbox) for a clean re-init.
146+
// Preserves: bin/mcpp (self-contained mode), config.toml, log/.
147+
void reset_registry(const GlobalConfig& cfg) {
148+
std::error_code ec;
149+
auto registry = cfg.xlingsHome();
150+
if (std::filesystem::exists(registry))
151+
std::filesystem::remove_all(registry, ec);
152+
153+
// Also clear BMI and metadata caches (stale after registry reset).
154+
if (std::filesystem::exists(cfg.bmiCacheDir))
155+
std::filesystem::remove_all(cfg.bmiCacheDir, ec);
156+
if (std::filesystem::exists(cfg.metaCacheDir))
157+
std::filesystem::remove_all(cfg.metaCacheDir, ec);
158+
}
159+
105160
// Ensure the project-level .mcpp/ directory exists and contains a
106161
// .xlings.json seeded with the custom (non-builtin, non-local) index
107162
// entries. Returns true if a .mcpp/ directory was created/updated.
@@ -489,6 +544,16 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
489544
#endif
490545
ensure_sandbox_ninja(cfg, quiet, onBootstrapProgress);
491546

547+
// 8. Verify bootstrap completed. If something is missing (e.g. Ctrl+C
548+
// interrupted a previous bootstrap), report the problem up-front
549+
// rather than letting a cryptic error surface later.
550+
auto initProblem = check_base_init(cfg);
551+
if (!initProblem.empty()) {
552+
return std::unexpected(ConfigError{std::format(
553+
"{}\n hint: run `mcpp self init --force` to reset and re-initialize",
554+
initProblem)});
555+
}
556+
492557
return cfg;
493558
}
494559

src/xlings.cppm

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -833,9 +833,17 @@ void ensure_init(const Env& env, bool quiet) {
833833
void ensure_patchelf(const Env& env, bool quiet,
834834
const BootstrapProgressCallback& cb)
835835
{
836-
auto marker = paths::xim_tool(env, "patchelf", pinned::kPatchelfVersion)
837-
/ "bin" / "patchelf";
838-
if (std::filesystem::exists(marker)) return;
836+
auto toolDir = paths::xim_tool(env, "patchelf", pinned::kPatchelfVersion);
837+
auto binary = toolDir / "bin" / "patchelf";
838+
if (std::filesystem::exists(binary)) return;
839+
840+
// Clean up incomplete installation residue (e.g. from Ctrl+C interrupt).
841+
if (std::filesystem::exists(toolDir)) {
842+
if (!quiet)
843+
print_status("Repairing", "patchelf (incomplete installation, cleaning up)");
844+
std::error_code ec;
845+
std::filesystem::remove_all(toolDir, ec);
846+
}
839847

840848
if (!quiet)
841849
print_status("Bootstrap", "patchelf into mcpp sandbox (one-time)");
@@ -852,12 +860,16 @@ void ensure_ninja(const Env& env, bool quiet,
852860
const BootstrapProgressCallback& cb)
853861
{
854862
auto root = paths::xim_tool_root(env, "ninja");
863+
auto ninja_name = std::string("ninja") + std::string(mcpp::platform::exe_suffix);
855864
if (std::filesystem::exists(root)) {
856865
std::error_code ec;
857-
auto ninja_name = std::string("ninja") + std::string(mcpp::platform::exe_suffix);
858866
for (auto& v : std::filesystem::directory_iterator(root, ec)) {
859867
if (std::filesystem::exists(v.path() / ninja_name)) return;
860868
}
869+
// Directory exists but no version has a working binary — residue.
870+
if (!quiet)
871+
print_status("Repairing", "ninja (incomplete installation, cleaning up)");
872+
std::filesystem::remove_all(root, ec);
861873
}
862874
if (!quiet)
863875
print_status("Bootstrap", "ninja into mcpp sandbox (one-time)");

0 commit comments

Comments
 (0)