LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

Chapter 1. Starting with Gradle

Declarative builds and convention over configuration

Support for Ant Tasks and Maven repositories

Gradle supports Ant Tasks and projects. We can import an Ant build and reuse all the tasks. However, we can also write Gradle tasks dependent on Ant Tasks. The integration also applies for properties, paths, and so on.

Maven and Ivy repositories are supported to publish or fetch dependencies. So, we can continue to use any repository infrastructure that we already have.

Incremental builds

With Gradle, we have incremental builds. This means the tasks in a build are only executed if necessary.

Gradle Wrapper

The Gradle Wrapper allows us to execute Gradle builds even if Gradle is not installed on a computer.

We can use the wrapper to enforce a certain Gradle version to be used so that the whole team is using the same version.

Writing our first build script

Gradle uses the concept of projects to define a related set of tasks. A Gradle build can have one or more projects.

A project has one or more tasks. Tasks are a unit of work that need to be executed by the build.

We use the gradle command to run a build. Gradle will look for a file named build.gradle in the current directory. This file is the build script for our project. We define our tasks that need to be executed in this build script file.

Default Gradle tasks

We can ask Gradle to show us the available tasks for our project.

gradle -q tasks

The properties task is very useful to see the properties available for our project.

gradle -q properties

The dependencies task will show dependencies (if any) for our project.

gradle -q dependencies

The projects tasks will display subprojects (if any) for a root project.

gradle -q projects

Task name abbreviation

We can also abbreviate each word in a CamelCase task name. For example, our helloWorld task name can be abbreviated to hW.

Executing multiple tasks

Gradle executes the tasks in the same order as they are defined in the command line. Gradle will only execute a task once during the build. So even if we define the same task multiple times, it will only be executed once. This rule also applies when tasks have dependencies on other tasks. Gradle will optimize the task execution for us and we don’t have to worry about that.

Command-line options

Changing the build file and directory

We created our build file with the build.gradle name. This is the default name for a build file. Gradle will look for a file with this name in the current directory to execute the build. However, we can change this with the --build-file (or -b) and --project-dir (or -p) command-line options.

Gradle daemon

We can reduce the build execution time if we don’t have to load JVM and Gradle classes and libraries each time we execute a build. The --daemon command-line option starts a new Java process that will have all Gradle classes and libraries already loaded and then execute the build.

Chapter 2.  Creating Gradle Build Scripts

Writing a build script

project.description = 'Simple project'
task simple << {
    println 'Running simple task for project ' + project.description
}

Gradle creates a Project object for us. The Project object has several properties and methods and it is available in our build scripts. We can use the project variable name to reference the Project object, but we can also leave out this variable name to reference properties and methods of the Project object. Gradle will automatically try to map properties and methods in the build script to the Project object.

The following build script uses a different syntax, which is a bit more like Java, to get the same result:

project.setDescription('Simple project')
project.getTasks().create('simple') {
    println 'Running simple task for project ' + project.description
} 

Defining tasks

A task is made up of actions.

We can use the doFirst and doLast methods to add actions to our task, and we can use the left-shift operator (<<) as a synonym for the doLast method.

task first {
    doFirst {
        println 'Running first'
    }
}
task second {
    doLast { Task task ->
        println "Running ${task.name}"
    }
}
task third << { taskObject ->
    println 'Running ' + taskObject.name
}

A closure is defined by enclosing the piece of code with curly brackets ({... }). We can pass one or more parameters to the closures. If the closure has only one argument, an implicit parameter, it, can be used to reference the parameter value.

task second {
    doLast {
        println "Running ${it.name}"
    }
}

Defining actions with the Action interface

task first {
    doFirst(
        new Action() {
          void execute(O task) {
            println "Running ${task.name}"
          }
       }
    )
}

Defining dependencies between tasks

task first << { task ->
    println "Run ${task.name}"
}
task second << { task ->
    println "Run ${task.name}"
}
second.dependsOn 'first'

Another way of defining the dependency between tasks is to set the dependsOn property instead of using the dependsOn method.

task third(dependsOn: 'second') << { task ->
    println "Run ${task.name}"
}

Setting default tasks

To set the default task or tasks, we use the defaultTasks method.

defaultTasks 'first', 'second'

Organizing tasks

Adding a description to tasks

task first(description: 'Base task') << {
    println "I am first"
}

Grouping tasks together

def taskGroup = 'base' 
task first(description: 'Base task', group: taskGroup) << {
    println "I am first"
}

Adding tasks in other ways

Using task rules

These rules are very flexible and allow us to add tasks to our project based on several parameters and project properties.

task first(description: 'First task')
task second(description: 'Second task')
tasks.addRule("Pattern: desc<TaskName>: " + "show description of a task.") { taskName ->
    if (taskName.startsWith('desc')) {
        def targetTaskName = taskName - 'desc'
        def targetTaskNameUncapitalize = targetTaskName[0].toLowerCase() + targetTaskName[1..-1]
        def targetTask = project.tasks.findByName(targetTaskNameUncapitalize)
        if (targetTask) {
            task(taskName) << {
                println "Description of task ${targetTask.name} " + " -> ${targetTask.description}"
            }
        } 
    } 
} 

Accessing tasks as project properties

Each task that we add is also available as a project property, and we can reference this property like we can reference any other property in our build script.

task simple << { task ->
    println "Running ${task.name}"
}
simple.description = 'Print task name'
simple.doLast {
    println "Done"
}
project.simple.doFirst {
    println "Start"
}

Adding additional properties to tasks

task simple << {
    println "Hello ${message}"
}
simple.ext.message = 'world'

Skipping tasks

Using onlyIf predicates

