DEV Community

Maksim
Maksim

Posted on • Originally published at maksimrv.Medium on

Compile Clojure to native binary using GraalVM

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
Enter fullscreen mode Exit fullscreen mode

add basic deps.edn

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

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

(ns echo.main)

(defn -main [& args]
  (apply println args))
Enter fullscreen mode Exit fullscreen mode

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

clj -M -m echo.main foo bar
foo bar
Enter fullscreen mode Exit fullscreen mode

🥈 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 }))
Enter fullscreen mode Exit fullscreen mode

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}} }
Enter fullscreen mode Exit fullscreen mode

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

(ns echo.main
  (:gen-class))

(defn -main [& args]
  (apply println args))
Enter fullscreen mode Exit fullscreen mode

Now we are ready to build uberjar

clj -T:build uberjar
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

🥉 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}}}
Enter fullscreen mode Exit fullscreen mode

Rebuild jar

clj -T:build uberjar
Enter fullscreen mode Exit fullscreen mode

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

native-image -jar target/echo.jar --no-fallback --no-server target/echo
Enter fullscreen mode Exit fullscreen mode

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

Let’s test our binary

./target/echo foo bar
Enter fullscreen mode Exit fullscreen mode

The end 🎉

Top comments (0)