Clojure in Eclipse, Part 1: Maven
March 13, 2012 at 22:24
Nico in Clojure, Clojure, Eclipse, Maven

This post explains how to write and deploy a Clojure application with Eclipse and Maven, the build automation tool for Java that is bundled in the Eclipse package “Eclipse IDE for Java”.

In comparison to other articles about Clojure and Maven, this article is Eclipse centric and task focused. We will:

Maven did not start as an Eclipse tool. It is a standalone piece of software, which exists separately from Eclipse and is usable from inside Eclipse via the m2e plugin. Maven is itself extensible with plugins, one of which allows it to compile Clojure code.

For now, we will not use Counterclockwise, the Clojure plugin for Eclipse. This is on purpose, in order to help us understand what we can do without it.

We will start by writing the code and creating the project. Then I'll explain about Maven. From there we will configure the project and we will finally run and package it.

The resulting project can be found on github. Any trouble with pom.xml during the course of this tutorial, have a look at the final pom.xml.

The Code

Our application is a standalone command-line program that exclaims: "Yes, I wrote Clojure in Eclipse and tested/ran/deployed it with Maven!" (a bit of pride never hurts).

The project shall consist of four Clojure codes.

Namespace chaomancy.maven is the application.

(ns chaomancy.maven

  (:gen-class))

 

(defn exclaim []

      "Yes, I wrote Clojure in Eclipse and tested/ran/deployed it with Maven!")

 

(defn -main [& args] (println (exclaim)))

Namespace chaomancy.test.maven contains a single test, which uses the clojure.test toolset:

(ns chaomancy.test.maven

  (:use chaomancy.maven)

  (:use clojure.test))

 

(deftest

  test-maven-msg

  (is (= (exclaim)

         "Yes, I wrote Clojure in Eclipse and tested/ran/deployed it with Maven!")))

the script maven-run.clj is a basic launcher for the application

