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
62 changes: 57 additions & 5 deletions src/cli.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -4268,6 +4268,53 @@ int cmd_self_version(const mcpplibs::cmdline::ParsedArgs& /*parsed*/) {
return 0;
}

int cmd_self_init(const mcpplibs::cmdline::ParsedArgs& parsed) {
bool force = parsed.is_flag_set("force");

if (force) {
// --force: delete registry (sandbox) + caches and re-bootstrap.
// Preserves: bin/mcpp (self-contained mode), config.toml, log/.
mcpp::ui::info("Resetting", "mcpp sandbox (registry, caches)");

// Resolve MCPP_HOME without running bootstrap (which may fail).
std::filesystem::path home;
if (auto* e = std::getenv("MCPP_HOME"); e && *e)
home = e;
else {
const char* userHome = nullptr;
#if defined(_WIN32)
userHome = std::getenv("USERPROFILE");
#endif
if (!userHome) userHome = std::getenv("HOME");
if (userHome) home = std::filesystem::path(userHome) / ".mcpp";
}
if (!home.empty()) {
std::error_code ec;
std::filesystem::remove_all(home / "registry", ec);
std::filesystem::remove_all(home / "bmi", ec);
std::filesystem::remove_all(home / "cache", ec);
}
}

// (Re-)run the full load_or_init, which does bootstrap.
mcpp::ui::info("Initializing", "mcpp sandbox");
auto cfg = mcpp::config::load_or_init();
if (!cfg) {
mcpp::ui::error(cfg.error().message);
return 1;
}

// Verify result.
auto problem = mcpp::config::check_base_init(*cfg);
if (!problem.empty()) {
mcpp::ui::error(std::format("init incomplete: {}", problem));
return 1;
}

mcpp::ui::status("Ready", "sandbox initialized");
return 0;
}

