Running Compiled YS on Other Clojure Runtimes
YS code compiles to Clojure code.
Normally the ys command line tool compiles and runs it for you in one step,
but you can also compile it once with ys -c and run the result on another
Clojure runtime like Babashka
or JVM Clojure.
To do that, the compiled code needs the YS standard library.
It is published to Clojars as
org.yamlscript/ys.v0
.
Use the -T / --to option with a code target (bb, clj, jolt or
glj)
and ys will compile (no -c needed; a code target implies it) with this
header added to the output:
(ns main (:require ys.v0))
(ys.v0/init)
plus a target specific form that resolves the ys.v0 dependency at run time.
The ys.v0/init call sets up the namespace to work like the ys runtime:
- All of the YS standard library functions and macros are referred in
- The YS namespace aliases (
str/,json/,fs/, etc) are set up - The YS runtime variables (
ARGS,ENV,FILE,CWD, etc) are bound
The same compiled file still runs under ys itself (ys -C file.clj),
where the header is a no-op.
Babashka🔗
$ ys -T bb program.ys | bb /dev/stdin
The -T bb header form (active only under Babashka; it does nothing on
other runtimes) puts the ys.v0 jar and its data.json dependency on the
classpath with babashka.classpath/add-classpath when they are already in
~/.m2, and otherwise fetches them with babashka.deps/add-deps.
The add-classpath path needs no java at all; the add-deps fallback
runs java once to resolve the dependencies from Clojars.
Installing a ys release also installs those two jars into your
~/.m2/repository, so the java free path works out of the box.
The jars are bundled in the release archive; make install skips them
when run as root (system installs), and you can install them per user at
any time with:
$ ys-sh-0.2.32 --install-m2
(Homebrew installs print this as a caveat, since brew cannot write to your home directory.)
With -o / --output, the compiled file also gets a #!/usr/bin/env bb
shebang line and the executable bit, so it is a ready to run script:
$ ys -T bb program.ys -o program
$ ./program
(The shebang line is a comment to Clojure readers, so the same file still
runs under ys -C and JVM Clojure.)
JVM Clojure🔗
$ ys -T clj program.ys > program.clj
$ clojure -M program.clj
The -T clj header form is a no-op when ys.v0 is already on the
classpath (or under the ys, Babashka and jolt runtimes); otherwise it
resolves the dependency at run time with clojure.repl.deps/add-libs,
which needs Clojure 1.12 or later.
You can also skip the runtime resolution by providing the dependency
yourself:
$ clojure -Sdeps '{:deps {org.yamlscript/ys.v0 {:mvn/version "0.2.32"}}}' \
-M program.clj
Portable Clojure dialects🔗
The star target emits a dialect-neutral dependency preamble through
the clojurestar.deps API.
$ ys --to=star program.ys > program.clj
Compatible runtimes such as Glojure, Jolt, and Gobb provide that API.
They resolve org.yamlscript/ys.v0, then run the same portable program
body.
Limitations🔗
A few standard library functions need the YS compiler and only work under the ys runtime. On other runtimes they fail with a clear error message:
eval— evaluates YS source codeload— loads a YS fileuse— loads YS modulesload-url— loads YS code from a URLnew— reflective constructor calls (works on JVM Clojure but not Babashka)
The yaml, json, http, shell and ordered map functions resolve their backend libraries (clj-yaml, data.json, http-client, process, flatland ordered) lazily at first call, so the library loads even on runtimes that can't load those backends (like jolt, currently); the functions themselves fail with a clear message there.
Data documents (which ys prints as YAML output) are evaluated but not printed when running compiled code on other runtimes.