deps phase 1: loader searches multiple source roots
The loader resolved a namespace against one hardcoded root (src/jolt, .clj only). Now it searches an ordered list (ctx env :source-paths, stdlib first) and tries .clj then .cljc. init adds roots from the :paths opt and JOLT_PATH. This is the foundation for loading deps.edn libraries; verified loading medley from an added root. No change to stdlib loading (3913 suite baseline held).
This commit is contained in:
parent
47bcc743b4
commit
6ea43fb623
5 changed files with 92 additions and 21 deletions
|
|
@ -91,11 +91,10 @@ already does path-based namespace lookup — we widen it from one root to a few.
|
|||
|
||||
jpm stays the build tool for the Jolt binary; this lives beside it and *calls
|
||||
into* `jpm/pm` for the git/cache work (import the module at dev/build time — jpm
|
||||
is always present then; the shipped binary doesn't need it). Open question:
|
||||
whether to depend on jpm's internal `pm` functions (not a stable public API) or
|
||||
shell `git` ourselves into the same cache layout. Reusing `pm` is less code and
|
||||
shares jpm's cache; shelling git is ~15 lines and avoids the internal-API risk.
|
||||
Leaning toward reusing `pm` first.
|
||||
is always present then; the shipped binary doesn't need it). **Decision: reuse
|
||||
`jpm/pm`** (`resolve-bundle` + `download-bundle`, after `jpm/config/load-default`)
|
||||
rather than shelling `git` ourselves — less code, and it shares jpm's cache. The
|
||||
internal-API risk is acceptable since it's only used at dev/build time.
|
||||
|
||||
## Limitations
|
||||
|
||||
|
|
@ -107,10 +106,11 @@ Leaning toward reusing `pm` first.
|
|||
|
||||
## Plan
|
||||
|
||||
1. **Loader roots.** Generalize `maybe-require-ns` to search an ordered root list
|
||||
and try `.clj` + `.cljc`; add a `JOLT_PATH`/`--path` to set roots by hand.
|
||||
Point it at a checked-out lib's `src` and load it. Independently testable,
|
||||
unblocks the rest.
|
||||
1. **Loader roots.** *(done)* `maybe-require-ns` now searches an ordered root
|
||||
list (`ctx.env :source-paths`, stdlib first) trying `.clj` then `.cljc`;
|
||||
`init` adds roots from the `:paths` opt and `JOLT_PATH` (colon-separated).
|
||||
Verified loading a real lib (medley) from an added root. See
|
||||
`test/integration/deps-loader-test.janet`.
|
||||
2. **Resolve git deps via jpm.** Read `deps.edn`, resolve `:git/*` (+
|
||||
`:local/root`) through `jpm/pm` into `jpm_tree/.cache`, recurse for transitive
|
||||
git deps, register the roots. `jolt deps` to resolve/print; auto on startup
|
||||
|
|
|
|||
|
|
@ -30,10 +30,17 @@
|
|||
opts may contain:
|
||||
:namespaces — map of {ns-name → {sym → value, ...}, ...}
|
||||
:mutable? — use Janet mutable data structures instead of persistent
|
||||
:compile? — enable compilation of Clojure forms to Janet"
|
||||
:compile? — enable compilation of Clojure forms to Janet
|
||||
:paths — extra source roots to search for namespaces (after the stdlib)"
|
||||
[&opt opts]
|
||||
(default opts {})
|
||||
(let [ctx (make-ctx opts)]
|
||||
# Extra source roots: opts :paths, then JOLT_PATH (colon-separated). These are
|
||||
# searched after the stdlib so (require ...) finds deps.edn-resolved libs.
|
||||
(let [roots (get (ctx :env) :source-paths)]
|
||||
(each p (get opts :paths []) (array/push roots p))
|
||||
(when-let [jp (os/getenv "JOLT_PATH")]
|
||||
(each p (string/split ":" jp) (when (> (length p) 0) (array/push roots p)))))
|
||||
# Collection representation (persistent vs mutable) is selected at BUILD time
|
||||
# via JOLT_MUTABLE (see config.janet); init-core! registers vec/vector/conj/
|
||||
# etc. that produce the mode-appropriate values, so nothing extra to load.
|
||||
|
|
|
|||
|
|
@ -185,12 +185,24 @@
|
|||
[sym-s]
|
||||
(if (sym-s :ns) (string (sym-s :ns) "/" (sym-s :name)) (sym-s :name)))
|
||||
|
||||
(defn- ns->path
|
||||
"Map a namespace name to its Jolt stdlib source path (dots->dirs, dashes->_)."
|
||||
(defn- ns->relpath
|
||||
"Namespace name to its file-relative path (dots->dirs, dashes->_), no extension."
|
||||
[ns-name]
|
||||
(string "src/jolt/"
|
||||
(string/replace-all "." "/" (string/replace-all "-" "_" ns-name))
|
||||
".clj"))
|
||||
(string/replace-all "." "/" (string/replace-all "-" "_" ns-name)))
|
||||
|
||||
(defn- find-ns-file
|
||||
"Search the context's source roots (stdlib first, then deps.edn dirs) for the
|
||||
namespace's source, trying .clj then .cljc. Returns the path or nil."
|
||||
[ctx ns-name]
|
||||
(let [rel (ns->relpath ns-name)
|
||||
roots (or (get (ctx :env) :source-paths) @["src/jolt"])]
|
||||
(var found nil)
|
||||
(each root roots
|
||||
(each ext [".clj" ".cljc"]
|
||||
(when (nil? found)
|
||||
(let [p (string root "/" rel ext)]
|
||||
(when (os/stat p) (set found p))))))
|
||||
found))
|
||||
|
||||
(defn- load-ns-file
|
||||
"Parse and evaluate every form in a .clj file in the given context."
|
||||
|
|
@ -202,17 +214,18 @@
|
|||
(when (not (nil? f)) (eval-form ctx @{} f))))
|
||||
|
||||
(defn- maybe-require-ns
|
||||
"If namespace ns-name isn't populated yet and a stdlib source file exists,
|
||||
load it. Restores the current namespace afterwards (the file's `ns` form
|
||||
"If namespace ns-name isn't populated yet and source for it exists on the
|
||||
context's source roots, load it. Restores the current namespace afterwards (a
|
||||
library's own `ns` form, or our manual switch for ns-form-less stdlib files,
|
||||
changes it). No-op for already-loaded namespaces."
|
||||
[ctx ns-name]
|
||||
(let [ns (ctx-find-ns ctx ns-name)]
|
||||
(when (and (= 0 (length (ns :mappings))) (not= ns-name "clojure.core"))
|
||||
(let [path (ns->path ns-name)]
|
||||
(when (os/stat path)
|
||||
(let [path (find-ns-file ctx ns-name)]
|
||||
(when path
|
||||
(let [saved (ctx-current-ns ctx)]
|
||||
# Jolt stdlib files have no `ns` form; switch into the target ns so
|
||||
# their defs intern there, then restore.
|
||||
# Stdlib files have no `ns` form, so switch into the target ns first
|
||||
# (their defs intern there); a library's own `ns` form overrides this.
|
||||
(ctx-set-current-ns ctx ns-name)
|
||||
(load-ns-file ctx path)
|
||||
(ctx-set-current-ns ctx saved)))))))
|
||||
|
|
|
|||
|
|
@ -366,6 +366,9 @@
|
|||
:class->opts @{}
|
||||
:current-ns "user"
|
||||
:compile? compile?
|
||||
# Ordered roots searched (after the stdlib) to resolve a namespace
|
||||
# to a .clj/.cljc file. deps.edn resolution appends dep src dirs.
|
||||
:source-paths @["src/jolt"]
|
||||
:compiled-cache @{}
|
||||
:type-registry @{}
|
||||
:data-readers (let [dr @{}]
|
||||
|
|
|
|||
48
test/integration/deps-loader-test.janet
Normal file
48
test/integration/deps-loader-test.janet
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# The loader resolves namespaces against an ordered list of source roots (the
|
||||
# stdlib first, then deps.edn-resolved dirs), trying .clj then .cljc. This is the
|
||||
# foundation for loading Clojure libraries via deps.edn — here we point a root at
|
||||
# a hand-written "library" and require it.
|
||||
|
||||
(use ../../src/jolt/api)
|
||||
(use ../../src/jolt/types)
|
||||
|
||||
(def tmp (string (os/getenv "TMPDIR") "jolt-deps-test-" (os/time)))
|
||||
(os/mkdir tmp)
|
||||
(os/mkdir (string tmp "/mylib"))
|
||||
(os/mkdir (string tmp "/other"))
|
||||
|
||||
# a .cljc library with its own ns form and a require of a sibling ns
|
||||
(spit (string tmp "/mylib/core.cljc")
|
||||
"(ns mylib.core (:require [other.util :as u]))\n(defn double [x] (* 2 x))\n(defn doubled-inc [x] (u/inc1 (double x)))\n")
|
||||
# a .clj sibling, with a dash in the name (-> underscore in the path)
|
||||
(os/mkdir (string tmp "/other"))
|
||||
(spit (string tmp "/other/util.clj") "(ns other.util)\n(defn inc1 [x] (+ x 1))\n")
|
||||
|
||||
(def ctx (init {:paths [tmp]}))
|
||||
(ctx-set-current-ns ctx "user")
|
||||
|
||||
(var fails 0)
|
||||
(defn check [label expr expected]
|
||||
(let [r (protect (eval-string ctx expr))
|
||||
got (if (r 0) (normalize-pvecs (r 1)) (string "ERR " (r 1)))]
|
||||
(if (= got expected)
|
||||
(print " ok " label)
|
||||
(do (++ fails) (printf " FAIL %s: want %q, got %q" label expected got)))))
|
||||
|
||||
# require a .cljc lib from the added root and call it
|
||||
(check "require .cljc lib" "(do (require (quote [mylib.core :as m])) (m/double 21))" 42)
|
||||
# transitive require (mylib.core requires other.util) resolved from the root too
|
||||
(check "transitive .clj require" "(mylib.core/doubled-inc 10)" 21)
|
||||
# the stdlib still resolves from its default root
|
||||
(check "stdlib still loads" "(do (require (quote [jolt.interop :as j])) (j/janet-type 1))" :number)
|
||||
|
||||
# clean up
|
||||
(defn rmrf [p]
|
||||
(if (= :directory (os/stat p :mode))
|
||||
(do (each e (os/dir p) (rmrf (string p "/" e))) (os/rmdir p))
|
||||
(os/rm p)))
|
||||
(rmrf tmp)
|
||||
|
||||
(if (> fails 0)
|
||||
(error (string "deps-loader-test: " fails " failing check(s)"))
|
||||
(print "\nAll deps-loader tests passed!"))
|
||||
Loading…
Add table
Add a link
Reference in a new issue