Gradle Dependencies and Dependency Configurations TODEPLETE: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
Line 151: Line 151:


==Declaring File Dependencies==
==Declaring File Dependencies==
[[#File_Dependency|File dependencies]] are declared as follows:


<syntaxhighlight lang='groovy'>
<syntaxhighlight lang='groovy'>

Revision as of 22:07, 19 May 2018

External

Internal

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 the project in an automated fashion.

Gradle Dependency Concepts

Dependency

A dependency is an artifact, usually located in a repository, the project needs. From a very generic perspective, a dependency can be thought of as a pointer to another piece of software. Gradle supports several types of dependencies:

Module Dependency

https://docs.gradle.org/current/userguide/dependency_types.html#sub:module_dependencies

Module dependency is the most common form of dependency. It refers to a module in a repository. They are declared as shown below:

Declaring Module Dependencies

File Dependency

https://docs.gradle.org/current/userguide/dependency_types.html#sub:file_dependencies
https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:declaring_file_dependency

A file dependency allows you to add it directly to a dependency configuration, without first adding it to a repository. They are declared as shown below:

Declaring File Dependencies

Project Dependency

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

They are declared as shown below:

Declaring Project Dependencies

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 and artifacts, 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-project 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 the dependencies that are needed during the build
  • To expose artifacts for consumption: plugins use dependency configurations to define what artifacts it generates. 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 for any of its parent configurations.

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. TODO: https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html. Gradle offers facilities to inspect the transitive dependency graph.

Transitive dependency resolution is enabled by default, but it can be turned off when the dependency is declared, as shown here.

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 removing 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 [#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, 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.

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 Configuration Container

https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html

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.

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

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

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:

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

The Dependency Cache

https://docs.gradle.org/current/userguide/dependency_cache.html

Once a dependency is resolved as result of the dependency resolution, Gradle stores the associated artifacts in a local cache, known as the dependency cache.

The Gradle dependency cache consists of two storage types located under Gradle user home caches subdirectory: file-based storage for downloaded artifacts and raw metadata files, and a binary store of resolved module meta-data.

Example:

$HOME/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.12/8e20852d05222dc286bf1c71d78d0531e177c317/slf4j-api-1.7.12.jar

TO Deplete

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

In Maven, changing versions are referred to as napshots.

TODO when needed https://docs.gradle.org/current/userguide/declaring_dependencies.html#sub:declaring_dependency_with_changing_version

Resolution Rule

A resolution rule influences the behavior of how a dependency is resolved. Resolution rules are defined as part of the build logic. TODO https://docs.gradle.org/current/userguide/customizing_dependency_resolution_behavior.html.