feat(runtime): refuse a runtime too old to run the compiled file - #27
Open
BuddhiLW wants to merge 2 commits into
Open
feat(runtime): refuse a runtime too old to run the compiled file#27BuddhiLW wants to merge 2 commits into
BuddhiLW wants to merge 2 commits into
Conversation
The runtime carried a MELPA Version: header but defined no constant, so nothing at run time could tell which runtime was loaded. It now defines clel-runtime-version, stamped from VERSION at regen, and every emitted file opens with a guarded require. Harmless while the runtime is bundled beside its application, since that copy always compiled the file. It stops being harmless once the runtime is package managed, which is the MELPA goal: MELPA ships HEAD and MELPA Stable ships the latest tag, so a 0.9 file can meet a 0.7 runtime. That surfaces as void-function clel-count, which reads as a broken package, or as a silently changed semantic - 0.7.2 lazy forcing being the live example. The boundp check is load-bearing: a runtime older than 0.7.2 defines no constant, and an unguarded read would signal void-variable, which is the undiagnosable failure the guard replaces. The guard names version/minimum-runtime-version, a deliberate constant, not the project version. Deriving it from VERSION would rewrite every compiled file on every patch release and make the message untrue. Tested at both rungs: the Clojure side asserts the emitted shape and the stamped constant; test/elisp/clojure-elisp-runtime-guard-test.el loads a real compiled file in a FRESH Emacs against three runtimes (current, older, pre-guard) and reads the verdict from the exit code. The fixture it loads is generated by the compiler via make, not committed, so it cannot drift from the emitter it stands for. Closes 20260905161109-65e51703.
A PR stacked on another branch got no run at all, and silently: an empty check list looks the same as a passing one. #27 opened against fix/lazy-seq-forcing and was never tested.
This was referenced Sep 5, 2026
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.
Closes CLJEL-VERSION-GUARD (
20260905161109-65e51703). Stacked on #26 —review that first; the base retargets to
mainonce it merges.The gap
clojure-elisp-runtime.elcarried a MELPA;; Version:header but defined noconstant, so nothing at run time could tell which runtime was loaded.
Harmless today, because the runtime is bundled beside its application and that
copy is always the one that compiled the file. It stops being harmless the
moment the runtime is package managed, which is the MELPA goal
(
20260713171046-63c616a7): MELPA ships HEAD, MELPA Stable ships the latesttag, so a 0.9 file can meet a 0.7 runtime.
void-function clel-count— reads as a broken package.compiled before and after it behaves differently against the same runtime.
The fix
The runtime defines
clel-runtime-version, stamped fromVERSIONat regenalongside the header it already stamps. Every emitted file opens with:
Two decisions worth flagging:
boundpis load-bearing. A runtime older than 0.7.2 defines no constant atall, so an unguarded read signals
void-variable clel-runtime-version— whichis precisely the undiagnosable failure the guard exists to replace. Measured:
against the 0.7.1 runtime the message is
clojure-elisp-runtime (pre-0.7.2) is too old for this file (needs 0.7.2).The guard names a minimum, not the compiler version.
version/minimum-runtime-versionis a deliberate constant, bumped only when emitted output stops working against
the previous runtime. Deriving it from
VERSIONwould rewrite every compiledfile on every patch release, and would make the message untrue: a 0.7.2 file
does not require a 0.7.2 runtime, it requires whichever runtime first provided
what it uses.
Evidence at both rungs
Clojure:
runtime_version_guard_test.cljasserts the emitted shape, theboundpguard, and that the runtime's stamped constant matchesVERSIONEmacs:
clojure-elisp-runtime-guard-test.elloads a real compiled fileagainst three runtimes — current, older-versioned, and pre-guard — and reads
the verdict from the process exit code
Each case runs in a fresh Emacs. In a shared process
requireis a no-opafter the first load and
clel-runtime-versionstays bound, so all threechecks passed vacuously until this was fixed.
The fixture is written by the compiler via
make test-elispand gitignored,not committed: a hand-written copy of emitter output would be a second source
of truth for the guard's shape, free to drift from the emitter producing it.
608 Clojure tests, 3058 assertions, 0 failures
28 ERT tests across three suites, 0 unexpected
Output change
Every compiled file's header grows the guard. The one golden snapshot was
regenerated; its diff is the guard and nothing else.