Various notes about Gradle tasks from basics to applications.
-Introduction to build.gradle reading and writing for those who do not know Groovy --Qiita --Basic knowledge of Groovy ――Various syntax sugar and basic grammar
Gradle
5.0
Java
openjdk 11.0.1
OS
Windows 10
To define a task in Gradle, write:
build.gradle
task foo
The definition of this foo
task is task (String) of Project. You are calling the .gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project: task (java.lang.String)) method.
The task ()
method returns an object that represents the created task, so you can get the object for the foo
task as follows:
build.gradle
def foo = task foo
println(foo)
println(foo.class)
Execution result
> gradle foo
task ':foo'
class org.gradle.api.DefaultTask_Decorated
The actual task of Gradle is an object of Task, if it is simply created by task ()
An object of DefaultTask is created.
Action
The task keeps a list of Action internally.
This ʻAction has a specific action to perform on that task. Then, when the task is started, ʻAction
is executed in order from the top of the list.
ʻAction can be added to either the beginning or end of the list with the
doFirst () ,
doLast () methods of
Task`.
build.gradle
def foo = task foo
foo.doFirst {
println "two"
}
foo.doFirst {
println "one"
}
foo.doLast {
println "three"
}
foo.doLast {
println "four"
}
Execution result
> gradle foo
one
two
three
four
doFirst ()
adds ʻAction to the beginning of the list, and
doLast () adds ʻAction
to the end.
In the above example, doFirst ()
is executed twice.
In this case, ʻAction, which outputs
"one" executed later, will be inserted at the top of the list, so it is output in the order of
"one"-> "two" `.
build.gradle
task foo {
println(delegate)
println(delegate.class)
doFirst {
println("FOO!!")
}
}
Execution result
> gradle foo
> Configure project :
task ':foo'
class org.gradle.api.DefaultTask_Decorated
> Task :foo
FOO!!
When defining a task with the task ()
method, [can pass closure as second argument](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html # org.gradle.api.Project: task (java.lang.String,% 20groovy.lang.Closure).
The delegate
of this closure is the created task object.
Therefore, the methods and properties of the created task can be implicitly accessed within the closure.
Gradle execution is roughly divided into three phases.
Phase | Contents |
---|---|
Initialization phase (Initialization) |
Determine if the project is single or multi, etc.Project Create an object of. |
Configuration phase (Configuration) |
Run the build script andproject Build objects. |
Execution phase (Execution) |
Actually execute the task specified on the command line. |
Specifically, in build.gradle
, the inside of ʻAction` set in the task is executed in the execution phase, and the others are executed in the setting phase.
build.gradle
println("Setting phase 1")
task foo {
println("Setting phase 2")
doFirst {
println("Execution phase 1")
}
println("Setting phase 3")
doLast {
println("Execution phase 2")
}
}
task bar {
println("Setting phase 4")
}
println("Setting phase 5")
Execution result
> gradle foo
> Configure project :
Setting phase 1
Setting phase 2
Setting phase 3
Setting phase 4
Setting phase 5
> Task :foo
Execution phase 1
Execution phase 2
The part that is executed in the configuration phase is always executed regardless of the specified task. Therefore, if you mistakenly write the process of the task in the part to be executed in the setting phase, the process will be executed even though the task is not specified, so be careful.
build.gradle
task foo {
doFirst {
println foo
println project.foo
println tasks.foo
println tasks.getByName("foo")
println tasks["foo"] // getAt()Method syntax sugar
}
}
Execution result
task ':foo'
task ':foo'
task ':foo'
task ':foo'
task ':foo'
If you define a task in a project, you can refer to the task only by the task name in the project.
This is achieved by adding the created task object to the properties of the Project
object.
The created task object is registered in TaskContainer of Project
.
The TaskContainer
can be referenced from the Project
's tasks property. , TaskContainer
allows you to reference tasks using property access and thegetByName ()
,getAt ()
methods.
In rare cases, there are plugins that add an extension object with the same name as the task name.
For example, Eclipse Plugin is named ʻeclipseand [Task](https://docs.gradle.org/current/userguide/ eclipse_plugin.html # eclipsetasks) and [extension object](https://docs.gradle.org/current/userguide/eclipse_plugin.html#sec:eclipse_configuration) are defined. In this case, the object referenced in
project.eclipsewill be the extension object. If you want to refer to a task, you need to go through
TaskContainer like
tasks.eclipse`.
Note that the method specified by a character string such as getByName ()
can also refer to the tasks of other projects as : someSubProject: fooTask
.
build.gradle
task foo {
doFirst {
println("foo")
}
}
task bar {
doFirst {
println("bar")
}
}
foo.dependsOn bar
Execution result
> gradle foo
bar
foo
Task
dependsOn () You can define task dependencies by using the method.
Here, the foo
task is defined to depend on the bar
task.
This ensures that whenever you run the foo
task, the bar
task will run first.
Gradle's task execution order is basically controlled by using this dependency definition (control methods other than dependencies are also available).
Gradle examines the dependent tasks before performing the tasks specified on the command line. Then, the tasks are executed in order from the dependent side.
At this time, even if there are tasks that are dependent on a plurality of tasks, all the tasks are controlled so that they are always executed only once.
build.gradle
task foo {
doFirst { println("foo") }
}
task bar {
doFirst { println("bar") }
}
task hoge {
doFirst { println("hoge") }
}
foo.dependsOn bar
hoge.dependsOn foo
hoge.dependsOn bar
Execution result
> gradle hoge
bar
foo
hoge
The task hoge
depends on both foo
and bar
.
Since foo
also depends on bar
, a simple diagram of task dependencies is as follows.
Dependencies are represented graphically
hoge -> foo -> bar
| ^
+--------------+
If you simply execute all the dependent tasks, bar
will be executed twice.
However, as mentioned above, Gradle controls that even if it depends on multiple tasks, it will be executed only once.
Since dependsOn
is a method of Task
, it can also be written in the configuration block as follows.
build.gradle
task foo {
dependsOn "bar"
doFirst {
println("foo")
}
}
task bar {
doFirst {
println("bar")
}
}
Here, the bar
task is specified as a character string.
If you make this a property reference instead of a string, you'll get the following error:
build.gradle
task foo {
dependsOn bar //★ Replaced with property reference
doFirst {
println("foo")
}
}
task bar {
doFirst {
println("bar")
}
}
Execution result
> gradle foo
...
> Could not get unknown property 'bar' for task ':foo' of type org.gradle.api.DefaultTask.
...
BUILD FAILED in 4s
An error occurs because the property bar
cannot be found.
The part where dependsOn bar
is written is the part that is executed in the" configuration phase "in the build life cycle.
In the configuration phase, build scripts are executed in order from the top.
By the time dependsOn bar
is executed, the task bar
is undefined because task bar
has not yet been executed.
Therefore, the bar
task cannot be accessed by property reference within the foo
task's configuration block above the bar
task definition.
This problem can be avoided by any of the following methods.
--As mentioned above, define by specifying a character string
--Bring the bar
task definition above the foo
task definition
If it depends on the order of description, it becomes vulnerable to change, so I personally feel that it is good to specify a character string.
build.gradle
task foo(dependsOn: "bar") {
doFirst {
println("foo")
}
}
task bar {
doFirst {
println("bar")
}
}
Receive Map
[task ()](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:task (java.util.Map) ,% 20java.lang.String,% 20groovy.lang.Closure))) You can also specify the dependent task with the argument Map
.
build.gradle
apply plugin: "java"
clean.doFirst { println "clean" }
build.doFirst { println "build" }
task cleanBuild(dependsOn: [clean, build])
Execution result
> gradle cleanBuild
build
clean
[clean, build]
is specified as the dependent task of the cleanBuild
task.
However, when the cleanBuild
task is executed, the tasks are executed in the order of build
-> clean
.
In this way, simply specifying dependsOn
does not determine the execution order of tasks (name order?).
How can I control it to run in the order clean
-> build
?
The first simple idea is to define dependsOn
so that build
depends on clean
.
build.gradle
apply plugin: "java"
clean.doFirst { println "clean" }
build.doFirst { println "build" }
build.dependsOn clean
task cleanBuild(dependsOn: [clean, build])
Execution result
> gradle cleanBuild
clean
build
Now the tasks are executed in the order clean
-> build
.
However, this method makes it impossible to execute only build
(it is always clean
).
Specify only build and execute
> gradle build
clean
build
If possible, I don't want to run clean
when running build
alone.
If you want to limit the order when specified at the same time like this, but you want to be able to execute it independently, [mustRunAfter (Object ...)](https://docs.gradle.org/current/ Use dsl / org.gradle.api.Task.html#org.gradle.api.Task: mustRunAfter (java.lang.Object [])).
build.gradle
apply plugin: "java"
clean.doFirst { println "clean" }
build.doFirst { println "build" }
build.mustRunAfter clean
task cleanBuild(dependsOn: [clean, build])
Execution result
> gradle cleanBuild
clean
build
> gradle build
build
By using taskA.mustRunAfter (taskB)
, you can force the order when taskA
and taskB
are executed at the same time to be taskB
-> taskA
.
build.gradle
task foo {
doFirst { println "foo" }
finalizedBy "finalizer"
}
task bar {
doFirst {
println "bar"
throw new Exception("test")
}
finalizedBy "finalizer"
}
task finalizer {
doFirst { println "finalizer" }
}
Execution result
> gradle foo
foo
finalizer
> gradle bar
bar
finalizer
...
Execution failed for task ':bar'.
> java.lang.Exception: test
...
BUILD FAILED in 3s
[finalizedBy ()](https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task:finalizedBy(java.lang.Object [])) The set task is always executed regardless of whether the task ends normally or abnormally.
Specify when there is a process that must be executed, such as releasing resources.
build.gradle
task foo {
doFirst { println "foo" }
onlyIf { project.hasProperty("hoge") }
}
Execution result
> gradle foo
[No output]
> gradle foo -Phoge
foo
With onlyIf () , You can specify the conditions to execute the action of the task.
The action of the task is executed only when the closure passed as an argument returns true
.
If the closure returns false
, the task's action is skipped.
However, only that task is skipped, and the dependent tasks and tasks specified by finalizedBy
are executed.
build.gradle
task foo {
doFirst { println "foo" }
onlyIf { project.hasProperty("hoge") }
dependsOn "bar"
finalizedBy "finalizer"
}
task bar {
doFirst { println "bar" }
}
task finalizer {
doFirst { println "finalizer" }
}
Execution result
> gradle foo
bar
finalizer
Execution of foo
is skipped, but the dependent bar
task and the finalizer
task specified in the finalizer are being executed.
Gradle provides a flexible API for selecting files and directories in the Project
class.
When writing task settings and actions, this API can be used to make file operations concise.
build.gradle
import java.nio.file.Path
task foo {
doFirst {
File file1 = file("foo.txt")
println(file1)
File file2 = file(new File("foo.txt"))
println(file2)
File file3 = file(Path.of("foo.txt"))
println(file3)
}
}
Execution result
> gradle foo
F:\etc\...\foo.txt
F:\etc\...\foo.txt
F:\etc\...\foo.txt
The file ()
method parses the value received as an argument and returns a File
object.
It supports a variety of inputs, from string paths to File
and Path
(see API documentation for details).
If you want Gradle to nicely convert any input to a File
object, thisfile ()
method works pretty well.
build.gradle
task foo {
doFirst {
FileCollection files = files("foo.txt", "bar.txt")
files.each { println it }
}
}
Execution result
> gradle foo
F:\etc\...\foo.txt
F:\etc\...\bar.txt
The files ()
method replaces FileCollection, which is a collection of multiple File
s. Generate.
The argument is a variadic argument, and the value that can be specified by file ()
can be specified in the same way.
Besides that, you can also pass ʻIterable,
Collection`, and so on.
Anyway, it also parses most values nicely into a FileCollection
.
The following methods are provided in FileCollection
.
build.gradle
FileCollection files = files("foo.txt", "bar.txt")
println "asPath = ${files.asPath}"
Execution result
asPath = F:\etc\...\foo.txt;F:\etc\...\bar.txt
Get the string that concatenates each path with the delimiter for each platform.
The delimiter is, for example, :
on Linux and ;
on Windows.
It can be used with options such as classpath and module path that specify multiple files.
build.gradle
FileCollection files = files("foo.txt", "bar.txt", "fizz.txt", "buzz.txt")
FileCollection filtered = files.filter { it.name.startsWith("b") }
filtered.each { println it }
Execution result
F:\etc\...\bar.txt
F:\etc\...\buzz.txt
The closure passed to filter ()
returns a FileCollection
with the elements narrowed down to only File
which returned true
.
build.gradle
task foo {
doFirst {
FileTree tree = fileTree("fromDir")
tree.each { println it }
}
}
Execution result
> gradle foo
F:\etc\...\fromDir\hoge.txt
F:\etc\...\fromDir\hoge.xml
F:\etc\...\fromDir\sub\foo.txt
F:\etc\...\fromDir\sub\foo.xml
When you use the fileTree ()
method, [FileTree](https://docs.gradle.org/current/javadoc/org/gradle/api/file/FileTree. html) can be obtained.
Since FileTree
inherits from FileCollection
, it can be operated in the same way as FileCollection
.
build.gradle
task foo {
doFirst {
FileTree tree = fileTree("fromDir") {
include "**/*.xml"
}
tree.each { println it }
}
}
Execution result
> gradle foo
F:\etc\...\fromDir\hoge.xml
F:\etc\...\fromDir\sub\foo.xml
fileTree ()
can pass a closure as the second argument.
The closure delegate
is the ConfigurableFileTree object in the directory specified by the first argument. ..
As the name suggests, ConfigurableFileTree
is a configurable FileTree
that allows you to filter files such as ʻinclude () and ʻexclude ()
.
The narrowing pattern can be specified in Ant format [^ 2].
[^ 2]: For Ant format, search for "ant pattern format" to get various information, so refer to that.
build.gradle
task foo {
doFirst {
FileTree tree = fileTree(dir: "fromDir", include: "**/*.xml")
tree.each { println it }
}
}
Execution result
> gradle foo
F:\etc\...\fromDir\hoge.xml
F:\etc\...\fromDir\sub\foo.xml
In fileTree ()
which receives Map
as an argument, the value to be set in the property of ConfigurableFileTree
can be specified by Map
.
Check the relationship between the classes around the file operation again.
FileCollection
is a collection of File
.
FileTree
is a recursive collection of File
under a particular directory.
There are ConfigurableFileCollection
and ConfigurableFileTree
that inherit each, and it is possible to set to narrow down the target File
.
build.gradle
task foo {
doFirst {
println "foo"
file("foo.txt").text = project.hoge
}
}
The task foo
is a task that outputs the value set in the hoge
property of project
to foo.txt
.
Try running this task several times.
Execution result
> gradle foo -Phoge=HOGE
> Task :foo
foo
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
> gradle foo -Phoge=HOGE
> Task :foo
foo
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
As a matter of course, when the task is executed, the action of the task is executed and the file is output.
However, as long as the input hasn't changed, this task will only output the same results no matter how many times it is run. It's wasteful to run it every time, even though you know the output doesn't change.
If this task is a time consuming task, it can have a significant impact on build time.
Gradle provides a mechanism to skip the execution of a task if the result of the task does not change. In order to use this mechanism, it is necessary to define the input / output of the task.
build.gradle
task foo {
def outputFile = file("foo.txt")
inputs.property "hoge", project.hoge
outputs.file outputFile
doFirst {
println "foo"
outputFile.text = hoge
}
}
Execution result
> gradle foo -Phoge=HOGE
> Task :foo
foo
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
> gradle foo -Phoge=HOGE
> Task :foo UP-TO-DATE
BUILD SUCCESSFUL in 3s
1 actionable task: 1 up-to-date
> gradle foo -Phoge=HOGEHOGE
> Task :foo
foo
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
When the hoge
value is set to the same value as the first time and executed, ʻUP-TO-DATE (latest) is output to the right of the
footask display, and the task execution is skipped. After changing the value of
hoge, the
foo` task was executed again.
build.gradle
task foo {
...
inputs.property "hoge", project.hoge
outputs.file outputFile
...
}
To define the input / output of a task, first access the object for defining the input / output using the following two properties defined in Task
.
The respective properties are TaskInputs and TaskOutputs. current / javadoc / org / gradle / api / tasks / TaskOutputs.html) is returned. Use the methods provided by these classes to define specific inputs and outputs.
Gradle records the contents of input and output when a task is executed. Then, the next time the same task is executed, check if the input / output contents have changed [^ 1]. If the I / O hasn't changed, Gradle will skip executing the task. If any of the values specified for I / O have changed, the task will be executed normally.
[^ 1]: If the output file is deleted, it needs to be re-executed, so you need to check the output status as well.
Both inputs and outputs must be defined. If you specify only one (input only, output only), this skip mechanism will not work.
The following three can be widely used for input.
--Properties - property(String, Object) - properties(Map)
The property can be any key and value, as shown in the example above. However, the values must be serializable and comparable with ʻequals () `.
If you specify a file, it is checked to see if the contents of the file have changed.
If a directory is specified, the status under that directory is checked. When a file or directory is created in a directory or the contents of the file are changed, it is judged that the input has changed. This judgment recursively targets subdirectories and below.
build.gradle
task foo {
...
inputs.file file("input.txt") //Input file specification
inputs.dir file("inputDir") //Specifying the input directory
...
}
The following two outputs can be used.
build.gradle
task foo {
...
outputs.file file("output.txt") //Specifying the output file
outputs.dir file("outputDir") //Specifying the output directory
...
}
build.gradle
tasks.addRule("Pattern: echo<MESSAGE>") { taskName ->
if (taskName.startsWith("echo")) {
task(taskName) {
doFirst { println(taskName - "echo") }
}
}
}
Execution result
> gradle echoHoge
Hoge
> gradle tasks
...
Rules
-----
Pattern: echo<MESSAGE>
AddRule () method of TaskContainer
You can use to define what is called a task rule.
ʻAddRule ()passes the task rule description in the first argument and the closure in the second argument. The closure is passed the name of the task it was trying to reference (here, the string
" echoHoge "` specified on the command line is passed).
If you define a task with the name received in this closure, no error will occur even if the task with that name is not predefined, and the dynamically defined task will be adopted. In other words, even for tasks for which the task name cannot be defined statically in advance, the task can be dynamically defined at runtime by providing a pattern (rule) in the task name.
The rules defined in this task rule can also be referred to by dependsOn
etc.
build.gradle
tasks.addRule("Pattern: echo<MESSAGE>") { taskName ->
if (taskName.startsWith("echo")) {
task(taskName) {
doFirst { println(taskName - "echo") }
}
}
}
task foo(dependsOn: echoBar) {
doFirst { println "foo" }
}
Execution result
> gradle foo
Bar
foo
Here, ʻechoBaris specified in
dependsOn of the
foo task. Again, the task rules apply and the ʻechoBar
task is dynamically generated.
build.gradle
apply plugin: "base"
Execution result
> gradle tasks
...
Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
When you load the base
plugin, the clean
rule will be applied.
(This plugin is loaded together if you apply Java plugin etc., so basically it is not loaded explicitly)
This rule creates a task that deletes the output file of any task.
build.gradle
apply plugin: "base"
task foo {
def outputFile = file("foo.txt")
outputs.file outputFile
doFirst {
outputFile.text = "foo!!"
}
}
Here we define a task called foo
.
This task defines a file called foo.txt
as the output file.
Execution result
> type foo.txt
The specified file could not be found.
> gradle foo
...
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
> type foo.txt
foo!!
> gradle cleanFoo
...
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
> type foo.txt
The specified file could not be found.
The task cleanFoo
is dynamically generated by the clean
rule and is defined to delete the output file of the foo
task.
Tasks created by task ()
can only be used in that project.
Also, the same process cannot be reused in multiple locations with different parameters.
If you want to reuse the same task in multiple projects with different parameters, create a custom task.
build.gradle
class FooTask extends DefaultTask {
@TaskAction
def foo() {
println("FOO!!")
}
}
task foo(type: FooTask)
Execution result
> gradle foo
FOO!!
Custom tasks are created by inheriting DefaultTask
.
If you define an arbitrary method and annotate it with @TaskAction, that method will be the action of the task. Will be executed.
To use the custom task you created, receive a Map
[task ()](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api" Use .Project: task (java.util.Map,% 20java.lang.String)).
In this argument Map
, specify the type of task you want to create in type
.
Then, the task object will be created with the specified type.
Here, custom tasks are defined in the same build script for the sake of simplicity.
However, in reality, it will be defined in an external location that can be referenced by multiple projects (such as buildSrc
described later or an external jar file).
build.gradle
class FooTask extends DefaultTask {
String message
@TaskAction
def foo() {
println("FOO!! message=${message}")
}
}
task foo(type: FooTask) {
message = "Hello World"
}
Execution result
> gradle foo
FOO!! message=Hello World
A task created by specifying a type with type
is an object of that type.
In other words, if you define the properties and methods for setting in the custom task, you can specify the setting of the task object anywhere you can access the generated task object.
Here, the value is set in the message
property in the setting block.
build.gradle
class FooTask extends DefaultTask {
List inputFiles = []
Object output
@TaskAction
def foo() {
def out = project.file(this.output)
out.text = ""
project.files(this.inputFiles)
.each { out.append("${it.name}\n") }
}
}
task foo (type: FooTask) {
inputFiles = ["fromDir/hoge.txt", "build.gradle", file(".gitignore")]
output = file("output/test.txt")
}
When defining a file or directory for task input / output, the type should be a type that can contain anything such as ʻObject or
List. Then, when actually using the setting value, convert it using
file ()or
files ()of
Project`.
This allows you to specify files in a variety of formats supported by Project # file ()
, such as strings and File
objects, giving you greater configuration flexibility.
build.gradle
class FooTask extends DefaultTask {
@Input
String message
@OutputFile
Object output
@TaskAction
def foo() {
project.file(output).text = message
}
}
task foo(type: FooTask) {
message = "Hello World!!"
output = "foo.txt"
}
Execution result
> gradle foo
...
BUILD SUCCESSFUL in 4s
1 actionable task: 1 executed
> type foo.txt
Hello World!!
> gradle foo
...
BUILD SUCCESSFUL in 4s
1 actionable task: 1 up-to-date
Custom tasks provide a way to define task I / O with annotations.
You can define task input / output by annotating custom task fields and Getter methods with the following annotations.
--Input - @Input - @InputFile - @InputFiles - @InputDirectory --Output - @OutputFile - @OutputFiles - @OutputDirectory - @OutputDirectories
Task classes are provided as standard for processes that are likely to be used frequently. For example, there are the following tasks.
Copy
build.gradle
task foo(type: Copy) {
from "fromDir"
into "toDir"
}
Execution result
> tree /f fromDir
...
│ hoge.txt
│
└─sub
foo.txt
> gradle foo
...
> tree /f toDir
│ hoge.txt
│
└─sub
foo.txt
Copy The task performs a copy of a file or directory.
CopySpec implemented by Copy
provides the setting of the copy method. Use the API that is available.
CopySpec
provides various methods for narrowing down the copy target and specifying the copy destination, and it is possible to make quite flexible settings.
build.gradle
task foo(type: Copy) {
from "fooDir", "barFile"
from file("fizzDir"), file("buzzFile")
...
}
Specify the copy source file or directory.
You can specify multiple arguments, or you can call from ()
itself multiple times.
The value passed as an argument is finally Project
's [files (Object ...)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle .api.Project: passed to the files (java.lang.Object [])) method.
Therefore, you can pass various values such as a string path or a File
object.
build.gradle
task foo(type: Copy) {
from("fromDir") {
include "**/*.txt"
}
...
}
You can pass a closure as the second argument of from ()
.
The delegate
of this closure is CopySpec
, and it is possible to further narrow down the copy conditions only under the directory specified by from ()
.
Here, the files to be copied by ʻinclude ()are limited to
* .txt`.
build.gradle
task foo(type: Copy) {
into "toDir"
...
}
Specify the copy destination directory.
build.gradle
task foo(type: Copy) {
include "*.txt", "**/*.xml"
...
}
You can specify the conditions to be included in the copy target by specifying the pattern in Ant format.
build.gradle
task foo(type: Copy) {
exclude "**/*.class", "**/*.bk"
...
}
Here you can also specify conditions to exclude from copying with an Ant format pattern.
build.gradle
task foo(type: Copy) {
from "fromDir"
into "toDir"
rename { name ->
name.toUpperCase()
}
}
You can use rename ()
to rename the file when copying.
In the case of rename ()
that receives a closure, the file name before copying (String
) is passed to the argument of the closure.
Then, the value returned by the closure is used as the file name after copying.
In the case of the above implementation, the file name in which the original file name is all uppercase is used as the copy destination file name.
build.gradle
task foo(type: Copy) {
from "fromDir"
into "toDir"
rename(/(.*)\.([a-z]+)/, '$1_copy.$2')
}
Rename ()
, which receives two String
as arguments, passes a regular expression as the first argument and the renamed expression as the second argument.
The part defined as a group in the regular expression of the first argument (the part enclosed by ()
) can be referred to by $ n
in the expression of the second argument ( n
is a serial number from 1).
In the above example, the first group is (. *)
, Which points to the base name of the file before the extension.
The second group is the ([a-z] +)
part, which points to the extension part.
Then, by specifying $ 1_copy. $ 2
in the expression after renaming, the file is renamed to the name with _copy
at the end of the base name and copied.
When actually moved, it becomes as follows.
Execution result
> tree /f fromDir
...
│ hoge.txt
│ hoge.xml
│
└─sub
foo.txt
foo.xml
> gradle foo
...
> tree /f toDir
...
│ hoge_copy.txt
│ hoge_copy.xml
│
└─sub
foo_copy.txt
foo_copy.xml
For the Project
class, copy (Closure).
The argument closure delegate
implements the CopySpec
interface, which allows you to specify the copy target in the same way as the Copy
task.
build.gradle
task foo {
doFirst {
copy {
from "fromDir"
into "toDir"
include "**/*.txt"
}
}
}
If you want to perform several copy operations in a single task, you can also use the copy ()
method of this Project
.
Delete
build.gradle
task foo(type: Delete) {
delete "targetDir/aaa.txt", "targetDir/hoge"
}
Execution result
> tree /f targetDir
│ aaa.txt
│ bbb.txt
│
└─hoge
│ ccc.txt
│
└─fuga
ddd.txt
> gradle foo
...
BUILD SUCCESSFUL in 5s
> tree /f targetDir
bbb.txt
Delete You can use the task to delete files and folders.
The Delete
class implements the DeleteSpec interface.
[Delete (Object ...)] defined in this interface (https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Delete.html#org.gradle.api.tasks .Delete: delete (java.lang.Object [])) Specify the deletion target with the method.
Since the value specified by the argument is passed to the Project # files (Object ...)
method, it can be specified by various types such as a character string and File
.
Delete
itself does not provide an API that allows you to specify" a file in a specific directory with a specific pattern ".
However, as mentioned above, the value passed to delete ()
is passed to Project # files ()
, so you can also pass FileCollection
.
In other words, it can be specified as follows.
build.gradle
task foo(type: Delete) {
delete fileTree(dir: "targetDir", include: "hoge/**/*.txt")
}
Execution result
> tree /f targetDir
│ aaa.txt
│ bbb.txt
│
└─hoge
│ ccc.txt
│
└─fuga
ddd.txt
> gradle foo
...
BUILD SUCCESSFUL in 5s
> tree /f targetDir
│ aaa.txt
│ bbb.txt
│
└─hoge
└─fuga
Like copy ()
, [delete (Action)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:delete (org.org.) A method called gradle.api.Action)) is provided in Project
.
The closure's delegate
implements DeleteSpec
, so you can specify what to delete in the same way as the Delete
task.
build.gradle
task foo {
doFirst {
project.delete { delete "targetDir" }
}
}
The caveat here is that every time you call the delete ()
method, you must prefix it with project
, such asproject.delete ()
.
build.gradle
task foo {
doFirst {
project.delete {
println "project.delete.delegate=${delegate.class}"
}
delete {
println "delete.delegate=${delegate.class}"
}
}
}
Execution result
> gradle foo
project.delete.delegate=class org.gradle.api.internal.file.delete.DefaultDeleteSpec
delete.delegate=class build_32bm3o8iprxruz9mv43mbtt86$_run_closure1$_closure2
For project.delete ()
, delegate
is DeleteSpec
, but for delete ()
only, it is not DeleteSpec
.
By the way, for copy ()
, there is no problem because delegate
becomes CopySpec
even if project
is not added.
Apparently, if you only have a method that takes ʻAction as an argument, you'll need
project (
copy ()has
copy (Closure)and
copy (Action), but
delete ( ) Only
delete (Action) `).
To be honest, I'm not sure why this is happening.
Sync
build.gradle
task foo(type: Sync) {
from "fromDir"
into "toDir"
include "**/*.txt"
}
Execution result
> tree /f fromDir
│ aaa.txt
│ bbb.txt
│ eee.xml
│
└─hoge
│ ccc.txt
│ fff.xml
│
└─fuga
ddd.txt
ggg.xml
> tree /f toDir
hoge.txt
> gradle foo
...
BUILD SUCCESSFUL in 5s
> tree /f toDir
│ aaa.txt
│ bbb.txt
│
└─hoge
│ ccc.txt
│
└─fuga
ddd.txt
Sync The task is the same as the directory specified by ʻintoand the directory specified by
from. Update to state. Since
Sync implements
CopySpec, you can control the copying method in the same way as the
Copy` task.
The hoge.txt
that existed in toDir
before the task was executed has been deleted.
Syncing with Sync
is done ** by default after deleting all the destination files and folders **.
If you have a file or folder that exists only in the sync destination and you don't want to delete it, [preserve](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Sync.html Specify # org.gradle.api.tasks.Sync: preserve (org.gradle.api.Action)).
build.gradle
task foo(type: Sync) {
from "fromDir"
into "toDir"
include "**/*.txt"
preserve {
include "hoge.txt"
}
}
Execution result
> tree /f toDir
fuga.txt
hoge.txt
piyo.txt
> gradle foo
...
BUILD SUCCESSFUL in 5s
> tree /f toDir
│ aaa.txt
│ bbb.txt
│ hoge.txt
│
└─hoge
│ ccc.txt
│
└─fuga
ddd.txt
The target specified by ʻinclude in
preserve is no longer deleted (
preserve` means" save ").
It is also possible to specify ʻexclude, and if ʻexclude" hoge.txt "
is specified, only hoge.txt
will be deleted (it will not be saved).
If ʻinclude and ʻexclude
are specified at the same time, the setting of ʻinclude` seems to take precedence.
As you can imagine, sync (Action) The method is also provided in Project
.
However, the closure's delegate
implements the CopySpec
interface.
In other words, preserve
cannot be specified (the reference says that preserve
can be specified, but if you actually write it, an error will occur).
build.gradle
task foo {
doFirst {
project.sync {
from "fromDir"
into "toDir"
include "**/*.txt"
}
}
}
Since sync ()
has only sync (Action)
, an error will occur unless the project
qualification is added likedelete ()
.
Exec
build.gradle
task foo(type: Exec) {
workingDir "workDir"
environment MESSAGE: "Hello World!!"
commandLine "cmd", "/c", "echo", "%MESSAGE%", ">", "hello.txt"
}
Execution result
> gradle foo
...
BUILD SUCCESSFUL in 5s
> type workDir\hello.txt
Hello World!!
The Exec (https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html) task allows you to execute any command. ʻExec` implements the ExecSpec interface, which defines the methods for configuration. ..
[commandLine (Object ...)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html#org.gradle.api.tasks.Exec: commandLine (java.) In lang.Object [])), specify the command you want to execute.
On Linux you can start with the command you want to execute directly, but on Windows you need to continue with cmd / c <command you actually want to execute>
.
[workingDir (Object)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html#org.gradle.api.tasks.Exec:workingDir (java.lang.Object) You can specify the working directory at runtime with)).
environment (Map)), You can set environment variables in the subprocess that is executing the command.
build.gradle
task foo {
doFirst {
exec {
commandLine "cmd", "/c", "echo", "Hello World!!"
}
}
}
Project
exec (Closure)) You can use the method to execute commands in the same way as ʻExec`.
The closure's delegate
is ʻExecSpec`.
Zip
build.gradle
task foo(type: Zip) {
baseName = "foo"
from "fromDir"
destinationDir = file("toDir")
include "**/*.txt"
}
Execution result
> tree /f fromDir
│ aaa.txt
│ bbb.txt
│ eee.xml
│
└─hoge
│ ccc.txt
│ fff.xml
│
└─fuga
ddd.txt
ggg.xml
> gradle foo
...
BUILD SUCCESSFUL in 5s
> dir /b toDir
foo.zip
The Zip task allows you to zip any directory.
The Zip
implements the CopySpec
interface, so it can be targeted in the same way as the Copy
task.
However, the output destination is destinationDir Specify in the property.
Note that this property is File
, not ʻObject. If you have applied the
base plugin,
destinationDirdefaults to
build / distributions. If the
base plugin is not applied (basically impossible ...), specifying
destinationDir` is mandatory.
The name of the zip file is created by concatenating the values set in some properties.
build.gradle
task foo(type: Zip) {
baseName = "baseName"
appendix = "appendix"
version = "version"
classifier = "classifier"
extension = "extension"
from "fromDir"
destinationDir = file("toDir")
include "**/*.txt"
}
Execution result
> gradle foo
...
BUILD SUCCESSFUL in 5s
> dir /b toDir
baseName-appendix-version-classifier.extension
The entire file name is created by connecting each property with a hyphen as follows.
(The property that is not set is ignored (if ʻextension is not set, it becomes
"zip" `))
baseName-appendix-version-classifier.extension
If you want to specify the entire file name at once, [archiveName](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html#org.gradle.api.tasks .bundling.Zip: archiveName) Specify the property.
The path of the zip file generated by the Zip
task is [archivePath](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html#org.gradle" You can check it with the .api.tasks.bundling.Zip: archivePath) property.
Unfortunately? There is no method called zip ()
in Project
.
It's not a substitute, but conversely [zipTree (Object)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle. api.Project: zipTree (java.lang.Object)) method is provided.
If you specify the path of the target zip file as an argument, FileTree
containing the information of the contents of the zip is returned.
build.gradle
task foo {
doFirst {
def zip = zipTree("toDir/foo.zip")
zip.each { println it }
}
}
Execution result
> gradle foo
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\aaa.txt
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\bbb.txt
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\eee.xml
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\hoge\ccc.txt
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\hoge\fff.xml
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\hoge\fuga\ddd.txt
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\hoge\fuga\ggg.xml
Immediately after doing zipTree ()
, the zip file has not been unzipped yet.
When you try to access the information of the file in ZipTree
, the zip file is actually unzipped to the temporary directory and you can access the information of the file.
By combining this zipTree ()
and copy ()
, you can reproduce the decompression of the zip.
build.gradle
task foo {
doFirst {
copy {
from zipTree("toDir/foo.zip")
into "build/unzip"
}
}
}
This will unzip the contents of the zip under the build / unzip
directory.
buildSrc Gradle has its own project for deploying classes used in build scripts.
Folder structure
|-build.gradle
`-buildSrc/
`-src/main/groovy/
`-sample/
`-HogeTask.groovy
The root buildSrc
directory becomes a dedicated project.
You can place your own task and plugin code under the src / main / groovy
directory of this project.
HogeTask.groovy
package sample
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class HogeTask extends DefaultTask {
@TaskAction
def hoge() {
println("Hello Hoge.")
}
}
build.gradle
task foo(type: sample.HogeTask)
Execution result
> gradle foo
Hello Hoge.
If the source code exists in buildSrc
, it will be automatically compiled when the task is executed and made available in the build script.
By using a custom task, the logic of the task can be defined in advance, and only the set value can be switched and reused in multiple places. However, a custom task is just a single task, and all task generation and detailed settings must be specified by the user.
In order to share and reuse a wider range of processes such as creating multiple tasks and defining dependencies between tasks, a mechanism called ** plug-in ** is used.
Folder structure
|-build.gradle
`-buildSrc/
`-src/main/groovy/
`-sample/
`-FooPlugin.groovy
FooPlugin.groovy
package sample
import org.gradle.api.Project
import org.gradle.api.Plugin
class FooPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.task("hello") {
doFirst { println("FOO!!") }
}
}
}
build.gradle
apply plugin: sample.FooPlugin
Execution result
> gradle hello
FOO!!
Plugin is created by implementing the Plugin interface (type argument should be Project
) ..
ʻApply (T)method is implemented, and the plug-in process is described in it. The content to be described is the same as the content normally described in
build.gradle, and there is no problem. However, in the case of
build.gradle, since the delegation to
Projectwas implicitly done, it was possible to call
task ()etc. directly, but here the implicit delegation is done. Since there is no such thing, it is necessary to explicitly call the method of
Project`.
The created plugin can be loaded with ʻapply` like any other plugin.
If you want to pass the setting value to the plug-in, use the mechanism of the extension object.
FooPlugin.groovy
package sample
import org.gradle.api.Project
import org.gradle.api.Plugin
class FooPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
def foo = project.extensions.create("foo", FooPluginExtension)
project.task("hello") {
doFirst { println(foo.message) }
}
}
}
class FooPluginExtension {
String message
}
To create an extension object, use the create ()
method of ExtensionContainer.
ʻExtensionContaineris obtained from the [extensions](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:extensions) property of
Project` it can.
The create ()
method passes the name of the extension object in the first argument and the type of the extension object in the second argument.
Here, " foo "
is specified as the name, and FooPluginExtension
is specified as the type.
The return value of create ()
is an object of the class specified by the second argument, and the setting value specified in the build script can be accessed through this object.
The generated extension object has been added as a property to Project
so that it can be referenced by the name specified increate ()
.
build.gradle
apply plugin: sample.FooPlugin
foo.message = "Hello Foo!!"
Execution result
> gradle hello
Hello Foo!!
Since the setting block is available for the extension object, it can also be described as follows.
build.gradle
apply plugin: sample.FooPlugin
foo {
message = "Hello Foo!!"
}
When there are multiple setting values, the description can be simplified by using the setting block.
If you want to receive the argument in the constructor of the extension object, write as follows.
FooPlugin.groovy
package sample
import org.gradle.api.Project
import org.gradle.api.Plugin
class FooPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
def foo = project.extensions.create("foo", FooPluginExtension, project)
project.task("hello") {
doFirst { println(foo.outputDir) }
}
}
}
class FooPluginExtension {
Object outputDir
FooPluginExtension(Project project) {
this.outputDir = project.file("${project.buildDir}/foo")
}
}
The extension object FooPluginExtension
takes a Project
object and sets the default value of ʻoutputDir to the value generated from
Project`.
To pass a value to this constructor argument, implement it so that the corresponding value is passed after the third argument of the create ()
method.
The third argument of create ()
is a variadic argument, and the value specified here is passed to the constructor of the extension object as it is.
FooPlugin.groovy
package sample
import org.gradle.api.Project
import org.gradle.api.Plugin
class FooPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
def foo = project.extensions.create("foo", FooPluginExtension)
project.task("copyFile") {
inputs.file foo.inputFile
outputs.dir foo.outputDir
doFirst {
project.copy {
from foo.inputFile
into foo.outputDir
}
}
}
}
}
class FooPluginExtension {
Object inputFile
Object outputDir
}
It creates a task called copyFile
and implements it to copy the extension object ʻinputFile to ʻoutputDir
.
Also, ʻinputFile and ʻoutputDir
are set for input / output of the copyFile
task, respectively.
build.gradle
apply plugin: sample.FooPlugin
foo {
inputFile = "fromDir/aaa.txt"
outputDir = "build"
}
When I do this, the build fails as follows:
Execution result
> gradle copyFile
...
> Task :copyFile FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Some problems were found with the configuration of task ':copyFile'.
> No value has been specified for property '$1'.
...
With that alone, I don't know what you're talking about, but if you add the following debug output, you'll see what's going on.
FooPlugin.groovy
package sample
import org.gradle.api.Project
import org.gradle.api.Plugin
class FooPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println "FooPlugin.apply()start"
def foo = project.extensions.create("foo", FooPluginExtension)
project.task("copyFile") {
inputs.file foo.inputFile
outputs.dir foo.outputDir
println "foo.inputFile=${foo.inputFile}"
println "foo.outputDir=${foo.outputDir}"
doFirst {
project.copy {
from foo.inputFile
into foo.outputDir
}
}
}
println "FooPlugin.apply()End"
}
}
class FooPluginExtension {
Object inputFile
Object outputDir
}
build.gradle
println "Before applying FooPlugin"
apply plugin: sample.FooPlugin
println "After applying FooPlugin"
println "foo Specify setting value for extension object"
foo {
inputFile = "fromDir/aaa.txt"
outputDir = "build"
}
Execution result
> gradle createFile
...
Before applying FooPlugin
FooPlugin.apply()start
foo.inputFile=null
foo.outputDir=null
FooPlugin.apply()End
After applying FooPlugin
foo Specify setting value for extension object
> Task :copyFile FAILED
...
At the stage of setting the input / output of the createFile
task, the value has not been set for the foo
extension object yet.
Therefore, the input / output specification is null
.
The application of the plug-in is performed in the setting phase of the build life cycle, and basically the setting process is executed in order from the top.
Therefore, in the ʻapply ()method of the custom plug-in, the
foo` extension object has not been set yet, so the value cannot be referenced.
In short, it is in a strange state because you are trying to refer to the value that will be set after applying the plugin while applying the plugin. If all the values set in the extension object are referenced in the execution phase, such a problem does not occur.
However, since the input / output settings must be specified in the setting phase, it will be impossible to set the input / output as it is.
This problem can be avoided by using Property
.
FooPlugin.groovy
package sample
import org.gradle.api.Project
import org.gradle.api.Plugin
import org.gradle.api.provider.Property
class FooPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
def foo = project.extensions.create("foo", FooPluginExtension, project)
project.task("copyFile") {
inputs.file foo.inputFile
outputs.dir foo.outputDir
doFirst {
project.copy {
from foo.inputFile
into foo.outputDir
}
}
}
}
}
class FooPluginExtension {
Property<Object> inputFile
Property<Object> outputDir
FooPluginExtension(Project project) {
this.inputFile = project.objects.property(Object)
this.outputDir = project.objects.property(Object)
}
}
build.gradle
apply plugin: sample.FooPlugin
foo {
inputFile = "fromDir/aaa.txt"
outputDir = "build"
}
Execution result
> gradle copyFile
...
BUILD SUCCESSFUL in 6s
ʻInputFile, ʻoutputDir
uses the type Property for the property type.
An instance of Property
can be created with theproperty (Class)
method of ObjectFactory.
ʻObjectFactory can be obtained from the
getObjects () method of
Project`.
File ()
etc. of Project
supports this Property
type, so you can pass it as it is, but if you need the value of the contents, you can get it by using the get ()
method.
You can also change the value with the set ()
method.
By the way, in the above example, a character string (String
type) is set by the assignment operator for a property that should be of Property
type.
Normally, such a description would result in an error because of the different types.
This is possible because Gradle automatically generates a setter method for a property of type Property
.
build.gradle
apply plugin: sample.FooPlugin
foo {
inputFile = "fromDir/aaa.txt"
outputDir = "build"
println delegate.class.methods.find { it.name == "setInputFile" }
}
Automatically generated Property setter method
> gradle copyFile
...
public void sample.FooPluginExtension_Decorated.setInputFile(java.lang.Object)
...
This assignment operator is syntactic sugar for the automatically generated setInpputFile (Object)
method call.
This Property
type is also available for custom task properties and supports automatic setter generation.
CustomCopyTask.groovy
package sample
import org.gradle.api.provider.Property
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputDirectory
class CustomCopyTask extends DefaultTask {
@InputFile
Property<Object> inputFile = project.objects.property(Object)
@OutputDirectory
Property<Object> outputDir = project.objects.property(Object)
@TaskAction
def copy() {
project.copy {
from this.inputFile
into this.outputDir
}
}
}
build.gradle
task foo(type: sample.CustomCopyTask) {
inputFile = "fromDir/aaa.txt"
outputDir = "build"
}
If you want to declare a property in a collection, you can use ListProperty (https://docs.gradle.org/current/javadoc/org/gradle/api/provider/ListProperty.html) or SetProperty (https://docs. There are subtypes such as gradle.org/current/javadoc/org/gradle/api/provider/SetProperty.html). In both cases, ʻObjectFactory` has a factory method, so you can create an object from it.
Gradle has a ** lifecycle task **.
Lifecycle tasks do not process themselves. Instead, it plays a role by expressing some concept and relying on other tasks.
For example, in Base Plugin, [check](https://docs.gradle.org/current/userguide/base_plugin.html# A task called sec: base_tasks) is defined.
This check
task itself does nothing, but expresses the concept of" performing project verification processing ".
If a plugin adds a task that performs validation, add that validation task to the check
dependency.
If all the plugins are implemented this way, all you need to do is run check
and all the validations will work.
For example, the Java Plugin (https://docs.gradle.org/current/userguide/java_plugin.html#lifecycle_tasks) adds the test
task to the check
dependency.
This means that if you run check
, you will also run test
.