This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Maven For Beginners

This article is a more in-depth look at what maven is and how a new user can make use of it.  If you'd like a getting-started-quickly guide, please check out: http://scala-blogs.org/2008/01/maven-for-scala.html

What is Maven?

Maven is modern build/project management tool.  According to its website, the core purposes are:

  • Make the build process easy
  • Provide a uniform build system
  • Provide quality project information
  • Provide guidelines for "best practices" in development
  • Allows transparent migration to new features

In practice, maven can greatly simplify builds for "standard" projects, and makes it easier to add new libraries/modules to existing projects.  Maven builds tend to consist mostly of "declarations" and configuration, with very little scripting.  Maven also promotes the "convention over configuration" idea to reduce the length of build files.

 

Downloading Maven

The first thing you need to get started with maven, is maven itself.  It can be downloaded from http://maven.apache.org/download.html.  Unzip/untar it into a directory of your choice and make sure ${MAVEN_HOME}/bin is in your PATH (even on windows).  For some operating systems their are maven-packages you can download using your package manager.  There are also IDE integrations for most Java IDEs.  Some of these integration come with a version of maven "bundled" with the plugin (e.g. Eclipse IAM + Eclipse M2E).

Creating your first project

After downloading maven, you should create a simple project to ensure that everything is working correctly.  This easiest way to do this is by using the Maven Archetype Plugin.  In maven an "archetype" is the general skeleton structure (or template) of a project.  You can run the archetype plugin as follows:

 

mvn archetype:generate

You may notice that maven has started downloading jar files onto your computer at this point.  Maven uses a dependency resolution mechanism that will download dependencies as needed (and only once).  If you're using a fresh install of maven, it may need to resolve the archetype plugin.

