Меню:


Ad for Russian fund for functional programming

This article describes how to use Maven to build projects, written in Clojure (or in Clojure & Java).

What is Maven?

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.

Maven and Clojure

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.6</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.6</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.

Goals, defined in clojure-maven-plugin

clojure-maven-plugin implements several commands (goals) that could be divided into two groups:

Clojure-related repositories

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/.

Dependencies

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.2.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.2.0</version>
 </dependency>
 <dependency>
   <groupId>org.clojure</groupId>
   <artifactId>clojure-contrib</artifactId>
   <version>1.2.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.4.0-SNAPSHOTS (or other actual in current time) instead of version 1.2.0.

To perform some tasks, implemented by clojure-maven-plugin, you need to specify additional dependencies:

 <dependency>
  <groupId>swank-clojure</groupId>
  <artifactId>swank-clojure</artifactId>
  <version>1.3.0</version>
</dependency>
 <dependency>
  <groupId>de.kotka</groupId>
  <artifactId>vimclojure</artifactId>
  <version>X.X.X</version>
 </dependency>
 <dependency>
  <groupId>jline</groupId>
  <artifactId>jline</artifactId>
  <version>0.9.94</version>
 </dependency>

Plugin's configuration

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.6</version>
   <configuration>
     <testScript>src/test/clojure/test.clj</testScript>
   </configuration>
   .....
 </plugin>

Following options are used to specify options related to source code & compilation:

sourceDirectories
this option defines list of directories (each of them should be wrapped into sourceDirectory tag) with source code in Clojure, that will packed into resulting jar (and compiled, if corresponding option is specified);
testSourceDirectories
defines list of directories (each of them should be wrapped into testSourceDirectory tag) with tests, written in Clojure;
warnOnReflection
option that enables (true) 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 scripts
defines one (script 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;
testScript
defines name of script that will executed when you run clojure: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;
replScript
defines name of script, that will executed if you'll run clojure: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;
runWithTests
enable (true) 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:repl
-Dclojure.runwith.test=false;
clojureOptions
using this option you can specify command-line options that will be passed to java process on every invocation.

Conclusion

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:

1. Clojars also provides downloading of Clojure & clojure-contrib, so you can add only one entry into list of repositories.

Last change: 09.03.2012 14:36

blog comments powered by Disqus