Every task has an onlyIf method that accepts a closure as an argument. The result of the closure must be true or false. If the task must be skipped, the result of the closure must be false, otherwise the task is executed. The task object is passed as a parameter to the closure. Gradle evaluates the closure just before the task is executed.

import static java.util.Calendar.*
task longrunning {
    onlyIf { task ->
        def now = Calendar.instance
        def weekDay = now[DAY_OF_WEEK]
        def weekDayInWeekend = weekDay in [SATURDAY, SUNDAY]
        return weekDayInWeekend
    }
    doLast {
        println "Do long running stuff"
    }
}

Skipping tasks by throwing StopExecutionException

Another way to the skip execution of a task is to throw a StopExecutionException exception. If such an exception is thrown, the build will stop the current task and continue with the next task.

Enabling and disabling tasks

Every task has an enabled property. By default, the value of the property is true, which means that the task is enabled and executed.

task listDirectory {
    def dir = new File('assemble')
    enabled = dir.exists() 
    doLast {
        println "List directory contents: " + dir.listFiles().join(',')
    }
}

Skipping from the command line

We can use the --exclude-tasks (-x) command-line option if we run the build.

Chapter 3.  Working with Gradle Build Scripts

Project properties

The default properties that we can access in a Gradle build:

Name Type Default value
project Project The project instance.
name String The name of the project directory. The name is read-only.
path String The absolute path of the project.
description String The description of the project.
projectDir File The directory containing the build script. The value is read-only.
buildDir File The directory with the build name in the directory, containing the build script.
rootDir File The directory of the project at the root of a project structure.
group Object Not specified.
version Object Not specified.
ant AntBuilder An AntBuilder instance.

Defining custom properties in script

To add our own properties, we have to define them in an  ext{} script block in a build file. Prefixing the property name with ext. is another way to set the value. To read the value of the property, we don’t have to use the ext. prefix, we can simply refer to the name of the property. The property is automatically added to the internal project property as well.

ext.customProperty = 'custom'
ext {
    anotherCustomProperty = 'custom'
}
task showProperties {
    ext {
        customProperty = 'override'
    }
    doLast {
        println customProperty
        println project.ext.customProperty
        println project.customProperty
    }
}

Defining properties using an external file

We can also set the properties for our project in an external file. The file needs to be named gradle.properties, and it should be a plain text file with the name of the property and its value on separate lines. We can place the file in the project directory or Gradle user home directory. The default Gradle user home directory is $USER_HOME/.gradle.

Passing properties via the command line

Instead of defining the property directly in the build script or external file, we can use the -P command-line option to add an extra property to a build. We can also use the -P command-line option to set a value for an existing property. If we define a property using the -P command-line option, we can override a property with the same name defined in the external gradle.properties file.

Defining properties via system properties

We can also use Java system properties to define properties for our Gradle build. We use the -D command-line option just like in a normal Java application. The name of the system property must start with org.gradle.project, followed by the name of the property we want to set, and then by the value.

gradle -Dorg.gradle.project.version=2.0 -Dorg.gradle.project.customProperty=custom showProperties

Adding properties via environment variables

The environment variable name starts with ORG_GRADLE_PROJECT_ and is followed by the property name.

ORG_GRADLE_PROJECT_version=3.1 ORG_GRADLE_PROJECT_customProperty="Set by environment variable" gradle showProp

Using logging

The following table shows the log levels that are supported by Gradle:

Level Used for
DEBUG Debug messages
INFO Information messages
LIFECYCLE Progress information messages
WARNING Warning messages
QUIET Import information messages
ERROR Error messages

