1 Introduction
This article is the seventh article of the Gradle series , which brings you Task
the knowledge points related to the core of Gradle construction.
2. What is the Task in Gradle
Task
is a task and is Gradle
the smallest of the 构建单元
.
The core of Gradle's construction is a directed acyclic graph composed of Task:
Task is the core object of Gradle's construction. Task can receive input, perform certain operations, and generate output according to the executed operations.
Task manages a Action
List, you can insert an Action (doFirst) in front of the List, or insert (doLast) from the back of the List, Action is the smallest 执行单元
.
3. How to create a task
3.1 Where is the Task written?
First think about where is the Task written?
We introduced in the fourth life cycle of the Gradle series that there are three stages. The first stage initialization will determine which projects participate in the compilation. The second stage is to parse the configuration and generate the Task Registry (DAG). The third stage The stage is to execute the Task in sequence.
In reverse, the execution of a Task requires a Task registry, and the source of the Task needs to determine which projects participate in the compilation, that is to say, the Task registry is determined by the projects participating in the compilation, that is, it can be understood that the Task is determined by the object, Project
so Task is created in Project, a build.gradle file corresponds to a Project object, so we can create Task directly in build.gradle file.
As long as the running context is in the Project .
3.2. Create Task
To create a Task, you need to use the register method of TaskContainer.
Several ways to register:
- register(String name, Action<? super Task> configurationAction)
- register(String name, Class type, Action<? super T> configurationAction)
- register(String name, Class type)
- register(String name, Class type, Object… constructorArgs)
- register(String name)
1 and 2 are more commonly used.
- configurationAction refers to Action, that is, the operation of the Task, which will be executed at compile time;
- The type type refers to the Task type, which can be a custom type, or you can specify the built-in Copy, Delete, Zip, Jar and other types;
We can create a task directly in the build.gradle file:
tasks.register("yechaoa") {
println("Task Name = " + it.name)
}
The above call is the method 1 of register. If the last parameter is a closure, it can be written outside the parameter.
We created the above task through TaskContainer (tasks). The method of creating Task is also provided in the Project object. There is a little difference in the way of writing:
task("yechaoa") {
println "aaa"
}
Task can also be created through Plugin, and the rewritten apply method will have a Project object.
4. How to execute Task
4.1, execute a single Task
Order:
./gradlew taskname
Example:
tasks.register("yechaoa") {
println("Task Name = " + it.name)
}
implement:
./gradlew yechaoa
output:
Task Name = yechaoa
4.2, Execute multiple Tasks
./gradlew taskname taskname taskname
Tasks are separated by spaces.
4.3, Task with the same name
If there are two Tasks with the same name, the compilation will fail, that is, InvalidUserDataException
* What went wrong:
A problem occurred evaluating project ':app'.
> Cannot add task 'yechaoa' as a task with that name already exists.
4.4, Task execution results
We often see a label behind the Task at compile time, which indicates the execution result of the Task.
> Task :app:createDebugVariantModel UP-TO-DATE
> Task :app:preBuild UP-TO-DATE
> Task :app:preDebugBuild UP-TO-DATE
> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
> Task :app:compileDebugAidl NO-SOURCE
> Task :app:compileDebugRenderscript NO-SOURCE
> Task :app:generateDebugBuildConfig UP-TO-DATE
.....
4.4.1、EXCUTED
Indicates Task execution, common.
4.4.2、UP-TO-DATE
> Task :app:preBuild UP-TO-DATE
It indicates that the output of the Task has not changed.
There are several situations:
- Neither input nor output has changed;
- The output has not changed;
- Task has no operation and has dependencies, but the content of dependencies is the latest, or skipped, or reused;
- Task has no operations and no dependencies;
4.4.3、FOME-CACHE
Literally, it means that the last execution result can be reused from the cache.
4.4.4、SKIPPED
Literally, it means to skip.
For example is excluded:
$ gradle dist --exclude-task yechaoa
4.4.5、NO-SOURCE
Task does not need to be executed. There are inputs and outputs, but no sources.
5, Task Action
5.1、Action
The Action of Task is the operation required at compile time, it is not one, it is a group, that is, there can be multiple.
Multiple Tasks are generally used when we customize.
5.1.1, Custom Task
class YechaoaTask extends DefaultTask {
@Internal
def taskName = "default"
@TaskAction
def MyAction1() {
println("$taskName -- MyAction1")
}
@TaskAction
def MyAction2() {
println("$taskName -- MyAction2")
}
}
- Customize a class that inherits from
DefaultTask
; - The Action method needs to be
@TaskAction
annotated; - Externally exposed parameters need to use
@Internal
annotations;
Use a custom Task:
tasks.register("yechaoa", YechaoaTask) {
taskName = "我是传入的Task Name "
}
The type is passed into the custom Task class.
Results of the:
> Task :app:yechaoa
我是传入的Task Name -- MyAction1
我是传入的Task Name -- MyAction2
If the constructor of the Action method passes parameters, the parameters can be written after the type type:
tasks.register('yechaoa', YechaoaTask, 'xxx')
5.2、doFirst
It is a kind of Action and is executed at the head of Task Action. There can be more than one.
5.3、doLast
It is a kind of Action and is executed at the end of Task Action. There can be more than one.
Sample code:
tasks.register("yechaoa") {
it.doFirst {
println("${it.name} = doFirst 111")
}
it.doFirst {
println("${it.name} = doFirst 222")
}
println("Task Name = " + it.name)
it.doLast {
println("${it.name} = doLast 111")
}
it.doLast {
println("${it.name} = doLast 222")
}
}
Results of the:
Task Name = yechaoa
> Task :app:yechaoa
yechaoa = doFirst 222
yechaoa = doFirst 111
yechaoa = doLast 111
yechaoa = doLast 222
The output of Task Name is in the configuration phase of the Gradle life cycle, because it is under the closure, not in any Action, and there is no execution opportunity. When the configuration phase resolves to this Task, println will be executed.
Other outputs are under Task :app:yechaoa, because there is a clear timing for Action execution.
5.4, Action execution sequence
The execution order of Action is also a misunderstanding of most people. Through the above log, let's sort it out:
- doFirst: reverse order
- Action: normal order
- doLast: positive sequence
6. Task attribute
The attributes of Task are as follows:
String TASK_NAME = "name";
String TASK_DESCRIPTION = "description";
String TASK_GROUP = "group";
String TASK_TYPE = "type";
String TASK_DEPENDS_ON = "dependsOn";
String TASK_OVERWRITE = "overwrite";
String TASK_ACTION = "action";
Attribute configuration is easier to understand, as the name suggests.
If your IDEA has Gradle visual management, such as Android Studio, then you can find our custom Task in the Gradle panel menu on the right, and double-click to execute it.
7. Task dependency
Gradle already has a set of task building process by default. If you want to add a custom task to this process or do some aspect programming before and after a built-in task, you need to understand the dependencies of the task.
7.1、dependsOn
tasks.register("yechaoa111") {
it.configure {
dependsOn(provider {
tasks.findAll {
it.name.contains("yechaoa222")
}
})
}
it.doLast {
println("${it.name}")
}
}
tasks.register("yechaoa222") {
it.doLast {
println("${it.name}")
}
}
implement:
./gradlew yechaoa111
output:
> Task :app:yechaoa222
yechaoa222
> Task :app:yechaoa111
yechaoa111
The Task yechaoa111 that defines dependsOn is executed after the target Task yechaoa222.
In fact, compared to the above, the more common way of writing dependsOn is this:
def yechaoa111 = tasks.register("yechaoa111") {
it.doLast {
println("${it.name}")
}
}
def yechaoa222 = tasks.register("yechaoa222") {
it.doLast {
println("${it.name}")
}
}
yechaoa111.configure {
dependsOn yechaoa222
}
The task that dependsOn depends on can be a name or a path
It can also be a type type:
dependsOn tasks.withType(Copy)
If it is a task of another project (Project), it can also:
dependsOn "project-lib:yechaoa"
7.2、finalizedBy
Add the specified finalizer task to Task. That is to specify the next task to be executed, and dependsOn specifies the previous one.
task taskY {
finalizedBy "taskX"
}
Here it means that taskX is executed after taskY is executed.
If finalizedBy is replaced with dependsOn, it means that taskX must be executed before taskY is executed.
7.3、mustRunAfter
def yechaoa111 = tasks.register("yechaoa111") {
it.doLast {
println("${it.name}")
}
}
def yechaoa222 = tasks.register("yechaoa222") {
it.doLast {
println("${it.name}")
}
}
yechaoa111.configure {
mustRunAfter yechaoa222
}
implement:
./gradlew yechaoa111
output:
> Task :app:yechaoa111
yechaoa111
It can be seen that yechaoa222 has not been executed, because we executed yechaoa111 separately, and we must execute it together to see the order of dependencies.
Let's execute it together again:
./gradlew yechaoa111 yechaoa222
output:
> Task :app:yechaoa222
yechaoa222
> Task :app:yechaoa111
yechaoa111
It can be seen that mustRunAfter takes effect, and yechaoa111 is executed after yechaoa222.
7.4、shouldRunAfter
yechaoa111.configure {
shouldRunAfter yechaoa222
}
shouldRunAfter is written in the same way as mustRunAfter.
mustRunAfter is "must run", shouldRunAfter is "should run".
Such as taskB.mustRunAfter(taskA), when taskA and taskB run at the same time, taskB must always run after taskA.
The shouldRunAfter rule is similar, but not the same, because it is ignored in both cases. First, if using that rule would introduce a sort cycle; second, when using parallel execution, all dependencies of a task are satisfied except for the "should run" task, then regardless of whether its "should run" dependencies have run, will run this task.
8. Skip Task
Gradle provides multiple ways to skip Task.
- conditional skip
- exception skip
- disable skip
- timeout skip
8.1, conditional skip
Gradle provides onlyIf(Closure onlyIfClosure)
a method to execute the Task only when the result of the closure returns True.
tasks.register("skipTask") { taskObj ->
taskObj.configure {
onlyIf {
def provider = providers.gradleProperty("yechaoa")
provider.present
}
}
taskObj.doLast {
println("${it.name} is Executed")
}
}
implement:
./gradlew skipTask -Pyechaoa
output:
> Task :app:skipTask
skipTask is Executed
The skipTask will only be executed if parameters are added to the script command -Pyechaoa
.
By analogy, as long as the result of the onlyIf closure is True, the conditions are self-determined.
8.2, Exception skipping
If onlyIf does not meet the requirements, it can also be used StopExecutionException
to skip.
StopExecutionException is an exception. When an exception is thrown, the current Action and subsequent Actions will be skipped, that is, the current Task will be skipped to execute the next Task.
tasks.register("skipTask") { taskObj ->
taskObj.doFirst {
println("${it.name} is Executed doFirst")
}
taskObj.doLast {
def provider = providers.gradleProperty("yechaoa")
if (provider.present) {
throw new StopExecutionException()
}
println("${it.name} is Executed doLast")
}
}
output:
> Task :app:skipTask
skipTask is Executed doFirst
If parameters are added to the script command -Pyechaoa
, an exception will be thrown and skipped.
But the previous Action in the Task will be executed, such as doFirst in the example.
8.3, Disable Skip
Each Task has a enabled
switch, true is enabled, false is disabled, and no operation will be executed after being disabled.
tasks.register("skipTask") { taskObj ->
taskObj.configure {
enabled = true
}
taskObj.doLast {
println("${it.name} is Executed")
}
}
8.4, timeout skip
Task provides timeout
properties for limiting execution time.
If the Task's running time exceeds the specified time, the thread executing the Task will be interrupted.
By default, tasks never time out.
Example:
tasks.register("skipTask") { taskObj ->
taskObj.configure {
timeout = Duration.ofSeconds(10)
}
taskObj.doLast {
Thread.sleep(11 * 1000)
println("${it.name} is Executed")
}
}
output:
> Task :app:skipTask FAILED
Requesting stop of task ':app:skipTask' as it has exceeded its configured timeout of 10s.
---Gradle:buildFinished 构建结束了
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:skipTask'.
> Timeout has been exceeded
The execution is abnormal, and it prompts timeout of 10s, because I sleep for 11s in doLast.
9. Task incremental construction
增量构建
When the input and output of the Task do not change, the execution of the action is skipped. When the input or output of the Task changes, only the changed input or output is processed in the action, so that a Task that has not changed is avoided. Build repeatedly, and only process the changed part when the Task changes, which can improve the construction efficiency of Gradle and shorten the construction time.
An important part of any build tool is the ability to avoid doing work that's already been done. After compiling source files, they do not need to be recompiled unless something changes that affects the output, such as modifying the source file or deleting an output file. Compiling can take a lot of time, so skipping steps when not needed can save a lot of time.
Gradle provides this out-of-the-box incremental build functionality, when you compile, Task is marked as in the console output UP-TO-DATE
, which means the incremental build is working.
Let's take a look at how incremental builds work and how to ensure that Tasks support incremental runs.
9.1. Input and output
In general, tasks require some input and generate some output. We can regard the Java compilation process as an example of Task, the Java source file is used as the input of the Task, and the generated class file, that is, the result of compilation, is the output of the Task.
An important characteristic of an input is that it affects one or more outputs, as shown above, different bytecodes are generated depending on the content of the source file and the minimum version of the Java runtime you want to run the code on.
When writing a Task, you need to tell Gradle which Task properties are input and which are output. If a Task property affects an output, be sure to register it as an input, otherwise the Task will be considered up-to-date when it is not. Conversely, if the property does not affect the output, do not register the property as an input, otherwise the Task may be executed when it is not needed. Also beware of non-deterministic tasks that may generate different outputs for the exact same input, these tasks should not be configured for incremental builds, as the UP-TO-DATE check will not work.
The above two paragraphs of theory are taken from the official website. For novices, it may be a bit difficult to understand. The following will take you to practice it.
9.2. Two forms of incremental construction
- The first one, Task can be reused completely, and there is no change in input and output, that is, UP-TO-DATE;
- The second type has some changes, and only needs to operate on the changed part;
9.3, case practice
Scenario: Write a task that copies files and supports incremental builds.
class CopyTask extends DefaultTask {
// 指定输入
@InputFiles
FileCollection from
// 指定输出
@OutputDirectory
Directory to
// task action 执行
@TaskAction
def execute() {
File file = from.getSingleFile()
if (file.isDirectory()) {
from.getAsFileTree().each {
copyFileToDir(it, to)
}
} else {
copyFileToDir(from, to)
}
}
/**
* 复制文件到文件夹
* @param src 要复制的文件
* @param dir 接收的文件夹
* @return
*/
private static def copyFileToDir(File src, Directory dir) {
File dest = new File("${dir.getAsFile().path}/${src.name}")
if (!dest.exists()) {
dest.createNewFile()
}
dest.withOutputStream {
it.write(new FileInputStream(src).getBytes())
}
}
}
When writing a Task, we need to use annotations to declare the input and output. @InputXXX
means input, @OutputXXX
means output.
- The above code
from
is our input, that is, the file to be copied; to
is our output, the folder to receive;- Then
execute()
the method is the Action executed by the Task.
Let's see how to use
tasks.register("CopyTask", CopyTask) {
from = files("from")
to = layout.projectDirectory.dir("to")
}
Before execution, create data, add a from folder in the app directory, and add a txt1.txt file under it
├── app
│ ├── from
│ │ └── txt1.txt
implement:
./gradlew CopyTask
The txt1.txt file has been copied to the to folder.
The directory structure at this time:
├── app
│ ├── from
│ │ └── txt1.txt
│ └── to
│ └── txt1.txt
9.3.1、UP-TO-DATE
The log just executed:
➜ GradleX git:(master) ✗ ./gradlew CopyTask
...
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
To verify the incremental build, execute again:
➜ GradleX git:(master) ✗ ./gradlew CopyTask
...
BUILD SUCCESSFUL in 1s
1 actionable task: 1 up-to-date
This time we can find that the execution result of the Task has executed
changed from up-to-date
to , indicating that our incremental build has taken effect.
Although the incremental build has taken effect at this time, the degree of completion is not enough, and finer-grained processing is needed, so let's see.
9.3.2. Incremental build
Above we talked about the two forms of incremental construction, and up-to-date has been implemented, and now we need to simulate the input/output 部分变化
scenario.
Scenario: Based on the above scenario, add a txt2.txt file under the from folder, and support incremental builds.
When adding a txt2.txt file and executing the above command again, you will find that the txt1.txt file has been copied again.
This is because our input has changed, and the Action of CopyTask will be fully constructed, and the effect we want is to only copy the txt2.txt file. Only the newly added or modified files are copied, and the unchanged files are not copied.
To achieve this effect, the Action method must be supported 增量构建
.
We need to add a InputChanges
parameter to the Action method. The Action method with the InputChanges type parameter indicates that this is an incremental task operation method. This parameter tells Gradle that the Action method only needs to process the changed input. In addition, the Task also needs to use @Incremental
or @SkipWhenEmpty
to Specify at least one delta file input attribute.
class CopyTask extends DefaultTask {
// 指定增量输入属性
@Incremental
// 指定输入
@InputFiles
FileCollection from
// 指定输出
@OutputDirectory
Directory to
// task action 执行
@TaskAction
void execute(InputChanges inputChanges) {
boolean incremental = inputChanges.incremental
println("isIncremental = $incremental")
inputChanges.getFileChanges(from).each {
if (it.fileType != FileType.DIRECTORY) {
ChangeType changeType = it.changeType
String fileName = it.file.name
println("ChangeType = $changeType , ChangeFile = $fileName")
if (changeType != ChangeType.REMOVED) {
copyFileToDir(it.file, to)
}
}
}
}
/**
* 复制文件到文件夹
* @param src 要复制的文件
* @param dir 接收的文件夹
* @return
*/
static def copyFileToDir(File file, Directory dir) {
File dest = new File("${dir.getAsFile().path}/${file.name}")
if (!dest.exists()) {
dest.createNewFile()
}
dest.withOutputStream {
it.write(new FileInputStream(file).getBytes())
}
}
}
The changes here are divided into two steps:
- Add
@Incremental
annotations to the from attribute to indicate incremental input attributes; InputChanges
The action method execute() has been rewritten, and parameters have been added to support incremental copying of files. ThenChangeType
, only newly added or modified files will be copied according to the verification of the files.
Several types of ChangeType:
public enum ChangeType {
ADDED,
MODIFIED,
REMOVED
}
- ADDED: Indicates that the file is newly added;
- MODIFIED: Indicates that the file is modified;
- REMOVED: Indicates that the file is deleted;
Let's execute it first:
./gradlew CopyTask
output:
> Task :app:CopyTask
isIncremental = false
ChangeType = ADDED , ChangeFile = txt1.txt
Execute for the first time, and there is no incremental build, execute it again to see.
BUILD SUCCESSFUL in 2s
1 actionable task: 1 up-to-date
The second direct up-to-date.
9.3.3、ADDED
At this time, we have not verified the incremental build, we add a txt2.txt file to the from folder, and then execute it
> Task :app:CopyTask
isIncremental = true
ChangeType = ADDED , ChangeFile = txt2.txt
It can be seen from the log that the incremental build of the action has taken effect, and it indicates that the txt2.txt is newly added, and the txt1.txt file is not copied again.
The directory structure at this time:
├── app
│ ├── build.gradle
│ ├── from
│ │ └── txt1.txt
│ └── txt2.txt
│ └── to
│ ├── txt1.txt
│ └── txt2.txt
9.3.4、MODIFIED
We can further verify, add a line "yechaoa" in the txt1.txt file to simulate the modification, and execute it again
> Task :app:CopyTask
isIncremental = true
ChangeType = MODIFIED , ChangeFile = txt1.txt
It is still an incremental build, and indicates that the txt1.txt file is modified, and the txt2.txt file is not copied again.
9.3.5、REMOVED
After verifying the deletion, we delete the txt2.txt file in the from folder and execute it to see
> Task :app:CopyTask
isIncremental = true
ChangeType = REMOVED , ChangeFile = txt2.txt
It is still an incremental build, and indicates that the txt2.txt file has been deleted.
The directory structure at this time:
├── app
│ ├── from
│ │ └── txt1.txt
│ └── to
│ ├── txt1.txt
│ └── txt2.txt
It can be found that although we have deleted the txt2.txt file under the from folder, and the Action of the Task does support incremental builds, the txt2.txt file under the to folder is still there. If the contents of the to folder change If it affects your build results, you still need to deal with it to keep it in sync.
9.4. Incremental vs full volume
Not every execution of Task is an incremental build. We can use the isIncremental method of InputChanges to judge whether this build is an incremental build. However, there are the following situations where a full build will be performed:
- The Task is executed for the first time;
- The Task has only input but no output;
- The upToDateWhen condition of the Task returns false;
- One of the output files of this Task has changed since the last build;
- Since the last build, an attribute input of the Task has changed, such as some basic types of attributes;
- Since the last build, a non-incremental file input of the Task has changed. A non-incremental file input refers to a file input that is not annotated with @Incremental or @SkipWhenEmpty.
When the Task is in full construction, that is, when the isIncremental method of InputChanges returns false, all input files can be obtained through the getFileChanges method of InputChanges, and the ChangeType of each file is ADDED. When the Task is in incremental construction, that is, the InputChanges When the isIncremental method returns true, the input file that only changes can be obtained through the getFileChanges method of InputChanges.
9.5. Commonly used annotation types
annotation | type | meaning |
---|---|---|
@Input | Any Serializable type or dependency resolution result type | A simple input value or dependency resolution result |
@InputFile | File* | a single input file (not a directory) |
@InputDirectory | File* | a single input directory (not a file) |
@InputFiles | Iterable* | iterable of input files and directories |
@OutputFile | File* | a single output file (not a directory) |
@OutputDirectory | File* | a single output directory (not a file) |
@OutputFiles | Map<String, File>*或Iterable | Iterable or map of output files. Using a file tree turns off caching for tasks . |
@OutputDirectories | Map<String, File>*或Iterable | Iterable of output directories. Using a file tree turns off caching for tasks . |
@Nested | any custom type | A custom type that may not implement Serializable, but has at least one field or property marked with one of the annotations in this table. It might even be another @Nested. |
@Internal | any type | Indicates that the property is used internally, but is neither an input nor an output. |
@SkipWhenEmpty | File or Iterable* | Used with @InputFiles or @InputDirectory, tells Gradle to skip the task if the corresponding file or directory is empty, and all other input files declared with this annotation. Tasks that are skipped due to all input files declaring this annotation empty will result in apparently "no source". For example, NO-SOURCE will be emitted in the console output. |
Hint @Incremental . | ||
@Incremental | any type | Used with @InputFiles or @InputDirectory, instructs Gradle to track changes to annotated file properties, so changes can be queried via @ InputChanges.getFileChanges() . Incremental tasks required. |
@Optional | any type | Used with any of the attribute type annotations listed in the optional API documentation. This annotation disables validation checks on the corresponding property. See the Validation section for more details . |
More can be viewed in the document .
9.6. Incremental construction principle
Before executing the Task for the first time, Gradle takes the fingerprint of the input, which contains the path of the input file and a hash of the content of each file. Then execute the Task. If the Task is successfully completed, Gradle will get the output fingerprint, which contains a set of output files and a hash of the content of each file. Gradle will retain two fingerprints the next time the Task is executed.
Each subsequent time before executing the Task, Gradle will perform new fingerprinting on the input and output. If the new fingerprint is the same as the previous fingerprint, Gradle assumes that the output is the latest and skips the Task. If they are not the same, Gradle will execute the Task. . Gradle will keep two fingerprints the next time it executes the Task.
If the file's statistics (i.e. lastModified and size) have not changed, Gradle will reuse the file fingerprint from the last run, i.e. when the file's statistics have not changed, Gradle will not detect changes.
Gradle also considers the Task's code as part of the task's input, and when the Task, Action, or its dependencies change between executions, Gradle considers the Task to be out of date.
Gradle understands whether file properties (such as those holding the Java classpath) are order-sensitive, and when comparing the fingerprints of such properties, even if the file order changes, it will cause the Task to be out of date.
Note that if a Task specifies an output directory, any files added to that directory since the last execution will be ignored and will not cause the Task to become obsolete, so unrelated Tasks may share an output directory without mutual Interference, if for some reason this is not the behavior you want, consider using TaskOutputs.upToDateWhen(groovy.lang.Closure).
Note also that changes to the availability of unavailable files (for example, modifying the target of a broken symlink to a valid file and vice versa), will be detected and handled by up-to-date checks.
The Task's input is also used to compute the build cache key used to load the Task's output when enabled.
10. Find Task
Sometimes we need to find an official Task to hook operations, such as adding an Action; sometimes we can also find a custom Task to make it depend on an official Task.
Finding the Task mainly involves the TaskContainer object. As the name suggests, it is the management class of the Task container, which provides two methods:
- findByPath(String path), the parameter can be empty
- getByPath(String path), the parameter can be empty, if the Task cannot be found, an UnknownTaskException will be thrown
At the same time, TaskContainer inherits from TaskCollection
and NamedDomainObjectCollection
adds two methods that can be used:
- findByName
- getByName
The parameter definition is the same as the xxxByPath method.
10.1、findByName
Example:
def aaa = tasks.findByName("yechaoa").doFirst {
println("yechaoa excuted doFirst by findByName")
}
Find a Task named "yechaoa", add a doFirst Action, and print the log in doFirst.
When we execute aaa at this time, it will not trigger the execution of yechaoa Task Action, because there is no dependency, so we have to execute yechaoa Task.
implement:
./gradlew yechaoa
output:
> Task :app:yechaoa
yechaoa excuted doFirst by findByName
...
You can see that the log we added has been printed out.
10.2、findByPath
Example:
def bbb = tasks.findByPath("yechaoa").doFirst {
println("yechaoa excuted doFirst by findByPath")
}
output:
> Task :app:yechaoa
yechaoa excuted doFirst by findByPath
yechaoa excuted doFirst by findByName
...
10.3、named
Look up Task by name, if there is no exception, UnknownTaskException will be thrown
tasks.named("yechaoa") {
it.doFirst {
println("yechaoa excuted doFirst by named")
}
}
output:
> Task :app:yechaoa
yechaoa excuted doFirst by named
yechaoa excuted doFirst by findByPath
yechaoa excuted doFirst by findByName
...
10.4 Others
withType:
tasks.withType(DefaultTask).configureEach(task -> {
if (task.name.toLowerCase().contains("copytask")) {
println(task.class.name)
}
})
each/forEach/configureEach:
tasks.each {
// do something
}
tasks.forEach(task->{
// do something
})
tasks.configureEach(task -> {
// do something
})
11, extra
11.1 The difference between register and create
In addition to the methods in the above example register
, there are create
methods to create Task, so what is the difference between them?
- When created by register, only when this task is needed, it will be created and configured;
- When created by create, the Task will be created and configured immediately, and added to the TaskContainer;
To put it bluntly, register is a way to create tasks on demand, so that the performance of gradle execution is better (not the performance of your project).
The way create creates a task is no longer officially recommended. Although @Deprecated is not marked yet, it may be abandoned in the future.
However, it should be noted that register is lazy loading, and the task created by nesting cannot be initialized during the configuration phase, so it will not be executed.
In addition to register and create, there is also a replace method, which is used to replace the existing Task with the name.
11.2、Task Tree
We can ./gradlew tasks
view all Tasks through , but we cannot see the dependencies of Tasks.
To view the dependencies of a Task, we can use the task-tree plugin
plugins {
id "com.dorongold.task-tree" version "2.1.1"
}
taskTree
When using it, we only need to add it after the command
gradle <task 1>...<task N> taskTree
Example:
./gradlew build taskTree
output:
:app:build
+--- :app:assemble
| +--- :app:assembleDebug
| | +--- :app:mergeDebugNativeDebugMetadata
| | | \--- :app:preDebugBuild
| | | \--- :app:preBuild
| | \--- :app:packageDebug
| | +--- :app:compileDebugJavaWithJavac
| | | +--- :app:compileDebugAidl
| | | | \--- :app:preDebugBuild *
| | | +--- :app:compileDebugKotlin
......
12. Finally
So far, the introduction of Gradle Task is over.
From the aspects of what is the Task, where to write it, how to write it, how to run it, and how to write it well, etc., it introduces the execution sequence of Task Action, custom Task, Task dependency, and Task incremental construction from shallow to deep. Generally speaking, there are still a lot of knowledge points involved, and it needs to be understood and applied in practice.
I hope this article is helpful to you~
13、Github
https://github.com/yechaoa/GradleX