Skip to content

Commit 074bcdd

Browse files
committed
feat: mcpp why / resolve --explain + capability-level doctor
mcpp why [toolchain|runtime|deps] (alias: resolve --explain) explains the resolved toolchain (incl. abi), the runtime library dirs baked into RUNPATH (surfacing the compat.glx-runtime host-GL closure), and locked deps — so defaults are inspectable, not magic (I4). self doctor: add a runtime-capabilities section probing x11/wayland display and the host GLVND opengl.glx.driver (libGLX + vendor), reporting the provider — capability-level, not just env health.
1 parent 6e79919 commit 074bcdd

1 file changed

Lines changed: 110 additions & 2 deletions

File tree

src/cli.cppm

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4277,13 +4277,113 @@ int cmd_doctor(const mcpplibs::cmdline::ParsedArgs& /*parsed*/) {
42774277
ok(std::format("BMI cache size = {}", human_bytes(sz)));
42784278
}
42794279

4280+
mcpp::ui::status("Checking", "runtime capabilities");
4281+
{
4282+
#if defined(__APPLE__) || defined(_WIN32)
4283+
ok("host GL/windowing provided by platform framework");
4284+
#else
4285+
if (const char* d = std::getenv("DISPLAY"); d && *d)
4286+
ok(std::format("x11.display: ok ($DISPLAY={})", d));
4287+
else if (const char* w = std::getenv("WAYLAND_DISPLAY"); w && *w)
4288+
ok(std::format("wayland.display: ok ($WAYLAND_DISPLAY={})", w));
4289+
else
4290+
warn("display: none — windowed apps need $DISPLAY or $WAYLAND_DISPLAY");
4291+
4292+
const char* gldirs[] = {"/usr/lib/x86_64-linux-gnu", "/lib/x86_64-linux-gnu",
4293+
"/usr/lib64", "/usr/lib"};
4294+
auto find_lib = [&](std::string_view prefix) -> std::string {
4295+
for (auto* dir : gldirs) {
4296+
std::error_code ec;
4297+
if (!std::filesystem::exists(dir, ec)) continue;
4298+
for (auto& e : std::filesystem::directory_iterator(dir, ec)) {
4299+
auto fn = e.path().filename().string();
4300+
if (fn.rfind(prefix, 0) == 0) return e.path().string();
4301+
}
4302+
}
4303+
return {};
4304+
};
4305+
auto glx = find_lib("libGLX.so");
4306+
auto gl = find_lib("libGL.so");
4307+
auto vnd = find_lib("libGLX_nvidia.so");
4308+
if (vnd.empty()) vnd = find_lib("libGLX_mesa.so");
4309+
if (!glx.empty() && !gl.empty()) {
4310+
ok(std::format("opengl.glx.driver: ok (provider compat.glx-runtime; {}, {})",
4311+
std::filesystem::path(glx).filename().string(),
4312+
vnd.empty() ? "no GLVND vendor" : std::filesystem::path(vnd).filename().string()));
4313+
} else {
4314+
warn("opengl.glx.driver: host libGL/libGLX not found — `mcpp run` of GL apps may fail");
4315+
}
4316+
#endif
4317+
}
4318+
42804319
std::println("");
42814320
if (errors) std::println("Doctor result: {} errors, {} warnings", errors, warns);
42824321
else if (warns) std::println("Doctor result: {} warnings", warns);
42834322
else std::println("Doctor result: all checks passed");
42844323
return errors ? 2 : (warns ? 1 : 0);
42854324
}
42864325

