Book Image

Gradle Dependency Management

By : Hubert Klein Ikkink
Book Image

Gradle Dependency Management

By: Hubert Klein Ikkink

Overview of this book

Table of Contents (14 chapters)
Gradle Dependency Management
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Declaring dependencies


We defined configurations or applied a plugin that added new configurations to our project. However, a configuration is empty unless we add dependencies to the configuration. To declare dependencies in our Gradle build file, we must add the dependencies configuration block. The configuration block will contain the definition of our dependencies. In the following example Gradle build file, we define the dependencies block:

// Dependencies configuration block.
dependencies {
    // Here we define our dependencies.
}

Inside the configuration block, we use the name of a dependency configuration followed by the description of our dependencies. The name of the dependency configuration can be defined explicitly in the build file or can be added by a plugin we use. In Gradle, we can define several types of dependencies. In the following table, we will see the different types we can use:

Dependency type

Description

External module dependency

This is a dependency on an external module or library that is probably stored in a repository.

Client module dependency

This is a dependency on an external module where the artifacts are stored in a repository, but the meta information about the module is in the build file. We can override meta information using this type of dependency.

Project dependency

This is a dependency on another Gradle project in the same build.

File dependency

This is a dependency on a collection of files on the local computer.

Gradle API dependency

This is a dependency on the Gradle API of the current Gradle version. We use this dependency when we develop Gradle plugins and tasks.

Local Groovy dependency

This is a dependency on the Groovy libraries used by the current Gradle version. We use this dependency when we develop Gradle plugins and tasks.

External module dependencies

External module dependencies are the most common dependencies in projects. These dependencies refer to a module in an external repository. Later in the book, we will find out more about repositories, but basically, a repository stores modules in a central location. A module contains one or more artifacts and meta information, such as references to the other modules it depends on.

We can use two notations to define an external module dependency in Gradle. We can use a string notation or a map notation. With the map notation, we can use all the properties available for a dependency. The string notation allows us to set a subset of the properties but with a very concise syntax.

In the following example Gradle build file, we define several dependencies using the string notation:

// Define dependencies.
dependencies {
  // Defining two dependencies.
  vehicles 'com.vehicles:car:1.0', 'com.vehicles:truck:2.0'

  // Single dependency.
  traffic 'com.traffic:pedestrian:1.0'
}

The string notation has the following format: moduleGroup:moduleName:version. Before the first colon, the module group name is used, followed by the module name, and the version is mentioned last.

If we use the map notation, we use the names of the attributes explicitly and set the value for each attribute. Let's rewrite our previous example build file and use the map notation:

// Compact definition of configurations.
configurations {
  vehicles
  traffic.extendsFrom vehicles
}

// Define dependencies.
dependencies {
  // Defining two dependencies.
  vehicles(
    [group: 'com.vehicles', name: 'car', version: '1.0'],
    [group: 'com.vehicles', name: 'truck', version: '2.0'],
  )

  // Single dependency.
  traffic group: 'com.traffic', name: 'pedestrian', version: '1.0'
}

We can specify extra configuration attributes with the map notation, or we can add an extra configuration closure. One of the attributes of an external module dependency is the transitive attribute. We learn more about how to work with transitive dependencies in Chapter 3, Resolving Dependencies. In the next example build file, we will set this attribute using the map notation and a configuration closure:

dependencies {
  // Use transitive attribute in map notation.
  vehicles group: 'com.vehicles', name: 'car',
      version: '1.0', transitive: false

  // Combine map notation with configuration closure.
  vehicles(group: 'com.vehicles', name: 'car', version: '1.0') {
    transitive = true
  }

  // Combine string notation with configuration closure.
  traffic('com.traffic:pedestrian:1.0') {
    transitive = false
  }
}

In the rest of this section, you will learn about more attributes you can use to configure a dependency.

Once of the advantages of Gradle is that we can write Groovy code in our build file. This means that we can define methods and variables and use them in other parts of our Gradle file. This way, we can even apply refactoring to our build file and make maintainable build scripts. Note that in our examples, we included multiple dependencies with the com.vehicles group name. The value is defined twice, but we can also create a new variable with the group name and reference of the variable in the dependencies configuration. We define a variable in our build file inside an ext configuration block. We use the ext block in Gradle to add extra properties to an object, such as our project.

