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 2 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

Filtering out unwanted transitive dependencies

This should be done at the POM level, since it doesn't make sense to exclude a dependency in one place and allow it from another transitive dep at the same time. This should be done using the dependency management element and a modified scope.

Changes to the Dependency Management element

Once this is inherited, this gives a full profile over your dependnecy 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
  • explicitly exclude some dependencies (through the scope none)

Changes to the Scope element

The only necessary change to scope for the dependency mediation is none as described above, and the possible container scope discussed in a different use case: Build Profiles

The none scope means that the dependency is not resolved, and excluded from all class paths.

At this point, there will not be a way to disable the transitive dependencies of a particular dependency, as there is no supporting use case other than poor metadata which can we worked through using exclusions.

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]

Up to and including 1.0

1.0

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

(1.0)

"Soft requirement, equivalent to 1.0 on its own

[1.0]

Hard requirement on 1.0

[1.2,1.3]

Version 1.2 to version 1.3 inclusive

[1.5)

Version 1.5 and up

(1.0],[1.2)

Up to including 1.0 or 1.2 and above, excludes 1.1 (Multiple sets are comma-separated)

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).

Specification Dependencies

Maven 2.1

Icon

This feature will be left for the next release

Specification dependencies allow the dependency on a particular library that only provides an API, and that should resolve to one or more implementing libraries.

Would need to be able to provide:

  • a way to specify that a project implements another project
  • a default, eg to use Xerces [2.2) whenever someone depends on jaxp 1.3 and no implementing library is found in the dependency closure
  • hinting, so that a set of wagons could be used to implement the wagon-provider-api

Original Requirements

  • filtering out transitive dependencies you don't want
    • done at the POM level, or individual the dependency level? (seems to make sense to do it at the pom if you are already excluding by name)
  • only resolve dependencies once
    • 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.
  • declare minimum, maximum allowed versions of a dependency (both min and max may be optional). Allow holes for known incompatible versions
    • suggest syntax of comma separated list of ranges, eg -1.0, 1.2-1.3, 1.5+ (this excludes 1.1 and 1.4, but 0.9 and 1.6 are fine)
    • single version is considered a "soft requirement" and backwards compatible (more of a recommendation)
    • explicit version would be 1.4-1.4
    • use of - may impeded its use inside version string, maybe a better alternate syntax is
      • (1.0], [1.2,1.3], [1.5) as above, or [1.4] for an explicit version
    • of the set of possible versions, the default would be to use the newest
  • local definition always wins
    • would rather not have to redeclare a dep not explicitly used just to force a version
    • should use dependencyManagement for all this information if possible
  • resolve ranges to actual versions at release to make builds reproducible
  • warn on workable but possibly bad combinations of versions
  • pluggable strategies for conflict resolution
    • use-range (as above), use-nearest (ignore ranges, always get closest), fail (if any mismatch found)
    • strategy might be specified on project or dependency?
  • describe the versioning strategy to ensure consistency.
    • may later make this pluggable, but not for 2.0
    • <major>.<minor>.<revision>[-<build>] where the build section is optional (and is SNAPSHOT, alpha-1, alpha-2, etc leading up to release, then -0, -1, -2 after release for reconfigured builds) and any '0' build or revision elements can be omitted.
  • possiblity of spec dependencies if it can be made to fit
  • No labels