feat(doctor): detect missing toolchain runtime deps / dangling subos symlinks#141
Merged
Conversation
…symlinks
Add a new "toolchain runtime deps" section to `mcpp self doctor` that
catches the failure class where a provider xim package gets removed,
leaving an installed toolchain's RUNPATH (and subos/default/lib symlinks)
pointing at files that no longer exist.
Concretely: deleting `xim-x-zlib` left clang++'s baked RUNPATH pointing
at the gone `xim-x-zlib/<v>/lib` dir, so the package compiled fine but
the produced binary died at runtime with
"libz.so.1: cannot open shared object" (exit 127). Nothing surfaced the
broken state until a build mysteriously failed.
The new section (Linux/ELF only, guarded by
`#if !defined(__APPLE__) && !defined(_WIN32)`):
1. Enumerates installed xim toolchains the same way `mcpp toolchain
list` does (iterate <xlingsHome>/data/xpkgs/xim-x-<compiler>/<ver>,
resolve the frontend via toolchain_frontend()).
2. Reads each compiler's RUNPATH/RPATH via `readelf -d` (reusing
mcpp::platform::process::capture — no new process code, no new
deps) and warns for every absolute RUNPATH dir that is now missing,
naming the toolchain and hinting the providing xim package may have
been removed.
3. Scans <xlingsHome>/subos/default/lib for dangling symlinks
(std::filesystem::exists follows links -> false when the target is
gone) and warns with the symlink + its broken target.
Exit-code semantics are unchanged (warnings only). RUNPATH parsing is
factored into an exported, process-free `parse_readelf_runpath()` helper
with self-contained gtest coverage (tests/unit/test_doctor_runpath.cpp),
including the zlib-removal RUNPATH shape, legacy DT_RPATH, no-runpath,
and empty-token cases.
Sunrisepeak
added a commit
that referenced
this pull request
Jun 21, 2026
* fix(build): self-heal stale build.ninja on 'missing and no known rule' When a dependency package under the registry is reinstalled/moved but keeps the same version string, the build fingerprint (which does not yet cover registry dep state) is unchanged, so the cached build.ninja is reused. Ninja then aborts with 'missing and no known rule to make it' and the build hard-fails, forcing the user to run `mcpp clean` by hand. Add that signature to is_stale_ninja_failure so try_fast_build drops to a full graph regeneration (same invocation) instead of failing — the stale graph is rewritten against the current dep state and the build proceeds. (Folding registry dep state into the fingerprint to avoid the regen entirely is a larger follow-up; see .agents/docs/2026-06-22 §T-j.) * chore: bump version 0.0.57 -> 0.0.58 Release carrying the follow-up batch: scanner raw-string fix (#138), first-run progress (#139), toolchain effective-resolution (#140), doctor runtime-dep check (#141), and the build.ninja self-heal above.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When a provider xim package is removed, an already-installed toolchain is left in a silently-broken state: its compiler binary keeps an absolute RUNPATH baked in (and
subos/default/libholds symlinks) that now point at files which no longer exist.The concrete case that motivated this: deleting
xim-x-zlibleftclang++'s RUNPATH pointing at the now-gonexim-x-zlib/<v>/lib. The package still compiled fine, but the produced binary died at runtime with:Nothing in
mcpp self doctorsurfaced this — the breakage only showed up later as a mysterious build/link failure.Fix
A new
Checking toolchain runtime depssection indoctor_report(), Linux/ELF only (guarded by#if !defined(__APPLE__) && !defined(_WIN32); macOS/Windows skip it):mcpp toolchain listdoes — iterate<xlingsHome>/data/xpkgs/xim-x-<compiler>/<version>/binand resolve the frontend viatoolchain::toolchain_frontend().readelf -d, run through the existingmcpp::platform::process::capturehelper (no new process code, no new deps). Parse theLibrary runpath: [a:b:c]line, split on:. For each absolute entry whose directory is missing,warn(...)naming the toolchain + missing path, hinting the providing xim package may have been removed (reinstall the toolchain to repair).<xlingsHome>/subos/default/libfor dangling symlinks (std::filesystem::existsfollows links → false when the target is gone) andwarn(...)with the symlink + its broken target.Exit-code semantics are unchanged — warnings only, no new errors.
The RUNPATH parsing is factored into an exported, process-free helper
parse_readelf_runpath()so it can be unit-tested without spawning anything.Testing
mcpp build— passes (only the pre-existing imgui warnings).mcpp test— all 23 unit test binaries pass, including the newtest_doctor_runpath(covers the zlib-removal RUNPATH shape, legacyDT_RPATH, no-runpath, and empty-token cases).mcpp self doctor(freshly built binary) — runs without crashing and prints the new section:xim-x-zlib/.../libz.so.1reports as non-existent (follows the link), matching the detection branch.Files changed
src/doctor.cppm— new section + exportedparse_readelf_runpath()helper + imports (mcpp.platform.process,mcpp.toolchain.registry).tests/unit/test_doctor_runpath.cpp— self-contained gtest coverage for the parser.