Every Gradle build file and task has a logger object. The logger object is an instance of a Gradle-specific extension of the Simple Logging Facade for Java (SLF4JLogger interface.

To use the logger object in our Gradle build files, we only have to reference logger and invoke the method for the logging level we want to use, or we can use the common log()method and pass the log level as a parameter to this method.

We know that every Gradle project and task has a logger we can use. However, we can also explicitly create a logger instance with the Logging class.

class Simple {
    private static final Logger logger = Logging.getLogger('Simple')
    int square(int value) {
        int square = value * value
        logger.lifecycle "Calculate square for ${value} = ${square}"
        return square
    }
} 
logger.lifecycle 'Running sample Gradle build.'
task useSimple {
    doFirst {
        logger.lifecycle 'Running useSimple'
    }
    doLast {
        new Simple().square(3)
    }
}

Controlling output

Every project and task has an instance of the org.gradle.api.logging.LoggingManager class with the name logging. The LoggingManager class has the methods captureStandardOutput() and captureStandardError() that we can use to set the log level for output and error messages. Remember that Gradle will, by default, use the QUIET log level for output messages and ERROR log level for error messages.

task redirectLogging {
    doFirst {
          println 'Start task redirectLogging'
    }
    doLast {
        logging.captureStandardOutput LogLevel.INFO
        println 'Finished task redirectLogging' 
    }
}

Using the Gradle Wrapper

Creating wrapper scripts

gradle wrapper

After the execution of the task, we have two script files (gradlew.bat and gradlew) in the root of our project directory. These scripts contain all the logic needed to run Gradle. If Gradle is not downloaded yet, the Gradle distribution will be downloaded and installed locally.

In the gradle/wrapper directory, relative to our project directory, we find the gradle-wrapper.jar and gradle-wrapper.properties files. The gradle-wrapper.jar file contains a couple of class files necessary to download and invoke Gradle. The gradle-wrapper.properties file contains settings, such as the URL, to download Gradle. The gradle-wrapper.properties file also contains the Gradle version number. If a new Gradle version is released, we only have to change the version in the gradle-wrapper.properties file and the Gradle Wrapper will download the new version so that we can use it to build our project.

All the generated files are now part of our project. If we use a version control system, then we must add these files to the version control. Other people that check out our project can use the gradlew scripts to execute tasks from the project. The specified Gradle version is downloaded and used to run the build file.

If we want to use another Gradle version, we can invoke the wrapper task with the --gradle-version option.
To specify a different download location for the Gradle installation file, we must use the --gradle-distribution-url option of the wrapper task.

Customizing the Gradle Wrapper

If we want to customize properties of the built-in wrapper task, we must add a new task to our Gradle build file with the org.gradle.api.tasks.wrapper.Wrapper type. We will not change the default wrapper task, but create a new task with new settings that we want to apply. We need to use our new task to generate the Gradle Wrapper shell scripts and support files.

task createWrapper(type: Wrapper) {
    gradleVersion = '2.12'
    scriptFile = 'startGradle'
    jarFile = "${projectDir}/gradle-bin/gradle-bootstrap.jar"
}

If we run the createWrapper task, we get a Windows batch file and shell script and the Wrapper bootstrap JAR file with the properties file is stored in the gradle-bin directory.

$> tree .
.
├── gradle-bin
│ ├── gradle-bootstrap.jar
│ └── gradle-bootstrap.properties
├── startGradle
├── startGradle.bat
└── build.gradle

Chapter 4. Using Gradle for Java Projects

Why plugins?

In Gradle, we can apply plugins to our project. A plugin basically adds extra functionalities such as tasks and properties to our project. By using a plugin, functionality is decoupled from the core Gradle build logic. We can write our own plugins, but Gradle also ships with plugins that are ready out of the box.

Getting started with the Java plugin

apply plugin: 'java'

Using the Java plugin

To build the source code, our Java source files must be in the src/main/java directory, relative to the project directory. If we have non-Java source files that need to be included in the JAR file, we must place them in the src/main/resources directory. Our test source files need to be in the src/test/java directory and any non-Java source files required for testing can be placed in src/test/resources. These conventions can be changed if we want or need it, but it is a good idea to stick with them so that we don’t have to write any extra code in our build file, which could lead to errors.

To compile the Java source file and process the properties file, we run the classes task. Note that the classes task has been added by the Java plugin. This is the so-called life cycle task in Gradle. The classes task is actually dependent on two other tasks – compileJava and processResources.

If we execute the classes task again, we will notice that the tasks support the incremental build feature of Gradle.
To package our class file and properties file, we invoke the jar task. This task is also added by the Java plugin and depends on the classes task.

The default name of the resulting JAR file is the name of our project. So if our project is called sample, then the JAR file is called sample.jar. We can find the file in the build/libs directory.

We can also execute the assemble task to create the JAR file. The assemble task, another life cycle task, is dependent on the jar task and can be extended by other plugins.

Working with source sets

The Java plugin also adds a new concept to our project-source sets. A source set is a collection of source files that are compiled and executed together.

Without any configuration, we already have the main and test source sets, which are added by the Java plugin. For each source set, the plugin also adds the following three tasks: compile<SourceSet>Javaprocess<SourceSet>Resources, and <SourceSet>Classes. When the source set is named main, we don’t have to provide the source set name when we execute a task. For example, compileJava applies to the main source test, but compileTestJava applies to the test source set.

Each source set also has some properties to access the directories and files that make up the source set.

Source set property Type Description
java SourceDirectorySet These are the Java source files for this project. Only files with the .java extension are in this collection.
allJava SourceDirectorySet By default, this is the same as the Java property, so it contains all the Java source files. Other plugins can add extra source files to this collection.
resources SourceDirectorySet These are all the resource files for this source set. This contains all the files in the resources source directory, excluding any files with the .java extension.
allSource SourceDirectorySet By default, this is the combination of the resources and Java properties. This includes all the source files of this source set, both resource and Java source files.
output SourceSetOutput These are the output files for the source files in the source set. This contains the compiled classes and processed resources.
java.srcDirs Set<File> These are the directories with Java source files.
resources.srcDirs Set<File> These are the directories with the resource files for this source set.
output.classesDir File This is the output directory with the compiled class files for the Java source files in this source set.
output.resourcesDir File This is the output directory with the processed resource files from the resources in this source set.
name String This is the read-only value with the name of the source set.

Creating a new source set

To define this source set, we only have to put the name in the sourceSets property of the project, as follows:

apply plugin: 'java'
sourceSets {
    api
}

Gradle will create three new tasks based on this source set – apiClasses, compileApiJava, and processApiResources.

The classes task is dependent on the apiClasses tasks. First, the interface must be compiled and then the class that implements the interface must be compiled .

Next, we must add the output directory with the compiled interface class file to the compileClasspath property of the main source set.

apply plugin: 'java'
sourceSets {
    api
    main {
        compileClasspath += files(api.output.classesDir)
    }
}
classes.dependsOn apiClasses

Custom configuration

we have a project with the following source directory structure:

.
├── resources
│   ├── java
│   └── test
├── src
│   └── java
├── test
│   ├── integration
│   │   └── java
│   └── unit
│       └── java
└── tree.txt

We will need to reconfigure the main and test source sets, but we must also add a new integration-test source set:

apply plugin: 'java'
sourceSets {
    main {
        java {
            srcDir 'src/java'
        }
        resources {
            srcDir 'resources/java'
        }
    }
    test {
        java {
            srcDir 'test/unit/java'
        }
        resources {
            srcDir 'resources/test'
        }
    }
    'integeration-test' {
        java {
            srcDir 'test/integration/java'
        }
        resources {
            srcDir 'resources/test'
        }
    }
}

Working with properties

Custom properties of a plugin are set in a Convention object of the org.gradle.api.plugins.Convention type. A Convention object is used by a plugin to expose properties and methods that we can use in our project. The Convention object of the plugin is added to the convention property of a project. The convention property of a Gradle project is a container for all the Convention objects from the plugins.

We can access the properties from the plugin’s Convention object directly as project properties or we can specify the complete path to the Convention object of the plugin in order to get to a property or invoke a method.

task showConvention << {
    println sourceSets.main.name
    println project.sourceSets.main.name
    println project.convention.plugins.java.sourceSets.main.name
} 

Creating Javadoc documentation

To generate a Javadoc documentation, we must use the javadoc task of the org.gradle.api.tasks.javadoc.Javadoc type. The task generates a documentation for the Java source files in the main source set. If we want to generate documentation for the source sets in our project, we must configure the javadoc task or add an extra javadoc task to our project.

Chapter 5.  Dependency Management

Dependency configuration

Java has no real support for working with versioned libraries as dependencies. There are some open source solutions that deal with dependencies and allow us to express whether our Java code depends on lib-1.0.jar or lib-2.0.jar. The most popular are Maven and Apache Ivy. Maven is a complete build tool and has a mechanism for dependency management. Ivy is only about dependency management.

Both tools support repositories where versioned libraries are stored together with metadata about these libraries. A library can have dependencies on other libraries and is described in the metadata of the library. The metadata is described in the descriptor XML files. Ivy fully supports Maven descriptor files and repositories; it also adds some extra functionality. Therefore with Ivy, you get what you would with Maven, and then some more. This is why Gradle uses the Ivy API under the hood to perform dependency management. Gradle also adds some extra sugar on top of Ivy, so we can define and use dependencies in a very flexible way.

In a Gradle build file, we group dependencies together in a configuration. A configuration has a name and configurations can extend each other. With a configuration, we can make logical groups of dependencies.

Every Gradle build has a ConfigurationContainer object. This object is accessible via the Project property with the name containers. We can use a closure to configure the container with Configuration objects. Each Configuration object has at least a name, but we can change more properties. We can set a resolution strategy, if a configuration has version conflicts with dependencies, or we can change the visibility of a configuration so that it will not be visible outside of our project. A Configuration object also has a hierarchy. So we can extend from the existing Configuration objects to inherit the settings.

configurations {
    commonsLib {
        description = 'Common libraries'
    }
    mainLib {
        extendsFrom commonsLib
        description = 'Main libraries'
    }
}
println configurations['mainLib'].name
println configurations.commonsLib.name
Configuration Extends Used by task Description
compile - compileJava These are the dependencies needed at compile time to compile the source files
runtime compile - These are the dependencies for runtime of the application, but are not needed for compilation
testCompile compile compileTestJava These are the dependencies to compile test source files
testRuntime testCompile test These are all the dependencies needed to run the tests
archives - uploadArchives This contains artifacts, such as JAR files created by the project
default runtime - This is the default configuration that contains all runtime dependencies

Repositories

We can declare several repository types in the Gradle build file. Gradle provides some preconfigured repositories, but it is also very easy to use a custom Maven or Ivy repository. We can also declare a simple filesystem repository to be used for resolving and finding dependencies.

Repository type Description
Maven repository This is the Maven layout repository on a remote computer or filesystem.
Bintray JCenter repository This is the preconfigured Maven layout repository to search for dependencies in the Bintray JCenter repository. This is a superset of the Maven central repository.
Maven central repository This is the preconfigured Maven layout repository to search for dependencies in the Maven central repository.
Maven local repository This is the preconfigured Maven repository that finds dependencies in the local Maven repository.
Ivy repository This is the Ivy repository that can be located on a local or remote computer.
Flat directory repository This is a simple repository on the local filesystem of the computer or a network share.

Adding Maven repositories

repositories {
    jcenter()
    mavenCentral()
}

We can also add a custom repository that follows the Maven layout.

repositories {
    maven {
        name = 'Main Maven repository'
        url = 'http://intranet/repo'
    }
    mavenRepo(name: 'Snapshot repository', url: 'http://intranet/snapshots')
}

Both methods configure a repository via a combination of a closure and method arguments. Sometimes we must access a Maven repository that stores the metadata in the descriptor XML files, but the actual JAR files are in a different location. To support this scenario, we must set the artifactUrls property and assign the addresses of the servers that store the JAR files:

repositories {
    maven {
        url: 'http://intranet/mvn'
        artifactUrls 'http://intranet/jars'
        artifactUrls 'http://intranet/snapshot-jars'
    }
}

Adding Ivy repositories

repositories {
    ivy(url: 'http://intranet/ivy-repo', name: 'Our repository')
    ivy {
        url = 'http://intranet/ivy-snapshots'
    }
}

Adding a local directory repository

Gradle will resolve files in the configured directory using the first match it finds with the following patterns:

  • [artifact]-[version].[ext]
  • [artifact]-[version]-[classifier].[ext]
  • [artifact].[ext]
  • [artifact]-[classifier].[ext]
repositories {
    flatDir(dir: '../lib', name: 'libs directory')
    flatDir {
        dirs '../project-files', '/volumes/shared-libs'
        name = 'All dependency directories'
    }
}

Defining dependencies

We define dependencies in our build project with the dependencies{} script block. We define a closure to pass to the dependencies{} script block with the configuration of the dependency.

We can define different types of dependencies.

Dependency type Method Description
External module dependency - This is a dependency on an external module or library in a repository.
Project dependency project() This is a dependency on another Gradle project.
File dependency files()fileTree() This is a dependency on a collection of files on the local computer.
Client module dependency module() 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.
Gradle API dependency gradleApi() 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 localGroovy() 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.

Using external module dependencies

apply plugin: 'java'
repositories {
    jcenter()
}
ext {
    springVersion = '4.2.3.RELEASE'
    springGroup = 'org.springframework'
}
dependencies { 
    compile(group: springGroup, name: 'spring-core', version: springVersion)
    runtime("$springGroup:spring-aop:$springVersion") 
} 

To download only the artifact of an external dependency and not the transitive dependencies, we can set the transitive property for the dependency to false.

apply plugin: 'java'
repositories {
    jcenter()
}
dependencies {
    compile('org.slf4j:slf4j-simple:1.7.13') {
        transitive = false
    }
    compile(group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.13', transitive: false)
}

We can also exclude some transitive dependencies with the exclude() method. Gradle will look at the descriptor file of the module and exclude any dependencies that we have added with the exclude() method.

apply plugin: 'java'
repositories {
    jcenter()
}
dependencies {
    compile('org.slf4j:slf4j-simple:1.7.13') {
        exclude 'org.slf4j:slf4j-api'
    }
}

To get an artifact of only the external module dependency, we can use the artifact-only notation. We must also use this notation when a repository doesn’t have a module descriptor file and we want to get the artifact. We must add an @ symbol before the extension of the artifact. Gradle will not look at the module descriptor file, if available, when we use this notation:

apply plugin: 'java'
repositories {
    jcenter()
}
dependencies {
    runtime('org.slf4j:slf4j-simple:1.7.13@jar')
    runtime(group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.13', ext: 'jar')
}

We can even set the transitive behavior on a complete configuration. Each configuration has a transitive property.

apply plugin: 'java'
repositories {
    jcenter()
}
dependencies {
    compile('org.slf4j:slf4j-simple:1.7.13')
}
configurations.compile.transitive = false

In a Maven repository, we can use classifiers for a dependency.

apply plugin: 'java'
repositories {
    jcenter()
}
dependencies { 
    compile('sample:simple:1.0:jdk16@jar')
    compile(group: 'sample', name: 'simple', version: '1.0', classifier: 'jdk16')
}

The module descriptor of a module in a Maven repository can only have one artifact; but in an Ivy repository, we can define multiple artifacts for a single module. Each set of artifacts is grouped together in a configuration. The default configuration contains all the artifacts belonging to the module. If we don’t specify the configuration property when we define the dependency for an Ivy module, the default configuration is used. We must specify the configuration property if we want to use artifacts belonging to this specific configuration, as follows:

apply plugin: 'java'
repositories {
    ivy {
        url = 'http://intranet/custom'
        ivyPatterns '[module]/[revision]/ivy.xml'
        artifactPatterns '[module]/[revision]/[artifact](/Book/Content/Computer%20Science/Programming%20Language/Java/Gradle%20Effective%20Implementations%20Guide/../.[ext])'
    }
}
dependencies {
    testCompile group: 'sample', name: 'logging', version: '1.0', configuration: 'test'
    testCompile('sample:logging:1.0') {
        configuration = 'test'
    }
}

Using project dependencies

Gradle projects can be dependent on each other. Gradle will look for a default dependency configuration in this project and use this dependency configuration. We can use the configuration property to use different dependency configurations as a dependency for each project:

apply plugin: 'java'
dependencies {
    compile(project(':projectA'))
    compile(project(':projectB')) {
        configuration = 'compile'
    }
}

Using file dependencies

apply plugin: 'java'
dependencies {
    compile files('spring-core.jar', 'spring-aop.jar')
    compile fileTree(dir: 'deps', include: '*.jar')
}

Using client module dependencies

Normally, Gradle will use a descriptor XML file for dependencies found in the repository to see which artifacts and optional transitive dependencies need to be downloaded. However, these descriptor files can be misconfigured, and so, we may want to override the descriptors ourselves in order to ensure that the dependencies are correct.

apply plugin: 'java'
ext {
    springGroup = 'org.springframework'
    springRelease = '4.2.3.RELEASE'
}
dependencies {
    compile module("$springGroup:spring-context:$springRelease") {
        dependency("$springGroup:spring-aop:$springRelease") {
            transitive = false
        }
    }
}

Using Gradle and Groovy dependencies

When we develop Grails plugins and tasks, we can define a dependency on the Gradle API and Groovy libraries used by the current Gradle version.

apply plugin: 'groovy'
dependencies {
    compile gradleApi()
    groovy localGroovy()
}

Setting dynamic versions

To set a minimum version number, we can use a special dynamic version syntax.

apply plugin: 'java'
repositories {
    jcenter()
}
dependencies {
    compile(group: 'org.springframework', name: 'spring-core', version: '4.2.+')
}

We can also set a version range with a minimum and maximum version number.

Range Description
[1.0, 2.0] All versions greater than or equal to 1.0 and lower than or equal to 2.0
[1.0, 2.0[ All versions greater than or equal to 1.0 and lower than 2.0
]1.0, 2.0] All versions greater than 1.0 and lower than or equal to 2.0
]1.0, 2.0[ All versions greater than 1.0 and lower than 2.0
[1.0, ) All versions greater than or equal to 1.0
]1.0, ) All versions greater than 1.0
(, 2.0] All versions lower than or equal to 2.0
(, 2.0[ All versions lower than 2.0

Resolving version conflicts

If one module has a dependency on sample:logging:1.0 and another on sample:logging:2.0, Gradle will use the newest version number by default.

To change the default behavior, we set the resolutionStrategy property of a dependency configuration.

apply plugin: 'java'
configurations.all {
    resolutionStrategy {
        failOnVersionConflict()
    }
}

To force a certain version number to be used for all dependencies (even transitive dependencies), we can use the force() method of resolutionStrategy.

apply plugin: 'java'
configurations.all {
    resolutionStrategy {
        force('org.springframework:spring-core:4.2.3.RELEASE')
    }
}

Adding optional ANT tasks

We can reuse existing Another Neat Tool (ANT) tasks in Gradle build files. Gradle uses Groovy’s AntBuilder for ANT integration. However, if we want to use an optional ANT task, we must do something extra as the optional tasks and their dependencies are not in the Gradle classpath. Luckily, we only have to define our dependencies for the optional task in the build.gradle file and we can then define and use the optional ANT task.

configurations {
    sshAntTask
}
repositories {
    jcenter()
}
dependencies {
    sshAntTask('org.apache.ant:ant-jsch:1.7.1', 'jsch:jsch:0.1.29')
}
task update {
    description = 'Update files on remote server.'
    def console = System.console()
    def passphrase = console.readPassword('%s: ', 'Please enter the passphrase for the keyfile')
    ant.taskdef(name: 'scp', classname: 'org.apache.tools.ant.taskdefs.optional.ssh.Scp', classpath: configurations.sshAntTask.asPath)
    ant.scp(todir: 'mrhaki@servername:/home/mrhaki', keyfile: '${user.home}/.ssh/id_rsa', passphrase: passphrase as String, verbose: 'true') {
        fileset(dir: 'work') {
            include(name: '**/**')
        }
    }
}

Using dependency configurations as files

Each dependency configuration implements the FileCollection interface of Gradle. This means that we can use a configuration reference if we need a list of files somewhere. The files that make up the resolved dependency configuration are then used.

repositories {
    jcenter()
}
configurations {
    springLibs
}
dependencies {
    springLibs('org.springframework:spring-web:4.2.3.RELEASE')
}
task copyCompileDeps(type: Copy) {
    from configurations.springLibs
    into "$buildDir/compileLibs"
}

Chapter 6.  Testing, Building, and Publishing Artifacts

Testing our projects

Gradle has a built-in support for running tests for our Java projects. When we add the Java plugin to our project, we will get new tasks to compile and run tests. We will also get the testCompile and testRuntime dependency configurations.

Using TestNG for testing

Gradle also supports tests that are written with the TestNG test framework. Gradle scans the test class path for all class files and checks whether they have specific JUnit or TestNG annotations.

For Gradle to use either JUnit or TestNG tests when we run the test task, we invoke the useJUnit() or useTestNG() method, respectively, to force Gradle to use the correct testing framework. Gradle uses JUnit as a testing framework by default so that we don’t have to use the useJUnit() method when we use JUnit or JUnit-compatible test frameworks to test our code.

Configuring the test process

The tests that are executed by the test task run in a separate isolated JVM process. We can use several properties to control this process. We can set system properties and JVM arguments and we can configure the Java class that needs to be executed to run the tests.

To debug the tests, we can set the debug property of the test task. Gradle will start the test process in the debug mode and listen on port 5005 for a debug process to attach to. This way we can run our tests and use an IDE debugger to step through the code.

By default, Gradle will fail the build if any test fails. If we want to change this setting, we must set the ignoreFailures property to true. Our build will not fail then, even if we have errors in our tests.

apply plugin: 'java'
repositories {
    jcenter()
}
dependencies {
    testCompile('junit:junit:4.12')
}
test {
    systemProperty 'sysProp', 'value'
    jvmArgs '-Xms256m', '-Xmx512m'
    debug = true
    ignoreFailures = true
    enableAssertions = true
}

Determining tests

To determine the files that are tests, Gradle will inspect the compiled class files. If a class or its methods have the @Test annotation, Gradle will treat it as a JUnit or TestNG test. If the class extends TestCase or GroovyTestCase or is annotated with @RunWith, Gradle will handle it as a JUnit test. Abstract classes are not inspected.

We can disable this automatic inspection with the scanForTestClasses property of the test task. If we set the property to false, Gradle will use the implicit /Tests.class and /*Test.class include rules and the */Abstract*.class exclude rule.

We can also set our own include and exclude rules to find tests. We can use the include() method of the test task to define our own rule for test classes. If we want to exclude certain class files, we can use the exclude() method to define the exclude rules. Alternatively, we can use the includes and excludes properties.

apply plugin: 'java'
repositories {
    jcenter()
}
dependencies {
    testCompile('junit:junit:4.12')
}
test {
    scanForTestClasses = false
    include('**/*Test.class', '**/*Spec.class')
    exclude('**/Abstract*.class', '**/Run*.class')
}

Changing the test report directory

Besides the generated HTML report, we have XML files that are generated by the test task with the results of the tests. These XML files are actually the input for the generated HTML report. There are a lot of tools available that can use the XML files generated by JUnit or TestNG and perform an analysis on them.

apply plugin: 'java'
repositories {
    jcenter()
}
dependencies {
    testCompile('junit:junit:4.12')
}
task testReport(type: TestReport) {
    destinationDir = file("$buildDir/test-reports")
    testResultDirs = files("$buildDir/test-results")
    reportOn(test)
}
test.finalizedBy(testReport)

Running Java applications

Running an application from a project

apply plugin: 'java'
task runJava(dependsOn: classes, description: 'Run gradle.sample.SampleApp') << {
    javaexec {
        main = 'gradle.sample.SampleApp'
        classpath sourceSets.main.runtimeClasspath
        maxHeapSize = '128m'
        systemProperty 'sysProp', 'notUsed'
        jvmArgs '-client'
    }
}
repositories {
    jcenter()
}

Running an application as a task

apply plugin: 'java'
task runJava(type: JavaExec) {
    dependsOn classes
    description = 'Run gradle.sample.SampleApp'
    main = 'gradle.sample.SampleApp'
    classpath sourceSets.main.runtimeClasspath
    systemProperty 'sysProp', 'notUsed'
    jvmArgs '-client'
    args 'mainMethodArgument', 'notUsed'
}

Running an application with the application plugin

apply plugin: 'application'
mainClassName = 'gradle.sample.SampleApp'
run {
    systemProperty 'sysProp', 'notUsed'
    jvmArgs '-client'
    args 'mainMethodArgument', 'notUsed'
}

Publishing artifacts

apply plugin: 'java'
archivesBaseName = 'gradle-sample'
version = '1.0'
repositories {
    flatDir {
        name 'uploadRepository'
        dirs 'upload'
    }
}
uploadArchives {
    repositories {
        add project.repositories.uploadRepository
        flatDir {
            dirs 'libs'
        }
    }
}

Uploading our artifacts to a Maven repository

apply plugin: 'java'
apply plugin: 'maven'
archivesBaseName = 'gradle-sample'
group = 'gradle.sample'
version = '1.0'
uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: 'file:./maven')
        }
    }
}

