Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 6 Next »

This document describes the rest of the requirements for dependency management that have not yet been implemented for Maven 2.0, especially with regards to transitive dependencies.

Design

The Dependency Management element

This is inherited, and gives a full profile over your dependency set. While it will not add new dependencies itself, it will enforce the following constraints:

  • your policy on the version allowed for a dependency
  • affects both your declared dependencies and transitive dependencies
  • apply a specific scope to a dependency

Resolve Dependencies once only

This is a relatively straightforward task in the resolver:

  • eg if maven-artifact is in the top level pom, go through the tree and find all the references to it, decide the version, and then use that version's dependencies, not the others
  • this would probably be done with a DAG using just group/artifact ID with counts on the edges. When a version is chosen all the other versions are removed, and when a count drops to 0 it is removed from the graph. Proceed until all versions are determined.
  • this ensures the pool of dependencies is smaller when trying to determine the version to use, and much more accurate.

Dependency Version Ranges

Need to be able to declare minimum, maximum allowed versions of a dependency (both min and max may be optional), and allow "holes" for known incompatible versions.

Proposed syntax:

Range

Meaning

(,1.0]

x <= 1.0

1.0

"Soft" requirement on 1.0 (just a recommendation - helps select the correct version if it matches all ranges)

[1.0]

Hard requirement on 1.0

[1.2,1.3]

1.2 <= x <= 1.3

[1.0,2.0)

1.0 <= x < 2.0

[1.5,)

x >= 1.5

(,1.0],[1.2,)

x <= 1.0 or x >= 1.2. This excludes 1.1 (Multiple sets are comma-separated)

Mathematical syntax chosen to avoid the use of - as it would conflict with what is used in many version number.

Of the overlapping ranges, the highest soft requirement is the version to be used in the default strategy. If there are no soft requirements inside the prescribed ranges, the RELEASE version is used. If that does not fit the described ranges, then the uppermost found version number in the prescribed ranges is used. If the ranges exclude all versions, an error occurs.

Addition of ranges leads to additional necessary specifications on the dependency element.

Version comparison requirement

The effectiveness of the above ranges reqires that we are able to compare two versions and determine which is newer. This should be designed such that it is pluggable, but initially we should only provide one scheme and attempt to use it uniformly.

Default Version comparison definition

The default specification should be composed as follows:

<major>.<minor>.<revision>[-<qualifier>][_<build>]

where:

  • the qualifier section is optional (and is SNAPSHOT, alpha-1, alpha-2)
  • the build section is optional (and increments starting at 1 if specified)
  • any '0' build or revision elements can be omitted.
  • _ is used for build to avoid confusion (eg alpha-1 only is a release with no build number)

For ordering, the following is done in order until an element is found that are not equal:

  • numerical comparison of major version
  • numerical comparison of minor version
  • if revision does not exist, add ".0" for comparison purposes
  • numerical comparison of revision
  • if qualifier does not exist, it is newer than if it does
  • case-insensitive string comparison of qualifier
    • this ensures timestamps are correctly ordered, and SNAPSHOT is newer than an equivalent timestamp
    • this also ensures that beta comes after alpha, as does rc
  • if build does not exist, add "_0" for comparison purposes
  • numerical comparison of build

Build numbers and timestamps

Icon

Currently have an issue here: snapshots are compared by build number before timestamp, but it appears last in the version, so we may need to regex it rather than just doing a string comparison. Also, the existing timestamps use ...-build instead of ..._build so may require changing. The existing versions, and the description of comparison to use timestamps first.

Addition of resolvedVersion element

The resolved version element should be added to the dependency element, in both the dependencies list and the dependencyManagement. When present, it specifies what version was resolved at release time. It is not intended for entry by users. Its benefit is that you are able to retain both the dependency version specification, as well as discovering the reproducible equivalent.

Question

Icon

How would we determine when to utilise the resolved version for a reproducible build in the past, vs. the dependency specification? Even with all the main dependencies having their resolved version, that doesn't guarantee the transitive dependency calculation will work the same way as it previously did. Should the release process fill in all resolved artifacts and their versions into the POM, then flip it back to the original? If so, how do the deployed and committed POMs separate the user's specification vs. the resolved dependencies?


One important thing is that we can't use the dependencyManagement section for doing the population of the dependencies, as it is also a user specification element that may just operate differently.

An alternative I considered was to have version behave as it is now, and introduce versionSpec which would give the ranges. However, the above solution is better in the following ways:

  • it will also apply to SNAPSHOT dependencies
  • it allows existing dependencies to work as soft references without modification

Preventing RELEASE dependencies

Given that an open ended version specification such as [1.0,) will use a RELEASE version dependency if available, there is no reason to allow and resolve such a version for a dependency.

Version specifications for Parents

Parent references are handled differently to dependencies. They do not need to incorporate ranges, and are generally treated as unversioned. See the Release Management document for a discussion of this.

Incorporating SNAPSHOT versions into the specification

Resolution of dependency ranges should not resolve to a snapshot unless it is included as an explicit boundary. There is no need to compile against development code unless you are explicitly using a new feature, under which the snapshot will become the lower bound of your version specification. As releases are considered newer than the snapshot they belong to, they will be chosen over an old snapshot if found.

It is possible that applications such as Continuum may have a mode that enables always resolving to the snapshot version, but this is external to the POM itself.

Forcing a version

A version will always be honoured if it is declared in the current POM with a hard requirement on a particular version - however, it should be noted that this will also affect other poms downstream if it is itself depended on using transitive dependencies.

The preferred technique for forcing a particular version to be used should then be the use of the dependencyManagement section.

Improved Diagnostics

It is important that we are able to easily and clearly represent the state of a projects dependency tree both from m2 and graphical tools given the added complexity of transitive dependencies and version management. The diagnostics should as much as possible explain why a version was chosen.

Conflict Resolution

Strategies for conflict resolution need to be pluggable and stackable.

In some cases, the resolution of a version may be workable, but not ideal. For example instead of erroring out, we may just drop to a different method of conflict resolution if there is no match in the first method (ending up using the current nearest wins algorithm).

Some suggested techniques would be:

  • use version specification (as defined above, default)
  • use nearest (always get closest transitive dep regardless of version specifications)
  • fail if no match found

Should the strategy be per project or per dependency?

Icon

I'm originally inclined to say per project to avoid a large amount of duplication if you want to use it across the board, but it seems reasonable to use a different strategy per dependency. There is also the question of whether you can apply it globally, or whether you have to honour the setting of the original project as you traverse the transitive dependencies. If it is given per dependency, this strategy could well be used to disable transitive dependencies with a new strategy implementation (even though it was discounted earlier, this might later be decided to be in the user's best interests to have available).

  • No labels