The following rules have been set in order to satisfy important development constraints:
- ensure build reproducibility,
- ease maintenance,
- provide versions consistency,
- allow POM inheritance or import.
Basic Rules
POM's Header
Maven POMs are written in XML, always start them with:
<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>
Code Formatting
Format your code.
- Use (two) spaces instead of tabs.
- The Eclipse plugin m2eclipse has many defects but it provides at least a great formatter.
- When putting lines in comments, keep them readable
<!-- <dependency> -->
<!-- <groupId>org.nuxeo.runtime</groupId> -->
<!-- <artifactId>nuxeo-runtime-start</artifactId> -->
<!-- <version>${nuxeo.runtime.version}</version> -->
<!-- </dependency> -->
Dependencies and Plugins Definition
Define dependencies' versions in the dependencyManagement
section and plugins' versions in the pluginManagement
section.
Note that not only the version is managed, those definitions also include the exclusions
, configuration
, ...
The reporting
plugins versions are not inherited from the pluginManagement
, they must be repeated. Using a property to avoid duplication is a good idea.
LATEST
and RELEASE
keywords are forbidden, as well as pom.*
properties but you can use variables prefixed "project.
" to reference any field of the POM that is a single value element. For instance, you can use "${project.version}
".
You can also use variables named nuxeo.something.version
or marketplace.something.version
: when releasing Nuxeo, the version replacement only applies on each POM on the parent version, the project version and all the properties named "nuxeo|marketplace.*version
".
SNAPSHOT plugins
SNAPSHOT
plugins are forbidden.
If you need to temporarily use a SNAPSHOT plugin:
add it to the
unCheckedPluginList
inorg.nuxeo:nuxeo-ecm
build/pluginManagement/plugins/maven-enforcer-plugin/configuration/rules/requirePluginVersions/unCheckedPluginList<unCheckedPluginList>org.nuxeo.build:ant-assembly-maven-plugin</unCheckedPluginList>
- create a blocker JIRA issue requiring the plugin upgrade for next Nuxeo LTS/FT release
SNAPSHOT dependencies
SNAPSHOT
dependencies are only allowed in development.
If you need to temporarily use a SNAPSHOT dependency:
add it to the
requireReleaseDeps/excludes
inorg.nuxeo:nuxeo-ecm
profile/nightly/build/pluginManagement/plugins/maven-enforcer-plugin/configuration/rules/requireReleaseDeps/excludes<exclude>org.nuxeo:nuxeo-ftest:*:zip</exclude>
- create a blocker JIRA issue requiring the dependency upgrade for next Nuxeo LTS/FT release
Note that the SNAPSHOT dependency will be tolerated during nightly releases, but would make the LTS/FT release fail even with that exclusion.
Dependency convergence and consistency
Dependency version doubt is forbidden and transitive dependency upgrade is allowed but not the downgrade.
If a project libA
depends on the artifact libZ
and another project libB
also depends on libZ
, then Maven will perform automatic version resolution to find the best match. On Nuxeo code, we want that version being explicitly set in the dependencyManagement.
Here are the possible cases:
- libA => libZ:1, libB => libZ:1, no dependencyManagement on libZ Ok, we don't care about transitive dependency versions when they are consistent between direct dependencies.
- libA => libZ:1, libB => libZ:2, no dependencyManagement on libZ Error: you have to state libZ:2 in the dependencyManagement.
- libA => libZ:1, libB => libZ:2, dependencyManagement libZ:2 Ok
- libA => libZ:1, libB => libZ:3, dependencyManagement libZ:2 Error: you have to state libZ:3 in the dependencyManagement.
- libA => libZ:1, libB => libZ:2, dependencyManagement libZ:3 Ok, it's an upgrade.
Duplication
Avoid duplication as much as possible (even if it is not always possible or easy since Maven syntax is verbose and not really designed for it).
For instance, if using the same version for multiple dependencies, then use a property for setting the version:
Sample use of a property for setting a version<properties> <apacheds.version>1.5.1</apacheds.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.directory.server</groupId> <artifactId>apacheds-protocol-shared</artifactId> <version>${apacheds.version}</version> </dependency> <dependency> <groupId>org.apache.directory.server</groupId> <artifactId>apacheds-core</artifactId> <version>${apacheds.version}</version> </dependency> </dependencies> </dependencyManagement>
Do not define the same things twice (either in the same POM, either using inheritance). Maven 3 logs useful warnings when it finds duplicate definitions:
Maven 3 duplication warning sample[WARNING] 'profiles.profile[itest].plugins.plugin.(groupId:artifactId)' must be unique but found duplicate declaration of plugin org.apache.maven.plugins:maven-antrun-plugin @ line 261, column 19
Make use of Maven inheritance. There are multiple ways:
Parent POM inheritance<parent> <groupId>org.nuxeo</groupId> <artifactId>nuxeo-ecm</artifactId> <version>5.7-SNAPSHOT</version> </parent>
Dependencies inheritance<dependencies> <dependency> <groupId>org.nuxeo.ecm.distribution</groupId> <artifactId>nuxeo-distribution-cap</artifactId> <type>pom</type> </dependency> </dependencies>
BOM (Bill Of Materials) import sample<dependencyManagement> <dependencies> <dependency> <groupId>org.nuxeo.ecm.distribution</groupId> <artifactId>nuxeo-distribution-cap</artifactId> <version>5.7-SNAPSHOT</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Direct versus Transitive Dependencies
Declare all your direct dependencies, do not rely on transitivity. Using a transitive dependency in your code is weak and error prone: for instance, if the dependency change, your code won't compile without any visible change.
There are two complementary solutions to optimize your POM and add missing direct dependencies:
Use the maven-dependency-plugin:
$ mvn dependency:analyze [WARNING] Used undeclared dependencies found: [WARNING] org.nuxeo.ecm.core:nuxeo-core-schema:jar:5.9.3:compile [WARNING] org.nuxeo.ecm.platform:nuxeo-platform-audit-api:jar:5.9.3:compile [WARNING] org.mockito:mockito-all:jar:1.9.5:test [WARNING] org.nuxeo.runtime:nuxeo-runtime-test:jar:5.9.3:test [WARNING] javax.servlet:servlet-api:jar:2.5:compile [WARNING] Unused declared dependencies found: [WARNING] org.freemarker:freemarker:jar:2.3.11:compile [WARNING] org.nuxeo.ecm.platform:nuxeo-platform-directory-sql:jar:5.9.3:compile [WARNING] org.nuxeo.ecm.platform:nuxeo-platform-directory-core:jar:5.9.3:compile [WARNING] org.nuxeo.ecm.platform:nuxeo-platform-webapp:jar:5.9.3:compile [WARNING] com.h2database:h2:jar:1.1.114-nx:provided [WARNING] hsqldb:hsqldb:jar:1.8.0.1:compile [WARNING] net.sf.opencsv:opencsv:jar:2.1:compile [WARNING] org.nuxeo.ecm.platform:nuxeo-connect-client-wrapper:jar:5.9.3:test [WARNING] org.nuxeo.ecm.platform:nuxeo-platform-directory-types-contrib:jar:5.9.3:test
A call to
mvn dependency:tree
will help to visualize the transitivity making the compile possible.
Maven Issues, Common Mistakes and Rules
- Since Maven 3.0, all
pom.*
properties are deprecated, replaced withproject.*
- Plugin configuration can be done in
plugin/some.plugin/configuration
andplugin/some.plugin/executions/execution/configuration
depending on what you want (set a global configuration or an execution's specific configuration) but be careful that when using both kinds of configuration, the way they are merged or overwritten depends on the plugin implementation. Do not expect any default behavior. - All modules should set a
description
in complement of thename
. Do not use two different POMs for defining the
modules
and thedependencies
.The
relativePath
definition works differently with Maven 2 and 3 and is useless if equal to../pom.xml
. As of Maven 3, it is mandatory if../pom.xml
does not point at the right parent.<parent> <groupId>org.nuxeo</groupId> <artifactId>nuxeo-ecm</artifactId> <version>5.7-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent>
If the parent is not supposed to be resolved locally, then set an empty relative path to force Maven looking for the parent POM in the repositories: <relativePath />
.
Do not rely on
~/.m2/settings.xml
for build-ability: a project must be build-able without information provided from outside however it is legitimate and highly recommended to use the local settings for setting passwords and custom environments (for instance repositories replacement, mirrors, ...). m2eclipse plugin can have some incompatibilities with other plugins (see for instance ) and require such workaround configuration:<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself. --> <plugin> <groupId>org.eclipse.m2e</groupId> <artifactId>lifecycle-mapping</artifactId> <version>1.0.0</version> <configuration> <lifecycleMappingMetadata> <pluginExecutions> <pluginExecution> <pluginExecutionFilter> <groupId>net.java.maven-incremental-build</groupId> <artifactId>incremental-build-plugin</artifactId> <versionRange>[1.4,)</versionRange> <goals> <goal>incremental-build</goal> </goals> </pluginExecutionFilter> <action> <ignore></ignore> </action> </pluginExecution> </pluginExecutions> </lifecycleMappingMetadata> </configuration> </plugin>
Define a Corporate POM
A lot of information is common to all your projects. That information is useful and should not be forgotten but there's no need to redefine it in all POMs: use a corporate POM as a common parent POM for your projects.
Here is information you should inherit from the corporate POM:
<url>http://www.nuxeo.com/en/products</url>
<organization>
<name>Nuxeo</name>
<url>http://www.nuxeo.com</url>
</organization>
<licenses>
<license>
<name>GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1</name>
<url>http://www.gnu.org/copyleft/lesser.txt</url>
</license>
</licenses>
<mailingLists>
<mailingList>
<name>Nuxeo ECM list</name>
<post>[email protected]</post>
<subscribe>http://lists.nuxeo.com/mailman/listinfo/ECM</subscribe>
<unsubscribe>http://lists.nuxeo.com/mailman/listinfo/ECM</unsubscribe>
<archive>http://lists.nuxeo.com/pipermail/ecm/</archive>
<otherArchives>
<otherArchive>
http://dir.gmane.org/gmane.comp.cms.nuxeo.bugs
</otherArchive>
<otherArchive>
http://www.mail-archive.com/[email protected]/
</otherArchive>
</otherArchives>
</mailingList>
</mailingLists>
<issueManagement>
<system>jira</system>
<url>http://jira.nuxeo.org/browse/NXP</url>
</issueManagement>
<ciManagement>
<system>Jenkins</system>
<url>http://qa.nuxeo.org/jenkins/</url>
<notifiers>
<notifier>
<type>mail</type>
<configuration>
<address>[email protected]</address>
</configuration>
</notifier>
</notifiers>
</ciManagement>
<developers>
<developer>
<id>Nuxeo</id>
<name>Nuxeo Developers Team</name>
<email>[email protected]</email>
<timezone>+1</timezone>
</developer>
</developers>
Define a Super POM per Project
A lot of definitions (artifacts' and plugins' versions, helper plugins configurations, ...) are common to all your projects or modules in a given version. Factorize those definitions into a super POM.
Nuxeo defines a super POM (which is also the corporate POM) inherited by all other Nuxeo projects and modules: see org.nuxeo:nuxeo-ecm:5.6.
It is generally a good idea to gather all dependencyManagement
and pluginManagement
sections of all modules into a unique one, preferably a common parent project to those modules, a common BOM or the super POM.
In Nuxeo, they are mainly set in org.nuxeo:nuxeo-ecm
, org.nuxeo:nuxeo-addons-parent
and org.nuxeo.ecm.distribution:nuxeo-distribution
POMs.
Every project super POM should define the following if not defined (or different from those) into the corporate POM:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<scm>
<connection>scm:git:git://github.com/nuxeo/nuxeo.git</connection>
<developerConnection>scm:git:ssh://[email protected]:nuxeo/nuxeo.git</developerConnection>
<url>https://github.com/nuxeo/nuxeo</url>
</scm>
<repositories>
<repository>
<id>public</id>
<url>http://maven.nuxeo.org/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>public-snapshot</id>
<url>http://maven.nuxeo.org/nexus/content/groups/public-snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<updatePolicy>always</updatePolicy>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<url>http://maven.nuxeo.org/nexus/content/groups/public</url>
<name>Nuxeo virtual release repository</name>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>public-snapshot</id>
<url>http://maven.nuxeo.org/nexus/content/groups/public-snapshot</url>
<name>Nuxeo virtual snapshot repository</name>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<updatePolicy>always</updatePolicy>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<distributionManagement>
<site>
<id>maven-website</id>
<url>scpexe://gironde.nuxeo.com/home/mavenweb/site/</url>
</site>
<repository>
<id>public-releases</id>
<url>https://mavenin.nuxeo.com/nexus/content/repositories/public-releases</url>
</repository>
<snapshotRepository>
<id>public-snapshots</id>
<url>https://mavenin.nuxeo.com/nexus/content/repositories/public-snapshots</url>
<uniqueVersion>true</uniqueVersion>
</snapshotRepository>
</distributionManagement>
Since you host your own repository manager, you must not add other repositories in your POMs. Instead, ask to your administrator for adding them into your corporate repository manager. This provides artifacts caching and central management.
Make Use of Helper Plugins
There are a lot of available Maven plugins which can help to check, enforce and improve your POMs.
Versions have been removed from the following extracts: pick up the latest one from the dedicated sites and remember to define them in the pluginManagement
section (and/or reporting
section if relevant).
maven-compiler-plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.7</source>
<target>1.7</target>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
maven-help-plugin
Get relative information about a project or the system. The "mvn help:effective-pom
" command is particularly useful.
maven-dependency-plugin
That plugin has various analysis and reporting goals. The "mvn dependency:tree"
command can be used to better understand where an artifact comes from in a given version. It is very similar to the output of "artifact:print"
command of nuxeo-distribution-tools plugin.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>analyze</id>
<goals>
<goal>analyze-only</goal>
</goals>
<configuration>
</configuration>
</execution>
</executions>
</plugin>
maven-enforcer-plugin
Various rules and constraints/enforcements on environment and POM content.
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<configuration>
<rules>
<requirePluginVersions>
<message>Set plugin versions in pluginManagement section.</message>
<banLatest>true</banLatest>
<banRelease>true</banRelease>
<banSnapshots>true</banSnapshots>
<!-- <unCheckedPluginList>org.nuxeo.build:nuxeo-distribution-tools</unCheckedPluginList> -->
</requirePluginVersions>
<requireReleaseDeps>
<message>No Snapshots Allowed!</message>
<onlyWhenRelease>true</onlyWhenRelease>
<excludes>
<!-- <exclude>org.nuxeo.build:nuxeo-distribution-tools</exclude> -->
<!-- <exclude>org.nuxeo:nuxeo-ftest:*:zip</exclude> -->
<!-- <exclude>org.nuxeo.connect:nuxeo-connect-client</exclude> -->
</excludes>
</requireReleaseDeps>
<requireMavenVersion>
<version>[2.2.1,)</version>
</requireMavenVersion>
<requireJavaVersion>
<version>[1.6,)</version>
</requireJavaVersion>
</rules>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<!-- Especially used at Nuxeo for releases and nightly builds to ensure there are no SNAPSHOT dependencies: -->
<profiles>
<profile>
<id>release</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce-plugin-versions</id>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
versions-maven-plugin
Help to set plugins and dependencies versions, check for available upgrades.
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<reportSets>
<reportSet>
<reports>
<report>dependency-updates-report</report>
<report>plugin-updates-report</report>
<report>property-updates-report</report>
</reports>
</reportSet>
</reportSets>
</plugin>
</plugins>
</reporting>
$ mvn versions:display-plugin-updates versions:display-dependency-updates versions:display-property-updates
[INFO] The following plugin updates are available:
[INFO] net.sourceforge.maven-taglib:maven-taglib-plugin ....... 2.3.1 -> 2.4
[INFO] org.apache.felix:maven-bundle-plugin ................. 2.0.1 -> 2.1.0
[INFO] maven-clean-plugin ....................................... 2.2 -> 2.4
[INFO] maven-compiler-plugin .................................. 2.0.2 -> 2.3
[WARNING] The following plugins do not have their version specified:
[WARNING] maven-deploy-plugin ......................... (from super-pom) 2.5
[WARNING] maven-install-plugin ........................ (from super-pom) 2.3
[WARNING] maven-site-plugin ........................... (from super-pom) 2.1
[INFO] The following dependencies in Dependency Management have newer versions:
[INFO] bouncycastle:bcmail-jdk14 ................................. 136 -> 138
[INFO] bouncycastle:bcprov-jdk14 ................................. 136 -> 140
[INFO] com.google.gwt:gwt-dev .................................. 1.5.3 -> 2.7
[INFO] com.google.gwt:gwt-servlet ....................... 1.5.3 -> 2.0.0-6757
[INFO] com.google.gwt:gwt-user .......................... 1.5.3 -> 2.0.0-6757
[INFO] com.h2database:h2 ................................. 1.1.114 -> 1.2.135
[INFO] com.hp.hpl.jena:arq ..................................... 2.1 -> 2.8.3
[INFO] com.hp.hpl.jena:iri ....................................... 0.5 -> 0.8
[INFO] com.hp.hpl.jena:jena .................................. 2.5.4 -> 2.6.2
[INFO] com.noelios.restlet:com.noelios.restlet ............... 1.0.7 -> 1.1.3
[INFO] com.noelios.restlet:com.noelios.restlet.ext.servlet ... 1.0.7 -> 1.1.3
[INFO] The following dependencies in Dependencies have newer versions:
[INFO] junit:junit ............................................. 4.7 -> 4.8.1
[INFO] The following version property updates are available:
[INFO] ${apachedshared.version} ............................ 0.9.7 -> 0.9.19
[INFO] ${apacheds.version} .................................. 1.5.1 -> 1.5.5
[INFO] ${jackrabbit.version} ................................ 1.5.0 -> 1.6.1
See http://qa.nuxeo.org/jenkins/job/nuxeo-master-versions/lastSuccessfulBuild/console
tattletale-maven
Various checks and reports on artifacts.
<plugin>
<groupId>org.jboss.tattletale</groupId>
<artifactId>tattletale-maven</artifactId>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
<configuration>
<destination>${project.build.directory}/tattletale</destination>
<reports>
<report>jar</report>
<report>multiplejars</report>
</reports>
<profiles>
<profile>java6</profile>
</profiles>
<failOnWarn>true</failOnWarn>
</configuration>
</plugin>
maven-pmd-plugin
PMD code analysis tool (including the separate Copy/Paste Detector tool − or CPD − distributed with PMD).
maven-checkstyle-plugin
Check coding style.
incremental-build-plugin
Fix a Maven issue allowing not to use clean systematically (the plugin will automatically call the clean goal if the sources have changed).
<plugin>
<groupId>net.java.maven-incremental-build</groupId>
<artifactId>incremental-build-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>incremental-build</goal>
</goals>
</execution>
</executions>
</plugin>
buildnumber-maven-plugin
Generates a unique build number (based on timestamp, SCM revision, ...) which can then be stored, for instance, in the MANIFEST.MF
.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>create</goal>
</goals>
</execution>
</executions>
<configuration>
<format>{0,date,yyyyMMdd-HHmmss}</format>
<items>
<item>timestamp</item>
</items>
<doCheck>false</doCheck>
<doUpdate>false</doUpdate>
</configuration>
</plugin>
license-maven-plugin
This plugin manages the license of a maven project and its dependencies (update file headers, download dependencies licenses, check third-party licenses, ...)
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<configuration>
<excludedScopes>test,provided,system</excludedScopes>
</configuration>
<reportSets>
<reportSet>
<reports>
<report>third-party-report</report>
</reports>
</reportSet>
</reportSets>
</plugin>
</plugins>
</reporting>