May 25, 2021 Gradle
Most of the build work is done with files. Gradle adds concepts and APIs to help you achieve this.
You can use the Project.file() method to find a file relative to the project directory.
Find the file
build.gradle
// Using a relative path
File configFile = file('src/config.xml')
// Using an absolute path
configFile = file(configFile.absolutePath)
// Using a File object with a relative path
configFile = file(new File('src/config.xml'))
You can pass any object to the file() method, and it will try to convert it to a File object with an absolute path. T
ypically, you pass it on to an instance of String or File. T
he value of the tostring() method of the object provided will act as the file path. I
f this path is an absolute path, it is used to construct a File instance. O
therwise, the File instance is constructed by first calculating the relative path of the path provided relative to the project directory.
This file() method can also identify URLs, such as
file:/some/path.xml
This is a useful way to convert some user-provided values into a File object with a relative path. Because the file() method always calculates the path provided relative to the project directory, it is best to use new File (somePath), because it is a fixed path and does not change because the user runs the specific working directory of Gradle.
A collection of files simply represents a set of files. I t is represented by the FileCollection interface. M any objects in the Gradle API implement this interface. For example, relying on configuration implements fileCollection as an interface.
Using Project.files() is one of the ways to get an instance of FileCollection. Y ou can pass any object to this method, and they are converted to a set of File objects. T his Files() method accepts any type of object as its argument. A ccording to the description of the file() method in chapter 16.1, "Locating files," its results are calculated as relative paths relative to the project directory. Y ou can also pass collections, iterative variables, map, and arrays to the files() method. They are expanded and the content is converted to a File instance.
Create a collection of files
build.gradle
FileCollection collection = files('src/file1.txt', new File('src/file2.txt'), ['src/file3.txt', 'src/file4.txt'])
A collection of files is iterative and can be converted to other types of object collections using the as operator. Y ou can also add two file collections using the plus operator, or use the - operator to subtract a file collection. Here are some examples of using file collections.
Use a collection of files
build.gradle
// Iterate over the files in the collection
collection.each {File file ->
println file.name
}
// Convert the collection to various types
Set set = collection.files
Set set2 = collection as Set
List list = collection as List
String path = collection.asPath
File file = collection.singleFile
File file2 = collection as File
// Add and subtract collections
def union = collection + files('src/file3.txt')
def different = collection - files('src/file3.txt')
You can also pass a closure or a Callable instance to the files() method. I t is called when the contents of the collection are queried and its return value is converted to a set of file instances. T he return value of this closure or Callable instance can be any type of object supported by the files() method. This is an easy way to "implement" the FileCollection interface.
Implement a collection of files
build.gradle
task list << {
File srcDir
// Create a file collection using a closure
collection = files { srcDir.listFiles() }
srcDir = file('src')
println "Contents of $srcDir.name"
collection.collect { relativePath(it) }.sort().each { println it }
srcDir = file('src2')
println "Contents of $srcDir.name"
collection.collect { relativePath(it) }.sort().each { println it }
}
The output of the gradle -q list
> gradle -q list
Contents of src
src/dir1
src/file1.txt
Contents of src2
src2/dir1
src2/dir2
You can pass in some other types of objects to files():
FileCollection
They are expanded and the content is included in the file collection.
Task
The output file of the task is included in the file collection.
TaskOutputs
TaskOutputs' output files are included in the file collection.
One thing to note is that the contents of a collection of files are slowly calculated, and it is calculated only when needed. This means that you can, for example, create a FileCollection object and the files inside will not be created until later, for example, in some tasks.
A file tree is a collection of files sorted hierarchically. F or example, a file tree might represent the contents of a tree or ZIP file. I t is represented by the FileTree interface. T he FileTree interface inherits from FileCollection, so you can treat the file tree the same way you treat file collections. Several objects in Gradle implement the FileTree interface, such as source sets.
Using Project.fileTree() is one way to get a FileTree instance. It defines a base directory to create FileTree objects, and can choose to add some Ant-style inclusion and exclusion patterns.
Create a file tree
build.gradle
// Create a file tree with a base directory
FileTree tree = fileTree(dir: 'src/main')
// Add include and exclude patterns to the tree
tree.include '**/*.java'
tree.exclude '**/Abstract*'
// Create a tree using path
tree = fileTree('src').include('**/*.java')
// Create a tree using closure
tree = fileTree('src') {
include '**/*.java'
}
// Create a tree using a map
tree = fileTree(dir: 'src', include: '**/*.java')
tree = fileTree(dir: 'src', includes: ['**/*.java', '**/*.xml'])
tree = fileTree(dir: 'src', include: '**/*.java', exclude: '**/*test*/**')
You can use a file tree as if you were using a collection of files. You can also use Ant-style mode to access the contents of the file tree or select a sub-tree:
Use the file tree
build.gradle
// Iterate over the contents of a tree
tree.each {File file ->
println file
}
// Filter a tree
FileTree filtered = tree.matching {
include 'org/gradle/api/**'
}
// Add trees together
FileTree sum = tree + fileTree(dir: 'src/test')
// Visit the elements of the tree
tree.visit {element ->
println "$element.relativePath => $element.file"
}
You can use the contents of an archive, such as a ZIP or TAR file, as a file tree. Y ou can do this by using the Project.zipTree() or Project.tarTree() approach. T hese methods return a FileTree instance that you can use just like any other file tree or collection of files. For example, you can use it to expand an archive by copying content, or to merge some archives into another archive.
Use the archive as a file tree
build.gradle
// Create a ZIP file tree using path
FileTree zip = zipTree('someFile.zip')
// Create a TAR file tree using path
FileTree tar = tarTree('someFile.tar')
//tar tree attempts to guess the compression based on the file extension
//however if you must specify the compression explicitly you can:
FileTree someTar = tarTree(resources.gzip('someTar.ext'))
Many objects in Gradle have properties that accept a set of input files. F or example, the JavaCompile task has a source property that defines the source code file to compile. Y ou can set this property using any type of object supported by the files() method shown above. T his means that you can set this property by, for example, File, String, Collection, FileCollection object, or even a closure. Here are some examples:
Specify a set of files
build.gradle
// Use a File object to specify the source directory
compile {
source = file('src/main/java')
}
// Use a String path to specify the source directory
compile {
source = 'src/main/java'
}
// Use a collection to specify multiple source directories
compile {
source = ['src/main/java', '../shared/java']
}
// Use a FileCollection (or FileTree in this case) to specify the source files
compile {
source = fileTree(dir: 'src/main/java').matching { include 'org/gradle/api/**' }
}
// Using a closure to specify the source files.
compile {
source = {
// Use the contents of each zip file in the src dir
file('src').listFiles().findAll {it.name.endsWith('.zip')}.collect { zipTree(it) }
}
}
Typically, there is a method with the same name as the property, which you can append to this collection of files. Further, this method accepts any type of argument supported by the files() method.
Specify a set of files
build.gradle
compile {
// Add some source directories use String paths
source 'src/main/java', 'src/main/groovy'
// Add a source directory using a File object
source file('../shared/java')
// Add some source directories using a closure
source { file('src/test/').listFiles() }
}
You can use the Copy task to copy files. The replication task is very flexible and allows you to do things like filter the contents of the file you want to copy, or map the name of the file.
To use the Copy task, you must provide the source files and destination directories for replication. Y ou can also specify how to convert a file when you copy it. Y ou can do this with a replication specification. A replication specification is represented by the CopySpec interface. T he Copy task implements this interface. You can specify the source file using the CopySpec.from() method and the target directory using the CopySpec.into() method.
Copy the file with the copy task
build.gradle
task copyTask(type: Copy) {
from 'src/main/webapp'
into 'build/explodedWar'
}
The from() method accepts any parameters that are the same as the files() method. W hen parameters are resolved to a directory, all files in that directory (without the directory itself) are recursively copied to the target directory. W hen parameters are resolved to a file, the file is copied to the target directory. W hen an argument is resolved to a file that does not exist, the argument is ignored. I f the parameter is a task, the output file of the task (that is, the file created by the task) is copied and the task is automatically added as a dependency of the Copy task. T he into() method accepts any parameters that are the same as the files() method. Here's another example:
Example 16.11. Specify the source file and destination directory for the replication task
build.gradle
task anotherCopyTask(type: Copy) {
// Copy everything under src/main/webapp
from 'src/main/webapp'
// Copy a single file
from 'src/staging/index.html'
// Copy the output of a task
from copyTask
// Copy the output of a task using Task outputs explicitly.
from copyTaskWithPatterns.outputs
// Copy the contents of a Zip file
from zipTree('src/main/assets.zip')
// Determine the destination directory later
into { getDestDir() }
}
You can use Ant-style include or exclude modes, or use a closure to select the files to copy:
Select the file to copy
build.gradle
task copyTaskWithPatterns(type: Copy) {
from 'src/main/webapp'
into 'build/explodedWar'
include '**/*.html'
include '**/*.jsp'
exclude { details -> details.file.name.endsWith('.html') && details.file.text.contains('staging') }
}
In addition, you can use the Project.copy() method to copy files. I t works the same way as a task, although it has some major limitations. First, copy() cannot do incremental operations.
Copy the file using the copy() method, which does not have an up-to-date status check
build.gradle
task copyMethod << {
copy {
from 'src/main/webapp'
into 'build/explodedWar'
include '**/*.html'
include '**/*.jsp'
}
}
Second, when a task is used as a replication source (that is, as an argument to from(), the copy() method cannot establish a task dependency because it is a method, not a task. Therefore, if you use the copy() method in the action of a task, you must explicitly declare all inputs and outputs to get the correct behavior.
Copy files using the copy() method with the latest status checks
build.gradle
task copyMethodWithExplicitDependencies{
inputs.file copyTask // up-to-date check for inputs, plus add copyTask as dependency
outputs.dir 'some-dir' // up-to-date check for outputs
doLast{
copy {
// Copy the output of copyTask
from copyTask
into 'some-dir'
}
}
}
Where possible, it's best to use the Copy task because it supports incremental builds and task dependency reasoning without your extra effort. T he copy() method can copy files as part of a task's execution. T hat is, this copy() method is intended for custom tasks when file replication is required as part of its functionality. In this case, the custom task should fully declare the inputs/outputs related to the replication operation.
Rename the copied file
build.gradle
task rename(type: Copy) {
from 'src/main/webapp'
into 'build/explodedWar'
// Use a closure to map the file name
rename { String fileName ->
fileName.replace('-staging-', '')
}
// Use a regular expression to map the file name
rename '(.+)-staging-(.+)', '$1$2'
rename(/(.+)-staging-(.+)/, '$1$2')
}
Filter the files to copy
build.gradle
import org.apache.tools.ant.filters.FixCrLfFilter
import org.apache.tools.ant.filters.ReplaceTokens
task filter(type: Copy) {
from 'src/main/webapp'
into 'build/explodedWar'
// Substitute property references in files
expand(copyright: '2009', version: '2.3.1')
expand(project.properties)
// Use some of the filters provided by Ant
filter(FixCrLfFilter)
filter(ReplaceTokens, tokens: [copyright: '2009', version: '2.3.1'])
// Use a closure to filter each line
filter { String line ->
"[$line]"
}
}
The replication specification is used to organize a hierarchy. A replication specification inherits its target path, including patterns, exclusion patterns, replication operations, name maps, and filters.
Nested replication specifications
build.gradle
task nestedSpecs(type: Copy) {
into 'build/explodedWar'
exclude '**/*staging*'
from('src/dist') {
include '**/*.html'
}
into('libs') {
from configurations.runtime
}
}
The Sync task inherits the Copy task. W hen it executes, it copies the source file to the target directory, and then removes all files from the target directory that are not copied by it. This can be used to do things like install your application, create an exploded copy of your archive, or maintain a copy of the project's dependencies.
Here is an example of maintaining a copy that is dependent on the runtime of an item in the build/libs directory.
Copy dependencies with synchronous tasks
build.gradle
task libs(type: Sync) {
from configurations.runtime
into "$buildDir/libs"
}
A project can have as many JAR files as you want. Y ou can also add WAR, ZIP, and TAG files to your project. U se a variety of archiving tasks to create the following archives: Zip, Tar, Jar, War, and Ear. They all work the same way, so let's see how to create a ZIP file.
Create a ZIP file
build.gradle
apply plugin: 'java'
task zip(type: Zip) {
from 'src/dist'
into('libs') {
from configurations.runtime
}
}
Why java plug-ins?
The Java plug-in adds some default values to the archive task. I f you prefer, you can use archiving tasks without the need for a Java plug-in. Y ou need to provide some values to attached properties.
Archive tasks work the same way as Copy tasks and implement the same CopySpec interface. A s with copy tasks, you need to specify the entered files using the from() method, and you can choose whether to specify the final location in the archive through the into() method. You can filter the contents of a file, rename the file, and do other things you can through a copy specification.
The default name for the generated archive is projectName-version.type. For example:
Create a ZIP file
build.gradle
apply plugin: 'java'
version = 1.0
task myZip(type: Zip) {
from 'somedir'
}
println myZip.archiveName
println relativePath(myZip.destinationDir)
println relativePath(myZip.archivePath)
The output of gradle -q myZip
> gradle -q myZip
zipProject-1.0.zip
build/distributions
build/distributions/zipProject-1.0.zip
It adds a ZIP archive task called myZip, which results in a ZIP file zipProject 1.0 .zip. I t is important to distinguish between the name of the archive task and the name of the archive file generated by the archive task. T he default name of the archive can be changed by the project property archiveBaseName. You can also change the name of the archive at any later time.
There are many properties you can set in the archive task. T hey are listed in Table 16.1 below, Archive Task-Name Properties. You can, say, change the name of the archive:
Configure the archive task - customize the archive name
build.gradle
apply plugin: 'java'
version = 1.0
task myZip(type: Zip) {
from 'somedir'
baseName = 'customName'
}
println myZip.archiveName
The output of gradle -q myZip
gradle -q myZip customName-1.0.zip
You can further customize the archive name:
Configure the archive task - appendix and classifier
build.gradle
apply plugin: 'java'
archivesBaseName = 'gradle'
version = 1.0
task myZip(type: Zip) {
appendix = 'wrapper'
classifier = 'src'
from 'somedir'
}
println myZip.archiveName
The output of gradle -q myZip
> gradle -q myZip
gradle-wrapper-1.0-src.zip
Table 16.1. Archive task - Name the property
The name of the property | Type | The default | Describe |
archiveName
|
String
|
extension
If any of these properties are empty, the later
|
The base file name of the generated archive |
archivePath
|
File
|
archiveName
|
The absolute path to the generated archive. |
destinationDir
|
File
|
Depends on the type of archive. J
AR and WAR packages are generated
project.buildDir
ZIP files and TAR files are generated
project.buildDir
|
A directory that holds the generated archive |
baseName
|
String
|
project.name
|
The basic name part of the archive's name. |
appendix
|
String
|
null
|
The appendix section in the name of the archive. |
version
|
String
|
project.version
|
The version portion of the archive's name. |
classifier
|
String
|
null
|
The classification part of the name of the archive. |
extension
|
String
|
Depending on the type of archive used for TAR files, it can be the following compression type:
tbz2
.
|
The extended name section of the archive's name. |
You can use the Project.copySpec() method to share content between archives.
You often want to publish an archive so that you can use it from another project.