Maven 3.x: Paving the desire lines — Part Two


November 10, 2009 By Jason van Zyl

In the last entry, [Maven 3.x: Paving the desire lines -- Part One][1], I talked about some of the overall goals for Maven 3.x and the process by which we determined what needed to be changed. Today I’m going to start talking about some of the specific changes that have been made, and I’ll start with the way Model objects can be constructed from different sources and how those models are combined in Maven’s core to create new MavenProject instances.

# Refactored ModelReader & ProjectBuilder

The combination of changes in the ModelReader and ProjectBuilder now allows for very simple implementations of a ModelReader to harness the full capabilities of the ProjectBuilder. In Maven 2.x it was somewhat complicated to create a new implementation of a ProjectBuilder that could actually do anything useful without a serious amount of work. With Maven 3.x it is relatively straight forward to create a new ModelReader that can construct a Model instance which can then undergo all the standard inheritance, interpolation, and profile processing. If you do the work to deserialize a particular data source of your choosing into a Model then there is nothing more you need to do in order for Maven to work with it. We needed this ourselves to prepare for different versions of the standard XML variant of the POM, but it has the effect of allowing any source to be used which leads us to the first implementations of Polyglot Maven.

## Pluggable ModelReaders & Polyglot Maven

While speaking to several groups of people over the last week about the polyglot support in Maven it’s clear there is some confusion about what the polyglot support actually is, how it’s going to be introduced, and who will use it. It really started as a trial for using different ModelReader implementations and it grew from there.

To be clear the polyglot support is not going to be a standard part of Maven 3.x, it’s an extension of Maven 3.x being developed at [Sonatype][sonatype] and it’s an experiment at this point. A fully functional experiment, but an experiment nonetheless. Polyglot Maven is about demonstrating the powerful and interesting things you can do with Maven 3.x as a framework. I want people to think of, and use, Maven 3.x as a library. I think of [M2Eclipse][m2e] as being more a consumer of the Maven 3.x library then as a wrapper for the Maven CLI. I am also working on using the Maven 3.x library in Hudson, and Sonatype is working on using the Maven 3.x library in Nexus. Polyglot Maven is pushing this idea further as it’s challenging me to think about how a DSL might access core Maven 3.x internals to create new types of tools, or variants of Maven itself.

I’ll have a more extensive post on Polyglot Maven, but the primary concern I have here is interoperability. The first level of Polyglot Maven support is for different sources of the data model and this alone could potentially cause nasty interoperability problem. We can most likely serialize the model to the older Maven 2.x form of the POM so that Maven 3.x built projects will work nicely with Maven 2.x built projects but we don’t have this fully sorted out yet to be honest. But I would prefer to have to do as little work as possible on Maven 2.x as I would really like to focus on Maven 3.x.

## Creating your own ModelReader implementation

As mentioned, to create your own ModelReader implementation is pretty easy. If you understand the data source and the org.apache.maven.Model class then you can create a mapping between whatever you like. There is actually a corresponding ModelWriter that we use to translate XML to YAML and XML to Groovy in Polyglot Maven but more about that later. Below is an example of what you would have to do in order to create your own ModelReader:

package org.sonatype.maven.reader;
 
import java.io.IOException;
import java.io.Reader;
import java.util.Map;
 
import org.apache.maven.model.Model;
import org.apache.maven.model.io.ModelParseException;
import org.apache.maven.model.io.ModelReader;
import org.codehaus.plexus.component.annotations.Component;
import org.sonatype.maven.polyglot.io.ModelReaderSupport;
 
@Component(role = ModelReader.class, hint = "myreader")
public class YamlModelReader
    extends ModelReaderSupport
{
    public Model read( Reader input, Map<string, ?> options )
        throws IOException, ModelParseException
    {
        if ( input == null )
        {
            throw new IllegalArgumentException( "Reader is null." );
        }
 
        Model model = readModelFromTheSourceOfMyChoosing( input, options );
 
        return model;
    }
}

That’s it! We’re trying to make it very easy to leverage the framework. I think many more cutting edge variants of Maven will emerge and these changes don’t have to be in Maven proper. In Mavenland we’re definitely want to maintain a high level of consistency and introduce new features when the community can absorb them. But if some folks want to make a Ruby/Scala/Groovy-centric build system using the Maven’s internals I think that would be great.

