Showing posts with label dependency. Show all posts
Showing posts with label dependency. Show all posts

2011/12/05

Maven: The Aggregator vs. The Parent

I have recently realized that even professionals working with Maven for extended period do not fully grasp the differences of Maven's aggregators and parents. The reason for that is probably that both terms are related to multi-module projects and that both approaches often 'meet' in single file.

Multi-module projects usually have rather flat structure with a single top-level pom.xml file. That file than lists sub-modules and defines versions of dependencies and/or plugins inherited in these sub-modules. This is well-known thing - what is less known is that these roles of pom.xml can be  separated.

Aggregator
A top-level module serving for joining multiple modules under one roof is called aggregator. Its purpose is only represent more or less independently existing modules as a parts of a greater whole. 

Example of aggreator pom.xml:
<project xmlns="...">

<modelVersion>4.0.0</modelVersion>

<groupId>org.bithill</groupId>
<artifactId>aggregator</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<name>Project Aggregator</name>

<modules>
 <module>project1</module>
 <module>project2</module>
</modules>

</project>

Parent

As you see, aggegator does not include any information about dependencies. The source of to-be-inherited information about libraries and plugins is known as a parent POM. It includes all the properties, dependencyManagement and pluginManagement sections stating versions of projects dependencies and plugins and some plugin configurations when it comes handy.  Ideally this information should be de-duplicated and inherited by plugins is sub-modules, but that does not apply to reporting plugins.

Example of parent pom.xml:
<project xmlns="...">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.bithill</groupId>
    <artifactId>parent</artifactId>
    <packaging>pom</packaging>
    <version>1.0</version>
    <name>shared parent</name>

    <properties>
      <java.version>1.6</java.version>
      <spring.version>3.0.2.RELEASE</spring.version>
    </properties>

    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>1.6.0</version>
        </dependency>

        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>${spring.version}</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aop</artifactId>
          <version>${spring.version}</version>
        </dependency>
      </dependencies>
    </dependencyManagement>

    <build>
      <pluginManagement>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
              <source>${java.jdk.version}</source>
              <target>${java.jdk.version}</target>
            </configuration>
          </plugin>
        </plugins>
      </pluginManagement>
    </build>
</project>

Using Aggregator and Parent POM Together

So we showed that we can have two different artificial POM files serving two different roles - aggregation and inheritance.   


Diagram of relationships in a project consisting of two sub-modules:

The last missing thing in the picture is an example of sub-module's pom.xml. As you see, no dependency or build plugin need  to define their version - that is inherited from parent POM. Parent's pom.xml is deployed in Maven repository, but Maven's default relative path to parent is ".." -  to avoid aggregator being used as parent, property relativePath must be set empty, this is probably the only trick here.
<project xmlns="...">

    <modelVersion>4.0.0</modelVersion>
    <groupId>org.bithill</groupId>
    <artifactId>project1</artifactId>
    <packaging>pom</packaging>
    <version>1.6-SNAPSHOT</version>
    <name>Project #1</name>

    <parent>
      <groupId>org.bithill</groupId>
      <artifactId>parent</artifactId>
      <version>1.0</version>
      <relativePath/>
    </parent>
  
    <dependencies>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
      </dependency>
    </dependencies>

    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
        </plugin>
      </plugins>
    </build>

</project>

2011/06/22

Fighting Crawling Maven Dependency Changes With Git

You know the situation well - everything goes fine, continuous integration does what is used to do and new releases are produced on regular basis.

Then, out of the blue, integration tests fail with Java complaining about two different versions of the same library on classpath. You can be less lucky and use different version than you think, or different implementation of an API, or your JAR/WAR/whatever-package just grows without reason. Lot of bad things can happen when dependecies get out of control.

It does not depend on the source - one of your colleagues changing something without double-check or changes in some already used library. To fight these problems I tried to use Git pre-commit hook you can see below. It is not perfect but it works. It depends on Maven, if you use Ivy, Gradle or something else, you will have to adapt it.

Just put it in you projects' /git/hooks named pre-commit. When dependency changes are detected, they are printed and commit is aborted - you will have to use diff tool on deps.txt (old dependencies) and deps_tmp.txt (new dependencies) and update deps.txt if the dependency change is desired. If the change is unwanted, use mvn dependency:tree to find the source of problems.

It has only one more constraint -- all people using it must use the same version of Maven. Because of changes in dependency resolution in Maven 3,  Maven dependency plug-in produces slightly different list of changes in versions 2 and 3.

#!/bin/sh

mvn dependency:list -DincludeScope=compile | sort | grep jar \
| cut -c 11- | cut -d":" -f 1-4 | uniq >deps_tmp.txt

diff -a -B -b -u deps.txt deps_tmp.txt >deps_diff.txt

lines=`cat deps_diff.txt | wc -l`

if [ $lines -ne 0 ]; then
   echo "Dependency change detected - check 'mvn dependency:tree' and update deps.txt."
   cat deps_diff.txt
   if [ -a deps_diff.txt ]; then rm -f deps_diff.txt; fi
   exit 1
fi