|
| 1 | +# mcpp core: runtime closure (rpath) + toolchain defaults |
| 2 | + |
| 3 | +> 2026-06-03 · part of the mcpp ecosystem打通 plan |
| 4 | +> Master plan: /home/speak/workspace/github/agentdocs/2026-06-03-mcpp-ecosystem-architecture-plan.md |
| 5 | +
|
| 6 | +This change fixes two general, long-term issues that block "native + GUI" |
| 7 | +packages (e.g. the imgui module package) from working out of the box. Neither |
| 8 | +is a special-case for any one package. |
| 9 | + |
| 10 | +## R2 — dependency `[runtime] library_dirs` were dropped from binary RUNPATH |
| 11 | + |
| 12 | +### Symptom |
| 13 | +A fresh consumer that depends (transitively) on `compat.glfw` builds fine but |
| 14 | +`mcpp run` fails at window creation: `GLX: Failed to load GLX`. |
| 15 | + |
| 16 | +### Root cause (confirmed in source) |
| 17 | +- `compat.glx-runtime` (pulled in by `compat.glfw` on Linux) symlinks the host |
| 18 | + GLVND/GL/GLX libraries into its install dir and declares |
| 19 | + `[runtime] library_dirs = { mcpp_generated/glx_runtime/lib }`. |
| 20 | +- `src/build/plan.cppm` (~L220) already collects every dependency package's |
| 21 | + `runtime.library_dirs` into `plan.runtimeLibraryDirs` (resolved to absolute). |
| 22 | +- BUT `src/build/flags.cppm` (~L258) built the produced binary's RUNPATH by |
| 23 | + iterating only `plan.toolchain.linkRuntimeDirs` — i.e. the toolchain's own |
| 24 | + runtime dirs. The dependency runtime dirs in `plan.runtimeLibraryDirs` were |
| 25 | + never emitted as `-Wl,-rpath`. So the host-GL passthrough dir was not on the |
| 26 | + binary's RUNPATH, and the dlopen()'d `libGL.so.1` / `libGLX.so.0` were |
| 27 | + unreachable at run time. |
| 28 | + |
| 29 | +The dependency dirs were correctly used for the *build/process* environment but |
| 30 | +not baked into the *binary* — so anything reached via dlopen (GL/GLX, and any |
| 31 | +plugin-style runtime lib) failed. |
| 32 | + |
| 33 | +### Fix |
| 34 | +`src/build/flags.cppm`: iterate `plan.runtimeLibraryDirs` (the union of |
| 35 | +dependency runtime dirs + toolchain + payload) instead of |
| 36 | +`plan.toolchain.linkRuntimeDirs` when emitting `-L`/`-Wl,-rpath`. This is a |
| 37 | +superset, so toolchain dirs are still covered; it additionally bakes each |
| 38 | +dependency's declared runtime dir into RUNPATH. |
| 39 | + |
| 40 | +This is the correct general behavior: any package that declares |
| 41 | +`[runtime] library_dirs` is promising "binaries that use me need these dirs at |
| 42 | +run time"; the producer binary must carry them as RUNPATH. |
| 43 | + |
| 44 | +## R1 — fresh-machine bootstrap default toolchain was musl-static on Linux |
| 45 | + |
| 46 | +### Symptom |
| 47 | +On a clean machine, "First run no toolchain configured" auto-installs |
| 48 | +`gcc@15.1.0-musl` (musl, static). Building any package that links the glibc |
| 49 | +world (X11/GL/system libs) then fails, e.g. `libXdmcp` `arc4random_buf` |
| 50 | +implicit-declaration under musl. |
| 51 | + |
| 52 | +### Root cause |
| 53 | +`src/cli.cppm` (~L1390) hard-coded the Linux first-run default to |
| 54 | +`gcc@15.1.0-musl`. |
| 55 | + |
| 56 | +### Fix |
| 57 | +Default Linux first-run toolchain to the platform-native glibc gcc |
| 58 | +(`gcc@16.1.0`). musl-static remains fully available but **opt-in** via |
| 59 | +`mcpp build --target x86_64-linux-musl` (which the project already supports via |
| 60 | +`[target.x86_64-linux-musl]`). This mirrors Cargo/Rust: default triple is |
| 61 | +`-gnu` (glibc), `-musl` is an explicit target for portable static binaries. |
| 62 | +musl-static is a poor *default* because it cannot link the glibc/native world. |
| 63 | + |
| 64 | +## Why these are long-term/industrial, not workarounds |
| 65 | +- R2 makes the existing two-plane design actually work: the *host plane* |
| 66 | + (drivers/GLVND, provided by `compat.glx-runtime`, never vendored) is bound to |
| 67 | + the binary via RUNPATH, which is the standard ELF mechanism. No package code |
| 68 | + changes; no env hacks. |
| 69 | +- R1 aligns the default with the platform-native ABI, the same principle Cargo |
| 70 | + uses. Static/musl stays a first-class explicit option. |
| 71 | + |
| 72 | +## Test plan (acceptance, via imgui-m, no special-casing) |
| 73 | +1. Self-build mcpp with these changes. |
| 74 | +2. Fresh consumer: `mcpp new app && mcpp add imgui` then: |
| 75 | + - `mcpp build` → uses glibc gcc by default (R1), no musl error. |
| 76 | + - `readelf -d <bin>` → RUNPATH contains the `compat.glx-runtime` lib dir (R2). |
| 77 | + - `mcpp run` → window opens, ImGui renders, no `GLX: Failed to load GLX`. |
| 78 | +3. `mcpp test` headless still passes on all platforms. |
| 79 | + |
| 80 | +## Follow-up (separate, tracked in master plan) |
| 81 | +- Declarative `abi` capability on native packages so the resolver *derives* the |
| 82 | + ABI-correct toolchain instead of relying on a good default (defense in depth). |
| 83 | +- Capability→provider resolution for `opengl.glx.driver` (glvnd/cocoa/win32). |
0 commit comments