One documentation root: the prose docs (building-and-deps, self-hosting architecture/compiler, tools-deps, grammar.ebnf) join the spec and RFCs under docs/. References in README and deps-conformance-test updated.
4.6 KiB
deps.edn support — design notes
How Jolt loads pure-Clojure libraries from a deps.edn, and why it's built the
way it is. For how to use it, see building-and-deps.md.
Scope, decided up front:
- git + local deps only — no Maven/
~/.m2resolution. - pure
clj/cljc— anything needing the JVM won't load or run; expected. - no classpath abstraction —
requirejust needs to find a dep's namespaces; "the classpath" is an ordered list of source directories. - piggyback on jpm — reuse jpm's git fetch + cache; don't write a package manager.
- separate tool — resolution lives in
jolt-deps, beside the runtime, the wayjpmsits besidejanet. Thejoltruntime knows nothing about deps.edn.
How jpm handles dependencies
jpm's package code (jpm/pm.janet) splits into a fetch half and a build half,
and we use only the first:
resolve-bundlenormalizes a dep spec to{:url :tag :type :shallow}, accepting:url/:repo+:tag/:sha/:commit/:ref. A deps.edn{:git/url … :git/sha …}maps straight onto it.download-bundle url :git tag shallowclones into a content-addressed cache (<modpath>/.cache/git_<tag>_<sanitized-url>) and returns the path —git init+remote add+ fetch + reset, plus submodules. No build step.bundle-installis the half we skip: it then runsproject.janetbuild rules, which a Clojure lib doesn't have. It's cleanly separable from the clone.
So jpm gives us git resolution and a cache for free; calling download-bundle
needs jpm/config/load-default first (it sets gitpath and the cache dyns).
How it works
src/jolt/deps.janet reads deps.edn (Janet parses it directly — EDN and Janet
syntax overlap for the :deps/:paths subset), then walks :deps:
:git/url(+:git/shaor:git/tag) →resolve-bundle+download-bundleintojpm_tree/.cache;:local/root→ the path as-is;:mvn/*and anything else → ignored.
Each resolved dependency contributes its own :paths (default ["src"]) as
source roots, and we recurse into its deps.edn for transitive deps. The result
is a de-duplicated, ordered list of directories. resolve-deps-cached memoizes
that list in the tree keyed on a hash of deps.edn, so an unchanged file doesn't
re-fetch. jpm is loaded lazily (require, not import) so it's pulled in only
when resolving — never embedded in a built binary.
The loader (evaluator.janet/find-ns-file) resolves a namespace by searching the
context's :source-paths in order (the stdlib src/jolt first), trying <ns>.clj
then <ns>.cljc. Extra roots come from JOLT_PATH or init's :paths option.
jolt-deps (src/jolt/deps_cli.janet, its own declare-executable) ties it
together: it resolves the roots and runs the jolt binary with them on
JOLT_PATH. The runtime's only dependency interface is that env var.
jolt uberscript bundles a namespace and everything it requires into one
standalone .clj. It requires the entry namespace and uses the order in which
the loader finishes loading files — a dependency finishes before the file that
required it, so the order is topological — then concatenates that source. The
baked-in stdlib is excluded (it's part of the runtime, not bundled).
Gotcha worth remembering: the jolt CLI's context is built into its image at
build time, so JOLT_PATH is applied at runtime in main, not in init (whose
env read would be frozen at build).
Limitations
- Pure
clj/cljconly — JVM interop, host classes, and unimplementedclojure.corecorners fail. Coverage is per-function: a namespace can load with most functions working and a few not. - Source only; compiled
.classfiles in a git dep are ignored. - git
:git/shamust be a full SHA (git fetchcan't resolve a short one).
Conformance
test/integration/deps-conformance-test.janet resolves a few real pure-cljc
git libraries and reports whether their namespaces load and a sample call works.
It's network-gated behind JOLT_CONFORMANCE=1 so CI stays offline. Use it to
check a library against the current interpreter, and to drive fixes for whatever
gap a failure points at (the same loop as the clojure-test-suite battery). A
library fails when it relies on something Jolt doesn't provide — JVM interop, or
a regex feature like Unicode property classes (\p{…}).
Not yet
- Compiling deps into a binary image.
uberscriptalready produces a standalone.clj; baking a project's dependencies directly into a custom executable image is a heavier variant that isn't implemented.