4326+
// `mcpp why [topic]` / `mcpp resolve --explain` — explain how the toolchain,
4327+
// runtime closure, and dependencies were resolved (I4: defaults are not magic).
4328+
int cmd_why(const mcpplibs::cmdline::ParsedArgs& parsed) {
4329+
std::string topic = parsed.positional(0);
4330+
const bool all = topic.empty() || topic == "all";
4331+
4332+
auto ctx = prepare_build(/*print_fingerprint=*/false);
4333+
if (!ctx) { std::println(stderr, "error: {}", ctx.error()); return 2; }
4334+
auto& tc = ctx->tc;
4335+
auto& plan = ctx->plan;
4336+
4337+
auto abi_of = [](const mcpp::toolchain::Toolchain& t) -> std::string {
4338+
if (t.targetTriple.find("musl") != std::string::npos) return "musl";
4339+
if (t.stdlibId == "libc++") return "libc++";
4340+
if (t.compiler == mcpp::toolchain::CompilerId::MSVC) return "msvc";
4341+
return "glibc";
4342+
};
4343+
4344+
if (all || topic == "toolchain") {
4345+
std::println("toolchain: {}", tc.label());
4346+
std::println(" abi={} stdlib={} triple={}", abi_of(tc), tc.stdlibId, tc.targetTriple);
4347+
std::println(" reason: [toolchain] in mcpp.toml if set, else platform-native default");
4348+
}
4349+
if (all || topic == "runtime") {
4350+
std::println("runtime library dirs (baked into binary RUNPATH):");
4351+
if (plan.runtimeLibraryDirs.empty()) std::println(" (none)");
4352+
for (auto& d : plan.runtimeLibraryDirs) {
4353+
auto s = d.string();
4354+
std::string note;
4355+
if (s.find("glx_runtime") != std::string::npos)
4356+
note = " <- host GL/GLX runtime (compat.glx-runtime)";
4357+
else if (s.find("glibc") != std::string::npos) note = " <- glibc";
4358+
else if (s.find("xim-x-gcc") != std::string::npos
4359+
|| s.find("xim-x-llvm") != std::string::npos) note = " <- toolchain";
4360+
std::println(" - {}{}", s, note);
4361+
}
4362+
}
4363+
if (all || topic == "deps") {
4364+
std::println("dependencies (mcpp.lock):");
4365+
std::ifstream in(ctx->projectRoot / "mcpp.lock");
4366+
if (!in) {
4367+
std::println(" (no mcpp.lock — run `mcpp build` or `mcpp update`)");
4368+
} else {
4369+
std::string line, cur;
4370+
auto quoted = [](const std::string& l) -> std::string {
4371+
auto a = l.find('"'); if (a == std::string::npos) return {};
4372+
auto b = l.find('"', a + 1); if (b == std::string::npos) return {};
4373+
return l.substr(a + 1, b - a - 1);
4374+
};
4375+
while (std::getline(in, line)) {
4376+
if (line.find("[package.\"") != std::string::npos) cur = quoted(line);
4377+
else if (!cur.empty() && line.find("version") != std::string::npos) {
4378+
std::println(" - {} {}", cur, quoted(line));
4379+
cur.clear();
4380+
}
4381+
}
4382+
}
4383+
}
4384+
return 0;
4385+
}
4386+
42874387
// ─── M4 #4: mcpp cache list / prune / clean / info ──────────────────────
42884388
struct CacheEntry {
42894389
std::filesystem::path dir;
@@ -5426,6 +5526,14 @@ int run(int argc, char** argv) {
54265526
.description("Remove target/ (and optionally the global BMI cache)")
54275527
.option(cl::Option("bmi-cache").help("Also wipe the global BMI cache"))
54285528
.action(wrap_rc(cmd_clean)))
5529+
.subcommand(cl::App("why")
5530+
.description("Explain how the toolchain / runtime / deps were resolved")
5531+
.arg(cl::Arg("topic").help("toolchain | runtime | deps (default: all)"))
5532+
.action(wrap_rc(cmd_why)))
5533+
.subcommand(cl::App("resolve")
5534+
.description("Re-resolve the build plan and explain it")
5535+
.option(cl::Option("explain").help("Print resolved toolchain / runtime / deps"))
5536+
.action(wrap_rc(cmd_why)))
54295537
.subcommand(cl::App("add")
54305538
.description("Add a dependency to mcpp.toml")
54315539
.arg(cl::Arg("pkg").help("Package spec, e.g. foo@1.0.0").required())
@@ -5640,11 +5748,11 @@ int run(int argc, char** argv) {
56405748
{
56415749
std::string_view first = argv[1];
56425750
if (!first.starts_with('-')) {
5643-
static constexpr std::array<std::string_view, 19> known = {
5751+
static constexpr std::array<std::string_view, 21> known = {
56445752
"new", "build", "run", "test", "clean", "add", "remove",
56455753
"update", "search", "publish", "pack", "emit",
56465754
"toolchain", "cache", "index", "self", "explain",
5647-
"version", "dyndep",
5755+
"version", "dyndep", "why", "resolve",
56485756
};
56495757
bool ok = false;
56505758
for (auto k : known) if (k == first) { ok = true; break; }

0 commit comments

Comments
 (0)