Compiling Java Classes with Clojure Deps
In the past, I used Leinigen for Clojure projects. It downloads dependencies, compiles, handles
the classpath, runs tests etc. Clojure 1.9 introduced the clj
command line and
the deps tooling. You can specify a deps.edn file and declare the dependencies in there. Deps not a build tool by
design. it only downloads dependencies from Maven- and git repositories and builds the Java classpath. It doesn’t have
other build features. However, for smaller Clojure projects that is enough and you can go without an extra build tool.
So, I started to use Deps as the starting point and it serves me well so far. Recently I wanted to add a Java class to
my project. I do that when using Clojure-Java interop gets complex and it is just easier to have a few helper Java
classes. However, how do I compile these classes? Leiningen has the lein javac
command, but Deps doesn’t do builds.
Add Compiled Classes to Classpath
I started by compiling classes manually
with IntelliJ IDEA. Unfortunately, when trying to use the class I got class
not found exceptions.
user=> (blog_example.Test.)
Syntax error (ClassNotFoundException) compiling new at (REPL:1:1).
blog_example.Test
Well, the compiled Java classes need to be on the classpath build by Deps. You need to add the directory with the compiled classes to the :paths section in the deps.edn. I use the 'classes' directory which seems the Clojures default:
{:paths ["src" "classes"]
:deps {org.clojure/clojure {:mvn/version "1.10.1"}
...}
After that, clj
does find the compiled classes.
:aliases, Secret Weapon of clj and deps.edn
Deps has an :aliases section. Aliases allow to create different classpaths for different environments, like support for different Clojure/Clojurescript versions, test environments etc. It also allows to change the default start command for that alias. That combined you can create your small commands for a project:
:aliases {
; Test environment
:test {:extra-paths ["test"]}
; Find outdated dependencies: clojure -Aoutdated -a outdated
:outdated {:extra-deps {olical/depot {:mvn/version "1.8.4"}}
:main-opts ["-m" "depot.outdated.main" "-a" "outdated"]}
}
So, we can create a command to compile our Java class. I remembered the standard Java API to compile [https://docs.oracle.com/javase/8/docs/api/javax/tools/JavaCompiler.html]Java, but I wanted something which only needs a few lines. Or maybe I can invoke the 'javac' compiler myself. Anyway, the solution needs to be small, otherwise, I switch to Leinigen
Badigeon Library: Compile stuff
My search leads to the Badigeon library, which exactly gives you simple compile commands.
So, I created a build/build.clj file with a simple main function, which invokes the Java compiler:
(ns build
(:require [badigeon.javac :as j]))
(defn javac []
(println "Compiling Java")
(j/javac "src" {:compile-path "classes"
;; Additional options used by the javac command
:compiler-options ["-cp" "src:classes" "-target" "1.8"
"-source" "1.8" "-Xlint:-options"]})
(println "Compilation Completed"))
(defn -main []
(javac))
And in the deps.edn
file I added a :javac alias, which invokes this script.
:aliases {:javac {:extra-paths ["build"]
:extra-deps {badigeon/badigeon {:git/url "https://github.com/EwenG/badigeon.git"
:sha "c7588e6d2c66284dcda1a339adcba8cb9c74a8b0"
:tag "0.0.9"}}
:main-opts ["-m" "build"]}}
And then I invoke the compile step with:
gamlor@gamlor-t470p ~/h/example> clj -Ajavac Compiling Java SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. Compilation Completed
That’s good enough for me right now. As the project matures I probably will create a more extended build or switch to Leinigen if I need more.
More Deps Tool
Also, check out this list of Deps tools: https://github.com/clojure/tools.deps.alpha/wiki/Tools