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:
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!
Leave a Reply