This specification defines some improvements to dependency management.
- Replace the old dependency result graph with one that is easier to use and consumes less heap space.
- Plugin implements a custom component type.
This story separates the file store and meta-data cache versions so that they can evolve separately.
The Gradle cache layout will become:
$gradleHome/caches/modules-${l}/
- this is the cache base directory, which includes the lock. When the locking protocol changes, the version${l}
is changed.$gradleHome/caches/modules-${l}/files-${l}.${f}
- this is the file store directory. When the file store layout changes, the version${f}
is changed.$gradleHome/caches/modules-${l}/metadata-${l}.${m}
- this is the meta-data directory, includes the artifact, dependency and module meta-data caches. When the meta-data format changes, the version${m}
is changed.
Initial values: l
= 1, f
= 1, m
= 27.
- Change
CacheLockingManager.createCache()
to accept a relative path name instead of aFile
. This path should be resolved relative to the meta-data directory. - Add methods to
CacheLockingManager
to create the file store and module meta-dataPathKeyFileStore
implementations.
- Update the existing caching test coverage for the new locations. No new coverage is required.
This story is a step in moving towards components as the central dependency management concept, replacing the module version concept.
- Rename
ResolvedModuleVersionResult
toResolvedComponentResult
.- Rename the
allModuleVersions
methods onResolutionResult
toallComponents
.
- Rename the
- Rename
ModuleVersionSelectionReason
toComponentSelectionReason
. - Introduce a
org.gradle.api.artifacts.component.ComponentIdentifier
type.displayName
property returns some arbitrary human-consumable value.
- Introduce a
ModuleComponentIdentifier
type that extendsComponentIdentifier
and add a private implementation.group
propertyname
propertyversion
property
- Introduce a
org.gradle.api.artifacts.component.ComponentSelector
type.displayName
property returns some arbitrary human-consumable value.
- Introduce a
ModuleComponentSelector
type that extendsComponentSelector
and add a private implementation.group
propertyname
propertyversion
property
- Change
ResolvedComponentResult
:- Change
getId()
to return aComponentIdentifier
. Implementation should implementModuleComponentIdentifier
. - Add
ModuleComponentIdentifier getPublishedAs()
. Mark method as@Nullable
. Implementation should return the same as value asgetId()
(for now).
- Change
- Change the methods of
DependencyResult
andUnresolvedDependencyResult
to useComponentSelector
instead ofModuleVersionSelector
.
- Nothing beyond some unit tests for the new methods and types.
This story allows IDE integrations to map dependencies by exposing some information about the source of a resolved component.
- Introduce a
BuildComponentIdentifier
type that extendsComponentIdentifier
and add a private implementation.project
property
- Change
ModuleVersionMetaData
to add aComponentIdentifier getComponentId()
method.- Default should be a
ModuleComponentIdentifier
with the same attributes asgetId()
. - For project components (as resolved by
ProjectDependencyResolver
) this should return aBuildComponentIdentifier
instance.
- Default should be a
- Change
ResolvedComponentResult
implementations so that:getId()
returns the identifier fromModuleVersionMetaData.getComponentId()
.getPublishedAs()
returns aModuleComponentIdentifier
with the same attributes asModuleVersionMetaData.getId()
.- Add
<T extends ComponentIdentifier> T getId(Class<T> type)
that returns an id of the requested type.
- Introduce
BuildComponentSelector
type that extends `ComponentSelector and add a private implementation.project
property
- Change
DependencyMetaData
to add aComponentSelector getSelector()
- Default should be a
ModuleComponentSelector
with the same attributes asgetRequested()
. - For project dependencies this should return a
BuildComponentSelector
instance.
- Default should be a
- Change the dependency reports so that they render both
id
andpublishedAs
when they are not the equal.
A consumer can extract the external and project components as follows:
def result = configurations.compile.incoming.resolve()
def projectComponents = result.root.dependencies.selected.findAll { it.id instanceof BuildComponentIdentifier }
def externalComponents = result.root.dependencies.selected.findAll { it.id instanceof ModuleComponentIdentifier }
- Need to update the existing tests for the dependency tasks, as they will now render different values for projects and project dependencies.
- Update existing integration test cases so that, for the resolution result:
- for the root component
id
is aBuildComponentIdentifier
withproject
value referring to the consuming project.publishedAs
is aModuleComponentIdentifier
with correctgroup
,module
,version
values.getId(BuildComponentIdentifier) == id
getId(ModuleComponentIdentifier) == publishedAs
- for a project dependency
requested
is aBuildComponentSelector
withproject
value referring to the target project.
- for a resolved project component
id
is aBuildComponentIdentifier
withproject
value referring to the target project.publishedAs
is aModuleComponentIdentifier
with correctgroup
,module
,version
values.getId(BuildComponentIdentifier) == id
getId(ModuleComponentIdentifier) == publishedAs
- for an external dependency:
requested
is aModuleComponentSelector
with correctgroup
,module
,version
values.
- for an external module component:
id
is aModuleComponentIdentifier
with correctgroup
,module
,version
values.publishedAs
==id
.getId(ModuleComponentIdentifier) == id
getId(BuildComponentIdentifier) == null
- for the root component
- Convenience for casting selector? Selecting things with a given id type or selector type?
TBD
This story improves the resolution result to distinguish between the local and external identifiers for a component, and associates an external identifier only with those components that are published.
- Change
ModuleVersionMetaData
to add aModuleComponentIdentifier getPublishedAs()
- Default is to return the same as
getComponentId()
- Change the implementation of
ResolvedComponentResult.getPublishedAs()
to return this value.
- Default is to return the same as
- Add a private
ProjectPublicationRegistry
service, which collects the outgoing publications for each project. This replacesProjectModuleRegistry
. This service is basically a map from project path to something that can produce the component meta data for that project.- When a project is configured, register an implicit component with a null
publishedAs
. - When an
Upload
task is configured with an ivy repository, register a component withpublishedAs
=(project.group, project.name, project.version)
- When an
Upload
task is configured with aMavenDeployer
, register a component withpublishedAs
=(deployer.pom.groupId, deployer.pom.artifactId, deployer.pom.version)
- When an
IvyPublication
is defined, register a component withpublishedAs
taken from the publication. - When an
MavenPublication
is defined, register a component withpublishedAs
taken from the publication.
- When a project is configured, register an implicit component with a null
- Change
ProjectDependencyResolver
to use the identifier and metadata from this service. - Change the dependency tasks so that they handle a component with null
publishedAs
. - Change the
ivy-publish
andmaven-publish
plugins to use theProjectPublicationRegistry
service (TBD)
- Update the existing reporting task so that:
- An external module is rendered as the (group, module, version).
- A project that is not published is rendered as (project)
- A project that is published rendered as (project) and (group, module, version)
- Update existing tests so that, for resolution result:
- For the root component and any dependency components:
- A project that is not published has null
publishedAs
. - A project that is published using
uploadArchives
+ Ivy has non-nullpublishedAs
- A project that is published using
uploadArchives
+ Maven deployer has non-nullpublishedAs
- A project that is published using a Maven or Ivy publication has non-null
publishedAs
- A project that is not published has null
- For the root component and any dependency components:
- Need to expose component source.
- Need to expose different kinds of component selectors.
- Need to sync up with
ComponentMetadataDetails
. - Add Ivy and Maven specific ids and sources.
- Rename and garbage internal types.
Story: GRADLE-2713/GRADLE2678 Dependency management uses local component identity to when resolving project dependencies
Currently, dependency management uses the module version (group, module, version) of the target of a project dependency to detect conflicts. However, the module version of a project is not necessarily unique. This leads to a number of problems:
- A project with a given module version depends on another project with the same module version.
- A project depends on multiple projects with the same module version.
- A project declares an external dependency on a module version, and a project dependency on a project with the same module version.
In all cases, the first dependency encountered during traversal determines which dependency is used. The other dependency is ignored. Clearly, this leads to very confusing behaviour.
Instead, a project dependency will use the identity of the target project instead of its module version. The module version will be used to detect and resolve conflicts.
- Excludes should not apply to local components. This is a breaking change.
When two components have conflicting external identifiers, select a local component.
Note: This is a breaking change.
This story allows a plugin some control over which component a project dependency is resolved to for a given dependency resolution. Generally, this would be used to select components of a given type (eg select the Java library, if present).
- Wrap the meta-data and artifacts that are currently used in a 'legacy' component for backwards compatibility. This is used as the default.
- Add a new type of rule to
ResolutionStrategy
that is given the set of components in the target project + the currently selected component and can select a different component. - The rule is invoked once for each target project. The result is reused for subsequent dependencies with the same target project.
- Expose the component identifier/source of the selected component through the resolution result.
- Possibly add a declarative shortcut to select components of a given type (and implicitly, ignore the legacy component).
- What component meta-data is exposed to the rule?
This story allows a plugin some control over which packaging of a component will be used for a given project dependency. Generally, this would be used to select a variant + usage of a component.
- Add a new type of rule to
ResolutionStrategy
that is given the selected component + the requested packaging + the selected packaging. The rule can select a different packaging. - The rule is invoked once for each selected component + requested packaging. The result is reused for all dependencies that have selected this component and requested the same packaging.
- What component meta-data is exposed to the rule?
- Handle dependencies that declare a target configuration. Options:
- Don't invoke this rule for these dependencies.
- If the legacy component has not been selected, assert that at least one rule selects a packaging.
- Map configuration to a packaging using the component meta-data.
- Artifacts are implicit in the packaging.
- Introduce rules that can tweak the selected packaging, define new ones, and so on.
- Introduce rules that given a selected packaging, select the artifacts to use.
Map an external component to component meta-data and pass to the packaging rules.
Allow a dependency to be declared on a component instance. This will allow a plugin to include components built by the current project in dependency resolution, including conflict detection and resolution.
- Allow
SoftwareComponent
instances to be used on the RHS of a dependency declaration. - Use the meta-data and artifacts defined by
SoftwareComponentInternal
. - Expose the component identifier/source through the resolution result.
- If the component is published, use the publication identifier to apply conflict resolution.
- Expose native components as
SoftwareComponent
implementations. - Add direct dependencies for libraries in the same project.
- Use the resolve rules to select native libraries for a project dependency.
- Use the resolve rules to select the appropriate variant + usage for each selected component.
- Allow plugin to package up the rules into a component type definition.
Introduce a native Gradle component descriptor file, generate and publish this.
Use the component descriptor, if present, during resolution.
The new dependency graph also requires substantial heap (in very large projects). We should spool it to disk during resolution and load it into heap only as required.
- Existing dependency reports tests work neatly
- The report is generated when the configuration was already resolved (e.g. some previous task triggered resolution)
- The report is generated when the configuration was unresolved yet.
In order to remove an old feature, we should promote the replacement API.
TBD
Allow some substitutions to be expressed declaratively, rather than imperatively as a rule.