View on GitHub

PME @ Red Hat

Standardize Your Builds

Dependency Manipulation

Overview

PME can override a set of dependency versions using a remote source which may be either a pom (BOM) file or a remote REST endpoint.

Dependency Source

BOM and REST

There are two sources of dependencies used to align to in PME. The property dependencySource is used to alter the behaviour of how PME handles the multiple sources of dependency information. The BOM value (the default and the behaviour if this property is not specified) is that PME will use the BOM (i.e., Remote POM) source. Alternatively, the REST source may be specified to use only the REST Endpoint information. However, by setting the property to either RESTBOM or BOMREST it will instead merge the two sets of values. With RESTBOM, precedence is given to the REST information and for BOMREST precedence is given to the BOM information. If the setting is NONE no remote alignment will be performed.

Remote POM

By default, all dependencies listed in the remote pom will be added to the current build. This has the effect of overriding matching transitive dependencies, as well as those specified directly in the pom.

Use the dependencyManagement property to list your BOMs:

mvn install -DdependencyManagement=org.foo:my-dep-pom:1.0

Multiple remote dependency-management poms can be specified using a comma separated list of GAVs (groupId, artifactId, version). The list also supports a remote HTTP file containing a comma or newline separated list of GAVs. The poms are specified in order of priority, so if the remote BOMs contain some of the same dependencies, the versions listed in the first bom in the list will be used.

mvn install -DdependencyManagement=org.foo:my-dep-pom:1.0,org.bar:my-dep-pom:2.0

Note: If the BOM is specified in the format -DdependencyManagement=org.foo:my-dep-pom:1.0-rebuild i.e., a rebuild suffix is specified but without a numeric portion then PME will automatically replace the BOM GAV with the latest suffix via a REST call. For instance, assuming the latest suffix is rebuild-3, the above will be replaced implicitly by -DdependencyManagement=org.foo:my-dep-pom:1.0-rebuild-3.

REST Endpoint

Alternatively, rather than using a remote BOM file as a source, it is possible to instruct PME to pre-scan the project, collect up all group:artifact:version’s used and call a REST endpoint using the endpoint property restURL (provided from the Dependency Analysis tool here, versions >= 2.1, hereinafter referred to as DA) and specifying dependencySource of REST, which will then return a list of possible new versions. Note that the URL should be the subset of the endpoint e.g.,

http://foo.bar.com/da/rest/v-1

PME will then call the following endpoints

lookup/maven/latest
lookup/maven

in that order. By default, PME will pass all the GAVs to the endpoint automatically sizing the data sent to DA according to the project size. Note that the initial split batches can also be configured manually via -DrestMaxSize=<...>. If that value is set to 0, then everything is sent without any auto-sizing. If the endpoint returns a 503 or 504 timeout the batch is automatically split into smaller chunks in an attempt to reduce load on the endpoint and the request retried. It will by default chunk down to size of 4 before aborting. This can be configured with -DrestMinSize=<...>.

A boolean flag restBrewPullActive flag switches on and off the version lookup in Brew and the default value is false. Switching it off might have positive effect on performance. Finally, the string identifier restMode indicates type of versions to lookup. Modes are configurable in DA, so it is needed to check the DA config/consult with DA maintainers for the list of configured modes. Usual modes might be e.g.

with more to be added in the future. The lookup REST endpoint should follow:

Parameters Returns

[
    [ "brewPullActive": true, ]
    [ "mode": "MODE-ID", ]
    {
        "groupId": "org.foo",
        "artifactId": "bar",
        "version": "1.0.0.Final"
    },
    ...
]
    

[
    {
        "groupId": "org.foo",
        "artifactId": "bar",
        "version": "1.0.0.Final",
        "bestMatchVersion": "1.0.0.Final-rebuild-2",
    },
    ...
]
   

Note: For existing dependencies that reference a property, PME will update this property with the new version. If the property can’t be found (e.g., it was inherited), a new one will be injected at the top level. This update of the property’s value may implicitly align other dependencies using the same property that were not explicitly requested to be aligned.

REST Timeouts and retries

In case of a 504 response from DA, by default the operations will be retried after a waiting period of 30 seconds. This value can be optionally configured with -DrestRetryDuration=<...>, expressed in seconds.