std::string upper_ascii(std::string s) {
for (char& ch : s) {
if (ch >= 'a' && ch <= 'z') ch = static_cast<char>(ch - 'a' + 'A');
Expand Down Expand Up @@ -4635,6 +4682,10 @@ int run(int argc, char** argv) {
// ─── about mcpp itself ─────────────────────────────────────────
.subcommand(cl::App("self")
.description("Inspect and manage mcpp itself")
.subcommand(cl::App("init")
.description("Initialize or repair mcpp sandbox")
.option(cl::Option("force")
.help("Delete registry and re-initialize from scratch")))
.subcommand(cl::App("doctor")
.description("Diagnose mcpp environment health"))
.subcommand(cl::App("env")
Expand All @@ -4650,11 +4701,12 @@ int run(int argc, char** argv) {
.arg(cl::Arg("code").help("Error code such as E0001").required()))
.action(wrap_rc([&dispatch_sub](const cl::ParsedArgs& p) {
return dispatch_sub("self", p, {
{"doctor", cmd_doctor},
{"env", cmd_env},
{"config", cmd_self_config},
{"version", cmd_self_version},
{"explain", cmd_explain_action},
{"init", cmd_self_init},
{"doctor", cmd_doctor},
{"env", cmd_env},
{"config", cmd_self_config},
{"version", cmd_self_version},
{"explain", cmd_explain_action},
});
})))

Expand Down
65 changes: 65 additions & 0 deletions src/config.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,61 @@ mcpp::xlings::Env make_project_xlings_env(const GlobalConfig& cfg,
return { cfg.xlingsBinary, cfg.xlingsHome(), projectDir / ".mcpp" };
}

// Check that the sandbox bootstrap completed successfully.
// Returns empty string if ok, or a description of what's missing.
std::string check_base_init(const GlobalConfig& cfg) {
auto xlEnv = make_xlings_env(cfg);
struct Check { std::string_view name; std::filesystem::path path; };

auto patchelfBin = mcpp::xlings::paths::xim_tool(xlEnv, "patchelf",
mcpp::xlings::pinned::kPatchelfVersion) / "bin" / "patchelf";
auto ninjaRoot = mcpp::xlings::paths::xim_tool_root(xlEnv, "ninja");
auto ninja_name = std::string("ninja") + std::string(mcpp::platform::exe_suffix);

// Check xlings binary
if (!std::filesystem::exists(cfg.xlingsBinary))
return std::format("xlings binary missing: {}", cfg.xlingsBinary.string());

// Check sandbox init marker
auto marker = xlEnv.home / "subos" / "default" / ".xlings.json";
if (!std::filesystem::exists(marker))
return "sandbox not initialized (missing subos/default/.xlings.json)";

// Check patchelf (Linux only)
#if !defined(__APPLE__) && !defined(_WIN32)
if (!std::filesystem::exists(patchelfBin))
return std::format("patchelf missing: {}", patchelfBin.string());
#endif

// Check ninja
bool ninjaOk = false;
if (std::filesystem::exists(ninjaRoot)) {
std::error_code ec;
for (auto& v : std::filesystem::directory_iterator(ninjaRoot, ec)) {
if (std::filesystem::exists(v.path() / ninja_name)) { ninjaOk = true; break; }
}
}
if (!ninjaOk)
return "ninja missing from sandbox";

return {}; // all ok
}

// Delete the registry directory (sandbox) for a clean re-init.
// Preserves: bin/mcpp (self-contained mode), config.toml, log/.
void reset_registry(const GlobalConfig& cfg) {
std::error_code ec;
auto registry = cfg.xlingsHome();
if (std::filesystem::exists(registry))
std::filesystem::remove_all(registry, ec);

// Also clear BMI and metadata caches (stale after registry reset).
if (std::filesystem::exists(cfg.bmiCacheDir))
std::filesystem::remove_all(cfg.bmiCacheDir, ec);
if (std::filesystem::exists(cfg.metaCacheDir))
std::filesystem::remove_all(cfg.metaCacheDir, ec);
}

// Ensure the project-level .mcpp/ directory exists and contains a
// .xlings.json seeded with the custom (non-builtin, non-local) index
// entries. Returns true if a .mcpp/ directory was created/updated.
Expand Down Expand Up @@ -489,6 +544,16 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
#endif
ensure_sandbox_ninja(cfg, quiet, onBootstrapProgress);

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

return cfg;
}

Expand Down
20 changes: 16 additions & 4 deletions src/xlings.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -833,9 +833,17 @@ void ensure_init(const Env& env, bool quiet) {
void ensure_patchelf(const Env& env, bool quiet,
const BootstrapProgressCallback& cb)
{
auto marker = paths::xim_tool(env, "patchelf", pinned::kPatchelfVersion)
/ "bin" / "patchelf";
if (std::filesystem::exists(marker)) return;
auto toolDir = paths::xim_tool(env, "patchelf", pinned::kPatchelfVersion);
auto binary = toolDir / "bin" / "patchelf";
if (std::filesystem::exists(binary)) return;

// Clean up incomplete installation residue (e.g. from Ctrl+C interrupt).
if (std::filesystem::exists(toolDir)) {
if (!quiet)
print_status("Repairing", "patchelf (incomplete installation, cleaning up)");
std::error_code ec;
std::filesystem::remove_all(toolDir, ec);
}

if (!quiet)
print_status("Bootstrap", "patchelf into mcpp sandbox (one-time)");
Expand All @@ -852,12 +860,16 @@ void ensure_ninja(const Env& env, bool quiet,
const BootstrapProgressCallback& cb)
{
auto root = paths::xim_tool_root(env, "ninja");
auto ninja_name = std::string("ninja") + std::string(mcpp::platform::exe_suffix);
if (std::filesystem::exists(root)) {
std::error_code ec;
auto ninja_name = std::string("ninja") + std::string(mcpp::platform::exe_suffix);
for (auto& v : std::filesystem::directory_iterator(root, ec)) {
if (std::filesystem::exists(v.path() / ninja_name)) return;
}
// Directory exists but no version has a working binary — residue.
if (!quiet)
print_status("Repairing", "ninja (incomplete installation, cleaning up)");
std::filesystem::remove_all(root, ec);
}
if (!quiet)
print_status("Bootstrap", "ninja into mcpp sandbox (one-time)");
Expand Down
Loading