Working with multiple artifacts

apply plugin: 'java'
archivesBaseName = 'gradle-sample'
version = '1.0'
task sourcesJar(type: Jar) {
    classifier = 'sources'
    from sourceSets.main.allSource
}
task docJar(type: Jar, dependsOn: javadoc) {
    classifier = 'docs'
    from javadoc.destinationDir
}
artifacts {
    archives sourcesJar
    archives docJar
}
uploadArchives {
    repositories {
        flatDir {
            dirs 'upload'
        }
    }
}

Packaging Java Enterprise Edition applications

Creating a WAR file

To create a WAR file, we can add a new task of the War type to our Java project. The properties and methods of the War task are the same as for the other archive tasks, such as Jar. In fact, the War task extends the Jar task.

The War task has an extra webInf() method to define a source directory for the WEB-INF directory in a WAR file. The webXml property can be used to reference a web.xml file that needs to be copied to the WAR file. This is just another way to include a web.xml file, we can also place the web.xml file in the WEB-INF directory of the root source directory that we defined for the WAR file.

With the classpath() method, we can define a dependency configuration or directory with libraries or class files that we want copied to our WAR file. If the file is a JAR or ZIP file, it is copied to the WEB-INF/lib directory and other files are copied in the WEB-INF/classes directory.

apply plugin: 'java'
version = '1.0'
task war(type: War) {
    dependsOn classes
    from 'src/main/webapp'
    webInf {
        from 'src/main/webInf'
    }
    classpath sourceSets.main.runtimeClasspath
    classpath fileTree('libs')
    webXml = file('')
    baseName = 'gradle-webapp'
}
assemble.dependsOn war