The underlying HTTP client library responsible for calling the REST endpoints is set by default with a socket timeout of 10 minutes, and a connection timeout of 30 seconds. The values can be optionally configured respectively with -DrestSocketTimeout=<...> and -DrestConnectionTimeout=<...>, expressed in seconds.

REST Headers
NOTE : Available from version 4.0

You can add custom HTTP headers to your REST calls by setting a property named restHeaders. HTTP headers must be comma-separated and each non-empty name-value pair must be colon-separated.

For example:

-DrestHeaders=log-user-id:102,log-request-context:061294ff-088,log-process-context:,log-expires:,log-tmp:

Direct/Transitive Dependencies

NOTE : As of PME 2.13 (October 2017) the default for overrideTransitive has changed from true to false.

The extension can inject all dependencies declared in the remote BOM. This will also override dependencies that are not directly specified in the project. If these transitive dependencies should not be overridden, the option overrideTransitive can be set to false to disable this feature.

mvn install -DdependencyManagement=org.foo:my-dep-pom:1.0 -DoverrideTransitive=false

Parent Version Override

PME will also change any parent reference it finds that matches an entry in the remote BOM.

For example:

<parent>
  <groupId>org.switchyard</groupId>
  <artifactId>switchyard-parent</artifactId>
  <version>2.0.0.Alpha1</version>
</parent>

will change to:

<parent>
  <groupId>org.switchyard</groupId>
  <artifactId>switchyard-parent</artifactId>
  <version>2.0.0.Alpha1-rebuild-1</version>
</parent>

Exclusions and Overrides

In a multi-module build it is considered good practice to coordinate dependency version among the modules using dependency management. In other words, if module A and B both use dependency X, both modules should use the same version of dependency X. Therefore, the default behaviour of this extension is to use a single set of dependency versions applied to all modules.

It is possible to flexibly override or exclude a dependency globally or on a per-module basis. The property starts with dependencyOverride. and has the following format:

mvn install -DdependencyOverride.[groupId]:[artifactId]@[moduleGroupId]:[moduleArtifactId]=[version] | ,+[group:artifact]...
NOTE : Previously dependencyExclusion operated as an alias of dependencyOverride (and functioned _exactly the same_). As of PME 4.0 this has now been deprecated and must be explicitly enabled. If enabled and both are set then they will be merged and an error thrown if they clash.

Note: Multiple overrides/exclusions may be added using multiple instances of -DdependencyOverride....

Global Version Override

Sometimes it is more convenient to use the command line rather than a BOM. Therefore, extending the above it is possible to set the version of a dependency via:

mvn install -DdependencyOverride.junit:junit@*=4.10-rebuild-10

This will, throughout the entire project (due to the wildcard), apply the explicit 4.10-rebuild-10 version to the junit:junit dependency.

Note: Explicit overrides like this will take precedence over strict alignment and the BOM.

Per-Module Version Override

However, there are certain cases where it is useful to use different versions of the same dependency in different modules. For example, if the project includes integration code for multiple versions of a particular API. In that case it is possible to apply a version override to a specific module of a multi-module build. For example to apply an explicit dependency override only to module B of project foo.

mvn install -DdependencyOverride.junit:junit@org.foo:moduleB=4.10

Note: Explicit overrides like this will take precedence over strict alignment and the BOM.

Per-Module Prevention of Override

It is also possible to prevent overriding dependency versions on a per-module basis:

mvn install -DdependencyOverride.[groupId]:[artifactId]@[moduleGroupId]:[moduleArtifactId]=

For example:

mvn install -DdependencyOverride.junit:junit@org.foo:moduleB=

Override Prevention with Wildcards

Likewise, you can prevent overriding a dependency version across the entire project using a wildcard:

mvn install -DdependencyOverride.[groupId]:[artifactId]@*=

For example:

mvn install -DdependencyOverride.junit:junit@*=

Or, you can prevent overriding a dependency version across the entire project where the groupId matches, using multiple wildcards:

mvn install -DdependencyOverride.[groupId]:*@*=

For example:

mvn install -DdependencyOverride.junit:*@*=

Per Module Override Prevention with Wildcards

Linking the two prior concepts it is also possible to prevent overriding using wildcards on a per-module basis e.g., mvn install -DdependencyOverride.:@org.foo:moduleB=

