Gradle Dependencies and Dependency Configurations TODEPLETE: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(243 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#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:


=Overview=
===Module Dependency===


Configuration, in this context, does not refer to a configuration file, as in a settings configuration or a build configuration. It refers to named set of dependencies or artifacts, and it can probably thought of as being a concept similar to a Maven [[Maven_Concepts_-_Dependencies#Dependency_Scope|scope]].
{{External|[https://docs.gradle.org/current/userguide/dependency_types.html#sub:module_dependencies Gradle Docs - Module Dependencies]}}


=TO Deplete=
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===


A dependency is an artifact, usually located in a [[#Repository|repository]], the project needs to build. It can be seen as a pointer to another piece of software.
{{External|[https://docs.gradle.org/current/userguide/dependency_types.html#sub:file_dependencies Gradle Docs - File Dependencies]}}


In Gradle, dependencies are declared in [[build.gradle#dependencies|build.gradle]].
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>


At runtime, Gradle will attempt to locate declared dependencies needed for the task being executed. This process may involve access and download from a remote repository, retrieval from a local directory, or building another project, in case of a [[#Multi-Project_Build|multi-project build]]. <font color=darkgray>This process is called <span id='Dependency_Resolution'></span>''dependency resolution'' https://docs.gradle.org/current/userguide/introduction_dependency_management.html#sec:dependency_resolution</font>. 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 (transitive dependency).
More details are available in the [[#Declaring_File_Dependencies|Declaring File Dependencies]] section, below.


Whenever Gradle tries to resolve a dependency in a [[#Repository|repository]], it looks for a [[#Module_Metadata|metadata file]] and the default artifact file, a JAR. The build fails if none of these artifact files can be resolved. <font color=darkgray>Under certain conditions, tweaking the way Gradle resolves artifacts for a dependency might be desired. TODO https://docs.gradle.org/current/userguide/declaring_dependencies.html#sub:resolve_specific_artifacts_from_dependency</font>
===Project Dependency===


Once a dependency is resolved, Gradle stores into a local cache, referred to as [[#The_Dependency_Cache|the dependency cache]].
{{External|[https://docs.gradle.org/current/userguide/dependency_types.html#sub:project_dependencies Gradle Docs - Project Dependencies]}}


Dependencies are declared with:
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'>
<syntaxhighlight lang='groovy'>
dependencies {  
dependencies {
...
    implementation project(':subproject-A')
}
}
</syntaxhighlight>
</syntaxhighlight>


in:
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'.


{{Internal|Build.gradle#dependencies|build.gradle}}
For more details on the dependency configurations introduced by the Java plugin, see: {{Internal|Gradle_Java_Plugin#Dependency_Configurations|Java Plugin - Dependency Configurations}}


==Dependency Management==
===<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==


Dependency management is a technique for declaring, resolving and using dependencies required by the project in an automated fashion.
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.  


==Dependency Types==
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.


===Module Dependency===
<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>


Module dependency is the most common form of dependency. It refers to a [[#Module|module]] in a [[#Repository|repository]].
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.


<font color=darkgray>TODO https://docs.gradle.org/current/userguide/dependency_types.html#sub:module_dependencies</font>
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.


===Local File Dependency===
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>
<font color=darkgray>TODO b35g: https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html</font>.
TODO:
* https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:declaring_file_dependency
* https://docs.gradle.org/current/userguide/dependency_types.html#sub:file_dependencies</font>


===Project Dependency===
==Dynamic Version==


<font color=darkgray>
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:
TODO:
*  https://docs.gradle.org/current/userguide/dependency_types.html#sub:project_dependencies
* https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:declaring_project_dependency</font>


===Gradle Distribution-Specific Dependency===
<syntaxhighlight lang='groovy'>
dependencies {
    implementation 'org.slf4j:slf4j-api:1.2.+'
}
</syntaxhighlight>


<font color=darkgray>TODO https://docs.gradle.org/current/userguide/dependency_types.html#sub:api_dependencies</font>
==Changing Versions or Snapshots==


==Dependency Configuration==
{{External|[https://docs.gradle.org/current/userguide/declaring_dependencies.html#sub:declaring_dependency_with_changing_version Gradle Docs - Declaring Dependencies with Changing Version]}}


A dependency configuration is a named set of [[#Dependency|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. These are dependency scopes, and Gradle represents the scope of a dependency with the help of a [https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.Configuration.html Configuration]. Each configuration can be identified by a unique name. Many Gradle [[#Plugin|plugins]] add pre-defined configurations to the project. For example, the [[Gradle Java Plugin|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:
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.
* To declare dependencies: plugins use dependency configurations to allow build authors to declare what other subproject of external artifacts are needed during the execution of tasks defined by the plugin.
* To resolve dependencies: plugins use dependency configurations to find and download inputs to the tasks it defines.
* To expose artifacts for consumption: plugins use dependency configurations to define what artifacts it generates. Configurations are using during the [[Gradle_Artifact_Publishing_Concepts#Configuration|artifact publishing process]].


Configurations can be displayed with:
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'>
<syntaxhighlight lang='groovy'>
task configs {
...


     configurations.each { println it.name }
repositories {
    mavenCentral()
     ...
    maven {
        url = "s3://my-repo/snapshots"
        ...
    }
}
...
dependencies {
    ...
    implementation group: 'com.example', name: 'something', version: '1.0.0-SNAPSHOT'
    ...
}
}
</syntaxhighlight>
</syntaxhighlight>


The same configurations (with more details attached) are displayed by
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  
  gradle dependencies  


<font color=darkgray>
The dependency configuration can also be displayed by accessing the configuration container of the project:
Custom configurations may be defined: https://docs.gradle.org/current/userguide/managing_dependency_configurations.html#_defining_custom_configurations.
 
</font>
<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==


===Configuration Hierarchy===
{{External|[https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:declaring_project_dependency Gradle Docs - Declaring a Project Dependency]}}


<font color=darkgray>
[[#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:
A configuration can extend other configurations to form an inheritance hierarchy. Child configurations inherit the whole set of dependencies declared for any of its superconfigurations. Dependencies may be inherited from other configurations: https://docs.gradle.org/current/userguide/managing_dependency_configurations.html#sub:inheriting_dependencies_from_other_configurations.
</font>


==Dependency Constraint==
<syntaxhighlight lang='groovy'>
dependencies {
    ...
    implementation project(':subproject-A')
    ...
}
</syntaxhighlight>


A dependency constraint defines requirements that need to be met by a [[#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.
More details: {{Internal|Gradle_Multi-Project_Builds#Inter-Project_Dependencies|Inter-Project Dependencies}}


==Dynamic Version==
==Turning Off Transitive Dependency Resolution==


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 [[Build.gradle#cacheDynamicVersionsFor|can be configured]]. The [[build.gradle]] syntax for dynamic versioning is:
[[#Transitive_Dependency|Transitive dependency]] resolution can be turned off as follows:


<syntaxhighlight lang='groovy'>
<syntaxhighlight lang='groovy'>
dependencies {
dependencies {
     implementation 'org.slf4j:slf4j-api:1.2.+'
     implementation('org.hibernate:hibernate:3.0.5') {
        transitive = false
    }
}
}
</syntaxhighlight>
</syntaxhighlight>


==Changing Versions or Snapshots==
==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>


In Maven, changing versions are referred to as [[Maven_Concepts#Snapshots|napshots]].
=Configuring Dependency Configurations=


<font color=darkgray>TODO when needed https://docs.gradle.org/current/userguide/declaring_dependencies.html#sub:declaring_dependency_with_changing_version</font>
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.


==Resolution Rule==
<syntaxhighlight lang='groovy'>
configurations.all {
    resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes'
}
</syntaxhighlight>
 
Alternative:


A resolution rule influences the behavior of how a [[#Dependency|dependency]] is resolved. Resolution rules are defined as part of the build logic. <font color=darkgray>TODO https://docs.gradle.org/current/userguide/customizing_dependency_resolution_behavior.html</font>.
<syntaxhighlight lang='groovy'>
configurations.all {
    resolutionStrategy {
        cacheDynamicVersionsFor 10, 'minutes'
    }
}
</syntaxhighlight>


==Transitive Dependency==
<syntaxhighlight lang='groovy'>
configurations {
    testCompile.exclude group: 'io.example'
}
</syntaxhighlight>


A transitive dependency is a dependency of a dependency of a module. The immediate dependencies are declared in a [[#Module_Metadata|module's metadata]], and the build system is supposed to automatically resolve the dependency graph. <font color=darkgray>TODO: https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html</font>. Gradle offers facilities [[Gradle_Operations#Inspect_Dependencies|to inspect the transitive dependency graph]].
<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>


==<span id='The Local Gradle Dependency Cache'></span>The Dependency Cache==
==Configuring Cached Snapshots Time to Live==


{{External|https://docs.gradle.org/current/userguide/dependency_cache.html}}
<syntaxhighlight lang='groovy'>
configurations.all {
    resolutionStrategy.cacheChangingModulesFor 4, 'hours'
}
</syntaxhighlight>


Once a dependency is resolved as result of the [[#Dependency_Resolution|dependency resolution]], Gradle stores the associated artifacts in a local cache, known as the dependency cache.
To attempt to pull every time the latest version of a SNAPSHOT, this seems to work:


The Gradle dependency cache consists of two storage types located under [[#Gradle_User_Home|Gradle user home]] <tt>caches</tt> subdirectory: file-based storage for downloaded artifacts and raw metadata files, and a binary store of resolved module meta-data.
<syntaxhighlight lang='groovy'>
configurations.all {
    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
</syntaxhighlight>


Example:
=Organizatorium=


$HOME/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.12/8e20852d05222dc286bf1c71d78d0531e177c317/slf4j-api-1.7.12.jar
* [[Gradle_Operations#Print_the_Runtime_Classpath|Print the runtime classpath]]

Latest revision as of 00:45, 12 June 2021

DEPLETE TO

Gradle Dependencies and Dependency Configurations

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

Gradle Docs - Module Dependencies

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

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:

dependencies {
    implementation files('libs/a.jar')
 }

More details are available in the Declaring File Dependencies section, below.

Project Dependency

Gradle Docs - Project Dependencies

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

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

Java Plugin - Dependency Configurations

Dependency Configuration Container

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

Playground Gradle Transitive Dependencies Experiment

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

Gradle Docs - Declaring Dependencies with Changing Version

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:

  1. Listing the corresponding snapshot repository among the available repositories.
  2. 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

Gradle Docs - Customizing Dependency Resolution Behaivor

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

Gradle Docs - Declaring a File Dependency

File dependencies are declared as follows:

dependencies {
    ...
    implementation files('libs/a.jar', 'libs/b.jar')
    implementation fileTree(dir: 'libs', include: '*.jar')
    ...
}

Declaring Project Dependencies

Gradle Docs - Declaring a Project Dependency

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:

Inter-Project Dependencies

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:

Downloading Sources and Javadoc with Gradle IDEA Plugin

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'
}

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'
}

Organizatorium