The following sample code defines an extra variable to hold the group name:

// Define project property with
// dependency group name 'com.vehicles'
ext {
  groupNameVehicles = 'com.vehicles'
}

dependencies {
  // Using Groovy string support with
  // variable substition.
  vehicles "$groupNameVehicles:car:1.0"

  // Using map notation and reference
  // property groupNameVehicles.
  vehicles group: groupNameVehicles, name: 'truck', version: '2.0'
}

If we define an external module dependency, then Gradle tries to find a module descriptor in a repository. If the module descriptor is available, it is parsed to see which artifacts need to be downloaded. Also, if the module descriptor contains information about the dependencies needed by the module, those dependencies are downloaded as well. Sometimes, a dependency has no descriptor in the repository, and it is only then that Gradle downloads the artifact for that dependency.

A dependency based on a Maven module only contains one artifact, so it is easy for Gradle to know which artifact to download. But for a Gradle or Ivy module, it is not so obvious, because a module can contain multiple artifacts. The module will have multiple configurations, each with different artifacts. Gradle will use the configuration with the name default for such modules. So, any artifacts and dependencies associated with the default configuration are downloaded. However, it is possible that the default configuration doesn't contain the artifacts we need. We, therefore, can specify the configuration attribute for the dependency configuration to specify a specific configuration that we need.

The following example defines a configuration attribute for the dependency configuration:

dependencies {
  // Use the 'jar' configuration defined in the
  // module descriptor for this dependency.
  traffic group: 'com.traffic', 
      name: 'pedestrian', 
      version: '1.0',
      configuration: 'jar'

}

When there is no module descriptor for a dependency, only the artifact is downloaded by Gradle. We can use an artifact-only notation if we only want to download the artifact for a module with a descriptor and not any dependencies. Or, if we want to download another archive file, such as a TAR file, with documentation, from a repository.

To use the artifact-only notation, we must add the file extension to the dependency definition. If we use the string notation, we must add the extension prefixed with an @ sign after the version. With the map notation, we can use the ext attribute to set the extension. If we define our dependency as artifact-only, Gradle will not check whether there is a module descriptor available for the dependency. In the next build file, we will see examples of the different artifact-only notations:

dependencies {
  // Using the @ext notation to specify
  // we only want the artifact for this
  // dependency.
  vehicles 'com.vehicles:car:2.0@jar'

  // Use map notation with ext attribute
  // to specify artifact only dependency.
  traffic group: 'com.traffic', name: 'pedestrian',
      version: '1.0', ext: 'jar'

  // Alternatively we can use the configuration closure.
  // We need to specify an artifact configuration closure
  // as well to define the ext attribute.
  vehicles('com.vehicles:car:2.0') {
    artifact {
      name = 'car-docs'
      type = 'tar'
      extension = 'tar'
    }
  }
}

A Maven module descriptor can use classifiers for the artifact. This is mostly used when a library with the same code is compiled for different Java versions, for example, a library is compiled for Java 5 and Java 6 with the jdk15 and jdk16 classifiers. We can use the classifier attribute when we define an external module dependency to specify which classifier we want to use. Also, we can use it in a string or map notation. With the string notation, we add an extra colon after the version attribute and specify the classifier. For the map notation, we can add the classifier attribute and specify the value we want. The following build file contains an example of the different definitions of a dependency with a classifier:

dependencies {
  // Using string notation we can
  // append the classifier after
  // the version attribute, prefixed
  // with a colon.
  vehicles 'com.vehicles:car:2.0:jdk15'

  // With the map notation we simply use the
  // classifier attribute name and the value.
  traffic group: 'com.traffic', name: 'pedestrian',
      version: '1.0', classifier: 'jdk16'

  // Alternatively we can use the configuration closure.
  // We need to specify an artifact configuration closure
  // as well to define the classifier attribute.
  vehicles('com.vehicles:truck:2.0') {
    artifact {
      name = 'truck'
      type = 'jar'
      classifier = 'jdk15'
    }
  }
}

