Maven Build Reproducibility with Enforcer

Maven makes it easy enough to declare dependencies on libraries and add extensions to the build process through plugin references that a project can pretty quickly form a quite involved build with many steps and lots and lots of code at play. One of the dark sides to all of this is with guaranteeing the reproducibility of the build: does the build work for you this afternoon, for your colleague on their machine, for your customer on a different network, or a year later. One source of such problems is with artefacts hosted on third-party sites, which could move, disappear, or whose content could change over time; keeping local copies of artefacts using a proxy such as DSMP can insulate against such problems. What about other problems that interfere with build reproducibility?

Enter the Enforcer Maven plugin. For those that need a little more encouragement it bills itself as The Loving Iron Fist of Maven.

The Enforcer is a way to trigger checks during the Maven build process that can be used to fail the build when certain criteria aren’t met. There are a number of standard built in checks to help with common problems together with the ability for extending the range of checks by evaluating bean shell scripts or through a Java extension API.

Standard rules cover items such as ensuring that versions are specified for plugins, ensuring only released dependencies are allowed, specific versions of Java or Maven are used to build, properties are set or files are present, and so on.

Using Enforcer

To use Enforcer you need to first add a reference to the plugin to your Maven pom.xml and then tie it into an execution goal so it runs automatically:

<project>
  <build>
    <plugins>
      <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-enforcer-plugin</artifactId>
       <version>1.0-beta-1</version>
       <executions>
         <execution>
           <id>enforce-rules</id>
           <goals>
             <goal>enforce</goal>
           </goals>
           <configuration>
             <rules>
               <!-- insert rules here -->
             </rules>
           </configuration>
         </execution>
       </executions>
      </plugin>
    </plugins>
  </build>
</project>

You can then add the rules that you want to enable, and any configuration they need, to the rules section of the plugin configuration.

Minimum Version of Maven Rule

One simple check is to specify a minimum version of Maven required to be able to run the build. If a project has found that bugs in earlier versions of Maven can cause problems with the build then the requireMavenVersion rule could be helpful.

Add the following to the rules section of the pom:

<requireMavenVersion>
  <version>2.0.9</version>
</requireMavenVersion>

In this case Maven 2.0.9 or later needs to be used to run the build. If the check is met then there is no change in the behaviour of the build from before the Enforcer was used, however were Maven 2.0.6 to be used to run the build then it would fail with an error along the lines of:

[INFO] [enforcer:enforce {execution: enforce-rules}]
[WARNING] Rule 0: org.apache.maven.plugins.enforcer.RequireMavenVersion failed with message:
Detected Maven Version: 2.0.6 is not in the allowed range 2.0.9.
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Some Enforcer rules have failed. Look above for specific messages explaining why the rule failed.

Mandatory Plugin Versions Rule

Specifying the precise versions of dependencies and plugins and excluding non-releases can be an important step for improving reproducibility. The requirePluginVersions rule allows checking that versions are specified for each of the plugins used during the build. When the plugin’s version is not specified then whenever a new version of any of the plugins is made available it will be automatically downloaded and used. Perhaps the new version has a bug, or changes the behaviour subtly – it is bound to happen on the morning of a release to the customer…

Add the following rule to the rules section of the pom:

<requirePluginVersions>
  <message>Best Practice is to always define plugin versions!</message>
</requirePluginVersions>

On first running Maven after making this change you will see an error similar to the following:

[WARNING] Rule 2: org.apache.maven.plugins.enforcer.RequirePluginVersions failed with message:
Some plugins are missing valid versions:(LATEST RELEASE SNAPSHOT are not allowed )
org.apache.maven.plugins:maven-clean-plugin.    The version currently in use is 2.2
org.apache.maven.plugins:maven-resources-plugin.        The version currently in use is 2.2
org.apache.maven.plugins:maven-deploy-plugin.   The version currently in use is 2.4
org.apache.maven.plugins:maven-compiler-plugin.         The version currently in use is 2.0.2
org.apache.maven.plugins:maven-surefire-plugin.         The version currently in use is 2.4.2
org.apache.maven.plugins:maven-install-plugin.  The version currently in use is 2.2
org.apache.maven.plugins:maven-site-plugin.     The version currently in use is 2.0-beta-7
org.apache.maven.plugins:maven-jar-plugin.      The version currently in use is 2.2
Best Practice is to always define plugin versions!
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Some Enforcer rules have failed. Look above for specific messages explaining why the rule failed.

Now specify the version for each of the plugins directly or specify all the plugins in the pluginManagement element with version number for it to be inherited by children modules. For example:

<build>
      <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-assembly-plugin</artifactId>
          <version>2.2-beta-5</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-clean-plugin</artifactId>
          <version>2.4.1</version>
        </plugin>
        ....... more ......
      </plugins>
    </pluginManagement>
  </build>

Additional Rules

For additional standard rules see the documentation:
http://maven.apache.org/enforcer/enforcer-rules/index.html

I couldn’t mind many third-party rules beyond the standard set except for:

Which Rules to Use?

Only including dependencies on releases would probably make sense for most projects. Requiring plugin versions too makes sense though it will take a little work to specify them in the first place. Excluding buggy older versions of Java and Maven might also make sense for some projects.

As for a recommended list of rules and configurations for projects I can’t find much help online. I have included some examples from project’s poms I found in the wild. Which rules would you recommend?

Examples of usage from the wild:

  • Parent pom of the Sonar plugins
    • Uses pluginManagement to specify plugin versions and a few other rules
  • Exo
    • check for a reasonable tomcat setup for the build based on properties and file existence
  • JBoss-EJB3
    • Banning particular dependencies

Things to Watch out For

When running Sonar on your Maven project it may trip the requirePluginVersions check due to the way it dynamically adds the Maven dependencies in needs. In this case use the -Denforcer.skip=true when calling sonar:sonar. For example in Hudson when using the sonar plugin add -Denforcer.skip=true to the Additional properties of the project’s Sonar post-build action.

References

Enforcer plugin usage and examples:
http://maven.apache.org/plugins/maven-enforcer-plugin/index.html

Standard rules:
http://maven.apache.org/enforcer/enforcer-rules/index.html

Writing a new rule:
http://maven.apache.org/enforcer/enforcer-api/writing-a-custom-rule.html

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply