Reading a .asd¶
Three functions, one lexer. Each answers a question out of what a .asd
already declares rather than out of a second copy in a flake: two read its
:version, so a release edits one file rather than a file and a flake, and
one reads its :depends-on, so the registry a system is built against comes
from the same file ASDF itself would read.
The lexer is small — not a Common Lisp parser. It understands
string literals and both comment forms, which is exactly what is needed to
avoid accepting commented-out metadata, and nothing else. A .asd is
arbitrary Lisp; the moment the answer would require evaluating it, the right
response is to fail and make the caller pass version explicitly.
Every failure names the file, because the caller's Nix expression only
mentions a path — the offending line is in a .asd they have to go open.
fromAsdSystem¶
Extract the literal, unescaped :version declared as a direct option of a
defsystem form.
The operator is recognised as defsystem, asdf:defsystem or
asdf/defsystem:defsystem, case-insensitively, since all three occur in the
wild.
One .asd normally defines several systems — <pkg> and <pkg>/test — that
all carry the same version. That unanimity is accepted and returned as one
string. Versions that disagree are real drift and fail loudly, naming each
value and the system it came from.
Line comments and nested block comments are ignored. A :version nested
inside :components is never mistaken for a system's own. A missing or
dynamically computed version fails; pass version explicitly in those cases.
asdSystemVersions¶
The same extraction as an attrset keyed by system name.
cl.asdSystemVersions ./cl-prolog-kit.asd
# => { "cl-prolog-kit" = "1.1.0"; "cl-prolog-kit/test" = "1.1.0"; }
For the rarer case of needing one specific system's version rather than the
file's consensus. System-name designators are normalised, so "x", :x and
#:x all key as "x".
asdSystemDependencies¶
The :depends-on names each defsystem in the file declares, keyed by
system name. The question it answers is the one CL_SOURCE_REGISTRY asks:
which other systems have to be reachable before this one can load.
cl.asdSystemDependencies {
asd = ./cl-cli.asd;
features = [ "sbcl" ];
}
# => {
# "cl-cli" = [ "alexandria" "cl-host-kit" ];
# "cl-cli/test" = [ "cl-cli" "fiveam" ];
# }
Both arguments are required. The same lexer and the same defsystem
recognition as above apply, so line comments, block comments and
commented-out clauses are ignored here too.
features has no default¶
features is the *features* list the file's reader conditionals are
evaluated against. #+sbcl "cl-host-kit" contributes cl-host-kit when
"sbcl" is in features and contributes nothing when it is not.
(:feature :sbcl "cl-host-kit") is ASDF's own spelling of the same
conditional and is evaluated against the same list, with the same result.
Defaulting the list would mean guessing, and the two available guesses are
both wrong. A cl-nix-forge registry entry is a derivation built for one
specific implementation, so an SBCL-only dependency silently included in an
ECL build is not a harmless extra path on CL_SOURCE_REGISTRY — it is a
build against the wrong implementation's code and fail later in the build.
Dropping conditionals instead loses real dependencies from the SBCL
build. cl-cli is the org's live instance of this: its .asd guards
cl-host-kit with #+sbcl precisely so the ECL portability gate never sees
it, and a default in either direction would defeat that guard from the Nix
side. Requiring the argument makes the caller state which implementation the
answer is for, which it already knows, since it is the same choice it made
when it picked a Lisp to build with.
A system with no dependencies is kept, not dropped¶
asdSystemVersions omits a system that declares no :version. This
function omits nothing: a defsystem with no :depends-on is present with
[ ].
The asymmetry is deliberate, because the two absences are different facts.
A missing :version is unknown data — the file does not say what the version
is, and inventing a key for it would put a fiction in the attrset. "This
system depends on nothing" is not unknown; it is a complete and correct
answer, and dropping the key would force every caller to tell it apart from
"there is no such system in this file", which is the one distinction a
registry builder cannot afford to lose.
:depends-on () and :depends-on NIL say exactly what no :depends-on
clause says, and all three yield [ ].
The value is a list, not a set¶
Source order is preserved and duplicates are not collapsed. A name that
appears twice in one :depends-on is drift in the .asd that the caller has
to be able to see; deduplicating here would launder it into a well-formed
answer and delete the evidence. A caller that wants a set applies
lib.unique and can be held to having asked for it.
(:require ...) contributes no name¶
(:require :sb-cover) asks the implementation to load a module it ships
itself. That is not an ASDF system, there is no source tree for a registry to
point at, and so it contributes no name to the result. Its absence from the
list is the correct answer, not a gap.
Any other element shape fails, naming the file and quoting the element, on
the same reasoning as the rest of this page: the caller's Nix expression
mentions only a path, so the message has to say which file to open and what
in it was not understood. Unprefixed symbol designators are among the
rejected shapes — write #:alexandria or "alexandria", not a bare
alexandria.
The key set is not the same as asdSystemVersions'¶
The two functions do not agree on keys, and code that assumes they do is
wrong. Because a versionless system is omitted from one and kept in the
other, the keys of asdSystemVersions are a proper subset of these:
across the org's 108 systems, 13 declare dependencies and no :version.
A naive join — walking these keys and indexing the version attrset with each
— therefore throws on those 13. Take this function's key set as the
authoritative list of systems in the file, and look versions up defensively
(versions.<name> or null) rather than assuming a one-to-one.
Nesting¶
A :depends-on inside a :components entry names sibling files within the
system, not systems, and is correctly not attributed to the system — the same
rule that keeps a :version nested inside :components from being mistaken
for a system's own.