Chapter 7.  Multi-project Builds

Working with multi-project builds

Executing tasks by project path

Gradle takes a couple of steps to determine whether a project must be executed as a single or multi-project build, as follows:

  1. First, Gradle looks for a settings.gradle file in a directory with the name master at the same level as the current directory.
  2. If settings.gradle is not found, the parent directories of the current directory are searched for a settings.gradle file.
  3. If settings.gradle is still not found, the project is executed as a single-project build.
  4. If a settings.gradle file is found, and the current project is part of the multi-project definition, the project is executed as part of the multi-project build. Otherwise, the project is executed as a single-project build.

We can force Gradle to not look for a settings.gradle file in the parent directories with the --no-search-upward (or -u) command-line argument.

Using a flat layout

includeFlat('tree', 'flower')

Ways of defining projects

We can define all project tasks and properties in the root build.gradle file. We can use this to define the common functionality for all projects in a single place.

task printInfo << {
    println "This is ${project.name}"
}
project(':flower') {
    task printInfo << {
        println "This is ${project.name}"
    }
}
project(':tree') {
    task printInfo << {
        println "This is ${project.name}"
    }
}

Gradle also has the allprojects{} script block to apply project tasks and properties to all projects that are part of the multi-project build.

allprojects {
    task printInfo << {
        println "This is ${project.name}"
    }
}