Maven should give you a list of available project templates (scala was #30 on my list).  Select one that may interest you most (and if it's not scala... well....).

Next maven will begin asking you for a groupId, artifactId, versionId and packaging.  We'll cover these later, but for now just use an inverted domain name for groupId (e.g. org.scala-lang) a project name for artifactId (e.g. test-project) and the default for veresionId/package.  Next cd test-project and run mvn package.  Once again you should see maven automatically downloading dependencies (such as scala-library, scala-compiler, junit, etc.)  Congratulations, you've built your first maven project!

Build File Structure

Maven projects are defined by their "Project Object Model" or pom.  This file is located in the base directory of a maven project and is called pom.xml.   The Project Object Model includes a lot of different information:

  • The name/description of the project
  • The style/packaging of the project
  • The version of the project
  • The dependencies (libraries or otherwise) of the project
  • The configuration for the build (plugins, directories, etc.)
  • The configuration for reports (test coverage, static analysis, etc)
  • The developers/contributors for the project (name, email, etc.)
  • The Infrastructure (Source Control Repository, Continuous Integration Server, Issue Tracker) for the project
  • The maven repositories used for the project

As you can see, this is a lot of information!  In addition to all this, POM files are object-oriented and may inherit from one parent.  By default, all maven projects inherit from the maven "master pom".  The master pom defines the standard build layout/configuration for maven projects.  You can override/extend this behavior in your project pom if desired.

 

POM File

Here's a standard maven pom file for a scala project:

 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.scala-lang.demo</groupId>
    <artifactId>scala-test</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>Demo of maven for Scala Lang website</name>
    <url>http://scala-lang.org</url>
    <repositories>
        <repository>
            <id>scala-tools.org</id>
            <name>Scala-tools Maven2 Repository</name>
            <url>http://scala-tools.org/repo-releases</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>scala-tools.org</id>
            <name>Scala-tools Maven2 Repository</name>
            <url>http://scala-tools.org/repo-releases</url>
        </pluginRepository>
    </pluginRepositories>
    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>2.7.2-rc2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <sourceDir>src/main/java</sourceDir>
                    <jvmArgs>
                        <jvmArg>-Xms64m</jvmArg>
                        <jvmArg>-Xmx1024m</jvmArg>
                    </jvmArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Let's analyse this a bit.   First off, all pom files consist of a "project" xml element.  This encompases the entire xml document.   The modelVersion element is always the same and is required for maven2 projects.  Here's some simple descriptions of the other elements:

  • groupId - This corresponds to the inverted domain-name of the project.  Although you could use any arbitrary value, it's recommended to use your company domain name here
  • artifactId - This is a unique id for the project (within the group).  This name will be used when creating jar/war/ear files.
  • version - This represents the current version of the project.  More details later
  • name - This is the human-readable name of the project.
  • url -This is the url for the project website.
  • packaging - This defines the "style" of project your building (e.g. ear, jar, war, etc.).  For more information on packaging, check out the respective plugins (maven-jar-plugin, maven-war-plugin, etc.)
  • dependencies - Specifies the dependencies of the project.  This will be materialized from any defined maven repositories.  More details later
  • repositories - Specifies alternative locations for maven to look when materializing dependencies.
  • pluginRepositories - Specifies alternative location for maven to look when materializing build plugins
  • build - Specifies configuration on *how* to build the project
  • reports - Specifies configuration on *what* reports to generate for the project

 

 Default Directory Layout

Maven projects have default layouts defined by a combination of the maven super-pom and any build-plugins you have declared.   These defaults are customizable, but it is generally recommended to use the convention.  (The more you deviate the more XML you need to write).  Here is a sample project layout with links to descriptions on which maven plugins are responsible for which directories and how you can override the defaults.

 

project/
   pom.xml   -  Defines the project
   src/
      main/
          java/ - Contains all java code that will go in your final artifact.  
                  See maven-compiler-plugin for details
          scala/ - Contains all scala code that will go in your final artifact.  
                   See maven-scala-plugin for details
          resources/ - Contains all static files that should be available on the classpath 
                       in the final artifact.  See maven-resources-plugin for details
          webapp/ - Contains all content for a web application (jsps, css, images, etc.)  
                    See maven-war-plugin for details
     site/ - Contains all apt or xdoc files used to create a project website.  
             See maven-site-plugin for details       
     test/
         java/ - Contains all java code used for testing.   
                 See maven-compiler-plugin for details
         scala/ - Contains all scala code used for testing.   
                  See maven-scala-plugin for details
         resources/ - Contains all static content that should be available on the 
                      classpath during testing.   See maven-resources-plugin for details

Although the above represents the default layout for most scala projects, every maven plugin used could bring in its own "convention" for where it would like to see code.  An example of this is that the gmaven plugin for groovy expects to find groovy source code in src/main/groovy and src/test/groovy (similar to the scala plugin). 

 

Build Lifecycle

The maven build lifecycle is made up of "phases" and "goals".  A "goal" is a various action defined by a plugin.  Examples include scala:compile, jetty:run-war, jboss:deploy and (the one you should be familiar with) archetype:generate.   Goals are specific to a particular maven plugin and perform a single operation.  Phases on the other hand are more abstract ideas, and represent particular points in a standard build system.   Here's a listing of some of the phases for a maven build (taken from here):

  • validate - validate the project is correct and all necessary information is available
  • compile - compile the source code of the project
  • test - test the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed
  • package - take the compiled code and package it in its distributable format, such as a JAR.
  • integration-test - process and deploy the package if necessary into an environment where integration tests can be run
  • verify - run any checks to verify the package is valid and meets quality criteria
  • install - install the package into the local repository, for use as a dependency in other projects locally
  • deploy - done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects.

In reality, there are many more phases defined.  For a complete listing, read here.

When operating maven on the command line you can type "mvn" plus a list of goals and/or phases.   These will be executed in the order defined.   Some phases may depend on other phases, such that they do not need to be specified.   For example, package depends on test.   This means you cannot package your software unless all your tests pass, and packaging will always attempt to build.   When specifying goals on the command line, the goal will execute immediately with no dependencies.

Build Plugins

One of the great features of maven is that there's a build plugin for almost any activity required in a Java/Scala project.   These plugins are defined in the project/build/plugins section of the pom.  Most build plugins automatically attach their goals to appropriate build phases when used, however this is configurable at runtime.  For example, here is how we ask the scala:compile and scala:testCompile goals to automatically bind to their respective phases:

    <build>
        <plugins>
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
                ...
            </plugin>

Inside the execution block there's a (sometimes) option id element and a phase element.   The phase element can be used if you'd like to bind a goal to a different phase then it defaults too. As an example, I sometimes bind the jboss:undeploy goal to the package phase and the jboss:deploy goal to the install phase.  This way if my unit tests succeed, my application gets undeployed/redeployed right after packaging, and I only to call mvn install on the command line.

Dependencies

One of the powerful features of maven is declarative dependencies.  Maven allows projects to declare what dependencies they have, and will automatically materialize those dependencies (including transitive dependencies).  Maven dependency declaration looks like this:

<dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>2.7.2-rc2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

Each dependency consists of several items:

  • groupId - The group of the dependency to rely on
  • artifactId - The artifact in the group to rely on
  • version - The version of the dependency to rely on
  • scope - The "scope" of the dependency. Defaults to compile (more details later)
  • packaging - The packaging for the dependency.  Defaults to jar (e.g. jar, war, ear)

The default maven repository has a website that allows you to search for dependencies called http://mvnrepository.com.  Most open source projects are available either via the "central" maven repository and a separately hosted maven repository (e.g. jboss host's their own repository).

Dependencies in maven have scope.  The scope determines when/where the dependency will show up in the classpath.  Here are the commonly used scopes:

  • compile - The dependency is available when compiling/running/testing the project and as a transitive dependency
  • runtime - The dependency is available when running/testing but not for compilation.  Runtime dependencies show up as transitive dependencies.
  • test - The dependency is available during testing phases, but not compilation or (non-testing) runtime.  Test dependencies do not show up as transitive dependencies
  • provided - The dependency is available during compilation, but not runtime.  e.g. You expect the servlet-api will be provided by a servlet container and therefore should be a "provided" dependency

More information (and scopes) is available here.

 

Pom Inheritance

 The Second nicest feature of maven is pom-file inheritance. Pom file inheritance works similarly to how single-inheritance works in an OO language. The child POM inherits all configuration items of its parent, and can override/extend them with its own configuration.

This is done through specifying a pom with a packaging of "pom", and then using the "parent" tag in a child project. This can greatly reduce the amount of configuration done in "child" projects (Often times my child project poms only specify their parent and their artifact/group/version triplet).

Here's a sample "abstract" project for scala:

 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.scala-lang</groupId>
    <artifactId>scala-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <inceptionYear>2008</inceptionYear>
    <name>Scala "Abstract" Parent Project</name>
    <repositories>
        <repository>
            <id>scala-tools.org</id>
            <name>Scala-tools Maven2 Repository</name>
            <url>http://scala-tools.org/repo-releases</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>scala-tools.org</id>
            <name>Scala-tools Maven2 Repository</name>
            <url>http://scala-tools.org/repo-releases</url>
        </pluginRepository>
    </pluginRepositories>
    <profiles>
        <profile>
            <id>Scala Project</id>
            <activation>
                <file>
                    <exists>src/main/scala</exists>
                </file>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>org.scala-lang</groupId>
                    <artifactId>scala-library</artifactId>
                    <version>2.7.1</version>
                </dependency>
            </dependencies>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.scala-tools</groupId>
                        <artifactId>maven-scala-plugin</artifactId>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>compile</goal>
                                    <goal>testCompile</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

The above uses a mechanism called "profiles" in maven.  Although profiles are beyond the scope of this article, the basics of profiles are similar to traits.  A Profile gets applied to class when a certain condition applies.  In the above, that is when the "src/main/scala" directory exists.  This means projects without a src/main/scala directory that inherit the above profile will *not* have the scala plugin added to their lifecycle.

 

Below is a pom file for a normal scala project that makes use of the "abstract pom".


<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>org.scala-lang.test</groupId>
 <artifactId>scala-child</artifactId>
 <version>1.0-SNAPSHOT</version>
 <packaging>jar</packaging>
 <parent>
  <groupId>org.scala-lang</groupId>
  <artifactId>scala-parent</artifactId>
  <version>1.0-SNAPSHOT</version>
 </parent>
</project>

Notice how information no longer needs to be repeated in the pom.   This is particularly useful when creating multi-module projects.  Multi-module projects are beyond the scope of this article, but to describe them simply: they are nested projects where you can execute a lifecycle command in the parent that will recurse into all children.

 

If you ever need to know what your pom looks like after inheritance and profiles are applied, run

mvn help:effective-pom

Going Further

This article is only designed to help you take your first steps towards using maven as your build environment.  There are more advanced topics that can greatly help your efforts of creating a build system.  Some of these include

  • Multi-module projects
  • Profiles
  • Settings.xml
  • Setting up your own Maven Repository (and/or Proxy)
  • Deploying artifacts
  • Project reporting

I'm hoping to provide some more new-user documentation for the following in the near future.  For now, I'd recommend what most good maven-build-experts use for information: google and maven.apache.org

 

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland