In Chapter 1, Defining Dependencies, you learned how to add dependencies to your projects. We have seen different ways of specifying dependencies, such as module or project dependencies. In the previous chapter, we explored how to define the repositories that host our dependencies. Gradle will use this information to do the actual dependency resolution. In this chapter, we will see how Gradle resolves dependencies.
Gradle has a different way of resolving version conflicts than other build tools, so we will take a good look at what happens when a dependency is resolved. We will see how we can customize the resolution process in Gradle so that we can have the exact dependencies we want and have reliable and repeatable builds.
Gradle will use the information in the repositories
and dependencies
configuration blocks to gather and download all dependencies. This process is also called dependency resolution. The following steps are taken by Gradle to resolve dependencies:
If a dependency is defined with a static version, and Gradle finds a module descriptor file for this dependency in a repository, then the search for this dependency is complete, and other repositories will not be used for the search. The process cannot come up with a better repository candidate, so the dependency resolution is finished for the dependency.
Most of the time dependencies in our project are also dependent on other libraries. So, the dependencies have dependencies of their own. These are so-called transitive dependencies. Gradle must be able to resolve the transitive dependencies as well.
In the following example build file, we define the logback-classic
module dependency with the version 1.1.2
and the group name ch.qos.logback
:
apply plugin: 'java' repositories.jcenter() dependencies { // Dependency definition for Logback classic // library, used as implementation for SLF4J API. compile 'ch.qos.logback:logback-classic:1.1.2' }
When we run the Gradle dependencies
task, we can see that our defined dependency for logback-classic
depends on ch.qos.logback:logback-core:1.1.2
and org.slf4j:slf4j-api:1.7.6
. The following code shows this:
$ gradle -q dependencies --configuration compile ------------------------------------------------------------ Root project ------------------------------------------------------------ compile - Compile classpath for source set 'main'. --- ch.qos.logback:logback-classic:1.1.2 +--- ch.qos.logback:logback-core:1.1.2 --- org.slf4j:slf4j-api:1. 7.6
If we don't want to have transitive dependencies in our project, we must reconfigure the dependency or configuration. With Gradle, we have different ways to disable transitive behavior for dependencies. First, we can add a configuration closure to our dependency definition, use the transitive
property, and set the value to false
. By default, all dependencies are treated as transitive, as we saw in our example build file.
In the following example build file, we specify that we want to treat or use the logback-classic
dependency as nontransitive:
apply plugin: 'java' repositories.jcenter() dependencies { // Dependency definition for Logback classic. compile 'ch.qos.logback:logback-classic:1.1.2', { // We don't want to have the transitive dependencies. transitive = false } }
If we run the dependencies
task again, we can see in the output that the transitive dependencies are no longer resolved:
$ gradle -q dependencies --configuration compile ------------------------------------------------------------ Root project ------------------------------------------------------------ compile - Compile classpath for source set 'main'. --- ch.qos.logback:logback-classic:1.1.2
We can also disable transitive dependencies for a dependency configuration as a whole. So, this means that any dependencies defined with the configuration will not have transitive dependencies. Single dependencies within the configuration can use the transitive
property in the configuration closure to enable transitive behavior again for that dependency. To accomplish this, perform the following steps:
compile
configuration in the next example build file:apply plugin: 'java' repositories.jcenter() configurations { // Disable transitive dependencies for // all dependencies defined in this // configuration. // Configurations extended // from the compile configuration will not // inherit this transitive property value. compile.transitive = false } dependencies { // Dependency definition for Logback classic compile 'ch.qos.logback:logback-classic:1.1.2' }
dependencies
task and see that transitive dependencies are no longer resolved:$ gradle -q dependencies --configuration compile ------------------------------------------------------------ Root project ------------------------------------------------------------ compile - Compile classpath for source set 'main'. --- ch.qos.logback:logback-classic:1.1.2
We can also have more fine-grained control of transitive dependencies. We can exclude certain transitive dependencies in our dependency definition. This way, we can choose to use only certain transitive dependencies and leave others out. To define which transitive dependencies we want to exclude, we use the exclude
method in the configuration closure of our dependency.
Let's see how we can include the logback-core
transitive dependency but remove the slf4j-api
dependency. We use the exclude
method in the configuration closure. The exclude
method takes Map
as an argument with one or both of the keys: module
and group
. In the following build file, we include the logback-core
transitive dependency:
apply plugin: 'java' repositories.jcenter() dependencies { // Dependency definition for Logback classic compile('ch.qos.logback:logback-classic:1.1.2') { // Exclude slf4j-api transitive dependency. exclude module: 'slf4j-api' // Alternative syntax: // Exclude all modules in the group org.slf4j: // exclude group: 'org.slf4j' // Or specify both group and module name: // exclude group: 'org.slf4j', module: 'slf4j-api' } }
We execute the dependencies
task to see whether our configuration definition has the desired effect:
$ gradle -q dependencies --configuration compile ------------------------------------------------------------ Root project ------------------------------------------------------------ compile - Compile classpath for source set 'main'. --- ch.qos.logback:logback-classic:1.1.2 --- ch.qos.logback:logback-core:1.1.2
Notice that in the output, the transitive dependency, org.slf4j:slf4j-api:1.7.6
, is no longer part of our transitive dependencies.
We can also set exclude rules on a configuration in addition to a single dependency. The exclude rule on a configuration will be used for all dependencies defined within the configuration. In the next example Gradle build file, we will exclude the slf4j-api
module from all dependencies in the compile
configuration:
apply plugin: 'java' repositories.jcenter() configurations { compile { // Exclude slf4j-api transitive dependency. exclude module: 'slf4j-api' // Alternative syntax: // Exclude all modules in the group org.slf4j: // exclude group: 'org.slf4j' // Or specify both group and module name: // exclude group: 'org.slf4j', module: 'slf4j-api' } // To exclude a module and/or group from all configurations // we can use the all method: // all { exclude module: 'slf4j-api' } } dependencies { // Dependency definition for Logback classic. compile('ch.qos.logback:logback-classic:1.1.2') }
Any exclude rule that we add to either the configuration or the dependency is accessible again via the excludeRules
property of the corresponding objects. We can use this to find out the configuration or dependency that is responsible for excluding a certain dependency. In the following example build file, we create a new task, showExcludeRules
, where we loop through all configurations and dependencies and collect exclude rules. At the end of the task, we print all the information to standard output. The following code shows this:
apply plugin: 'java' repositories.jcenter() configurations { compile { exclude module: 'slf4j-api' } } dependencies { compile('ch.qos.logback:logback-classic:1.1.2') { exclude group: 'ch.qos.logback', module: 'logback-core' } } task showExcludeRules { description 'Show exclude rules for configurations and dependencies' doFirst { // Store found exclude rules. def excludes = [] // Go through all configurations to find exclude rules // defined at configuration level and at // dependency level for dependencies in the configuration. configurations.all.each { configuration -> def configurationExcludes = configuration.excludeRules configurationExcludes.findAll().each { rule -> // Add found excludeRule to excludes collection. excludes << [type: 'container', id: configuration.name, excludes: rule] } def dependencies = configuration.allDependencies dependencies.all { dependency -> def excludeRules = dependency.excludeRules excludeRules.findAll().each { rule -> def dep = dependency def id = "${dep.group}:${dep.name}:${dep.version}" // Add found excludeRule to excludes collection. excludes << [type: 'dependency', id: id, excludes: rule] } } } // Printing exclude rule information for output. def printExcludeRule = { def rule = "${it.excludes.group ?: '*'}:${it.excludes.module ?: '*'}" println "$it.id >> $rule" } // Print formatted header for output. def printHeader = { header -> println() println '-' * 60 println header println '-' * 60 } // Group rules by organisation or dependency. def excludeRules = excludes.groupBy { it.type } printHeader 'Configurations' excludeRules.container.toSet().each(printExcludeRule) printHeader 'Dependencies' excludeRules.dependency.toSet().each(printExcludeRule) } }
When we run the task, we get the following output:
$ gradle -q showExcludeRules ------------------------------------------------------------ Configurations ------------------------------------------------------------ compile >> *:slf4j-api ------------------------------------------------------------ Dependencies ------------------------------------------------------------ ch.qos.logback:logback-classic:1.1.2 >> ch.qos.log back:logback-core
Finally, we can use the ext
attribute for an external module dependency if we know we only want to include a single artifact from the dependency. With this attribute, no transitive dependencies are resolved because we specify that we specifically want the artifact specified by the ext
attribute.
In our example, we can use the ext
attribute with the jar
value to resolve only the JAR artifact for the logback-classic
dependency. In the next example build file, we will use the ext
attribute for our logback-classic
dependency:
apply plugin: 'java' repositories.jcenter() dependencies { // Dependency definition for Logback classic library compile 'ch.qos.logback:logback-classic:1.1.2@jar' // Alternative syntax: //compile group: 'ch.qos.logback', // name: 'logback-classic', // version: '1.1.2', // ext: 'jar' }
18.221.117.214