If we only want to configure the tree and flower subprojects, we must use the subprojects{} script block.

subprojects {
    task printInfo << {
        println "This is ${project.name}"
    }
}

Filtering projects

allprojects {
    task printInfo << {
        println "This is ${project.name}"
    }
}
ext {
    projectsWithF = allprojects.findAll { project ->
        project.name.startsWith('f')
    }
}
configure(projectsWithF) {
    printInfo << {
        println 'Smells nice'
    }
}

Defining task dependencies between projects

allprojects {
    task printInfo << {
        println "This is ${project.name}"
    }
}
project(':flower') {
    printInfo.dependsOn(':tree:printInfo')
}

Working with Java multi-project builds

.
├── build.gradle
├── common
│   └── src
│       └── main
│           └── java
│               └── sample
│                   └── gradle
│                       └── util
│                           └── Logger.java
├── services
│   └── sample
│       └── src
│           ├── main
│           │   └── java
│           │       └── sample
│           │           └── gradle
│           │               ├── api
│           │               │   └── SampleService.java
│           │               └── impl
│           │                   └── SampleImpl.java
│           └── test
│               └── java
│                   └── sample
│                       └── gradle
│                           └── impl
│                               └── SampleTest.java
├── settings.gradle
└── web
    └── src
        └── main
            ├── java
            │   └── gradle
            │       └── sample
            │           └── web
            │               └── SampleServlet.java
            └── webapp
                └── WEB-INF
                    └── web.xml
