The generated lisp-info files never regenerate when the Lisp or C++ content changes, so a build can silently use package, symbol and class data belonging to a completely different tree state.
The broken chain
build …/generated/features.sexp …/runtime-packages.lisp …/cxx-classes.lisp …:
generate-lisp-info | boehmprecise/iclasp <- implicit dep: iclasp only
build boehmprecise/iclasp: link boehmprecise/src/main/main.o || boehmprecise/lib/libclasp.so
<- ORDER-ONLY dep on libclasp.so
generate-lisp-info runs iclasp, which loads libclasp.so at runtime via rpath and dumps the packages, functions, variables, classes and type map it finds there. Its output therefore depends on libclasp.so's contents. But the only path from the generator to that library goes through iclasp, and iclasp depends on it order-only (||), which in ninja means "build it first if missing, but changes to it do not make me dirty". iclasp itself is just main.o plus a link, so it rarely changes.
Net effect: edit any Lisp or C++ source, rebuild, and the generated lisp-info is not refreshed.
Reproduction
Touching the library and rebuilding:
generated stamp before: 2026-08-03 23:42:47
libclasp.so stamp: 2026-08-04 00:05:50 <- newer
$ touch build/boehmprecise/lib/libclasp.so && ninja -C build
ninja rc=0 generated stamp: 2026-08-03 23:42:47 <- unchanged
$ ninja -C build -n boehmprecise/generated/runtime-packages.lisp
ninja: no work to do.
Worse in practice: I walked one build tree through eight different branches in sequence (rebuilding at each), and the generated files kept their original timestamp and byte sizes the entire way — including across a branch that adds new C++ sources and therefore new scraped symbols. Every one of those eight images was built from another branch's lisp-info, and nothing indicated a problem.
Fix
Make the dependency real. In src/koga/ninja.lisp, the :base variant target:
(libclasp (make-source (lib-filename configuration "libclasp" :dynamic t)
:variant-lib))
...
(ninja:write-build output-stream :generate-lisp-info
...
:implicit-inputs (list iclasp libclasp))
lib-filename already returns .a under --static-linking, so one call is correct for both. With that in place:
edge: generate-lisp-info | boehmprecise/iclasp boehmprecise/lib/libclasp.so
$ touch build/boehmprecise/lib/libclasp.so
ninja sees generator dirty? YES
I have a PR ready if the approach looks right.
Relationship to #1823
These are separate bugs that produce similar-looking damage, and they interact badly:
Both yield an image built from lisp-info that does not match the source tree, with no diagnostic. Anything that reuses a build tree across branches — CI caches, git bisect, everyday local development — is exposed. Fixing #1823 alone is not sufficient, because this one means the regeneration that would apply the fix never happens.
Found on x86-64 Linux, LLVM 18, --build-mode=bytecode; nothing about it looks platform-specific.
The generated lisp-info files never regenerate when the Lisp or C++ content changes, so a build can silently use package, symbol and class data belonging to a completely different tree state.
The broken chain
generate-lisp-inforunsiclasp, which loadslibclasp.soat runtime via rpath and dumps the packages, functions, variables, classes and type map it finds there. Its output therefore depends onlibclasp.so's contents. But the only path from the generator to that library goes throughiclasp, andiclaspdepends on it order-only (||), which in ninja means "build it first if missing, but changes to it do not make me dirty".iclaspitself is justmain.oplus a link, so it rarely changes.Net effect: edit any Lisp or C++ source, rebuild, and the generated lisp-info is not refreshed.
Reproduction
Touching the library and rebuilding:
Worse in practice: I walked one build tree through eight different branches in sequence (rebuilding at each), and the generated files kept their original timestamp and byte sizes the entire way — including across a branch that adds new C++ sources and therefore new scraped symbols. Every one of those eight images was built from another branch's lisp-info, and nothing indicated a problem.
Fix
Make the dependency real. In
src/koga/ninja.lisp, the:basevariant target:lib-filenamealready returns.aunder--static-linking, so one call is correct for both. With that in place:I have a PR ready if the approach looks right.
Relationship to #1823
These are separate bugs that produce similar-looking damage, and they interact badly:
:if-exists :overwriteand never closes, so when it does run over a longer existing file it leaves a stale tail.Both yield an image built from lisp-info that does not match the source tree, with no diagnostic. Anything that reuses a build tree across branches — CI caches,
git bisect, everyday local development — is exposed. Fixing #1823 alone is not sufficient, because this one means the regeneration that would apply the fix never happens.Found on x86-64 Linux, LLVM 18,
--build-mode=bytecode; nothing about it looks platform-specific.