Compile Clojure to native binary using GraalVM

Maksim Ryzhikov
2 min readMar 31, 2022

--

We’ll write echo command on Clojure and compile it to native binary by GraalVM.

🥇 Setup

The first thing first. Create a project directory echo

mkdir /tmp/echo

add basic deps.edn

{:paths ["src"]
:deps {org.clojure/clojure {:mvn/version "1.11.0"}}}

Create src/echo/main.clj. This would be our entry point:

(ns echo.main)(defn -main [& args]
(apply println args))

Run clj -M -m echo.main to be sure that all works fine

clj -M -m echo.main foo bar
foo bar

🥈 Build uberjar

Add build.clj to project’s root directory. This file contains logic how to build a Java jar

(ns build
(:require [clojure.tools.build.api :as b]))
(def class-dir "target/classes")
(def basis (b/create-basis {:project "deps.edn"}))
(def jar-file "target/echo.jar")
(defn clean [_]
(b/delete {:path "target"}))
(defn uberjar [_]
(clean nil)
(b/copy-dir {:src-dirs ["src"]
:target-dir class-dir})
(b/compile-clj {:basis basis
:src-dirs ["src"]
:class-dir class-dir})
(b/uber {:class-dir class-dir
:uber-file jar-file
:basis basis
:main 'echo.main}))

Now let’s add tools.build and :build task to deps.edn

{:paths ["src"]
:deps {org.clojure/clojure {:mvn/version "1.11.0"}}
:aliases {:build
{:deps
{io.github.clojure/tools.build {:git/tag "v0.8.1" :git/sha "7d40500"}}
:ns-default build}}
}

And the last thing is to mark our echo.main as :gen-class

(ns echo.main
(:gen-class))
(defn -main [& args]
(apply println args))

Now we are ready to build uberjar

clj -T:build uberjar

This command will create a jar in the target folder. You can run it by java

java -jar target/echo.jar foo bar
foo bar

🥉 Build native

To build native script, we should install GraalVM after that we should add com.github.clj-easy/graal-build-time as a dependency to automate declaration of libraries by — initialize-at-build-time

{:paths ["src"]
:deps {org.clojure/clojure {:mvn/version "1.11.0"}
com.github.clj-easy/graal-build-time {:mvn/version "0.1.4"}}
:aliases
{:build
{:deps
{io.github.clojure/tools.build {:git/tag "v0.8.1" :git/sha "7d40500"}}
:ns-default build}}}

Rebuild jar

clj -T:build uberjar

and we are ready to run native-image to build binary

native-image -jar target/echo.jar --no-fallback --no-server target/echo

This command will generate a native binary from the jar to target/echo

Let’s test our binary

./target/echo foo bar

The end 🎉

--

--