Gradle Dependencies and Dependency Configurations TODEPLETE: Difference between revisions
(245 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
=Internal | =DEPLETE TO= | ||
{{Internal|Gradle Dependencies and Dependency Configurations|Gradle Dependencies and Dependency Configurations}} | |||
* [[Gradle_Concepts# | =Overview= | ||
<span id='Dependency_Management'></span>One of the most useful Gradle features is its '''dependency management''' capability. Dependency management is a technique for declaring, resolving and using dependencies required by a project in an automated fashion. | |||
=Gradle Dependency Concepts= | |||
==Dependency== | |||
A '''dependency''' is an artifact the project needs. Dependencies are usually located in a [[Gradle_Repositories#Overview|repository]]. A dependency can be thought of as a pointer to another piece of software. Gradle supports several types of dependencies: | |||
===Module Dependency=== | |||
{{External|[https://docs.gradle.org/current/userguide/dependency_types.html#sub:module_dependencies Gradle Docs - Module Dependencies]}} | |||
The module dependency is the most common form of dependency. It refers to an artifact published by a [[Gradle_Concepts#Module|Gradle module]] (or an equivalent of Gradle, such as Maven) in a [[#Repository|repository]]. They are declared similar to: | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
implementation '<group-id>:<artifact-id>:<version>' | |||
} | |||
</syntaxhighlight> | |||
More details are available in the [[#Declaring_Module_Dependencies|Declaring Module Dependencies]] section, below. | |||
===<span id='Local_File_Dependency'>File Dependency=== | |||
{{External|[https://docs.gradle.org/current/userguide/dependency_types.html#sub:file_dependencies Gradle Docs - File Dependencies]}} | |||
A file dependency allows specification of a file present on an accessible filesystem as dependency, without first adding it to a repository. They are declared similar to: | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
implementation files('libs/a.jar') | |||
} | |||
</syntaxhighlight> | |||
More details are available in the [[#Declaring_File_Dependencies|Declaring File Dependencies]] section, below. | |||
===Project Dependency=== | |||
{{External|[https://docs.gradle.org/current/userguide/dependency_types.html#sub:project_dependencies Gradle Docs - Project Dependencies]}} | |||
A project dependency expresses the dependency on artifacts produced by other project that is part of the same [[Gradle Multi-Project Builds|multi-project build]]. The project dependency is declared similar to: | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
implementation project(':subproject-A') | |||
} | |||
</syntaxhighlight> | |||
More details are available in the [[#Declaring_Project_Dependencies|Declaring Project Dependencies]] section, below. | |||
===Gradle Distribution-Specific Dependency=== | |||
{{External|https://docs.gradle.org/current/userguide/dependency_types.html#sub:api_dependencies}} | |||
==Dependency Configuration== | |||
Gradle groups dependencies in, and handles them as sets, referred to as '''dependency configurations'''. A dependency configuration is a named set of dependencies, grouped together for a specific goal. For example, some dependencies should be used during the compilation phase, whereas others need to be available during the testing phase, or at runtime. The concept is somewhat similar to the Maven [[Maven_Concepts_-_Dependencies#Dependency_Scope|dependency scope]]. Internally, a dependency configuration is implemented as a [https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.Configuration.html Configuration] instance. Each configuration is identified by a unique name. Many Gradle [[Gradle_Plugins#Overview|plugins]] add pre-defined configurations to the project. For example, the [[Gradle_Java_Plugin#Overview|Java plugin]] adds [[Gradle_Java_Plugin#Dependency_Configurations|configurations to represent the various classpaths needed for compilation, testing, etc]]. A dependency configuration has three main purposes: | |||
* To '''declare dependencies''': plugins use dependency configurations to allow build authors to declare what other sub-projects or external artifacts are needed during the execution of tasks defined by the plugin. | |||
* To '''resolve dependencies''': plugins use dependency configurations to find and download the dependencies that are needed during the build | |||
* To '''expose artifacts''' for consumption: plugins use dependency configurations to define what artifacts they generate. Configurations are using during the [[Gradle_Artifact_Publishing_Concepts#Configuration|artifact publishing process]]. | |||
Additionally to the dependency configurations introduced by plugins, [https://docs.gradle.org/current/userguide/managing_dependency_configurations.html#_defining_custom_configurations custom configuration may be defined]. A configuration [https://docs.gradle.org/current/userguide/managing_dependency_configurations.html#sub:inheriting_dependencies_from_other_configurations can extend other configurations] to form an '''configuration hierarchy'''. Child configurations inherit the whole set of dependencies declared in any of its parent configurations. | |||
The dependency configurations of a project can displayed as shown in the [[#Dependency_and_Dependency_Configuration_Info|Dependency and Dependency Configuration Info]] section. | |||
The dependency configurations of a project can be configured in the project's build.gradle, within the [[Gradle_Project_and_Build_Script#configurations.7B.7D|configurations{...}]] script block, as shown in the [[#Configuring_Dependency_Configurations|Configuring Dependency Configurations]] section. | |||
Example of a typical set of dependency configuration for a Java project: | |||
'''compile''' - Dependencies for source set 'main' (deprecated, use 'implementation ' instead). | |||
'''implementation''' - Implementation only dependencies for source set 'main'. | |||
'''runtime''' - Runtime dependencies for source set 'main' (deprecated, use 'runtimeOnly ' instead). | |||
'''runtimeOnly''' - Runtime only dependencies for source set 'main'. | |||
'''testCompile''' - Dependencies for source set 'test' (deprecated, use 'testImplementation ' instead). | |||
'''testImplementation''' - Implementation only dependencies for source set 'test'. | |||
'''annotationProcessor''' - Annotation processors and their dependencies for source set 'main'. | |||
'''apiElements''' - API elements for main. | |||
'''archives''' - Configuration for archive artifacts. | |||
'''bootArchives''' - Configuration for Spring Boot archive artifacts. | |||
'''compileClasspath''' - Compile classpath for source set 'main'. | |||
'''compileOnly''' - Compile only dependencies for source set 'main'. | |||
'''default''' - Configuration for default artifacts. | |||
'''docker''' | |||
'''jacocoAgent''' - The Jacoco agent to use to get coverage data. | |||
'''jacocoAnt''' - The Jacoco ant tasks to use to get execute Gradle tasks. | |||
'''runtimeClasspath''' - Runtime classpath of source set 'main'. | |||
'''runtimeElements''' - Elements of runtime for main. | |||
'''testAnnotationProcessor''' - Annotation processors and their dependencies for source set 'test'. | |||
'''testCompileClasspath''' - Compile classpath for source set 'test'. | |||
'''testCompileOnly''' - Compile only dependencies for source set 'test'. | |||
'''testRuntime''' - Runtime dependencies for source set 'test' (deprecated, use 'testRuntimeOnly ' instead). | |||
'''testRuntimeClasspath''' - Runtime classpath of source set 'test'. | |||
'''testRuntimeOnly''' - Runtime only dependencies for source set 'test'. | |||
For more details on the dependency configurations introduced by the Java plugin, see: {{Internal|Gradle_Java_Plugin#Dependency_Configurations|Java Plugin - Dependency Configurations}} | |||
===<span id='Configuration_Container'></span>Dependency Configuration Container=== | |||
{{External|[https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html Gradle Docs - Configuration Container]}} | |||
All dependency configurations associated with a project are maintained within a '''configuration container''', which allows declaring and managing configurations. The configuration container is one of the [[Gradle_Project_and_Build_Script#Project.27s_Containers_and_Handlers|project's containers]]. Dependency configuration is perform by declaring dependency details in the [[Gradle_Project_and_Build_Script#dependencies.7B.7D|build.gradle dependencies{...}]] script block, which applies the configuration closure on the delegate configuration container instance. | |||
==Transitive Dependency== | |||
A transitive dependency is a dependency of a dependency of a module. The immediate dependencies are declared in a [[Gradle_Concepts#Module_Metadata|module's metadata]], and the build system is supposed to automatically resolve the dependency graph. | |||
A well-behaved Maven-compatible build system publishes module artifacts accompanied by their corresponding POMs. Naturally, the [[Gradle Maven Publish Plugin|Gradle Maven publish plugin]] does so too. This is an example of a Gradle-published POM of a "playground:b:b:1.0" module that depends on a "playground:a:a:1.0" module. | |||
<syntaxhighlight lang='xml'> | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 | |||
http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||
xmlns="http://maven.apache.org/POM/4.0.0" | |||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<groupId>playground.b</groupId> | |||
<artifactId>b</artifactId> | |||
<version>1.0</version> | |||
<dependencies> | |||
<dependency> | |||
<groupId>playground.a</groupId> | |||
<artifactId>a</artifactId> | |||
<version>1.0</version> | |||
<scope>runtime</scope> | |||
</dependency> | |||
</dependencies> | |||
</project> | |||
</syntaxhighlight> | |||
Gradle offers facilities [[Gradle_Operations#Inspect_Dependencies|to inspect the transitive dependency graph]]. Transitive dependency resolution is enabled by default. When compiling "playground.b:b.1.0", whose POM is presented above, Gradle learns that "playground.b:b.1.0" depends on "playground.a:a.1.0", so it will automatically pull the dependency from its configured repositories, following [[#Resolution_Rule|dependency resolution rules]]. If, in turn "playground.a:a.1.0" has declared dependencies, the process will continue recursively. Plugins like '[[Gradle_Application_Plugin#Overview|application]]' are aware of the transitive dependency graph and will pull and package all required dependencies, recursively. | |||
Transitive dependency resolution it can be turned off, if desired, when the dependency is declared, as shown in the [[Gradle_Dependencies_and_Dependency_Configurations#Turning_Off_Transitive_Dependency_Resolution|Turning Off Transitive Dependency Resolution]] section. | |||
Transitive dependencies can be experimented with in: {{External|[https://github.com/ovidiuf/playground/tree/master/gradle/transitive-dependencies-experiment#transitive-dependencies-experiment Playground Gradle Transitive Dependencies Experiment]}} | |||
<font color=darkgray>TODO b35g: https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html</font>. | |||
==Dynamic Version== | |||
An aggressive approach for consuming dependencies is to declare that a module depends on the latest version of a dependency, or the latest version of a version range. This approach is called '''dynamic versioning'''. By default, Gradle caches dynamic versions of dependencies for 24 hours. Within this time frame, Gradle does not try to resolve newer versions from the declared repositories. This threshold [[Gradle_Dependencies_and_Dependency_Configurations#cacheDynamicVersionsFor|can be configured]]. The [[Gradle_Project_and_Build_Script#Overview|build.gradle]] syntax for dynamic versioning is: | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
implementation 'org.slf4j:slf4j-api:1.2.+' | |||
} | |||
</syntaxhighlight> | |||
==Changing Versions or Snapshots== | |||
{{External|[https://docs.gradle.org/current/userguide/declaring_dependencies.html#sub:declaring_dependency_with_changing_version Gradle Docs - Declaring Dependencies with Changing Version]}} | |||
What Maven refers to as [[Maven_Concepts#Snapshots|snapshots]] are known to Gradle as '''changing versions'''. A changing version indicates that the respective feature is still under development and hasn't released a stable dot version for general availability yet. In both Maven and Gradle, changing versions contain the suffix <tt>-SNAPSHOT</tt>. A snapshot version always points at the latest artifact published. | |||
A dependency on a changing version can be declared by performing both of the following configuration changes: | |||
# Listing the corresponding snapshot repository among the available repositories. | |||
# Declaring the dependency with the -SNAPSHOT suffix: | |||
<syntaxhighlight lang='groovy'> | |||
... | |||
repositories { | |||
mavenCentral() | |||
... | |||
maven { | |||
url = "s3://my-repo/snapshots" | |||
... | |||
} | |||
} | |||
... | |||
dependencies { | |||
... | |||
implementation group: 'com.example', name: 'something', version: '1.0.0-SNAPSHOT' | |||
... | |||
} | |||
</syntaxhighlight> | |||
The presence of "-SNAPSHOT" suffix in the dependency version string is sufficient to designate that dependency as "changing". Gradle will try to pull it according to its [[#Configuring_Cached_Snapshots_Time_to_Live|resolution policy]], even if the dot section of the version string does not change. | |||
It is possible to declare that a version is changing even if the version string does not contains "-SNAPSHOT". Gradle provides a "changing" keyword that can be used in the dependency declaration, as shown below. Note that this is not a common case and normally there should be no need to use "changing:". For more details see [https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/ExternalModuleDependency.html#setChanging-boolean- setChanging(true)]. | |||
<syntaxhighlight lang='groovy'> | |||
implementation group: 'com.example', name: 'something', version: '1.0.0', changing: true | |||
</syntaxhighlight> | |||
By default, Gradle caches changing versions for 24 hours. Within this timeframe, Gradle does not contact any of the declared remote repositories for newer snapshot versions. This default setting might be too long for intense development, when a specific artifacts changes and it is being published frequently. The threshold can be configured, so remote repositories can be checked more frequently. For details, see [[#Configuring_Cached_Snapshots_Time_to_Live|Configuring Cached Snapshots Time to Live]] below. Alternatively, the cached snapshots can be forcedly updated with every execution of a build, [[#Forced_Cache_Update|using --refresh-dependencies on command line]]. | |||
==Dependency Resolution== | |||
At runtime, Gradle will attempt to locate declared dependencies needed for the task being executed in the repositories associated with the project. This process is called '''dependency resolution'''. The dependencies may need downloading form a remote repository, retrieval from a local repository or building another project, in case of a [[Gradle_Multi-Project_Builds#Overview|multi-project build]] whose sub-projects declare dependencies on another sub-projects. A specific dependency may find itself in the dependency graph because it was declared in the build script or it is dependency of a declared dependency - a [[#Transitive_Dependency|transitive dependency]]. The dependency resolution works as follows: | |||
* Given a dependency, Gradle attempts to resolve the dependency by searching for the module the dependency points at. Each repository is inspected in order. Depending on the type of repository, Gradle looks for the [[#Module_Metadata|metadata file]] describing the module or directly for the default artifact files, usually a JAR. | |||
** If the dependency is declared as a [[#Dynamic_Version|dynamic version]], Gradle will resolve this to the highest available concrete version in the repository. | |||
** If the module metadata is a POM file that has a parent POM, Gradle will recursively attempt to resolve each of the parent modules for the POM. | |||
* Once each repository has been inspected for the module, Gradle will chose the "best" one using the following criteria: | |||
** For a [[#Dynamic_Version|dynamic version]], a "higher" concrete value is preferred to a "lower" version. | |||
** Modules declared by a module metadata file are preferred over modules that have an artifact file only. | |||
** Modules from earlier repositories are preferred over modules in later repositories | |||
** When the dependency is declared by a concrete version and a module metadata file is found in a repository, there is no need to continue searching later repositories. The remainder of the process is short-circuited. | |||
* All of the artifacts for the module are then requested from the same repository that was chosen in the process described above. | |||
* If none of the dependency artifacts can be resolved, the build fails. | |||
* Once a dependency is resolved, Gradle stores into a local cache, referred to as [[#The_Dependency_Cache|the dependency cache]]. | |||
Under certain conditions, we may want to tweak the dependency resolution mechanism, and that [https://docs.gradle.org/current/userguide/declaring_dependencies.html#sub:resolve_specific_artifacts_from_dependency is possible]. | |||
==Resolution Rule== | |||
{{External|[https://docs.gradle.org/current/userguide/customizing_dependency_resolution_behavior.html Gradle Docs - Customizing Dependency Resolution Behaivor]}} | |||
A resolution rule influences the behavior of how a [[#Dependency|dependency]] is resolved. Resolution rules are defined as part of the build logic. | |||
==<span id='Dependency_Constraint'></span>Dependency Constraints== | |||
Gradle allows specifying conditions dependencies must meet to qualify as valid dependency for the project. These conditions are referred to as '''dependency constraints'''. A dependency constraint defines requirements that need to be met by a [[Gradle_Concepts#Module|module]] to make it a valid result for a dependency resolution process. A recommended practice for large projects is to declare dependencies without versions and use dependency constraints for version declaration. The advantage is that dependency constrains allow you to manage versions of all dependencies, including transitive ones, in one place. | |||
=Dependency and Dependency Configuration Info= | |||
The dependencies of a project and their association with various dependency configurations can be displayed with: | |||
gradle dependencies | |||
The dependency configuration can also be displayed by accessing the configuration container of the project: | |||
<syntaxhighlight lang='groovy'> | |||
task displayconfigs { | |||
doFirst { | |||
println "project dependency configurations: " | |||
configurations.each { println ' ' + it.name } | |||
} | |||
} | |||
</syntaxhighlight> | |||
=Declaring Dependencies= | |||
Dependencies for a project are declared in the associated [[Gradle_Project_and_Build_Script#dependencies.7B.7D|build.gradle]] using the dedicated [[Gradle_Project_and_Build_Script#dependencies.7B.7D|dependencies{...}]] script block. The [https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:dependencies(groovy.lang.Closure) dependencies()] method executes the declared closure against the [[#Configuration_Container|configuration container]] of this project, which is passed as the delegate object to the closure. | |||
The syntax is: | |||
dependencies { | |||
''known-dependency-configuration-name'' ''dependency-coordinates-1'', ''dependency-coordinates-2'', ... | |||
} | |||
Note that the dependency configurations used to declare dependencies against must be known to the build, usually via the applied plugins. If the dependency configuration is not know, we get a build error. | |||
==<span id='External_Dependencies'></span>Declaring Module Dependencies== | |||
The dependency coordinates for a [[#Module_Dependency|module dependency]], pulled from a repository, must be in the following format: | |||
''group-id'':''artifact-id'':''version'':''classifier'' | |||
All declared dependencies must be available in a repository known to the project, so [[Gradle_Repositories#Declaring_Repositories|repositories must also be declared]]. | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
api 'org.slf4j:slf4j-api:1.7.12' | |||
implementation 'com.google.guava:guava:23.0' | |||
... | |||
} | |||
</syntaxhighlight> | |||
Dependencies can also be declared without a version, and [[#Dependency_Constraint|dependency constraints]] can be used to manage the version, including the transitive versions. | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
implementation 'org.slf4j:slf4j-api' | |||
constraints { | |||
implementation 'org.slf4j:slf4j-api:1.7.12' | |||
} | |||
} | |||
</syntaxhighlight> | |||
When declaring a dependency, a reason for the declaration can be provided in-line: | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
implementation('org.slf4j:slf4j-api:1.7.12') { | |||
because 'because we like SLF4J' | |||
} | |||
} | |||
</syntaxhighlight> | |||
===Alternative Dependency Declaration Format=== | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
... | |||
implementation group: 'com.example', name: 'something', version: '1.0.0-SNAPSHOT', changing: true | |||
... | |||
} | |||
</syntaxhighlight> | |||
For details see section [[#Changing_Versions_or_Snapshots|Declaring Dependencies on Changing Versions]] above. | |||
===Factoring Out Version Information=== | |||
There are situations when multiple dependency declaration lines contain the same version information, for closely related, but different artifacts. In those situations, the version information can be factored out and declared as an [[Gradle_Variables_and_Properties#Extra_Properties|extra property]] of the project, in a separated section. This practice improves the readability of the build file, and also simplifies reconfiguration in case of a version upgrade: | |||
<syntaxhighlight lang='groovy'> | |||
... | |||
ext { | |||
slf4jVersion="1.7.23" | |||
} | |||
... | |||
dependencies { | |||
implementation "org.slf4j:slf4j-api:${slf4jVersion}" | |||
implementation "org.slf4j:slf4j-log4j12:${slf4jVersion}" | |||
} | |||
</syntaxhighlight> | |||
Note that the ext closure is subject to [[Gradle_Variables_and_Properties#ext_Closure_Placement_Restrictions|placement restrictions]], the extra property names are also subject to [[Gradle_Variables_and_Properties#Extra_Property_Name_Constraints|constraints]], and when an extra property is used, the dependency coordinates should be declared in [[Groovy#Single-Quoted_vs._Double-Quoted_Strings|double quoted strings]], to allow property resolution. | |||
===Excluding a Dependency of a Dependency=== | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
implementation ('com.github.javafaker:javafaker:1.0.2') {exclude module: 'org.yaml'} | |||
} | |||
</syntaxhighlight> | |||
<syntaxhighlight lang='groovy'> | |||
implementation ('org.jenkins-ci.main:jenkins-core:2.102') { | |||
exclude group: 'org.jenkins-ci', module: 'trilead-ssh2' // transitively fails with a more distant dependency | |||
} | |||
</syntaxhighlight> | |||
==Declaring File Dependencies== | |||
{{External|[https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:declaring_file_dependency Gradle Docs - Declaring a File Dependency]}} | |||
[[#File_Dependency|File dependencies]] are declared as follows: | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
... | |||
implementation files('libs/a.jar', 'libs/b.jar') | |||
implementation fileTree(dir: 'libs', include: '*.jar') | |||
... | |||
} | |||
</syntaxhighlight> | |||
==<span id='Dependencies_between_Sub-Projects'></span>Declaring Project Dependencies== | |||
{{External|[https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:declaring_project_dependency Gradle Docs - Declaring a Project Dependency]}} | |||
[[#Project_Dependency|Dependencies between the sub-projects]] of the same [[Gradle_Multi-Project_Builds#Overview|multi-project build]] must be explicitly declared. If classes belonging to "subproject-B" depend on classes in "subproject-A", then in the [[Gradle_Project_and_Build_Script#Overview|build.gradle]] of subproject-B we must add: | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
... | |||
implementation project(':subproject-A') | |||
... | |||
} | |||
</syntaxhighlight> | |||
More details: {{Internal|Gradle_Multi-Project_Builds#Inter-Project_Dependencies|Inter-Project Dependencies}} | |||
==Turning Off Transitive Dependency Resolution== | |||
[[#Transitive_Dependency|Transitive dependency]] resolution can be turned off as follows: | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
implementation('org.hibernate:hibernate:3.0.5') { | |||
transitive = false | |||
} | |||
} | |||
</syntaxhighlight> | |||
==Forcing Source and Javadoc Download== | |||
Apparently, we need to use the "eclipse" or "idea" plugin and configure it to download sources and javadocs. For more details see: {{Internal|Gradle IDEA Plugin#Downloading_Sources_and_Javadoc|Downloading Sources and Javadoc with Gradle IDEA Plugin}} | |||
The alternative is to specify a classifier in the dependency declaration: | |||
<syntaxhighlight lang='groovy'> | |||
implementation 'io.novaordis:example:1.0.0:sources' | |||
implementation 'io.novaordis:example:1.0.0:javadoc' | |||
</syntaxhighlight> | |||
Equivalent configuration: | |||
<syntaxhighlight lang='groovy'> | |||
implementation group:'io.novaordis', name: 'example', version: '1.0.0', classifier: 'sources' | |||
implementation group:'io.novaordis', name: 'example', version: '1.0.0', classifier: 'javadoc' | |||
</syntaxhighlight> | |||
=Configuring Dependency Configurations= | |||
The [[Gradle_Project_and_Build_Script#configurations.7B.7D|configurations{...}]] script block applies the declared closure to the [[#Dependency_Configuration|dependency configurations]] of the project. | |||
<syntaxhighlight lang='groovy'> | |||
configurations.all { | |||
resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes' | |||
} | |||
</syntaxhighlight> | |||
Alternative: | |||
<syntaxhighlight lang='groovy'> | |||
configurations.all { | |||
resolutionStrategy { | |||
cacheDynamicVersionsFor 10, 'minutes' | |||
} | |||
} | |||
</syntaxhighlight> | |||
<syntaxhighlight lang='groovy'> | |||
configurations { | |||
testCompile.exclude group: 'io.example' | |||
} | |||
</syntaxhighlight> | |||
<font color=darkgray> | |||
* TODO https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html | |||
* TODO https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html | |||
</font> | |||
==Configuring Cached Snapshots Time to Live== | |||
= | <syntaxhighlight lang='groovy'> | ||
configurations.all { | |||
resolutionStrategy.cacheChangingModulesFor 4, 'hours' | |||
} | |||
</syntaxhighlight> | |||
To attempt to pull every time the latest version of a SNAPSHOT, this seems to work: | |||
<syntaxhighlight lang='groovy'> | |||
configurations.all { | |||
resolutionStrategy.cacheChangingModulesFor 0, 'seconds' | |||
} | |||
</syntaxhighlight> | |||
=Organizatorium= | |||
* [[Gradle_Operations#Print_the_Runtime_Classpath|Print the runtime classpath]] |
Latest revision as of 00:45, 12 June 2021
DEPLETE TO
Overview
One of the most useful Gradle features is its dependency management capability. Dependency management is a technique for declaring, resolving and using dependencies required by a project in an automated fashion.
Gradle Dependency Concepts
Dependency
A dependency is an artifact the project needs. Dependencies are usually located in a repository. A dependency can be thought of as a pointer to another piece of software. Gradle supports several types of dependencies:
Module Dependency
The module dependency is the most common form of dependency. It refers to an artifact published by a Gradle module (or an equivalent of Gradle, such as Maven) in a repository. They are declared similar to:
dependencies {
implementation '<group-id>:<artifact-id>:<version>'
}
More details are available in the Declaring Module Dependencies section, below.
File Dependency
A file dependency allows specification of a file present on an accessible filesystem as dependency, without first adding it to a repository. They are declared similar to:
dependencies {
implementation files('libs/a.jar')
}
More details are available in the Declaring File Dependencies section, below.
Project Dependency
A project dependency expresses the dependency on artifacts produced by other project that is part of the same multi-project build. The project dependency is declared similar to:
dependencies {
implementation project(':subproject-A')
}
More details are available in the Declaring Project Dependencies section, below.
Gradle Distribution-Specific Dependency
Dependency Configuration
Gradle groups dependencies in, and handles them as sets, referred to as dependency configurations. A dependency configuration is a named set of dependencies, grouped together for a specific goal. For example, some dependencies should be used during the compilation phase, whereas others need to be available during the testing phase, or at runtime. The concept is somewhat similar to the Maven dependency scope. Internally, a dependency configuration is implemented as a Configuration instance. Each configuration is identified by a unique name. Many Gradle plugins add pre-defined configurations to the project. For example, the Java plugin adds configurations to represent the various classpaths needed for compilation, testing, etc. A dependency configuration has three main purposes:
- To declare dependencies: plugins use dependency configurations to allow build authors to declare what other sub-projects or external artifacts are needed during the execution of tasks defined by the plugin.
- To resolve dependencies: plugins use dependency configurations to find and download the dependencies that are needed during the build
- To expose artifacts for consumption: plugins use dependency configurations to define what artifacts they generate. Configurations are using during the artifact publishing process.
Additionally to the dependency configurations introduced by plugins, custom configuration may be defined. A configuration can extend other configurations to form an configuration hierarchy. Child configurations inherit the whole set of dependencies declared in any of its parent configurations.
The dependency configurations of a project can displayed as shown in the Dependency and Dependency Configuration Info section.
The dependency configurations of a project can be configured in the project's build.gradle, within the configurations{...} script block, as shown in the Configuring Dependency Configurations section.
Example of a typical set of dependency configuration for a Java project:
compile - Dependencies for source set 'main' (deprecated, use 'implementation ' instead). implementation - Implementation only dependencies for source set 'main'. runtime - Runtime dependencies for source set 'main' (deprecated, use 'runtimeOnly ' instead). runtimeOnly - Runtime only dependencies for source set 'main'. testCompile - Dependencies for source set 'test' (deprecated, use 'testImplementation ' instead). testImplementation - Implementation only dependencies for source set 'test'. annotationProcessor - Annotation processors and their dependencies for source set 'main'. apiElements - API elements for main. archives - Configuration for archive artifacts. bootArchives - Configuration for Spring Boot archive artifacts. compileClasspath - Compile classpath for source set 'main'. compileOnly - Compile only dependencies for source set 'main'. default - Configuration for default artifacts. docker jacocoAgent - The Jacoco agent to use to get coverage data. jacocoAnt - The Jacoco ant tasks to use to get execute Gradle tasks. runtimeClasspath - Runtime classpath of source set 'main'. runtimeElements - Elements of runtime for main. testAnnotationProcessor - Annotation processors and their dependencies for source set 'test'. testCompileClasspath - Compile classpath for source set 'test'. testCompileOnly - Compile only dependencies for source set 'test'. testRuntime - Runtime dependencies for source set 'test' (deprecated, use 'testRuntimeOnly ' instead). testRuntimeClasspath - Runtime classpath of source set 'test'. testRuntimeOnly - Runtime only dependencies for source set 'test'.
For more details on the dependency configurations introduced by the Java plugin, see:
Dependency Configuration Container
All dependency configurations associated with a project are maintained within a configuration container, which allows declaring and managing configurations. The configuration container is one of the project's containers. Dependency configuration is perform by declaring dependency details in the build.gradle dependencies{...} script block, which applies the configuration closure on the delegate configuration container instance.
Transitive Dependency
A transitive dependency is a dependency of a dependency of a module. The immediate dependencies are declared in a module's metadata, and the build system is supposed to automatically resolve the dependency graph.
A well-behaved Maven-compatible build system publishes module artifacts accompanied by their corresponding POMs. Naturally, the Gradle Maven publish plugin does so too. This is an example of a Gradle-published POM of a "playground:b:b:1.0" module that depends on a "playground:a:a:1.0" module.
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>playground.b</groupId>
<artifactId>b</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>playground.a</groupId>
<artifactId>a</artifactId>
<version>1.0</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
Gradle offers facilities to inspect the transitive dependency graph. Transitive dependency resolution is enabled by default. When compiling "playground.b:b.1.0", whose POM is presented above, Gradle learns that "playground.b:b.1.0" depends on "playground.a:a.1.0", so it will automatically pull the dependency from its configured repositories, following dependency resolution rules. If, in turn "playground.a:a.1.0" has declared dependencies, the process will continue recursively. Plugins like 'application' are aware of the transitive dependency graph and will pull and package all required dependencies, recursively.
Transitive dependency resolution it can be turned off, if desired, when the dependency is declared, as shown in the Turning Off Transitive Dependency Resolution section.
Transitive dependencies can be experimented with in:
TODO b35g: https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html.
Dynamic Version
An aggressive approach for consuming dependencies is to declare that a module depends on the latest version of a dependency, or the latest version of a version range. This approach is called dynamic versioning. By default, Gradle caches dynamic versions of dependencies for 24 hours. Within this time frame, Gradle does not try to resolve newer versions from the declared repositories. This threshold can be configured. The build.gradle syntax for dynamic versioning is:
dependencies {
implementation 'org.slf4j:slf4j-api:1.2.+'
}
Changing Versions or Snapshots
What Maven refers to as snapshots are known to Gradle as changing versions. A changing version indicates that the respective feature is still under development and hasn't released a stable dot version for general availability yet. In both Maven and Gradle, changing versions contain the suffix -SNAPSHOT. A snapshot version always points at the latest artifact published.
A dependency on a changing version can be declared by performing both of the following configuration changes:
- Listing the corresponding snapshot repository among the available repositories.
- Declaring the dependency with the -SNAPSHOT suffix:
...
repositories {
mavenCentral()
...
maven {
url = "s3://my-repo/snapshots"
...
}
}
...
dependencies {
...
implementation group: 'com.example', name: 'something', version: '1.0.0-SNAPSHOT'
...
}
The presence of "-SNAPSHOT" suffix in the dependency version string is sufficient to designate that dependency as "changing". Gradle will try to pull it according to its resolution policy, even if the dot section of the version string does not change.
It is possible to declare that a version is changing even if the version string does not contains "-SNAPSHOT". Gradle provides a "changing" keyword that can be used in the dependency declaration, as shown below. Note that this is not a common case and normally there should be no need to use "changing:". For more details see setChanging(true).
implementation group: 'com.example', name: 'something', version: '1.0.0', changing: true
By default, Gradle caches changing versions for 24 hours. Within this timeframe, Gradle does not contact any of the declared remote repositories for newer snapshot versions. This default setting might be too long for intense development, when a specific artifacts changes and it is being published frequently. The threshold can be configured, so remote repositories can be checked more frequently. For details, see Configuring Cached Snapshots Time to Live below. Alternatively, the cached snapshots can be forcedly updated with every execution of a build, using --refresh-dependencies on command line.
Dependency Resolution
At runtime, Gradle will attempt to locate declared dependencies needed for the task being executed in the repositories associated with the project. This process is called dependency resolution. The dependencies may need downloading form a remote repository, retrieval from a local repository or building another project, in case of a multi-project build whose sub-projects declare dependencies on another sub-projects. A specific dependency may find itself in the dependency graph because it was declared in the build script or it is dependency of a declared dependency - a transitive dependency. The dependency resolution works as follows:
- Given a dependency, Gradle attempts to resolve the dependency by searching for the module the dependency points at. Each repository is inspected in order. Depending on the type of repository, Gradle looks for the metadata file describing the module or directly for the default artifact files, usually a JAR.
- If the dependency is declared as a dynamic version, Gradle will resolve this to the highest available concrete version in the repository.
- If the module metadata is a POM file that has a parent POM, Gradle will recursively attempt to resolve each of the parent modules for the POM.
- Once each repository has been inspected for the module, Gradle will chose the "best" one using the following criteria:
- For a dynamic version, a "higher" concrete value is preferred to a "lower" version.
- Modules declared by a module metadata file are preferred over modules that have an artifact file only.
- Modules from earlier repositories are preferred over modules in later repositories
- When the dependency is declared by a concrete version and a module metadata file is found in a repository, there is no need to continue searching later repositories. The remainder of the process is short-circuited.
- All of the artifacts for the module are then requested from the same repository that was chosen in the process described above.
- If none of the dependency artifacts can be resolved, the build fails.
- Once a dependency is resolved, Gradle stores into a local cache, referred to as the dependency cache.
Under certain conditions, we may want to tweak the dependency resolution mechanism, and that is possible.
Resolution Rule
A resolution rule influences the behavior of how a dependency is resolved. Resolution rules are defined as part of the build logic.
Dependency Constraints
Gradle allows specifying conditions dependencies must meet to qualify as valid dependency for the project. These conditions are referred to as dependency constraints. A dependency constraint defines requirements that need to be met by a module to make it a valid result for a dependency resolution process. A recommended practice for large projects is to declare dependencies without versions and use dependency constraints for version declaration. The advantage is that dependency constrains allow you to manage versions of all dependencies, including transitive ones, in one place.
Dependency and Dependency Configuration Info
The dependencies of a project and their association with various dependency configurations can be displayed with:
gradle dependencies
The dependency configuration can also be displayed by accessing the configuration container of the project:
task displayconfigs {
doFirst {
println "project dependency configurations: "
configurations.each { println ' ' + it.name }
}
}
Declaring Dependencies
Dependencies for a project are declared in the associated build.gradle using the dedicated dependencies{...} script block. The dependencies() method executes the declared closure against the configuration container of this project, which is passed as the delegate object to the closure.
The syntax is:
dependencies { known-dependency-configuration-name dependency-coordinates-1, dependency-coordinates-2, ... }
Note that the dependency configurations used to declare dependencies against must be known to the build, usually via the applied plugins. If the dependency configuration is not know, we get a build error.
Declaring Module Dependencies
The dependency coordinates for a module dependency, pulled from a repository, must be in the following format:
group-id:artifact-id:version:classifier
All declared dependencies must be available in a repository known to the project, so repositories must also be declared.
dependencies {
api 'org.slf4j:slf4j-api:1.7.12'
implementation 'com.google.guava:guava:23.0'
...
}
Dependencies can also be declared without a version, and dependency constraints can be used to manage the version, including the transitive versions.
dependencies {
implementation 'org.slf4j:slf4j-api'
constraints {
implementation 'org.slf4j:slf4j-api:1.7.12'
}
}
When declaring a dependency, a reason for the declaration can be provided in-line:
dependencies {
implementation('org.slf4j:slf4j-api:1.7.12') {
because 'because we like SLF4J'
}
}
Alternative Dependency Declaration Format
dependencies {
...
implementation group: 'com.example', name: 'something', version: '1.0.0-SNAPSHOT', changing: true
...
}
For details see section Declaring Dependencies on Changing Versions above.
Factoring Out Version Information
There are situations when multiple dependency declaration lines contain the same version information, for closely related, but different artifacts. In those situations, the version information can be factored out and declared as an extra property of the project, in a separated section. This practice improves the readability of the build file, and also simplifies reconfiguration in case of a version upgrade:
...
ext {
slf4jVersion="1.7.23"
}
...
dependencies {
implementation "org.slf4j:slf4j-api:${slf4jVersion}"
implementation "org.slf4j:slf4j-log4j12:${slf4jVersion}"
}
Note that the ext closure is subject to placement restrictions, the extra property names are also subject to constraints, and when an extra property is used, the dependency coordinates should be declared in double quoted strings, to allow property resolution.
Excluding a Dependency of a Dependency
dependencies {
implementation ('com.github.javafaker:javafaker:1.0.2') {exclude module: 'org.yaml'}
}
implementation ('org.jenkins-ci.main:jenkins-core:2.102') {
exclude group: 'org.jenkins-ci', module: 'trilead-ssh2' // transitively fails with a more distant dependency
}
Declaring File Dependencies
File dependencies are declared as follows:
dependencies {
...
implementation files('libs/a.jar', 'libs/b.jar')
implementation fileTree(dir: 'libs', include: '*.jar')
...
}
Declaring Project Dependencies
Dependencies between the sub-projects of the same multi-project build must be explicitly declared. If classes belonging to "subproject-B" depend on classes in "subproject-A", then in the build.gradle of subproject-B we must add:
dependencies {
...
implementation project(':subproject-A')
...
}
More details:
Turning Off Transitive Dependency Resolution
Transitive dependency resolution can be turned off as follows:
dependencies {
implementation('org.hibernate:hibernate:3.0.5') {
transitive = false
}
}
Forcing Source and Javadoc Download
Apparently, we need to use the "eclipse" or "idea" plugin and configure it to download sources and javadocs. For more details see:
The alternative is to specify a classifier in the dependency declaration:
implementation 'io.novaordis:example:1.0.0:sources'
implementation 'io.novaordis:example:1.0.0:javadoc'
Equivalent configuration:
implementation group:'io.novaordis', name: 'example', version: '1.0.0', classifier: 'sources'
implementation group:'io.novaordis', name: 'example', version: '1.0.0', classifier: 'javadoc'
Configuring Dependency Configurations
The configurations{...} script block applies the declared closure to the dependency configurations of the project.
configurations.all {
resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes'
}
Alternative:
configurations.all {
resolutionStrategy {
cacheDynamicVersionsFor 10, 'minutes'
}
}
configurations {
testCompile.exclude group: 'io.example'
}
- TODO https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html
- TODO https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html
Configuring Cached Snapshots Time to Live
configurations.all {
resolutionStrategy.cacheChangingModulesFor 4, 'hours'
}
To attempt to pull every time the latest version of a SNAPSHOT, this seems to work:
configurations.all {
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}