@@ -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 ──────────────────────
42884388struct 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