This will prevent any alignment within the org.foo:moduleB.

mvn install -DdependencyOverride.*:*@org.foo:*=

This will prevent any alignment within org.foo and all submodules within that.

Dependency Exclusion Addition

It is also possible to inject specific exclusions into a dependency. For instance

mvn install -DdependencyOverride.junit:junit@*=4.10-rebuild-1,+slf4j:slf4j

will, as per above, apply the explicit 4.10-rebuild-10 version to the junit:junit dependency but also add an exclusion e.g.,

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.1</version>
  <exclusions>
    <exclusion>
      <artifactId>slf4j</artifactId>
      <groupId>slf4j</groupId>
    </exclusion>
  </exclusions>
</dependency>

Multiple exclusions to a dependency may be added using comma separators e.g., mvn install -DdependencyOverride.junit:junit@*=+slf4j:slf4j,+commons-lang:commons-lang

Using a version from a Remote POM

While the dependencyManagement property does accept multiple remote POMs on its own, sometimes you might want a more limited effect from one of these extra remote POMs. For example, to align just one module of your project, or to align only certain artifacts to the versions defined in that POM.

  1. Set a property with the prefix dependencyManagement., suffixed by a unique ID string of your choice. The value of the property follows the same format as a standard dependencyManagement property, but only accepts one POM per ID string. You can define multiple POMs by repeating this dependencyManagement. property, but with a different ID suffix for each instance.
  2. Reference this ID string as the version of your dependencyOverride properties.

For example:

mvn install -DdependencyManagement=org.foo:my-dep-pom:1.0 -DdependencyManagement.xyzzy=org.foo:my-dep-pom:2.0 -DdependencyOverride.commons-lang:commons-lang:*@*=xyzzy

An error will be produced when the remote POM does not define a version for the group and artifact defined in the dependencyOverride.

These extra BOMs have no affect unless referenced by one or more dependencyOverrides.

Strict Mode Version Alignment

NOTE : As of PME 2.13 (October 2017) the default for strictAlignment has changed from false to true.
It is highly recommended to keep this value set to true.

When aligning dependency versions to some shared standard, it’s possible to introduce incompatibilities that stem from changing the version. This may be due to unexpected API changes. While in general it might be safe to revise a dependency’s version from 1.5 to 1.5.1, it may not be safe to revise it to 2.0, or even 1.6.

By default, strict-mode version alignment checks are enabled. This is highly recommended to prevent API conflicts and when dependencies are aligned via a BOM or REST source (not via explicit overrides, as explained above).

Strict-mode alignment will detect cases where the adjusted version, once OSGi and suffix are handled, does not match with the old version, not do the alignment and report the mismatch as a warning in the build’s console output. For example, if the incremental or manual suffix is configured to be rebuild then these are valid changes.

Original Version Potential New Versions
2.6 2.6-rebuild-2 ; 2.6.0.rebuild-4
3 3.0.0.rebuild-1 ; 3-rebuild-1
3.1 3.1.0.rebuild-1 ; 3.1-rebuild-1

Note that it will not consider 3 -> 3.1 as a valid transition.

If required, strict-mode version alignment checks can be disabled using:

mvn install -DstrictAlignment=false

If the build should fail when strict-mode checks are violated set strictViolationFails property to true (default: false):

mvn install -DstrictAlignment=true -DstrictViolationFails=true

This will cause the build to fail with a ManipulationException, and prevent the extension from rewriting any POM files.

Note: dependency exclusions they will not work if the dependency uses a version property that has been changed by another dependency modification. Explicit version override will overwrite the property value though.

Strict Alignment and Suffixes

NOTE : As of PME 2.13 (October 2017) the default for strictAlignmentIgnoreSuffix has changed from false to true.

The property strictAlignmentIgnoreSuffix will mean that the comparison will ignore the suffix depicted by version.incremental.suffix or version.suffix during version comparisons. It will also only allow alignment to a higher incrementing suffix e.g., 3.1.0.Final-rebuild-1 –> 3.1.0.Final-rebuild-3

Strict Property Validation