(use '(chaomancy maven))

 

(-main)

the script maven-repl.clj contains the instruction we want to be executed when we start the REPL.

(use '(chaomancy maven))

(use '(chaomancy.test maven))

(use '(clojure test))

 

(println "Namespaces loaded. You can now write some Clojure.")

Installing Eclipse

If you don't have Eclipse installed yet, then you can just download and install the “Eclipse IDE for Java Developers” (not the EE version). Install it, fire it up and go to Window > Open Perspective > Other..., pick Java (default) and off you go. If you are a Linux user, do not install Eclipse via your distribution's software installation center; get it from the Eclipse website instead (speaking from painful experience here).

If you already have Eclipse installed, update it to the latest version, and check whether you have plugin m2e - Maven Integration for Eclipse installed already. If not, then do install it (version 1.0 or later)

Creating the project

Let us create our project and place our code in it.

Maven Project

Execute File > New > Project... and choose Maven Project. In the New Maven Project window:

Tick Build automatically off in the Project menu, so that it doesn't slow things down when we declare dependencies and configure plug-ins later.

Source Folders and Clojure Files

The project is structured for Java codes. Let us keep the Java folders and create Clojure folders alongside them.

Using command File > New > Source Folder, create the following source folders in the project:

Using the commands File > New > Package and File > New > File, create the following packages and source files in the source folders we just created.

Now we are ready for some configuration action.

Planning the configuration of Maven and Eclipse

How Maven Works

The pom.xml file tells Maven how it should handle your project. Double click on it. See the actual content of the file in the pom.xml tab and how Maven interprets it in the Effective POM tab. Maven's default implicit POM is much larger than what our pom.xml says. Whatever we enter in pom.xml will override or complement what Maven does by default. An empty pom.xml doesn't mean that Maven won't do anything: it means that we haven't overriden any of Maven's defaults.

Have a look at the Effective POM, and in particular:

Maven splits the development lifecycle of a project in phases. We'll look at these in a second, but let's remain in the POM for now to look at one of them: the test phase.

Search for maven-surefire-plugin in the effective POM. What we see is that the test command (a “goal” in Maven linguo) of the Surefire plugin is bound to Maven's test phase. How does this work? When Maven runs the test phase, it goes through pom.xml, looks for all plugins that have an element <phase>test</phase>, and runs the indicated goal/command of the plugins it finds. In this case, Maven knows that, for the test phase, it needs to execute the test goal of the Surefire plugin (abbreviated surefire:test)

Exercise: What does Maven run by default at the compile phase? The compile goal of maven-compiler-plugin (abbreviated compiler:compile).

A bit cumbersome, but we can now guess how we will tell Maven to compile clojure code. It will be by adding a new plugin that knows how to handle Clojure and by binding some of its goals to some of Maven's phases. Cool?

Now let's look at the overall project lifecycle in Maven. Here is a list of Maven's phases for a jar deployed project (see the introduction to Maven's build lifecycle). I encourage you to locate how each of these phases is bound to a plug-in in the effective POM, and to check that it matches the table below.

Phase What it does plugin:goal identifier
process-resources copy and process the resources into the destination directory, ready for packaging resources:resources
compile compile the source code of the project compiler:compile
process-test-resources copy and process the resources into the test destination directory. resources:testResources
test-compile compile the test source code into the test destination directory compiler:testCompile
test test the compiled source code using a suitable unit testing framework surefire:test
package take the compiled code and package it in its distributable format, such as a JAR jar:jar
install install the package into the local repository, for use as a dependency in other projects locally install:install
deploy copies the final package to the remote repository for sharing with other developers and projects deploy:deploy

An important convention here is: when you call a phase in Maven, all the prior phases are executed too. If you call the deploy phase (the last one in the list), then all phases will be run; Maven will compile, re-test and re-package the project before deploying.

In practice and by default, Eclipse only allows you to call phases test and install via the GUI, but we can define launchers for other phases or for specific goals by ourselves, which we will merrily do.

Clojure-maven-plugin

Clojure functionalities for Maven are provided by the clojure-maven-plugin, which provides Clojure related functions, or goals. They can be bound to phases of the Maven life-cycle or can be called independently.

To know more about the clojure-maven-plugin, have a read through its documentation and How to use Maven to build Clojure code (which this post overlaps with).

The plug-in's goals (i.e. commands) that we'll use are:

Our Aim

Ok, let's review everything we want to do.

First, in the pom.xml, we will tell Maven what extra things it needs to do when it goes through its standard lifecycle phases.

We will also create launchers in Eclipse to:

So our bespoke lifecycle, with our changes and additions in red, will be:

Phase plugin:goal identifier Callable via GUI
process-resources resources:resources -
compile compiler:compile clojure:compile -
process-test-resources resources:testResources -
test-compile compiler:testCompile -
  clojure:repl Let's add it
  clojure:run Let's add it
test surefire:test clojure:test Already there
package jar:jar assembly:single Let's add it
install install:install Already there
deploy deploy:deploy -

I visually inserted clojure:repl and clojure:run in the life-cycle. We will make them call the phases that are before them in the list, but they will be “dead-ends”, in the sense that they won't contribute to the later phases of the lifecycle.

Still with me? Off we go... now it's just action.

Configuring Maven

Let us populate pom.xml. Most of our changes will be done directly in the XML file. But I will use GUI helpers when they exist, for they help working library versions out.

Repositories

First, add the following block to pom.xml. This tells Maven where Clojure libraries can be found.

<repositories>

    <repository>

        <id>Clojure Releases</id>

        <url>http://build.clojure.org/releases</url>

    </repository>

    <repository>

        <id>Clojars</id>

        <url>http://clojars.org/repo</url>

    </repository>

</repositories>

Dependencies

The only dependency we will declare here is the Clojure language library.

This is the place you should come back to when you want to add other libraries to your project later (e.g. incanter).

Go see the content of pom.xml and notice the <dependencies> block that was added.

Note that Maven will place downloaded libraries into a local repository. This way it won't need to go online when you need them next time.

Registering the Clojure plugin and binding it to Maven's phases

right-click on the project in the Package Explorer and go to Maven > Add Plugin.

Type clojure, which brings com.theoryinpractice clojure-maven-plugin. Expand it and pick the latest version.

The nice thing with using GUI tools is that they make you aware of the latest releases of libraries and plugins.

Now, there are two of the plug-in's goals (commands) that we want to bind to Maven phases. The compile goal to the compile phase and the test goal to the test phase. In order to do so, edit the pom.xml:

<plugin>

    <groupId>com.theoryinpractice</groupId>

    <artifactId>clojure-maven-plugin</artifactId>

    <version>1.3.9</version>

    <executions>

        <execution>

            <id>clojure-compile</id>

            <phase>compile</phase>

            <goals>

                <goal>compile</goal>

            </goals>

        </execution>

        <execution>

            <id>clojure-test</id>

            <phase>test</phase>

            <goals>

                <goal>test</goal>

            </goals>

        </execution>

    </executions>

</plugin>

For tests, the plugin uses a default test script, which you can change to your own. See the documentation .

Binding packaging plugins to Maven's package phase

In the effective POM, you can see that the maven-jar-plugin is bound by default to Maven's package phase. This plugin will create a jar file, dependencies excluded. Let us tell the plugin that the executable class of our project is chaomancy.maven. For this, add the following lines to pom.xml (note: for version number, use the version number that is in the effective POM).

<plugin>

    <artifactId>maven-jar-plugin</artifactId>

    <version>2.3.1</version>

    <configuration>

        <archive>

            <manifest>

                <mainClass>chaomancy.maven</mainClass>

            </manifest>

        </archive>

    </configuration>

</plugin>

This is all good, but I would also like to create a distribution of the application. This will be a jar with dependencies included (i.e. the bits of clojure that are used by our code) that we can execute on any machine that has java on it. This is something for the maven-assembly-plugin. My prefered option is to bind it to the package phase too. We also need to tell it that the executable class in our project is chaomancy.maven. I omitted the plugin version; at the time of writing, Maven seems to know how to manage version on its own.

<plugin>

    <artifactId>maven-assembly-plugin</artifactId>

    <executions>

        <execution>

            <phase>package</phase>

            <goals>

                <goal>single</goal>

            </goals>

        </execution>

    </executions>

    <configuration>

        <archive>

            <manifest>

                <mainClass>chaomancy.maven</mainClass>

            </manifest>

        </archive>

        <descriptorRefs>

            <descriptorRef>jar-with-dependencies</descriptorRef>

        </descriptorRefs>

    </configuration>

</plugin>

We are done with binding plugins to the Maven life-cycle.

Setting scripts paths

Before we go and setup launchers in Eclipse, let's go back to the <plugin> block of the clojure-maven-plugin and tell the repl goal and run goal what scripts to use. Just before the closing </plugin> tag, insert:

<configuration>

    <replScript>src/main/scripts/maven-repl.clj</replScript>

    <script>src/main/scripts/maven-run.clj</script>

</configuration>

Later, have a look at all the plugin's configuration options in the documentation

We are done with editing the POM. If it is a bit messy, open the code and execute Source > Format to make it all nice and indented.

Tidying up

Before going further, we need to deal with a peculiarity in the way Eclipse handles Maven plug-ins, and with a small Eclipse bug with source folders.

The POM now contains a new big <pluginManagement> block, which tells Eclipse to ignore what it doesn't like with the maven-clojure-plugin. Not very elegant, but well...

You might also see an error "Project configuration is not up-to-date". If so then right-click on it, select Quick Fix and then press Finish. This probably made Eclipse loose track of your source folders. If so, recreate them using command File > New > Source Folder:

This will make make source folders, packages and scripts reappear where they should.

Pfew. Done with Eclipse's idiosyncrasies, and your pom.xml should look like this.

Creating Eclipse Launchers

What I call launcher is called a Run Configuration in Eclipse. Let's create some.

Go to the menu Run > Run Configurations...

Click and highlight Maven Build and press the New icon (the blank document with a + in the top right corner), and create new configurations:

And we are done.

Running, testing and packaging the project

Let's have fun now. The first time you perform a task, Maven will spend a few seconds to download the packages it needs from online repositories, so don't worry if the log is cluttered at first. This is why we turned Build Automatically off. Once you've done something a first time, redo it and go through the uncluttered log to see what happened.

Let's start by running our application via the maven-run.clj script:

Now let's test our code:

Next Let's start a REPL, which we configured to be initialised with maven-repl.clj:

Now for the kill, time to package our app:

  • Finally, we can install the application in our local Maven repository to make it available to ourselves for future projects
  • We're done.

    What Next?

    I hope that this article gave you the confidence you can write, test and deploy whatever you want with Eclipse and Maven. Obiously, such an infrastructure makes sense for projects that are a tad bigger than the one we just created!

    You can get away with skipping some of the configuration steps we took. But my goal has been to make you take control of Maven, its life-cycle, its plug-ins, so that you can make Maven dance for you. You should now also be able to experiment with launching other phases (I left deploy for you) and other commands/goals of the clojure-maven-plugin. We haven't looked into what Maven does during its resource phases, which I leave you explore.

    Further readings I suggest from here are:

    Now, our clojure codes were just flat files in Eclipse. No code highlighting, and no Clojure orientated productivity gadgets. This is where Counterclockwise, the indispensable Clojure plugin for Eclipse, comes in. This is the object of the next tutorial: Part 2: Counterclockwise + Maven.

    Article originally appeared on Chaomancy (http://www.chaomancy.com/).
    See website for complete article licensing information.