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.
Story: Dependency resolution result produces a graph of components instead of a graph of module versions
Currently, dependency resolution effectively produces a graph of 'module versions'. There are a number of issues with this approach. One fundamental problem is that not all of the things that participate in dependency resolution are modules in a repository or are versioned. This series of stories changes dependency resolution so that it can deal with things which are not 'module versions'.
The approach here is to introduce the more general concept of a component and base dependency resolution on this concept. Then, different types of components will be introduced to model the different kinds of things that participate in dependency resolution.
In this story, the dependency resolution result is changed so that it produces a graph of components, rather than a graph of module versions.
- Rename
ResolvedModuleVersionResulttoResolvedComponentResult.- Rename the
allModuleVersionsmethods onResolutionResulttoallComponents.
- Rename the
- Rename
ModuleVersionSelectionReasontoComponentSelectionReason. - Introduce a
org.gradle.api.artifacts.component.ComponentIdentifiertype.displayNameproperty returns some arbitrary human-consumable value.
- Introduce a
ModuleComponentIdentifiertype that extendsComponentIdentifierand add a private implementation.grouppropertynamepropertyversionproperty
- Introduce a
org.gradle.api.artifacts.component.ComponentSelectortype.displayNameproperty returns some arbitrary human-consumable value.
- Introduce a
ModuleComponentSelectortype that extendsComponentSelectorand add a private implementation.grouppropertynamepropertyversionproperty
- 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
DependencyResultandUnresolvedDependencyResultto useComponentSelectorinstead ofModuleVersionSelector.
- Nothing beyond some unit tests for the new methods and types.
- Packages for the new types
This story changes the dependency resolution result to distinguish between components that are produced by the build and those that are produced outside the build. This will allow IDE integrations to map dependencies by exposing this information about the source of a component.
- Introduce a
BuildComponentIdentifiertype that extendsComponentIdentifierand add a private implementation.projectpropertydisplayNameshould be something likeproject :path.
- Change
ModuleVersionMetaDatato add aComponentIdentifier getComponentId()method.- Default should be a
ModuleComponentIdentifierwith the same attributes asgetId(). - For project components (as resolved by
ProjectDependencyResolver) this should return aBuildComponentIdentifierinstance.
- Default should be a
- Change
ResolvedComponentResultimplementations so that:getId()returns the identifier fromModuleVersionMetaData.getComponentId().getPublishedAs()returns aModuleComponentIdentifierwith the same attributes asModuleVersionMetaData.getId().- Add
<T extends ComponentIdentifier> T getId(Class<T> type)that returns an id of the requested type.
- Introduce
BuildComponentSelectortype that extendsComponentSelectorand add a private implementation.projectproperty
- Change
DependencyMetaDatato add aComponentSelector getSelector()- Default should be a
ModuleComponentSelectorwith the same attributes asgetRequested(). - For project dependencies this should return a
BuildComponentSelectorinstance.
- Default should be a
- Change the dependency reports so that they render both
idandpublishedAswhen they are not the equal.
This will allow a consumer to 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 report tasks, as they will now render different values for project dependencies.
- Update existing integration test cases so that, for the resolution result:
- for the root component
idis aBuildComponentIdentifierwithprojectvalue referring to the consuming project.publishedAsis aModuleComponentIdentifierwith correctgroup,module,versionvalues.getId(BuildComponentIdentifier) == idgetId(ModuleComponentIdentifier) == publishedAs
- for a project dependency
requestedis aBuildComponentSelectorwithprojectvalue referring to the target project.
- for a resolved project component
idis aBuildComponentIdentifierwithprojectvalue referring to the target project.publishedAsis aModuleComponentIdentifierwith correctgroup,module,versionvalues.getId(BuildComponentIdentifier) == idgetId(ModuleComponentIdentifier) == publishedAs
- for an external dependency:
requestedis aModuleComponentSelectorwith correctgroup,module,versionvalues.
- for an external module component:
idis aModuleComponentIdentifierwith correctgroup,module,versionvalues.publishedAs==id.getId(ModuleComponentIdentifier) == idgetId(BuildComponentIdentifier) == null
- for the root component
- Ensure there is coverage for the dependency report and the dependency HTML report where
- There are project dependencies in the graph
- Ensure there is coverage for the dependency insight report where:
- There are project dependencies in the graph
- There are project dependencies in the graph and the
--dependencyoption is used.
- Convenience for casting selector and id?
- Convenience for selecting things with a given id type or selector type?
This story changes the idea and eclipse plugins to use the resolution result to determine the IDE project classpath.
- Change IdeDependenciesExtractor and JavadocAndSourcesDownloader to use the resolution result to determine the project and external dependencies.
This story changes the resolution result to distinguish between local components that are published and those that are not published. The main change in this story is expose the fact that a component may not necessarily have a module identifier, and may have multiple different kinds of identifiers.
It introduces local and external identifiers for a component, and associates an external identifier only with those components that are published (or are to be published).
- Change
ModuleVersionMetaDatato 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
ProjectPublicationRegistryservice, 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
Uploadtask is configured with an ivy repository, register a component withpublishedAs=(project.group, project.name, project.version) - When an
Uploadtask is configured with aMavenDeployer, register a component withpublishedAs=(deployer.pom.groupId, deployer.pom.artifactId, deployer.pom.version) - When an
IvyPublicationis defined, register a component withpublishedAstaken from the publication. - When an
MavenPublicationis defined, register a component withpublishedAstaken from the publication.
- When a project is configured, register an implicit component with a null
- Change
ProjectDependencyResolverto use the identifier and metadata from this service. - Change the dependency tasks so that they handle a component with null
publishedAs. - Change
ProjectDependencyPublicationResolverto use theProjectPublicationRegistryservice.
- 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/GRADLE-2678 Dependency resolution 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
ResolutionStrategythat 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
ResolutionStrategythat 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
SoftwareComponentinstances 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
SoftwareComponentimplementations. - 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.