How to create a Clojure application

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
Advertisements

5 thoughts on “How to create a Clojure application

  1. Hi Pablo,

    thank you, great article especially for one without any basic knowledge of git/maven like me.

    I’looking for a way to build my clojure apps and I think I will give maven a try.

    But I think there’s a little cloud in the soup. When I create my clojure apps with netbeans, the clojure code is compiled into java classes and packed into a jar file that I can run simply by double clicking.

    Your way is to load the uncompiled clojure code at runtime. So you wrote a little wrapper for the clojure interpreter. This means starting the app takes a bit longer and execution speed is not at the level it could be.

    Hints for compiling clojure code are given in the excellent clojure article of Mark Volkmann at: http://java.ociweb.com/mark/clojure/article.html

    with best regards
    Bernd

    • Bernd, you are absolutely right. When I wrote this tutorial, the Clojure compiler couldn’t generate .class files, so you could only feed it source code at run-time and it would compile and and then run it. Nowadays with Clojure being a full-fledge Java compiler the way to go is really by compiling the code at compile-time. That should be doable with Maven as well, but probably more involved because you need to add support to Maven for another compiler. I don’t know how that is done. It’s possible someone done it already.

  2. Bernd, thank you for sharing the time to share this with us. I intend to start toying with clojure & git as soon as I get Eclipse up and running again. As for Maven : all good, except you do not have to specify the resources directory in the POM if you put them in the standard directory for resources, under src/main/resources. They will get copied to the jar. You can also use src/test/resources if you have test-only resources.

  3. Hi, there is a maven-plugin for compiling clojure-code at “scm:git:git://github.com/talios/clojure-maven-plugin.git”. Thanks to my collegue Christian now everything works fine, with assembly-plugin I even can create a zip file containing all referenced libraries for deployment. It’s great – creating real clojure applications by one small command.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s