Skip to content

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 External link or JVM Clojure.

To do that, the compiled code needs the YS standard library. It is published to Clojars as org.yamlscript/ys.v0 External link .

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 code
  • load — loads a YS file
  • use — loads YS modules
  • load-url — loads YS code from a URL
  • new — 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.