Aether/Transitive Dependency Resolution
An important task of Aether is to resolve transitive dependencies. This task can be split into two sub tasks:
- Determine the coordinates of the artifacts that make up the transitive dependencies.
- Resolve the files for the artifacts that have been identified in step 1.
Artifacts and their dependencies among each other form a dependency graph. So in other words, step 1 means to calculate this dependency graph and step 2 is a simple graph traversal that fetches the file for each artifact in the dependency graph. In Aether, this dependency graph can be easily inspected and extension points are provided to allow for more control over the construction of the dependency graph. To understand those extension points, we will have a closer look at the way the dependency graph is constructed.
Starting from a given root dependency like org.eclipse.aether:aether-impl:0.9.0, the repository system first reads the corresponding artifact descriptor (i.e. the POM when dealing with Maven repositories). The artifact descriptor tells about direct dependencies, dependency management and additional remote repositories to consider during the resolution. For each direct dependency, a dependency selector is given a chance to exclude the dependency from the graph. If the dependency is included, a dependency manager applies dependency management (if any). Next, the declared dependency version is expanded to a list of matching versions from the repositories. For a simple version like "1.0", the resulting list contains only that version. For a version range like "[1.0,2.0)", the version list generally contains multiple versions and is subject to filtering by a version filter. For each matching version of the dependency, a child node is added to the dependency graph. Recursion of the process for each child dependency is controlled by a dependency traverser.
The above process creates a dependency graph that often contains duplicate or conflicting dependencies or even cycles and as such is called a dirty graph. A chain of dependency graph transformers is then used to trim this graph down and to form a resolved graph.
So more technically, the dependency graph that the repository system returns to its caller is affected by instances of org.eclipse.aether.collection.DependencySelector, org.eclipse.aether.collection.DependencyManager, org.eclipse.aether.collection.VersionFilter, org.eclipse.aether.collection.DependencyTraverser and org.eclipse.aether.collection.DependencyGraphTransformer. Users of the repository system can directly control those extension points when creating the repository system session by providing implementations that fit their needs.
For example, a dependency selector can process exclusions on child dependencies, exclude optional dependencies or dependencies with certain unwanted scopes. A dependency traverser can be used to decide whether the dependencies of a (fat) WAR should be included in the dependency graph or not. The version filter can exclude specific versions of an artifact which are inacceptable in the current context, e.g. ban snapshots. Dependency graph transformers can identify and mark conflicting nodes in the dirty tree and resolve conflicting versions or scopes by pruning unwanted parts from the graph.
The class MavenRepositorySystemUtils from maven-aether-provider provides a session that mimics the resolution rules used by Maven. In case you want to customize the graph construction, feel free to have a look at the source of that class to learn about the implementation classes being used there to achieve Maven style behavior, you might want to reuse some of those for your own repository system session as well. Maven plugins can easily get access to the current repository system session via the usual parameter injection, see Using Aether in Maven Plugins for the actual code bits.