Before looking at dependency declarations themselves, the concept of dependency configuration need to be defined.
What are dependency configurations
Every dependency declared for a Gradle project applies to a specific scope. For example some dependencies should be used for compiling source code whereas others only need to be available at runtime. Gradle represents the scope of a dependency with the help of a Configuration. Every configuration can be identified by a unique name.
Many Gradle plugins add pre-defined configurations to your project. The Java plugin, for example, adds configurations to represent the various classpaths it needs for source code compilation, executing tests and the like. See the Java plugin chapter for an example.
Figure 1. Configurations use declared dependencies for specific purpose.
For more examples on the usage of confiugrations to navigate, inspect and post-process metadata and artifacts of assigned dependencies, have a look the resolution result APIs.
Configuration inheritance and composition
A configuration can extend other configurations to form an inheritance hierarchy. Child configuration inherit the whole set of dependenciess declared for any of its superconfigurations.
Configuration inheritance is heavily used by Gradle core plugins like the Java plugin. For example the testImplementation configuration extends that implementation configuration. The configuration hierarchy has a pratical purpose: compiling tests requires the dependencies of the source code under test on top of the dependencies needed write the test class. A Java project that uses JUnit to write and execute test code also needs Guava if its classes are imported in the production source code.
Figure 2. Configuration inheritance provided by the Java plugin
Under the covers the testImplementation and implementation configurations form an inheritance hierarchy by calling the method Configuration.extendsFrom(org.gradle.api.artifacts.Configuration[]). A configuration can extend any other configuration irrespective of its definition in the build script or a plugin.
Let's say you wanted to wirte a suite of smoke tests. Each smoke test makes a HTTP call to verify a web service endpoint. As the underlying test framework the project already uses JUnit. You can define a new configuration named smokeTest that extends from the testImplementation configuration to reuse the existing test framework dependency.
Resolvable and consumable configurations
Configurations and a fundamental part of dependency resolution in Gradle. In the context of depedency resolution, it is useful to distinguish between a consumer and a producer. Along these lines, configurations have at least 3 different roles:
1.
to declare dependencies
2.
as a consumer, to resolve a set of dependencies to files
3.
as a producer, to expose artifacts and their dependencies for consumption by other projects (such consumable configurations usually represent the variants the producer offers to its consumers)
For example, to express that an application app depends on library lib, at least one configuration is required:
Configurations can inherit dependencies from other configurations by extending from them. Now, notice that the code above doesn't tell us anything about the intended consumer of this configuration. In particular, it doens't tell us how the configuration is meant to be used. Let's say that lib is a Java library: it might expose different things, such as its API, implementation, or test fixtures. It might be necessary to change how we resolve the dependencies of app depending upon the task we're performing(compiling against the API of lib, executing the application, compiling tests, etc.). To address this problem, you'll often find companion configurations, which are meant to unambiguously declare the usage:
At this point, we have 3 different configurations with different roles:
•
someConfiguration declares the dependencies of my application. It's just a bucket that can hold a list of dependencies.
•
compileClasspath and runtimeClasspath are configurations meant to be resolved: when resolved they should contain the compile classpath, and the runtime classpath of the application repectively.