Update: this is not my preferred way to create a Clojure application and shouldn’t be yours either, check out Leiningen

This is one of those posts that I publish partly for myself. And partly so people can criticize my way, which is also for myself, and only incidentally for others to learn from it.

It seems Maven is popular in the Clojure world. Clojure itself uses it, Webjure uses and its demo application uses it as well, there’s a branch for Compojure that uses too. So after building all those components using Maven I’ve decided to use it myself when building Clojure applications.

I’m using the latest Clojure from trunk, so let’s get it:

git svn clone -s https://clojure.svn.sourceforge.net/svnroot/clojure

By the way, I don’t use Subversion anymore. Even if the repository is Subversion, or whatever, I use Git. Let’s build and install Clojure:

cd clojure
mvn install

Maven downloads every dependency it needs, so the first time you build Clojure, it may take a longer, much longer. If you using Ubuntu, installing Maven itself is also very easy:

aptitude install maven2

and if you are using another operating system that has a package manager, it’s likely to be as easy. Otherwise, reefer to the Maven documentation on getting it installed.

Let’s call the application Clap (Clojure application, get it?), and it’ll be the flagship product of Example Inc. with web site on example.com. Let’s create the application:

mvn archetype:create -DgroupId=com.example.clap -DartifactId=clap

after some noise it should end with:


[INFO] OldArchetype created in dir: /…/clap
[INFO] ————————————————————————
[INFO] BUILD SUCCESSFUL
[INFO] ————————————————————————
[INFO] Total time: 4 seconds
[INFO] Finished at: Sat Oct 25 10:42:10 CEST 2008
[INFO] Final Memory: 9M/95M
[INFO] ———————————————————————–

What is the first thing to do now? Come on, do an effort. Think. You are a good developer. What do good developers do with their code? They put it into a repository!

That was not for myself, I’ve been using source control almost since my hello world.

cd clap/
git init
Initialized empty Git repository in .git/
git add .
git commit -am "Project created: mvn archetype:create -DgroupId=com.example.clap -DartifactId=clap"
Created initial commit f042529: Project created: mvn archetype:create -DgroupId=com.example.clap -DartifactId=clap
 3 files changed, 69 insertions(+), 0 deletions(-)
 create mode 100644 pom.xml
 create mode 100644 src/main/java/com/example/clap/App.java
 create mode 100644 src/test/java/com/example/clap/AppTest.java

Of course I’m using Git! What did you expect!

Maven created three files for us. pom.xml is a kind of build file, App.java is the main class of the application and AppTest.java is its test. Note the different in path. We can try building the application now:

mvn package
...
[INFO] Building jar: /.../clap/target/clap-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5 seconds
[INFO] Finished at: Sat Oct 25 10:49:41 CEST 2008
[INFO] Final Memory: 15M/121M
[INFO] ------------------------------------------------------------------------

Built! Good. We can run it now:

java -cp target/clap-1.0-SNAPSHOT.jar com.example.clap.App
Hello World

Excellent, it works.

Let’s turn it into a Clojure application now (so far everything is pretty standard unless you are a Maven newbie like myself). We create a directory for the Clojure code like there’s one for the Java code:

mkdir -p src/main/clojure/com/example/clap/

Clojure namespaces are different from Java packages, but you can use them in a similar fashion and have a com.example.clap namespace and put those files into that directory. In that directory we put our Clojure hello-world with the name clap.clj and with this contents (gracefully copied from the projects generated by Enclojure, the Clojure plug in for Netbeans):

(ns com.example.clap)

(defn main [args]
  (println "Hello World!")
  (println "Java main called clojure function with args: "
     (apply str (interpose " " args))))

And we turn App into a simple wrapper that runs the Clojure code hopping to never ever touch Java again (again, gracefully copied):

package com.example.clap;

import clojure.lang.RT;

public class App {
    private static final String MAINCLJ = "com/example/clap/clap.clj";