In the following section, we will see how we can define client module dependencies in our build file.

Defining client module dependencies

When we define external module dependencies, we expect that there is a module descriptor file with information about the artifacts and dependencies for those artifacts. Gradle will parse this file and determine what needs to be downloaded. Remember that if such a file is not available on the artifact, it will be downloaded. However, what if we want to override the module descriptor or provide one if it is not available? In the module descriptor that we provide, we can define the dependencies of the module ourselves.

We can do this in Gradle with client module dependencies. Instead of relying on a module descriptor in a repository, we define our own module descriptor locally in the build file. We now have full control over what we think the module should look like and which dependencies the module itself has. We use the module method to define a client module dependency for a dependency configuration.

In the following example build file, we will write a client module dependency for the dependency car, and we will add a transitive dependency to the driver:

dependencies {
  // We use the module method to instruct
  // Gradle to not look for the module descriptor
  // in a repository, but use the one we have
  // defined in the build file.
  vehicles module('com.vehicles:car:2.0') {
    // Car depends on driver.
    dependency('com.traffic:driver:1.0')
  }
}

Using project dependencies

Projects can be part of a bigger, multi-project build, and the projects can be dependent on each other, for example, one project can be made dependent on the generated artifact of another project, including the transitive dependencies of the other project. To define such a dependency, we use the project method in our dependencies configuration block. We specify the name of the project as an argument. We can also define the name of a dependency configuration of the other project we depend on. By default, Gradle will look for the default dependency configuration, but with the configuration attribute, we can specify a specific dependency configuration to be used.

The next example build file will define project dependencies on the car and truck projects:

dependencies {
  // Use project method to define project
  // dependency on car project.
  vehicles project(':car')

  // Define project dependency on truck
  // and use dependency configuration api
  // from that project.
  vehicles project(':truck') {
    configuration = 'api'
  }

  // We can use alternative syntax
  // to specify a configuration.
  traffic project(path: ':pedestrian',
          configuration: 'lib')
}

Defining file dependencies

We can directly add files to a dependency configuration in Gradle. The files don't need to be stored in a repository but must be accessible from the project directory. Although most projects will have module descriptors stored in a repository, it is possible that a legacy project might have a dependency on files available on a shared network drive in the company. Otherwise, we must use a library in our project, which is simply not available in any repository. To add file dependencies to our dependency configuration, we specify a file collection with the files and fileTree methods. The following example build file shows the usage of all these methods:

dependencies {
  // Define a dependency on explicit file(s).
  vehicles files(
    'lib/vehicles/car-2.0.jar',
    'lib/vehicles/truck-1.0.jar'
  )

  // We can use the fileTree method to include
  // multiples from a directory and it's subdirectories.
  traffic fileTree(dir: 'deps', include: '*.jar')
}

The added files will not be part of the transitive dependencies of our project if we publish our project's artifacts to a repository, but they are if our project is part of a multi-project build.

Using internal Gradle and Groovy dependencies

When we write code to extend Gradle, such as custom tasks or plugins, we can have a dependency on the Gradle API and possibly the Groovy libraries used by the current Gradle version. We can use the gradleApi and localGroovy methods in our dependency configuration to have all the right dependencies.

If we are writing some Groovy code to extend Gradle, but we don't use any of the Gradle API classes, we can use localGroovy. With this method, the classes and libraries of the Groovy version shipped with the current Gradle version are added as dependencies. The following example build script uses the Groovy plugin and adds a dependency to the compile configuration on Groovy bundled with Gradle:

apply plugin: 'groovy'

dependencies {
  // Define dependency on Groovy
  // version shipped with Gradle.
  compile localGroovy()
}

When we write custom tasks or plugins for Gradle, we are dependent on the Gradle API. We need to import some of the API's classes in order to write our code. To define a dependency on the Gradle classes, we use the gradleApi method. This will include the dependencies for the Gradle version the build is executed for. The next example build file will show the use of this method:

apply plugin: 'groovy'

dependencies {
  // Define dependency on Gradle classes.
  compile gradleApi()
}