subprojects {
    apply plugin: 'java'
    repositories {
        mavenCentral()
    }
    dependencies {
        testCompile 'junit:junit:4.8.12'
    }
}
project(':services:sample') {
    dependencies {
        compile project(':common')
    }
}
project(':web') {
    apply plugin: 'war'
    dependencies {
        compile project(':services:sample')
        compile 'javax.servlet:servlet-api:2.5'
    }
}

Chapter 8. Mixed Languages

Using the Groovy plugin

To use Groovy sources in our project, we can apply the Groovy plugin. The Groovy plugin makes it possible to compile Groovy source files to class files. The project can contain both Java and Groovy source files. The compiler that Gradle uses is a joint compiler that can compile Java and Groovy source files.

The plugin also adds new tasks to our build. To compile the Groovy source files, we can invoke the compileGroovy task. Test sources written in Groovy can be compiled with the compileTestGroovy task.

Note that we also got all the tasks from the Java plugin. This is because the Groovy plugin automatically includes the Java plugin. So, even though we only defined the Groovy plugin in our build file, the Java plugin is applied as well.

The plugin adds the groovy configuration. The Groovy compiler uses this configuration. Therefore, to compile Groovy source files in our project, we must set a dependency on the compile configuration.

To compile Groovy source files, we must add a dependency with the Groovy library that we want to use to the compile configuration. We might expect that Gradle will use the Groovy version that is used by Gradle, but the compilation task is independent of the Groovy version used by Gradle. We have to define the Groovy library ourselves.