## Using a different source for your Model: YAML!

Just to give a little flavour of what is possible here is what the YAML-based POM looks like in the current implementation of Polyglot Maven. I used SnakeYAML to read the YAML and it was pretty straight forward to make a working ModelReader in a few hours. Once this was done, I had to hook the reader into Polyglot (which is also pretty easy) and then I could run a build with YAML-based POMs.

modelVersion: 4.0.0
groupId: org.sonatype.graven
artifactId: graven-parent
version: 1.0-SNAPSHOT
name: Polyglot Maven
packaging: pom

properties:
  mavenVersion: 3.0-SNAPSHOT

modules: [common, graven, raven, cli]

build:
  plugins:
  - artifactId: maven-surefire-plugin
    groupId: org.apache.maven.plugins
    version: 2.4.3
    configuration:
      redirectTestOutputToFile: 'true'
      forkMode: once
      argLine: -ea
      failIfNoTests: 'false'
      workingDirectory: ${project.build.directory}
      excludes: {exclude: '**/Test*.java'}
      includes: {include: '**/*Test.java'}
  - artifactId: maven-compiler-plugin
    groupId: org.apache.maven.plugins
    version: 2.0.2
    configuration: {source: '1.5', target: '1.5'}
  - artifactId: gmaven-plugin
    groupId: org.codehaus.groovy.maven
    version: '1.0'
    configuration: {providerSelection: '1.6'}
    executions:
    - goals: [generateStubs, compile, generateTestStubs, testCompile]
  - artifactId: plexus-component-metadata
    groupId: org.codehaus.plexus
    version: 1.4.0-SNAPSHOT
    executions:
    - goals: [generate-metadata, generate-test-metadata]
dependencies:
- {artifactId: junit, groupId: junit, scope: test, version: '4.7'}
- {artifactId: groovy, groupId: org.codehaus.groovy, scope: test}
dependencyManagement:
  dependencies:
  - {artifactId: apache-maven, classifier: bin, groupId: org.apache.maven, type: zip, version: '${mavenVersion}'}
  - {artifactId: maven-model-builder, groupId: org.apache.maven, version: '${mavenVersion}'}
  - {artifactId: maven-embedder, groupId: org.apache.maven, version: '${mavenVersion}'}
  - artifactId: groovy
    groupId: org.codehaus.groovy
    version: 1.6.5
    exclusions:
    - {artifactId: jline, groupId: jline}
    - {artifactId: junit, groupId: junit}
    - {artifactId: ant, groupId: org.apache.ant}
    - {artifactId: ant-launcher, groupId: org.apache.ant}
  - {artifactId: common, groupId: org.sonatype.graven, version: 1.0-SNAPSHOT}
  - {artifactId: cli, groupId: org.sonatype.graven, version: 1.0-SNAPSHOT}
  - {artifactId: graven, groupId: org.sonatype.graven, version: 1.0-SNAPSHOT}
  - {artifactId: raven, groupId: org.sonatype.graven, version: 1.0-SNAPSHOT}

The main point is that the internal component structure for building Models and how the MavenProject instances are created are very flexible and allow for some very sophisticated extensions. One side effect of this flexibility is that we’ve created an easier path for large projects to migrate to Maven. Sonatype is working with some of the biggest organizations in the world and while they wish to use Maven sooner rather then later there is simply no big bang approach to migration.

Anyone who understands infrastructure knows that these systems tend to move at a slower pace because they are so critical. You don’t just swap out your build infrastructure over the course of a week, that simply doesn’t happen in large organizations. Maven 3.x with the flexible data sources can actually allow large projects to used mixed models during lengthly transitions. Where one model is an internally used model whether that be properties files, another XML format, or a database. There are a lot more possibilities with the Maven 3.x core.

Next, I’m going to talk about mixins which will be a compositional approach to managing your POMs.

[1]: http://www.sonatype.com/people/2009/11/maven-3x-paving-the-desire-lines-part-one-2/
[hudson]: https://hudson.dev.java.net
[m2e]: http://m2eclipse.sonatype.org
[maven]: http://maven.apache.org
[nexus]: http://nexus.sonatype.org
[nxcm]: http://www.sonatype.com/nexus
[sat4j]: http://www.sat4j.org/
[sonatype]: http://www.sonatype.com