Project root path in a Maven multi module project

In a multi module Maven project, it seems non trival to reference the project root location from the sub modules deeper down in the module hierarchy. The following approach describes how to configure a plugin referencing a root POM relative file.

Here’s the usual multi module file layout:

Main POM (contains project global conf file)
       |
       +- Sub module 1 POM
       |
       +- Sub module 2 POM (needs project global configuration file)
       |
       +..

Typically, you need to reference a file resource for some plugin configuration issue and want it applied for all project modules. The pluginManagement section in your main POM allows you to configure plugin defaults for the project scope, which applies to all modules - a best practise for Maven projects.

Example: Shared findbugs plugin configuration

My example is a  findbugs plugin configuration having a filter file, configured in the main POM and applied for all sub modules when generating the project site. An alternative example could be a shared log4j configuration file used by surefire tests.

Main pom.xml:

<pluginManagement>
  <plugins>
    <plugin>
       <groupId>org.codehaus.mojo</groupId>
       <artifactId>findbugs-maven-plugin</artifactId>
       <version>2.0</version>
       <configuration>
           <threshold>Normal</threshold>
           <effort>Max</effort>
           <excludeFilterFile>${basedir}/tools/findbugs-excludes.xml</excludeFilterFile>
       </configuration>
    </plugin>
  </plugins>
</pluginManagement>

This works nicely for a single module maven project. For a multi module setup, this fails since ${basedir} always evaluates to the current modules directory root. So in a sub module, this is the directory containing the sub modules pom.xml.

Potential solutions?

Potential solutions could be to configure the plugin using an absolute  path or redundantly configure the path for each sub module. An absolute path violates portability, and redundant configurations in a 20+ module project increases complexity  (sure way to configuration hell and are plain ugly).

A better approach involves using a profiles.xml which configures the project root path as a variable:

<?xml version="1.0" encoding="UTF-8"?>
<profilesXml>
  <profiles xmlns="http://maven.apache.org/PROFILES/1.0.0"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://maven.apache.org/xsd/profiles-1.0.0.xsd">
    <profile>
      <id>env</id>
      <properties>
      <!-- Replace with current directory when initially creating profiles.xml
               In Windows, don't forget the leading '/' before the device name
               (e.g. /D:/projects/foo) -->
        <PROJECT_HOME>/Users/mm/devel/example-project</PROJECT_HOME>
      </properties>
      <activation>
        <file><missing>always_missing_file</missing></file>
      </activation>
    </profile>
  </profiles>
</profilesXml>

This is a personal profile, so it should not get checked into source code management (Subversion etc.). The profile is always active, triggered by the workaround activation of a non existing file always_missing_file. Your findbugs configuration now references the ${PROJECT_HOME} property, which will point to your project root for the main and all sub module POMs:

<excludeFilterFile>${PROJECT_HOME}/tools/findbugs-excludes.xml </excludeFilterFile>

Further simplifications

For a project setup for multiple people, it makes sense to have the profile file provided as a template, eg as profiles.xml.template. Upon initial project checkout, you copy this template to profiles.xml and edit the value of PROJECT_HOME.

Another trick can do this for the developer automatically on the first time Maven invocation, using the antrun or GMaven plugin.

In your projects main pom.xml:

<profiles>
  <profile>
    <id>setup</id>
    <build>
      <plugins>
        <plugin>
          <groupId>org.codehaus.groovy.maven</groupId>
          <artifactId>gmaven-plugin</artifactId>
          <version>1.0</version>
          <inherited>false</inherited> <!-- Works only if inheritance is deactivated -->
          <executions>
            <execution>
              <phase>initialize</phase>
              <goals>
                <goal>execute</goal>
              </goals>
              <configuration>
                <source>
                  ant.copy(file: 'profiles.xml.template',
                                tofile: 'profiles.xml',
                                filtering: true) {
                    filterset() {
                      filter(token: 'PROJECT_HOME',
                              value: new File('.').getCanonicalPath())
                      }
                    }
                  log.info('')
                  log.info('This is the first time you invoked Maven for this project.')
                  log.info('Initialized profiles.xml. Finished setup, exiting.')
                  log.info('You can now use maven as usual.')
                  log.info('')
                  System.exit(0)
                  </source>
                </configuration>
             </execution>
           </executions>
         </plugin>
      </plugins>
    </build>
    <activation> <!-- Only create profiles.xml if there's no profiles.xml yet -->
      <file><missing>profiles.xml</missing></file>
    </activation>
  </profile>
</profiles>

The profiles.xml.template which gets filtered once upon first Maven invocation:

<?xml version="1.0" encoding="UTF-8"?>
<profilesXml>
  <profiles xmlns="http://maven.apache.org/PROFILES/1.0.0"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://maven.apache.org/xsd/profiles-1.0.0.xsd">
    <profile>
      <id>env</id>
      <properties>
         <! -- Replaced by Groovy scriptlet when copying over to profiles.xml -- >
         <PROJECT_HOME><strong>@PROJECT_HOME@</strong></PROJECT_HOME>
      </properties>
      <activation>
        <file><missing>always</missing></file>
      </activation>
    </profile>
  </profiles>
</profilesXml>

If anyone knows a more elegant solution, I’d really be interested … it’s still a bit complex.

Enjoy, Marcel

Author: Admin
Tags: gmaven, Maven
Categories: maven, development
Monitoring-Workshop 2017 12./13.9. Düsseldorf