[JAVA] Gradle task definitions

Various notes about Gradle tasks from basics to applications.

Prerequisite knowledge

-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

environment

Gradle

5.0

Java

openjdk 11.0.1

OS

Windows 10

Task entity

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" `.

Defined in the configuration block

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.

Build life cycle

Gradle execution is roughly divided into three phases.

Phase Contents
Initialization phase
(Initialization)
Determine if the project is single or multi, etc.ProjectCreate an object of.
Configuration phase
(Configuration)
Run the build script andprojectBuild 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.

Task reference

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 inproject.eclipsewill be the extension object. If you want to refer to a task, you need to go throughTaskContainer 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.

Define task dependencies

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).

The task is executed only once

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.

Where and notes where you can write dependsOn

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.

Specified by the argument of task () method

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.

Enforce execution order

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.

Specify post-processing tasks

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.

Specify the conditions to execute

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.

File selection

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.

file(Object)

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.

files(Object...)

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 Files. 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.

getAsPath()

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.

filter(Closure)

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.

fileTree(Object)

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.

fileTree(Object, Closure)

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.

fileTree(Map)

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.

File related class diagram

Check the relationship between the classes around the file operation again.

gradle.png

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.

Task input / output

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 ofhoge, 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.

Input definition

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

Output definition

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

Task rules

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 independsOn of the foo task. Again, the task rules apply and the ʻechoBar task is dynamically generated.

Clean rule provided as standard

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.

Custom 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).

Custom task settings

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.

File settings are defined in Object

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 ()orfiles ()ofProject`.

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.

Input / output specification

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

Tasks provided as standard

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.

from(Object...)

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.

from(Object, Closure)

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`.

into(Object)

build.gradle


task foo(type: Copy) {
    into "toDir"
    ...
}

Specify the copy destination directory.

include(String...)

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.

exclude(String...)

build.gradle


task foo(type: Copy) {
    exclude "**/*.class", "**/*.bk"
    ...
}

Here you can also specify conditions to exclude from copying with an Ant format pattern.

rename(Closure)

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.

rename(String, String)

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

Project copy ()

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

Project delete () method

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 ()hascopy (Closure)andcopy (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 byfrom. 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.

Project sync () method

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.

Project's exec () method

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 tobuild / 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.

Project's zipTree () method

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.

Plugin

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 inbuild.gradle, and there is no problem. However, in the case of build.gradle, since the delegation to Projectwas implicitly done, it was possible to calltask ()etc. directly, but here the implicit delegation is done. Since there is no such thing, it is necessary to explicitly call the method ofProject`.

The created plugin can be loaded with ʻapply` like any other plugin.

Plugin settings

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 ofProject` 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.

Extension object constructor argument

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.

Use the value set in the extension object for the task setting value

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, thefoo` 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.

Define a property using Property

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.

Life cycle task

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.

reference

Recommended Posts

Gradle task definitions
Gradle
Gradle Memorandum
Gradle installation
View the Gradle task in the Spring Boot project