Clojure in Eclipse, Part 1: 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:
- create and structure a Maven project in Eclipse.
- understand the Maven life-cycle, and tweak it to compile, test and package our Clojure codes.
- configure Eclipse so that we can trigger all the phases of the Maven life-cycle from the GUI.
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 Create a simple project and press Next >
- Enter the GroupId, ArtifactId and Version
(mandatory, see their definitions)
as well as the name and description (optional). ArtifactId will be used as project
identifier in Eclipse. Also notice that the packaging is set to jar.
GroupId: chaomancy
ArtifactId: clj-maven-tutorial
Version: 0.0.1-SNAPSHOT
Name: clj-maven-tutorial
Description: Clojure in Eclipse with Maven
- Press Finish.
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:
- src/main/clojure
- src/test/clojure
- src/main/scripts
Using the commands File > New > Package and File > New > File, create the following packages and source files in the source folders we just created.
- In src/main/clojure
- create a package called chaomancy
- in this package, create a file called maven.clj and copy the code of namespace chaomancy.maven into it.
- In src/test/clojure
- create a package called chaomancy.test
- in this package, create another file called maven.clj and copy the code of namespace chaomancy.test.maven into it. See the pattern regarding package and file name? The word after the last dot in the namespace should be the name of the source file, and everything before the last dot is the name of the package in which the source file should be placed.
- In src/main/scripts
- create files maven-repl.clj and maven-run.clj and copy their respective codes into them.
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:
- <repositories> These are the online repositories in which Maven will search for libraries. We will provide new ones for clojure codes.
- <sourceDirectory> and following lines: Where Maven expects to find files / intends to write files. We will not need to override this, but be aware of them.
- <plugins> This is a list of all the bits of software that Maven delegates code processing tasks to. This is all explained in this introduction to Maven's build lifecycle
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:
- compile: compiles the clojure codes in the project. It should be bound to Maven's compile phase.
- test: executes the clojure tests in the project. It should be bound to Maven's test phase.
- repl: this command allows running an interactive REPL. It doesn't belong to any existing Maven phase, so we'll treat it as an extra bespoke phase.
- run: this commands allows running scripts in the project. It doesn't belong to any existing Maven phase either.
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.
- compile phase: execute the compile goal of the clojure plugin (clojure:compile)
- test phase: execute the test goal of the clojure plugin (clojure:test)
- package phase: package our application both as a jar file (with jar:jar) and as a distribution (with assembly:single). I haven't explained about this yet; will do further down.
We will also create launchers in Eclipse to:
- call the package phase (a nice to have for non production projects such as this one).
- Execute tasks that are not part of Maven's vanilla lifecycle:
- Launching a REPL and initialising it with the maven-repl.clj script.
- Launching the maven-run.clj script.
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.
- Move to to the Dependencies pane of the POM editor and click Add... (or right-click on the project and go to Maven > Add Dependency).
- Type clojure, which brings all the libraries that Maven found in the repositories and that have clojure in their name. Scroll to and expand org.clojure clojure, and pick the version of clojure you want (I am picking 1.3.0 [jar]).
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.
- Open the Problems window and notice errors there, as well as the error flags in the Package Explorer (white x on red square). This is because the maven-clojure-plugin doesn't implement some functions that Eclipse is expecting. We don't really care about this (as far as I am aware), so we are going to ask Eclipse to ignore this.
- In the Problems window, expand Errors and Warnings. right-click "plugin execution not covered...", select Quick Fix, and choose "permanently mark goal compile in pom.xml as ignored" . The error message should disappear; delete it (right-click) if it doesn't.
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:
- src/main/clojure
- src/test/clojure
- src/main/scripts
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:
- The first one to run the package phase
- Name: maven-tutorial package
- Base Directory: press Browse Workspace and choose the project, which should result in ${workspace_loc:/clj-maven-tutorial}
- Goals: package
- Press Apply and New again for the next configuration.
- The second one to start the REPL
- Name: maven-tutorial REPL
- Base Directory: {workspace_loc:/clj-maven-tutorial}
- Goals: clojure:repl
- Press Apply and New again for the next configuration.
- The last one to call the compile phase and then run our script
- Name: maven-tutorial run
- Base Directory: {workspace_loc:/clj-maven-tutorial}
- Goals: compile clojure:run (i.e. call the compile phase and then call goal clojure:run)
- Press Apply and Close.
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:
- Execute Run > Run As... > Maven Build and select maven-tutorial run
- See in the log how all phases from process-resources to compile were called, after which our script was executed ("Yes, I wrote Clojure in Eclipse and tested/ran/deployed it with Maven!") and a report was produced. Remember that we configured the run configuration to call the compile phase first and then clojure:run (in this case, calling clojure:run only would have run the clojure code fine; but I am not sure resources processing and java compilation would have taken place).
Now let's test our code:
- Execute Run > Run As... > Maven test
- Notice the Maven phases that were executed, and look at the test traces in the log. There are two of them: the first is the one for Java codes in the project (none). The second is the one for clojure codes. All successful. Go and change the test to make it fail to see how it looks like in the log.
Next Let's start a REPL, which we configured to be initialised with maven-repl.clj:
- Execute Run > Run As... > Maven Build and select maven-tutorial REPL
- And play. Try things such as (exclaim) or (run-tests 'chaomancy.test.maven)
- See in the log how all phases from process-resources to testcompile were called, after which our initialisation script was executed. When we created the run configuration for this, we didn't tell that we wanted test-compile to be executed prior to goal clojure:repl. The clojure-maven-plugin did this on its own.
Now for the kill, time to package our app:
- Execute Run > Run As... > Maven Build and select maven-tutorial package
- The log shows that your code was retested and then packaged as two jars, created by the two plugins we bound to the package phase earlier.
- Open a terminal window, go to the target directory in the
project, and type:
java -jar clj-maven-tutorial-0.0.1-SNAPSHOT-jar-with-dependencies.jarwhich should display "Yes, I wrote some Clojure in Eclipse and tested/ran/deployed it with Maven!".
Mission accomplished.
- Execute Run > Run As... > Maven install
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:
- documentation of clojure-maven-plugin
- How to use Maven to build Clojure code by Alex Ott. Check his other Clojure posts.
- maven.apache.org, and
in particular:
- Introduction to Maven's build lifecycle
- Available Plugins, which are plenty. Now you know how to declare them in pom.xml, bind them to the life-cycle or call their goals from the GUI.
- the complete code of this tutorial on github
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.
Reader Comments (12)
awesome!
seems "maven-repl.clj" should be added one more line "(use '(chaomancy.test maven))" ;D
Oups, indeed. Thank you for spotting it! Correction made.
Thank you also for your feedback. I am very glad that you found the article useful.
thanks, enjoyed the article very much as i am starting to learn clojure.
Great article, thanks!
One small problem so far: you've spelled "theoryinpracitse" as "theoryinpractiCe" (with 'c' instead of 's') in one place. For some reason eclipse didn't suggest anything, when I tried to add the plugin as you've described, so I tried by adding the groupId and artifactId manually, and got "bitten".
Hello, nice guide!
The "Plugin execution not covered..." message you're getting is from m2e. This is because it doesn't know whether to run these goals during *incremental* compilation or not. That lifecycle-mapping section tells m2e to ignore m-clojure-p. I guess this is not a problem for you because you explicitly call Maven. However, the goal of m2e is to allow seamless incremental build in Eclipse, same as you get if you aren't using Maven.
To enable this m2e needs a little help. Right now you need to install an m2e-clojure integration plugin, which as far as I know hasn't been written yet. When m2e 1.1 is released a small piece of XML included with the m-clojure-p should be sufficient.
If it wouldn't be too much work for them I might ask the m-clojure-p author to add that.
Bear in mind I'm just another user so I could be wrong about any of the above. I looked into the issue for m-less-p once already, which is how I've learnt about it.
Submitted https://github.com/talios/clojure-maven-plugin/issues/53 if you're interested :)
@ivant: corrected, many thanks! I hope you enjoy working with the Maven/Clojure combination!
@Alexis: Many thanks for figuring out and for articulating what is going on! Makes perfect sense, and I can see how much nicer it would be if the issue were fixed as you indicate. Thanks for having flagged the issue to mark@talios. Looking forward to this being fixed! :-)
The clojure-maven-plugin block references the wrong groupId. It should be "com.theoryinpractise" not "theoryinpratice".
Excellent article - this was a Clojure enabler for me. Every book on clojure should start with a chapter like this. With the techniques described in your article one is able now to produce artifacts which can be used by a Java project. Very useful, thanks again.
Very good article, very detailed. I did it according your doc. It works fine. Thanks indeed.
There is a lot of work and getting a great chance for it so many thing and informative post student helpful making a best career.
Informative it is useful and getting a lot of chance for it so many thing.It is useful and knowledgeable post full of idea about it.