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)