This article describes how to use Maven to build projects, written in Clojure (or in Clojure & Java).
Maven is a software project management and comprehension tool. It implements dependency resolving (with automatic download from repositories), building & testing of code, deploying of software, etc. Maven's functionality is extensible with plugins, so it's possible to use it not only for Java code (primary goal of this tool), but also for code, written in other languages. You can read more about Maven in following books (freely available).
Maven differs from other tools, such as Ant — it describes what we want to do, in contrast with Ant, that describes how to do something. Maven describes tasks to execute in declarative style, and all described tasks are performed by corresponding plugins.
Description of software lifecycle and project's information are stored in pom.xml file,
that should exist in root directory of the project (and in root directories of
sub-projects, if your project is separated into several modules). Project's information
includes name, identifier and version of the project, and often includes other
information: URL of project's site, information about source code repository (so you can
use mvn scm:update goal to update code, for example), etc.
Project Object Model (POM) defines set of stages for project's lifecycle — lifecycle
phases. Each of lifecycle phase could include several tasks (goals), that define what
will performed on given stage of lifecycle. There are several common stages: compilation
(compile), testing (test), creation of package (package) and installation (install). Each
of these lifecycle phases has dependencies on other phases, that should be executed before
its invocation (compilation should be executed before testing, testing before packaging,
etc.).
Usually developer uses phase's name to invoke build process. For example, mvn package or
mvn install, etc. But developer can also invoke concrete Maven's goal. To do this, he
should specify name of plugin, that implements concrete goal, and task name in given
plugin. For example, mvn clojure:run will run Clojure and execute script, specified in
configuration. We need to mention, that list of goals, that are executed for concrete
lifecycle phase isn't constant — you can change this list by modifying plugin's
configuration.
Clojure's support in Maven is implemented by clojure-maven-plugin, that is available in
Maven's central repository, so it automatically when it required. As a base for your
projects you can use pom.xml file from clojure-maven-example project.
If you already have pom.xml in your project, then to enable this plugin you need to add
following code into <plugins> section of pom.xml:
<plugin> <groupId>com.theoryinpractise</groupId> <artifactId>clojure-maven-plugin</artifactId> <version>1.3.2</version> </plugin>
Attention: version number could be changed as development continues. To find latest plugin's version number you can use sites mvnrepository or Jarvana, that contains information about packages, registered in Maven's repositories. Besides this, you can omit plugin version — in this case, Maven will automatically use latest available version.
Declaration of this plugin will give you all functionality — compilation, testing &
running of code, written in Clojure. But out of box you'll need to use full goals names,
such as clojure:compile, clojure:test & clojure:run. Although you can make your life
easier if you'll add these goals into list of goals of concrete lifecycle phases (compile
and test). To do this you need to add section <executions> into plugin's description, as
in following example:
<plugin> <groupId>com.theoryinpractise</groupId> <artifactId>clojure-maven-plugin</artifactId> <version>1.3.2</version> <executions> <execution> <id>compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>test</id> <phase>test</phase> <goals> <goal>test</goal> </goals> </execution> </executions> </plugin>
In this case, source code, written in Clojure will compiled — this useful if you
implement gen-class that will be used from Java, or if you don't want to provide source
code for your application. But sometimes it much better just to pack source code into
jar, and it will compiled during loading of package. This allows to avoid binary
incompatibility between different versions of Clojure. To put source code into jar, you
need to add following code into resources section:
<resource> <directory>src/main/clojure</directory> </resource>
By default Clojure's source code is placed in the src/main/clojure directory of the
project with sub-directories, according to structure of your program. While source for
tests are placed in the src/test/clojure directory. These default values could be changed
in plugin's configuration.
clojure-maven-pluginclojure-maven-plugin implements several commands (goals) that could be divided into two groups:
clojure:compileclojure:testclojure:runscript and/or scripts
configuration directives. This goals is often to run project with proper dependencies;clojure:replreplScript — you
can put some initialization code into it. If the JLine library was listed in
dependencies, then it will loaded automatically, making your work in REPL more
comfortable;clojure:swankclojure.swank.port);clojure:nailgunclojure.nailgun.port).There are several Clojure-related repositories. First, this is
http://build.clojure.org/releases/ with release versions of Clojure and clojure-contrib,
and http://build.clojure.org/snapshots/, with experimental versions. Second popular
repository is Clojars, that is used by Clojure community to publish their projects1.
To use these repositories you need to add following code into repositories section in
pom.xml:
<repository> <id>clojure-releases</id> <url>http://build.clojure.org/releases</url> </repository> <repository> <id>clojars</id> <url>http://clojars.org/repo/</url> </repository>
In this example we add repository with release versions of Clojure, and if you want to use bleeding edge version, then you need to change first address to http://build.clojure.org/snapshots/.
Maven automatically downloads all necessary dependencies from default repository &
repositories, specified by user (as shown above). Downloaded packages are stored in
user's home directory and could be used by other projects without additional downloading
of them. Each package is uniquely identified by combination of three parameters —
group's name (groupId tag), artifact name (artifactId tag), and version (version tag).
To use Clojure in your project you need at least specify dependency on language itself.
Besides Clojure, the clojure-contrib library is often used in many projects, so they are
often specified together. Currently, the stable version of both packages is version
1.1.0, that is in the release-repository of Clojure. To declare these dependencies add
following code into dependencies section of pom.xml file:
<dependency> <groupId>org.clojure</groupId> <artifactId>clojure</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>org.clojure</groupId> <artifactId>clojure-contrib</artifactId> <version>1.1.0</version> </dependency>
If you want to use latest version of the language, then you need to add corresponding
repository (snapshots) and use version number 1.2.0-SNAPSHOTS (or other actual in current
time) instead of version 1.1.0.
To perform some tasks, implemented by clojure-maven-plugin, you need to specify additional dependencies:
clojure:swank goal, then you need to specify dependency on swank-clojure package:<dependency> <groupId>swank-clojure</groupId> <artifactId>swank-clojure</artifactId> <version>1.2.0-SNAPSHOT</version> </dependency>
clojure:nailgun task, then you need to download distribution from
vimclojure's site, build it, as described in documentation, and install into local
Maven repository. And after this, you need to add following dependency on
vimclojure with following code:<dependency> <groupId>de.kotka</groupId> <artifactId>vimclojure</artifactId> <version>X.X.X</version> </dependency>
mvn clojure:repl goal is executed, so you can add dependency for
this library with following code:<dependency> <groupId>jline</groupId> <artifactId>jline</artifactId> <version>0.9.94</version> </dependency>
Developer can change plugin's configuration options, such as location of source code,
scripts names, etc. To change some parameter, you need to add its new value into
configuration section in the plugin's description. For example, you can specify name of
the script, that will executed during testing, with following code:
<plugin> <groupId>com.theoryinpractise</groupId> <artifactId>clojure-maven-plugin</artifactId> <version>1.3.2</version> <configuration> <testScript>src/test/clojure/unapalabra/test.clj</testScript> </configuration> ..... </plugin>
Following options are used to specify options related to source code & compilation:
sourceDirectoriessourceDirectory tag) with source code in Clojure, that will packed into
resulting jar (and compiled, if corresponding option is specified);
testSourceDirectoriestestSourceDirectory tag) with tests, written in Clojure;warnOnReflectiontrue) or disables (false) warnings about
reflection when compiling source code.Besides this, you can control which namespaces will compiled and/or for which namespaces
testing of source code will performed. To do this, you need to add namespaces tag into
configuration and inside it list corresponding namespaces (each of item should be wrapped
into namespace tag). You can use regular expressions to specify all necessary namespace,
and you can also use ! to exclude namespaces from this list. In addition to this option,
you can use other two: compileDeclaredNamespaceOnly ad testDeclaredNamespaceOnly (with
values true or false), that control, will these namespace limitations applied during
compilation and/or testing.
There are also several options that are used to specify parameters for execution of your code and/or tests:
script and scriptsscript tag) or several (scripts tag with nested script
tags) names of scripts with code, that will executed when you'll run the clojure:run
task;testScriptclojure:test task. If there
was no value specified in configuration of plugin, then plugin will automatically
generate run script for all tests, found in project;replScriptclojure:repl task
(it also used by clojure:swank and clojure:nailgun tasks). This code will executed
before entering into REPL, so you can use it to specify initialization code for your
working environment;runWithTeststrue) or disable (false) executions of tests if you run REPL or
your code via Maven. You can also change this value by using Maven's command-line
option. For example, with following command mvn clojure:replclojureOptionsjava process on every invocation.I think, that this article provides enough information to you to start use Maven together with Clojure. If you have Clojure-only project, and you don't plan to use all power of Maven, then may be you need to look to the Leiningen — this is tool, that was created to build projects, written only in Clojure. Another interesting project is Polyglot Maven, the main goal of it is creation of special DSL (Domain Specificl Languages) in different languages (Clojure, Scala, Groovy) for description of Maven's configurations (for Clojure this language is almost same as language implemented in Leiningen).
Other examples of use Maven with Clojure you can find in different projects: Incanter (as example of project, consisting from several modules), labrepl and clojure-maven-example. More information about Clojure and Maven you can also find in following bog posts:
clojure-maven-plugin);1. Clojars also provides downloading of Clojure & clojure-contrib, so you can add only
one entry into list of repositories.
Last change: 07.06.2010 08:34