Jim has posted 11 posts at DZone. View Full User Profile

Migrating to Maven 2 (Part 2)

05.03.2008
| 8754 views |
  • submit to reddit

Maven 2 has fantastic multi-deliverable project build capabilities. We'll investigate this feature by Mavenizing Guice's other Jar deliverables. We'll Mavenize Guice's Spring bridge, Servlet injector, and Struts 2 plugin in this part of the tutorial, and Mavenize the Struts 2 example in Part 3.

If you're starting this tutorial with part 2, you'll need to go back to part 1 of the tutorial and download Guice's source, install cglib-nodep-2.2_beta-1, and put the pom.xml listed at the end of part 1 in the root of Guice's source directory. Assuming you've done all of that (or you've finished part 1), you're ready to proceed with part 2 of the tutorial.

A Quick Tour of Multi-Module Maven Builds


Maven 2 has the ability to build multiple related (sub)projects in one build cycle, making it easy for multiple teams to share the same build cycle and dependencies, and easily find out if the build passes or fails as a whole. Pom.xml files are actually easier to manage for larger builds because only dependencies need to be listed each POM file, but not version numbers. Version numbers for dependencies, as well as all other project information, are listed in what's often referred to as the "parent POM" in the <dependencyManagement> section, and do not have to be repeated in other POMs. This feature makes both adding new project modules and upgrading dependencies very easy. The project's parent pom and has a <packaging>pom</packaging> instead of <packaging>jar</packaging>, making it fairly easy to spot.

Although a parent pom file can be located anywhere in the project, it is usually located at the root of the project's source directory. Each sub-project, known as a module, is typically a sibling of the parent pom.xml file. Each of these directories is listed in a <modules> section of the parent POM so Maven 2 knows to look for it, and each module's POM file is frequently referred to as a "child POM".

