Monday, February 06, 2006

Maven 2 Notes, Part 6: Multiproject Builds

****************************************************************
Maven 2 Notes, Part 6: Multiproject Builds
****************************************************************

----------------------------------------------------------------
Section 1: Basics for Building Multiple Maven Projects
----------------------------------------------------------------
* Maven 1's multiproject builds were notorioulsy vague and mysterious.
Fortunately Maven 2 seems to have cleaned things up, starting with
the directory structure.

* Now, sensibly, the directory structure is what you would expect:
the multiproject has a parent directory and various child directories.
Presumably the children can have children of their own, but this is
something to test.

* Maven 2's "Getting Started Guide"

http://maven.apache.org/guides/getting-started/index.html

has a pretty good overview, so I'm using this as a starting point. I'll
try to deviate a few places to keep it interesting.

* Multiproject builds are also sensibly related to the inheritance mechanism.
But note that this works even if you run the maven commands in the parent
directory: it will search for all pom.xml files in all of the children.

* To test this out, I created a shell of a real problem: we must in the OGCE
project build a ProxyManager portlet (as a war) that itself depends on several
other jars. We can simulate this by creating the following projects:
o Parent: a shell project with only a pom.xml and no source code.
o Child1: a standard project that creates a jar.
o Child2: a webapp project that creates a war.
Both projects extend the parent and additionally Child2 depends on the jar created
by Child1.

* Set all of this up with Maven's archetype:create tools. Here is my edited UNIX history:
1393 mvn archetype:create -DgroupId=xportlets.multitest -DartifactId=parent
1394 cd parent
1396 mvn archetype:create -DgroupId=xportlets.multitest -DartifactId=child1
1398 mvn archetype:create -DgroupId=xportlets.multitest -DartifactId=child2 -DarchetypeArtifactId=maven-archetype-webapp

Recall that these simple artifact steps create dummy java programs, as we
discussed in an earlier blog:

http://communitygrids.blogspot.com/2005/11/using-maven-2-part-1.html


* Our next step is to modify the generated pom.xml files, starting with the parent.
We need to do two things:
1. Change packaging from "jar" to "pom".
2. Add <modules> tags to specify by name the child artifacts.
My modified pom.xml looks like this.

<project>
<modelVersion>4.0.0</modelVersion>
<groupId>xportlets.multitest</groupId>
<artifactId>parent</artifactId>

<version>1.0-SNAPSHOT</version>
<name>The Parent</name>
<url>http://maven.apache.org</url>

<!-- Change this to pom -->
<!-- "pom" packages don't need source code! -->
<packaging>pom</packaging>

<!-- Ad these lines -->
<modules>
<module>child1</module>
<module>child2</module>
</modules>
</project>

I note that the <module></module> values are probably artifactIds
of the associated pom.xml files. This (by default) will scan the
directory "parent" for matching sub-directories named "child1" and
"child2". A little fooling around revealed
1. Child directory names are important. The project named
"child2" must be in a directory named parent/child2.
2. There does not seem to be a way to provide alternative
locations for child projects.


* Note also that our parent project has the packaging value of "pom".
This means that the project does not need any source: it only
exists to put its pom.xml in local and remote repositories.


I was so excited, I decided to boldface the above text, since it applies to
the portlet download pom.xml files we discussed in previous blogs.

* We may also add a <parent> definition to the pom.xml files
of both of the children. This is the simple inheritance that
we saw before. I think this is optional. It seems that way from
my early tests.

<project>
<!-- Optionally add this -->
<parent>
<groupId>xportlets.multitest</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>xportlets.multitest</groupId>
<artifactId>child2</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>Child #1</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>child2</finalName>
</build>
</project>

* You run all of this from the parent directory. If you issue the
command

[gateway@gridfarm002 parent]$ mvn install

You get

[INFO] Scanning for projects...
[INFO] Reactor build order:
[INFO] The Parent
[INFO] Child #1
[INFO] Child #2

...

And so on.

* After running the command, you will note that the appropriate "install" phase
build process is executed in parent and in each of the children.

* Note this is a bit different from our earlier use of inheritance,

http://communitygrids.blogspot.com/2006/01/using-maven-2-part-5-payoff-at-last.html
.

In the earlier examples, the child project would scan the local and remote repositories for
the parent's pom.xml, downloading project parts as necessary. There we were running the "mvn"
command from within the child directory. Here we are running it from within the parent.

----------------------------------------------------------------
Section 2: Inter-Project Dependencies
----------------------------------------------------------------

* The order of project builds must be deterministic if there are
dependencies.

* First, let's see what happens if I rename Child1 Child3 and update
directories, artifactIds, and names accordingly. Simply making these
superficial changes does not change the order that the modules are
executed. The order seems to be defined by the parent pom.xml's
<module> entry order. If I specify the order to be

<modules>
<module>child2</module>
<module>child3</module>
</modules>

then I get

[gateway@gridfarm002 parent]$ mvn clean install
[INFO] Scanning for projects...
[INFO] Reactor build order:
[INFO] The Parent
[INFO] Child #2
[INFO] Child #3

Switching this back to

<modules>
<module>child3</module>
<module>child2</module>
</modules>

causes Child3 to be built first.

* Ordering here is fine, but now we need to examine inter-project dependencies. Let's make
Child2 (a war build) dependent on the jar created by Child3. Add the following to your
child2's pom.xml.

<dependencies>
<dependency>
<groupId>xportlets.multitest</groupId>
<artifactId>child3</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>

This is the usual dependency. Also, change the module order to this:
<modules>
<module>child2</module>
<module>child3</module>
</modules>

This will prevent the cheap victory of compiling child3 first by default: we want to
force the project to check dependencies. A quick check confirms this:

[gateway@gridfarm002 parent]$ mvn install
[INFO] Scanning for projects...
[INFO] Reactor build order:
[INFO] The Parent
[INFO] Child #3
[INFO] Child #2

Removing the dependency of #2 on #3 reverses this order--it defaults to the module order.


----------------------------------------------------------------
Section 3: Dendency Management
----------------------------------------------------------------
* In a slight detour, we'll look at Maven 2's mechanism for handling
dependencies. This allows maven multi-projects to centralize their comprehensive dependency list.
This means you will need to edit only one file, for example, if you need to update a
particular jar version.

* This is described at

http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html
. However, I found it a bit confusing at first.

* As we have seen before, a child inherits all dependencies of its parent. However,
we often have the problem that two or more children will need to inherit a different
subset of the dependencies of the entire project. Although it makes bookkeeping simpler,
we don't want to make Child1 depend on jars that it doesn't need, just because Child2 needs them.

* For example, we have often used the Junit dependency--see the pom.xml files above. We can put this in the parent's pom.xml.

<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>


Let's say now that Child3 will need Junit but Child2 will not. Both of course extend the parent pom.xml
in the usual way. In Child3, we place following abbreviated dependency:

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>

Note we omit the scope, version and anything else. All of this gets inherited from the parent's master definition list. In Child2 we put none of this information, and the jar will not be used in any phase of Child2's build.

* On the other hand, we may want Child2 to include junit.jar in its WAR download. This means that
junit has a different scope (compile or similar) than the default scope. We manage this by simply as follows:

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>

That is, we add the <scope> parameter and override the default.

1 comment:

Archimedes Trajano said...

Thanks for writing about this. I am trying to create my own base project but it doesn't seem to add the Site data into the main project.