From 80581c23e7b8b9a2af3b7cdf3eebc1360d0e82ce Mon Sep 17 00:00:00 2001 From: Dawn Date: Fri, 4 Sep 2026 13:40:04 +0200 Subject: [PATCH] update new-module skill and record linking debug strategy in README --- .claude/skills/new-module/SKILL.md | 16 +++++++++++ README.md | 43 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/.claude/skills/new-module/SKILL.md b/.claude/skills/new-module/SKILL.md index 761df67..5473176 100644 --- a/.claude/skills/new-module/SKILL.md +++ b/.claude/skills/new-module/SKILL.md @@ -106,6 +106,22 @@ There is **no `--dry-run` and no `--check-mode`** — to sanity-check config/pat prints "done", installing the modulefile over a broken PREFIX. Guard every critical hook step with `|| std::die 42 ""` and end `install` with an existence check of the file the modulefile needs. +- **Shared-lib linking (compiled modules)**: RHEL bakes NO RPATH/RUNPATH into binaries + and build-time `-L` paths are not recorded — a binary that links fine can fail at + runtime with `cannot open shared object` / `GLIBCXX_... not found`. Diagnose: + `objdump -p | grep -E 'NEEDED|RPATH|RUNPATH'`, then `ldd` (look for + "not found"). Runtime search order: DT_RPATH → LD_LIBRARY_PATH → DT_RUNPATH → + ld.so.cache → /lib64,/usr/lib64. When the order bites: + - built with a gcc module → ext needs newer libstdc++ than system /usr/lib64: + add gcc to `runtime_deps:` (pymol-open-source) so the module's lib dir wins. + - install may land in `$PREFIX/lib` (autotools/venv) OR `$PREFIX/lib64` (CMake + GNUInstallDirs on RHEL) — check which exists, `prepend-path LD_LIBRARY_PATH` + that one (ImageMagick). Or bake it at build: `LDFLAGS="-Wl,-rpath,$PREFIX/lib"`. + - vendor binary WITH RPATH ignores LD_LIBRARY_PATH (patchelf/rebuild only); with + RUNPATH, LD_LIBRARY_PATH wins — leftover loaded modules can shadow bundled + libs (contaminated env again). + - verify after install: `ldd` the main binary and any compiled Python ext (e.g. + pymol `_cmd*.so`) — no "not found" lines (adxv README shows the pattern). - Interrupted downloads leave a **partial file in the distfiles dir which prep reuses silently** (no integrity check unless `shasums:` is set). Symptom: `gzip: unexpected end of file`. Fix: `rm` it there and re-run; verify big tarballs with `gzip -t` first; diff --git a/README.md b/README.md index 3354139..2125fb9 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,49 @@ rm -rf ~/.cache/Pmodules/distfiles common modbuild traps ("done" without success — no errexit in hooks, partial downloads reused silently, slow installs on shared /opt/psi). +## Debugging shared-library errors (lib vs lib64, RPATH) + +Compiled modules often build fine and then fail at load/run time with +`cannot open shared object file` or `GLIBCXX_x.y.z not found`. Diagnose +with binutils (works on any binary or `.so`, e.g. `/bin/cmake`): + +```bash +objdump -p | grep NEEDED # which libs it links +objdump -p | grep -E 'RPATH|RUNPATH' # usually EMPTY on RHEL +ldd # how each resolves; look for "not found" +``` + +The dynamic linker searches in this order: `DT_RPATH` (if set) → +`LD_LIBRARY_PATH` → `DT_RUNPATH` (if set) → `/etc/ld.so.cache` → +`/lib64`, `/usr/lib64`. On RHEL, RPATH/RUNPATH are normally **not set**, +and the build-time `-L` paths are not recorded in the binary — so build +time and runtime resolve libraries completely independently. + +Situations where the default order is not what you expect: + +1. **Compiled with a newer-gcc module** → the binary needs a newer + libstdc++, but at runtime the old system `/usr/lib64/libstdc++.so.6` + resolves first (no RPATH) → `GLIBCXX_... not found`. Fix: add the gcc + module to `runtime_deps:` so its lib dir is prepended to + `LD_LIBRARY_PATH` (see `pymol-open-source/files/config.yaml`). +2. **lib vs lib64 install dir**: system 64-bit libs live in `/usr/lib64`, + but local installs may land in `$PREFIX/lib` (autotools default, venvs) + or `$PREFIX/lib64` (CMake GNUInstallDirs picks lib64 on RHEL). Check + which dir the install actually produced and prepend that one: + `prepend-path LD_LIBRARY_PATH $PREFIX/lib` (see `ImageMagick/modulefile`). +3. **Vendor binary WITH RPATH baked in**: `DT_RPATH` beats + `LD_LIBRARY_PATH`, so the modulefile cannot override it — patchelf or + rebuild are the only fixes. With `DT_RUNPATH` instead, it is the other + way round: `LD_LIBRARY_PATH` wins, so a leftover loaded module can + shadow the vendor's bundled libs (another contaminated-environment + source — `module purge` first). +4. Alternative to LD_LIBRARY_PATH in the modulefile: bake the path at + build time with `LDFLAGS="-Wl,-rpath,$PREFIX/lib"`. + +After install, `ldd` the main binary and any compiled Python extension +(e.g. pymol's `_cmd*.so`) — no line may say "not found" +(see `adxv/README.md`). + ## Building with an AI agent The repo ships the **new-module** skill at `.claude/skills/new-module/`