The whole project build can be launched from the directory where the parent POM is located, building all modules listed in the parent pom's <modules> section. Alternatively, a single module may be built by running Maven in that module's directory. All project information is inherited from the parent pom, but in order for a dependency to be used in a module it must be listed in the <modules> section. If one project module is used as a dependency in another, the same dependencies do not need to be re-listed since they are transitive. Dependency scopes can be listed in either the parent or child POM, depending on if the scope is applicable project wide (such as JUnit) or for each module (such as Spring, which we'll see later)

Parent POM and child POM relationships are described very well in Eric Redmond's article The Maven 2 POM demystified, the first part of chapter 3 in Better Builds With Maven, and Chapter 6 and Chapter 7 of Maven: The Definitive Guide

The parent POM and the child POM together make up what's known as an "effective POM". A module's effective POM can be viewed at any time by running

>mvn help:effective-pom

 

Making Guice Modular

Our goal is to have the same deliverables Guice does with it's current Ant build scripts. To generate the Jars we're trying to replicate, navigate to the guice-1.0-src directory and run

>ant dist

The jars will be placed in the guice-1.0-src/build/dist directory.

The Guice Module

We need to make a few small changes to the directory structure of the code in order to make the build a little bit easier to manage, but not much:

  1. Create a directory in your guice-1.0-src directory called guice
  2. Copy the guice-1.0-src/src and guice-1.0-src/test directories into the guice directory you just created

Now create a pom.xml file in the guice directory, and use the following XML for it:

<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>
<parent>
<groupId>com.google.code.guice</groupId>
<artifactId>guice-parent</artifactId>
<version>1.0</version>
</parent>
<artifactId>guice</artifactId>
<name>guice</name>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>

You'll notice that the version numbers aren't listed in any of the dependencies. If you want to make a version of a jar "sticky" for a child POM, even though the version may be changed in the parent, you can declare the version number. However, the practice is not recommended. You'll also notice that we've added a <parent> section that indicates what POM should be used as a parent POM.

Now we need to update the POM from part 1 (in the guice-1.0-src directory) to make it into a parent POM.

  1. Change <packaging>jar</packaging> to <packaging>pom</packaging> at the top
  2. Change <artifactId>guice</artifactId> to <artifactId>guice-parent</artifactId> at the top
  3. Add the <modules> section just below the <build> section (it won't work if you put it before the <build> section, though I'm not sure why)
      <modules>
    <module>guice</module>
    </modules>
  4. Put a <dependencyManagement> and a corresponding </dependencyManagement> element around the <dependencies> section. This tells Maven you'll be using these dependencies throughout your child POMs

Notice that the name of the module is the same name as the directory where the code for that module resides, which is how Maven locates project modules that are to be built as part of a build cycle. Child modules do not have to be listed, but can still take advantage of the parent POM's definitions. We'll see this type of situation in part 3.

Once you've added the child POM in the guice directory and updated the parent POM in the guice-1.0-src directory, run

>mvn compile

in the guice-1.0-src directory and you should see BUILD SUCCESSFUL

If you build the site, you'll find the static project documentation located in the guice-1.0-src/target/site directory and the report results in guice-1.0-src/guice/target/site directory. This is a known issue -- it would make sense that all module websites be accessible from the parent website -- but it's something that unfortunately has to be coped with until it's fixed.

 

The Spring Bridge Module

  1. Add <module>spring</module> to the <modules> section of your parent POM
  2. Remove the <scope>test</scope> from both the spring-core and spring-beans dependencies in the parent POM since they are now being used as dependencies of Guice artifacts
  3. Add a version property - this will be helpful when we need to upgrade guice artifacts:
    <properties>
    <guiceVersion>1.0</guiceVersion>
    </properties>
  4. Since the Spring Bridge module requires the core Guice module, we need to add it to the <dependencyManagement> section. Add the following XML the parent POM in the <dependencies> section:
          <dependency>
    <groupId>com.google.code.guice</groupId>
    <artifactId>guice</artifactId>
    <version>${guiceVersion}</version>
    </dependency>
  5. Create a pom.xml file in the guice-1.0-src/spring directory with the following contents:
    <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>
    <parent>
    <groupId>com.google.code.guice</groupId>
    <artifactId>guice-parent</artifactId>
    <version>1.0</version>
    </parent>
    <artifactId>guice-spring</artifactId>
    <name>guice-spring</name>
    <packaging>jar</packaging>

    <dependencies>
    <dependency>
    <groupId>com.google.code.guice</groupId>
    <artifactId>guice</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    </project>

 


If your build isn't compiling at this point, the <scope>test</scope> declaration may be present in your parent POM for the spring-core and spring-beans dependencies. Once you remove them, it should compile just fine.

Attempting to run the tests though, we find that the SpringIntegration test class isn't compiling, indicating that there is no import com.google.inject.PerformanceComparison package. Looking into it, Maven is treating the parent class of the TeeImpl static inner class as a package on the import statement. Doing some quick searches on Google, it looks like this isn't the first time this problem has been encountered, but it's not clear if it's been entered into JIRA yet. I found a similar Surefire issue SUREFIRE-44, but I don't think this is quite the same. Fortunately, the import is not used in the SpringIntegrationTest class where is is being imported, so for now we'll remove the import; in all reality, though, this could be real showstopper and almost was in our case. After removing the line import com.google.inject.PerformanceComparison.TeeImpl; from the SpringIntegrationTest.java class, the unit tests work fine and we're back on track.

 

The Servlet Module

  1. Add <module>servlet</module> to the <modules> section of your parent POM
  2. Add the following dependencies to your parent POM in the <dependencies>section:
          <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    </dependency>
    <dependency>
    <groupId>org.easymock</groupId>
    <artifactId>easymock</artifactId>
    <version>2.2</version>
    <scope>test</scope>
    </dependency>
  3. Create a pom.xml file in the guice-1.0-src/servlet directory with the following contents:
    <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>
    <parent>
    <groupId>com.google.code.guice</groupId>
    <artifactId>guice-parent</artifactId>
    <version>1.0</version>
    </parent>
    <artifactId>guice-servlet</artifactId>
    <name>guice-servlet</name>
    <packaging>jar</packaging>

    <dependencies>
    <dependency>
    <groupId>com.google.code.guice</groupId>
    <artifactId>guice</artifactId>
    </dependency>
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.easymock</groupId>
    <artifactId>easymock</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    </project>

Thankfully all of the unit tests pass without any trouble and we can confidently move onto the Guice Struts 2 Plugin.

 

The Guice Struts 2 Plugin

  1. Add <module>struts2/plugin</module> to the <modules> section of the parent POM because the struts2 plugin is located in that directory relative to the guice-1.0-src directory
  2. Add the following dependencies to the parent POM in the <dependencies> section. Notice that even though there are quite a number of plugins that are required to use Struts2 in the struts2/lib folder, we only need to specify the struts2-core dependency
          <dependency>
    <groupId>org.apache.struts</groupId>
    <artifactId>struts2-core</artifactId>
    <version>2.0.5</version>
    </dependency>
    <dependency>
    <groupId>com.google.code.guice</groupId>
    <artifactId>servlet</artifactId>
    <version>1.0</version>
    </dependency>
  3. Create a pom.xml file in the guice-1.0-src/struts2/plugin directory with the following contents:
    <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>
    <parent>
    <groupId>com.google.code.guice</groupId>
    <artifactId>guice-parent</artifactId>
    <version>1.0</version>
    <relativePath>../..</relativePath>
    </parent>
    <artifactId>guice-struts2-plugin</artifactId>
    <name>guice-struts2-plugin</name>
    <packaging>jar</packaging>

    <build>
    <resources>
    <resource>
    <directory>src</directory>
    <excludes>
    <exclude>**/*.java</exclude>
    </excludes>
    </resource>
    </resources>
    </build>

    <dependencies>
    <dependency>
    <groupId>org.apache.struts</groupId>
    <artifactId>struts2-core</artifactId>
    </dependency>
    <dependency>
    <groupId>com.google.code.guice</groupId>
    <artifactId>guice-servlet</artifactId>
    </dependency>
    </dependencies>

    </project>
  4. The Guice team released version 1.0.1 of the Struts2 plugin after the initial release of Guice, and you may optionally update the GuiceObjectFactory source file in the module to reflect that change. The updated source for the GuiceObjectFactory can be found here. While not needed for the purposes of this tutorial series, I would recommend this change if you are building the plugin in your development team.

You've probably noticed how we added a <relativePath>../..</relativePath> element to the parent declaration. This doesn't need to be declared if the parent POM is in the parent directory directly above the module, but does if the parent POM is located elsewhere in the project. In our case, the parent POM is two directories up, but could have been in a completely different location.

We've also created a <resources> declaration in the <build> section. This allows the struts-plugin.xml file to be added to the jar when it is packaged. We've also added an <excludes> section that excludes all *.java files within the project to prevent them from being added. Even though we added a <build> section, we didn't have to re-declare the source version or build and test directories.

 

Putting It All Together

The Maven 2 commands can be executed from the root source directory (guice-1.0-src) to compile, test, and package, or the commands can be run in each of the project directories, such as guice-1.0-src/guice, guice-1.0-src/spring, etc. However, each of the modules a given module is dependent on must be installed in your local repository. For instance, if we want to work on the Struts 2 plugin code, the guice module and guice-servlet module must both be installed by running the command

>mvn install

If you run the command

>mvn package

from the guice-1.0-src directory, you'll find a Jar file in the target directory of each of the modules that is identical to the Jars in the guice-1.0-src/build/dist directory, file-for-file and almost byte-for-byte (okay -- except the core guice Jar, but that's because the dependencies aren't being repackaged into the Jar). All of the Jars can be posted to your favorite repository by running the

>mvn deploy

command. At that point, your teammates can now use the newly created Jars.

 

Conclusion

We've migrated the Guice project from generating a single Jar artifact to generating multiple Jar artifacts that can all be generated and shared with a single command. We've had to do a little bit of work along the way to get Maven to work with the Guice source, but the source code didn't undergo any major refactorings or restructuring.

In the third and final part of the tutorial, we'll look at how to use and take advantage of Maven's Jetty plugin.

 

Resources

  • The parent POM with all modules and dependencies declared up to this point can be found here.
  • The Glassfish team is using Maven 2's multimodule build capabilities to build Glassfish V3. Be sure to take a look at their source code repository.
  • Eric Redmond's article The Maven 2 POM demystified
AttachmentSize
GuicePartTwoPOM.txt7.65 KB
Published at DZone with permission of its author, Jim Bethancourt.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Ittay Dror replied on Mon, 2008/10/13 - 12:26am

Fantastic multi-module support? I don't think so.

* You can't have wildcards to add all modules under a certain one, since adding a module doesn't add any information besides its name, why make me explicitely add it? (of course I shouldn't have to use wildcards, it should be an option)

* You can't have the same module referenced from the two multi-module projects in the same tree. Say I have an application with two independent components. To build the application, I build both, but developers usually build only the one they are working on. Now, if I have a 'common' component that both need, I'm in a problem.

* Modules can't reference each other directly. This means two things: 1. any data you put in one is inaccessible to other modules (dependencies maven manages, the rest of the configuration is used to build that module only) , 2. when building, maven goes into each module and goes through the whole cycle of building and installing it, even if not required.

* You can't build a single module so that Maven uses the dependency information to build dependent modules. 

* You can't tell maven to skip some modules. 

* If you have some modules that are only built in some cases, you have to muck with profiles, there's no way to "programmatically" build the module list.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.