When we want to use the Groovy libraries shipped with Gradle, we can use the localGroovy() special dependency.
The default source directory for Groovy source files is src/main/groovy.

apply plugin: 'groovy'
repositories {
    jcenter()
}
dependencies {
    compile group: 'org.codehaus.groovy', name: 'groovy', version: '2.4.5'
}

The Groovy plugin also adds new source set properties.

Property name Type Description
groovy org.gradle.api.file.SourceDirectorySet These are the Groovy source files for this project. This contains both .java and .groovy These are the Groovy source files for this project. This contains both .java and .groovy source files if they are in the groovy directory.
groovy.srcDirs java.util.Set<java.io.File> These are the directories with the Groovy source files. They can also contain Java source files for joint compilation.
allGroovy org.gradle.api.file.FileTree These consist of only the Groovy source files. All files with extension .groovy are part of this collection.

When our Java code uses Groovy classes, and vice versa, we can use the joint compilation feature. We must make sure that both Java and Groovy source files are in the src/main/groovy directory.

Using the Scala plugin

We must apply the Scala plugin to enable the Scala support for our build. The plugin adds new tasks to compile the Scala source files. With the compileScala task, we compile our main Scala source files. The source files must be in the src/main/scala directory. The compileTestScala task compiles all Scala source code files that are in the src/test/scala directory.

The compile tasks support both Java and Scala source files with joint compilation. We can place our Java source files in say the  src/main/java directory of our project and the Scala source files in the src/main/scala directory. The compiler will compile both types of files. To be able to compile the files, we must add dependencies to the Scala library in our build file. We must assign the correct dependencies from a Maven repository to the compile configuration so that Gradle can invoke the compiler to compile the source files.

apply plugin: 'scala'
repositories {
    jcenter()
}
dependencies {
    compile group: 'org.scala-lang', name: 'scala-library', version: '2.11.4'
}

The Scala plugin also adds several new properties to a source set.

Property name Type Description
scala org.gradle.api.file.SourceDirectorySet This is the Scala source files for this project; it contains both .java and.scala source files if they are in the Scala directory.
scala.srcDirs java.util.Set<java.io.File> These are the directories with the Scala source files; they can also contain Java source files for joint compilation.
allScala org.gradle.api.file.FileTree These are only the Scala source files. All files with the .scala extension are part of this collection.

Chapter 10.  Writing Custom Tasks and Plugins

Creating a custom task

When we create a new task in a build and specify a task with the type property, we actually configure an existing task. The existing task is called enhanced task in Gradle.

Creating a custom task in the build file

class InfoTask extends DefaultTask {
    @Optional
    String prefix = 'Current Gradle version'
    @TaskAction
    def info() {
        println "$prefix: $project.gradle.gradleVersion"
    }
}
task info(type: InfoTask) {
    prefix = 'Running Gradle'
}

Creating a custom plugin

Creating a plugin in the build file

We can create a custom plugin right in the project build file. Similar to a custom task, we can add a new class definition with the logic of the plugin. We must implement the org.gradle.api.Plugin<T> interface. The interface has an apply()method. When we write our own plugin, we must override this method. The method accepts an object as a parameter. The type of the object is the same as the generic T type. When we create a plugin for projects, the Project type is used. We can also write plugins for other Gradle types, such as tasks. Then we must use the Task type.

class InfoPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.tasks.create('info') << {
            println "Running Gradle: $project.gradle.gradleVersion"
        }
    }
}
apply plugin: InfoPlugin

We can rewrite the task and make the text configurable. A Gradle project has an associated ExtensionContainer object. This object can hold all settings and properties that we want to pass to a plugin. We can add a JavaBean to ExtensionContainer so that we can configure the bean’s properties from the build file. The JavaBean is a so-called extension object.

class InfoPluginExtension {
    String prefix = 'Running Gradle'
}
class InfoPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.extensions.create('info', InfoPluginExtension)
        project.tasks.create('info') << {
            println "$project.info.prefix: $project.gradle.gradleVersion"
        }
    }
} 
apply plugin: InfoPlugin
info {
    prefix = 'Gradle version'
}

Creating a plugin in the project source directory

We will create the buildSrc/src/main/groovy/sample directory. In this directory, we will create an InfoPlugin.groovy file:

package sample
import org.gradle.api.*
class InfoPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.extensions.create('info', InfoPluginExtension)
        project.tasks.create('info') << {
            println "$project.info.prefix: $project.gradle.gradleVersion"
        }
    }
}

Next, we will create the InfoPluginExtension.groovy file in the directory:

package sample
class InfoPluginExtension {
    String prefix
}

In our build file in the root of the project, we will reference our plugin with the package and class name:

apply plugin: sample.InfoPlugin
info {
    prefix = 'Gradle version'
}