The vendored spork/http is gone — jpm owns janet packages. In its place: - The janet.* bridge autoloads jpm-installed modules on first reference: janet.spork.http/server requires spork/http from the module path and caches its bindings (failures are negatively cached). Works for any module, in every mode, including inside net/server connection fibers. - deps.edn grows a :jpm/module coordinate: jolt-deps verifies the module is importable at resolve time, optionally running `jpm install` on the :jpm/install package once when it isn't, and otherwise fails with the install hint. Contributes no source roots. ring-app declares spork/http this way. Docs: README's interop section, docs/tools-deps.md (:jpm/module reference), and the ring-app README (including the jpm-version caveat for spork HEAD's .janet native sources, which older jpm rejects).
156 lines
6.7 KiB
Text
156 lines
6.7 KiB
Text
# deps.edn resolution into source roots, then loading a library through them.
|
|
# Uses :local/root deps only so it needs no network (the git path is the same
|
|
# code, exercised manually against real repos).
|
|
|
|
(use ../../src/jolt/api)
|
|
(use ../../src/jolt/types)
|
|
(import ../../src/jolt/deps :as deps)
|
|
|
|
# Captured before any os/cd: subprocess tests below re-import src/jolt/deps.
|
|
(def repo-root (os/cwd))
|
|
|
|
(def base (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-deps-resolve-" (os/time)))
|
|
(defn rmrf [p]
|
|
(when (os/stat p)
|
|
(if (= :directory (os/stat p :mode))
|
|
(do (each e (os/dir p) (rmrf (string p "/" e))) (os/rmdir p))
|
|
(os/rm p))))
|
|
(rmrf base)
|
|
|
|
(defn mkdirs [p]
|
|
(def abs (string/has-prefix? "/" p))
|
|
(var acc nil)
|
|
(each seg (filter |(not= "" $) (string/split "/" p))
|
|
(set acc (cond (nil? acc) (if abs (string "/" seg) seg) (string acc "/" seg)))
|
|
(unless (os/stat acc) (os/mkdir acc))))
|
|
|
|
# project -> depends on lib-a (local), which depends on lib-b (local, transitive)
|
|
(each d ["proj/src" "a/src/liba" "b/src/libb"] (mkdirs (string base "/" d)))
|
|
(spit (string base "/proj/deps.edn") `{:paths ["src"] :deps {my/a {:local/root "../a"}}}`)
|
|
(spit (string base "/a/deps.edn") `{:paths ["src"] :deps {my/b {:local/root "../b"}}}`)
|
|
(spit (string base "/b/deps.edn") `{:paths ["src"]}`)
|
|
(spit (string base "/a/src/liba/core.clj") "(ns liba.core (:require [libb.core :as b]))\n(defn val [] (b/n))\n")
|
|
(spit (string base "/b/src/libb/core.clj") "(ns libb.core)\n(defn n [] 99)\n")
|
|
|
|
(var fails 0)
|
|
(defn check [label got expected]
|
|
(if (= got expected) (print " ok " label)
|
|
(do (++ fails) (printf " FAIL %s: want %q, got %q" label expected got))))
|
|
|
|
(os/cd (string base "/proj"))
|
|
(def roots (deps/resolve-deps "deps.edn" (string base "/proj/jpm_tree")))
|
|
# roots: proj/src, a/src (transitive: b/src) — at least the two dep srcs present
|
|
(check "resolves transitive local dep roots"
|
|
(truthy? (and (some |(string/has-suffix? "/a/src" $) roots)
|
|
(some |(string/has-suffix? "/b/src" $) roots)))
|
|
true)
|
|
|
|
# load through the resolved roots: liba.core requires libb.core transitively
|
|
(def ctx (init {:paths roots}))
|
|
(ctx-set-current-ns ctx "user")
|
|
(let [r (protect (eval-string ctx "(do (require (quote [liba.core :as a])) (a/val))"))]
|
|
(check "require local lib + transitive dep" (if (r 0) (r 1) (string "ERR " (r 1))) 99))
|
|
|
|
# cached resolution returns the same roots without re-walking
|
|
(check "cached resolve matches"
|
|
(deep= roots (deps/resolve-deps-cached "deps.edn" (string base "/proj/jpm_tree")))
|
|
true)
|
|
|
|
# The cache must hit across PROCESSES: janet's (hash ...) is seeded per process,
|
|
# so a key built with it never matches a cached one and every invocation
|
|
# re-resolved (and re-fetched git deps). Detection: plant a sentinel root in the
|
|
# cache file — a fresh process that hits the cache returns it; a process that
|
|
# misses re-resolves and overwrites it.
|
|
(os/cd (string base "/proj"))
|
|
(def cache-file ".cpcache/jolt-deps.jdn")
|
|
(def cached-now (parse (slurp cache-file)))
|
|
(spit cache-file
|
|
(string/format "%j" (merge cached-now
|
|
{:roots [;(get cached-now :roots) "/SENTINEL"]})))
|
|
(defn subprocess-roots []
|
|
(def code
|
|
(string `(os/cd "` repo-root `") `
|
|
`(import ./src/jolt/deps :as deps) `
|
|
`(os/cd "` base "/proj" `") `
|
|
`(print (string/join (deps/resolve-deps-cached "deps.edn" "` base "/proj/jpm_tree" `") ":"))`))
|
|
(def p (os/spawn ["janet" "-e" code] :px {:out :pipe}))
|
|
(def out (ev/read (p :out) :all))
|
|
(os/proc-wait p)
|
|
(string/trim (string out)))
|
|
(check "cache hits across processes"
|
|
(string/has-suffix? "/SENTINEL" (subprocess-roots))
|
|
true)
|
|
|
|
(os/cd "/")
|
|
(rmrf base)
|
|
|
|
# Git-dep resolution must keep stdout clean: `JOLT_PATH=$(jolt-deps path)` is
|
|
# the documented capture, and git's checkout chatter ("HEAD is now at …") was
|
|
# corrupting it. Uses a file:// git dep so no network is needed.
|
|
(def gbase (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-deps-gitout-" (os/time)))
|
|
(rmrf gbase)
|
|
(each d ["lib/src/glib" "proj2"] (mkdirs (string gbase "/" d)))
|
|
(spit (string gbase "/lib/src/glib/core.clj") "(ns glib.core)\n(defn n [] 7)\n")
|
|
(spit (string gbase "/lib/deps.edn") `{:paths ["src"]}`)
|
|
(defn sh-out [args &opt cwd]
|
|
(def p (os/spawn args :px {:out :pipe :err :pipe :cd (or cwd ".")}))
|
|
(def out (ev/read (p :out) :all))
|
|
(os/proc-wait p)
|
|
(string/trim (string out)))
|
|
(def git-ok
|
|
(truthy?
|
|
(protect
|
|
(do (sh-out ["git" "-c" "init.defaultBranch=master" "init"] (string gbase "/lib"))
|
|
(sh-out ["git" "add" "-A"] (string gbase "/lib"))
|
|
(sh-out ["git" "-c" "user.email=t@t" "-c" "user.name=t" "commit" "-m" "init" "-q"]
|
|
(string gbase "/lib"))))))
|
|
(if (not git-ok)
|
|
(print " skip git-dep stdout test (git unavailable)")
|
|
(do
|
|
(def sha (sh-out ["git" "rev-parse" "HEAD"] (string gbase "/lib")))
|
|
(spit (string gbase "/proj2/deps.edn")
|
|
(string `{:paths ["src"] :deps {my/glib {:git/url "file://` gbase `/lib" :git/sha "` sha `"}}}`))
|
|
(os/setenv "JOLT_GITLIBS" (string gbase "/gitlibs"))
|
|
(def code
|
|
(string `(os/cd "` repo-root `") `
|
|
`(import ./src/jolt/deps :as deps) `
|
|
`(os/cd "` gbase "/proj2" `") `
|
|
`(deps/resolve-deps "deps.edn" "` gbase "/proj2/jpm_tree" `") `
|
|
`(eprint "done")`))
|
|
(def p (os/spawn ["janet" "-e" code] :px {:out :pipe}))
|
|
(def out (ev/read (p :out) :all))
|
|
(os/proc-wait p)
|
|
(os/setenv "JOLT_GITLIBS" nil)
|
|
(check "git-dep resolution keeps stdout clean" (string (or out "")) ""))
|
|
)
|
|
(rmrf gbase)
|
|
|
|
# --- :jpm/module deps: janet libraries installed through jpm -----------------
|
|
# Verification only (jpm owns installation): an importable module passes and
|
|
# contributes no roots; a missing one errors with the install hint. jpm/pm is
|
|
# always importable wherever jpm itself runs (CI included).
|
|
(do
|
|
(def jbase (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-deps-jpm-" (os/time)))
|
|
(mkdirs (string jbase "/src"))
|
|
(spit (string jbase "/deps.edn")
|
|
`{:paths ["src"] :deps {janet/jpm-pm {:jpm/module "jpm/pm"}}}`)
|
|
(def cwd (os/cwd))
|
|
(os/cd jbase)
|
|
(def roots (deps/resolve-deps "deps.edn" (string jbase "/jpm_tree")))
|
|
(os/cd cwd)
|
|
(check "jpm module dep resolves" true (not (nil? roots)))
|
|
(check "jpm module contributes no roots" 1 (length roots))
|
|
|
|
(spit (string jbase "/deps.edn")
|
|
`{:paths ["src"] :deps {janet/nope {:jpm/module "no/such-module-xyz"}}}`)
|
|
(os/cd jbase)
|
|
(def r (protect (deps/resolve-deps "deps.edn" (string jbase "/jpm_tree"))))
|
|
(os/cd cwd)
|
|
(check "missing jpm module errors" false (r 0))
|
|
(check "error carries the install hint" true
|
|
(not (nil? (string/find "jpm install" (string (r 1))))))
|
|
(rmrf jbase))
|
|
|
|
(if (> fails 0)
|
|
(error (string "deps-resolve-test: " fails " failing check(s)"))
|
|
(print "\nAll deps-resolve tests passed!"))
|