The property strictPropertyValidation is similar to the property clash replacement below. However, while the below case detects two dependencies/plugins that update the property to different values, this validation ensures that every dependency (or plugin) that uses a matching property attempted to update it. This validates that the source of the information (e.g., BOM or REST) for the following example passed in both org.foo:bar1 and org.foo:bar2. If bar2 has not been passed in then the validation fails and, depending upon the configuration, either an Exception can be thrown (if set to true) or it will attempt to revert the changes (if set to revert). The default is false (i.e., off). Enabling this can avoid a potential later failure in a build where the bar2 alignment doesn’t exist but has been implicitly updated through the bar1 alignment. Note that this does not validate for dependencyManagement blocks but only for dependencies.

propertyX = 1.0

org.foo:bar1:$propertyX
org.foo:bar2:$propertyX

Property Clash Replacement

When replacing a dependency PME will trace back and update the property value. However, there are scenarios where the dependency versions returned from either the BOM or the REST source can be different even though they refer to the same property e.g., propertyX = 1.0

org.foo:bar1:$propertyX
org.foo:bar2:$propertyX

And the REST source returns 1.0.rebuild-2 for bar1 and rebuild-4 for bar2. In this case PME will detect the clash and throw an error. It is possible to configure PME so that it will not update the property and continue by setting propertyClashFails to false (default: true).

Dependency Relocations

In order to handle the situation where one GAV is changed to another (e.g., from community to product) the relocation manipulator can be used. An optional version component may be added; the version will override any prior version used in the dependency. The artifact override is optional. The relocated GAV is subject to alignment ; if the developer wishes to force a particular version (i.e., one with an existing suffix) they may use dependencyOverride.

NOTE : As of PME 4.1 dependency relocations are subject to alignment. The optional version is passed to DA for dependency alignment.
-DdependencyRelocations.oldGroupId:[oldArtifact]@newGroupId:[newArtifactId]=[version],...

Note: Multiple relocations may be added using multiple instances of -DdependencyRelocation....

Dependency Removal

If the property -DdependencyRemoval=group:artifact,.... is set, PME will remove the specified dependency from the POM files. The argument should be a comma separated list of group:artifact. The list also supports a remote HTTP file containing a comma or newline separated list of GAs.

Dependency Injection

If the property -DdependencyInjection=group:artifact:version,.... is set, PME will inject the specified dependency into the top level (inheritance root) POM files’ <dependencyManagement> sections. The argument should be a comma separated list of group:artifact:version. The list also supports a remote HTTP file containing a comma or newline separated list of GAVs. The injection dependencies are subject to alignment.

Maven Dependency Plugin

NOTE : Available from PME 4.13.

If the property dependencyInjectionAnalyzeIgnoreUnused is set to true (default: false) and if the maven-dependency-plugin with the analyze goal exists in the root pom, then for every injected dependency, it will add each to the ignoreUnusedDeclaredDependencies configuration.

BOM Generation

If the property -DbomBuilder=true is set, then the PME BOM Builder will be activated. This will deploy a new POM to the repository under the GAV <project-top-level-groupId>.<project-top-level-artifactId>:pme-bom:<version> e.g., org.projectOne.artifactOne:pme-bom:1.0.0. It will contain a list of all adjusted modules and is suitable for use as a remote BOM. As it has a predictable name it may be used in a build of a subsequent project to align to this one e.g., PWD= ; mvn -DdependencyManagement=org.projectOne.artifactOne:pme-bom:1.0.0 -Dversion.suffix=rebuild-1 clean install

Note: The new BOM will be installed and deployed via a custom plugin that is added to the top level project. The plugin is configured to only run on the top level POM and will not fail if the BOM POM does not exist.

Scope Exclusion

If the property -DexcludedScopes=<scope>,.... is set, PME will ignore any dependency with the specified scope. This means that, if for example the property was set to test then alignment (and any other relevant manipulation) would not happen for test scoped dependencies. Note that valid scopes are those specified in Maven Scopes and if an invalid scope is passed in an exception will be thrown.

REST and Suffix Alignment

NOTE : Available from version 3.8

If the property -DrestSuffixAlign is set to false (default: true) then PME will not attempt to align any dependency that has an existing suffix. This only applies during REST alignment. The use-case for this is where some dependencies have already been manually aligned to specific values and the user does not wish them to be changed.