    public static void main(String[] args){
        System.out.println("Java Hello World!" );
        try {
            RT.loadResourceScript(MAINCLJ);
            RT.var("com.example.clap", "main").invoke(args);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

Ready? Let’s build it:

/…/clap/src/main/java/com/example/clap/App.java:[3,19] package clojure.lang does not exist

Failing as expected. Let’s add Clojure as a dependency in the pom.xml file by adding:

  jvm.clojure
  clojure-lang
  1.0-SNAPSHOT

inside <dependencies>, next to the JUnit dependency. Let’s rebuild:

mvn package
...
[INFO] Building jar: /.../clap/target/clap-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6 seconds
[INFO] Finished at: Sat Oct 25 11:07:07 CEST 2008
[INFO] Final Memory: 15M/120M
[INFO] ------------------------------------------------------------------------

Good. Let’s run it:

java -cp target/clap-1.0-SNAPSHOT.jar com.example.clap.App
Java Hello World!
Exception in thread "main" java.lang.NoClassDefFoundError: clojure/lang/RT
	at com.example.clap.App.main(App.java:11)
Caused by: java.lang.ClassNotFoundException: clojure.lang.RT
	at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:276)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
	at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
	... 1 more

What’s happening here is that Clojure was installed by Maven somewhere in ~/.m2/ but that is not in our classpath. There are various solutions for this problem, I’ve used the Application Assembler Maven Plugin. Just go and check its documentation how to modify the pom.xml, I don’t want to keep dumping pieces of XML here (I’ll dump my whole pom.xml at the end). Let’s build and try it:

mvn package appassembler:assemble
...
[INFO] Building jar: /.../clap/target/clap-1.0-SNAPSHOT.jar
[INFO] [appassembler:assemble]
[INFO] Installing /.../.m2/repository/jvm/clojure/clojure-lang/1.0-SNAPSHOT/clojure-lang-1.0-SNAPSHOT.jar to /.../clap/target/appassembler/repo/jvm/clojure/clojure-lang/1.0-SNAPSHOT/clojure-lang-1.0-SNAPSHOT.jar
[INFO] Installing /.../clap/target/clap-1.0-SNAPSHOT.jar to /.../clap/target/appassembler/repo/com/example/clap/clap/1.0-SNAPSHOT/clap-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4 seconds
[INFO] Finished at: Sat Oct 25 11:13:57 CEST 2008
[INFO] Final Memory: 11M/117M
[INFO] ------------------------------------------------------------------------
sh ./target/appassembler/bin/clap
Java Hello World!
java.io.FileNotFoundException: Could not locate Clojure resource on classpath: com/example/clap/clap.clj
	at clojure.lang.RT.loadResourceScript(RT.java:370)
	at clojure.lang.RT.loadResourceScript(RT.java:352)
	at clojure.lang.RT.loadResourceScript(RT.java:344)
	at com.example.clap.App.main(App.java:11)

What is happening now is that everything builds, all the dependencies are correctly in place but the file clap.clj was not included in the jar file. To achieve that we have to tell Maven to treat src/main/clojure as resources. We do that with more XML in our pom.xml file, inside <build>:


    src/main/clojure

Let’s try again:

mvn package appassembler:assemble
...
[INFO] Building jar: /.../clap/target/clap-1.0-SNAPSHOT.jar
[INFO] [appassembler:assemble]
[INFO] Installing /.../.m2/repository/jvm/clojure/clojure-lang/1.0-SNAPSHOT/clojure-lang-1.0-SNAPSHOT.jar to /.../clap/target/appassembler/repo/jvm/clojure/clojure-lang/1.0-SNAPSHOT/clojure-lang-1.0-SNAPSHOT.jar
[INFO] Installing /.../clap/target/clap-1.0-SNAPSHOT.jar to /.../clap/target/appassembler/repo/com/example/clap/clap/1.0-SNAPSHOT/clap-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4 seconds
[INFO] Finished at: Sat Oct 25 11:13:57 CEST 2008
[INFO] Final Memory: 11M/117M
[INFO] ------------------------------------------------------------------------
sh ./target/appassembler/bin/clap
Java Hello World!
Hello World!
Java main called clojure function with args:

And that’s it. Last step, submit it:

git add pom.xml src/main/java/com/example/clap/App.java src/main/clojure/
git commit -am "Turned into a Clojure application."

Done. My pom.xml looks like this:

  4.0.0
  com.example.clap
  clap
  jar
  1.0-SNAPSHOT
  clap
  http://maven.apache.org




        src/main/clojure





        org.codehaus.mojo
        appassembler-maven-plugin



              com.example.clap.App
              clap









      junit
      junit
      3.8.1
      test



      jvm.clojure
      clojure-lang
      1.0-SNAPSHOT


Happy hacking!

Document Actions

You may also like:

If you want to work with me or hire me? Contact me

You can follow me or connect with me:

Or get new content delivered directly to your inbox.

Join 5,047 other subscribers

I wrote a book:

Stack of copies of How to Hire and Manage Remote Teams

How to Hire and Manage Remote Teams, where I distill all the techniques I’ve been using to build and manage distributed teams for the past 10 years.

I write about:

announcement blogging book book review book reviews books building Sano Business C# Clojure ClojureScript Common Lisp database Debian Esperanto Git ham radio history idea Java Kubuntu Lisp management Non-Fiction OpenID programming Python Radio Society of Great Britain Rails rant re-frame release Ruby Ruby on Rails Sano science science fiction security self-help Star Trek technology Ubuntu web Windows WordPress

I’ve been writing for a while:

Mastodon