Custom Tasks
Tasks are an essential and complex topic for anyone working with Gradle. This section focuses on simple task creation and straightforward extensions based on Gradle's default tasks or AGP Variant API. It does not cover advanced topics like lazy parameters, task dependency graphs, or complex plugins and tasks. Before diving in, it's helpful to review Gradle's official documentation for a foundational understanding:
Creating Tasks
With the recent development of Gradle's lazy APIs, tasks can now be created lazily or immediately. The create() API immediately executes task creation and configuration, while register() defers it until the task is confirmed to be executed. Generally, the lazy creation API (register()) is preferred, reducing unnecessary performance overhead during the Configuration phase. Understand the differences between these two through the following resources:
- "Task Configuration Avoidance"@Gradle
- "What is the difference between registering and creating in Gradle Kotlin DSL"
While create() returns a Task, register() yields a TaskProvider. If you need to access Task APIs not available in TaskProvider, you can use the configure() method:
Apart from native API creation, it's also essential to know the delegating API creation method, which is more common in build.gradle.kts scripts and other script plugins:
Usually, when extending tasks, it's common to create a custom task class that extends DefaultTask. The most critical aspect of custom tasks is declaring inputs and outputs, as they influence cacheability and task graph computation:
- "Gradle task inputs and outputs"@Tom Gregory
- "Understanding Gradle #06 – Configuring Task Inputs and Outputs"@Jendrik Johannes
For simple tasks like copying a file, you can directly create a Gradle built-in task:
For interaction with other plugins, it often involves configuring built-in tasks from these plugins:
tasks.named('generateDocumentation').configure {
// configuration code ...
}Creating Task Actions
Task actions are the actual carriers of task logic. There are two primary ways to add task actions. The first is by annotating methods within the task with @TaskAction:
Task actions annotated with @TaskAction are automatically detected and executed, but their order is not guaranteed.
The second method, doFirst() / doLast(), is not for ordering but for quickly adding tasks or extending pre/post tasks for existing ones:
- "gradle custom task execution phase"@Peter Ledbrook
- "kotlin-dsl-samples/samples/extra-properties"@Gradle
- "kotlin-dsl-samples/samples/multi-project-with-buildSrc"@Gradle
doFirst() / doLast() should be lightweight, especially as supplements to existing tasks; more complex extensions should be handled with new tasks.
Task Dependencies
Task dependencies in a Gradle project form a Directed Acyclic Graph. To manually adjust task dependencies:
- The
dependsOn(...)API is used to add forward dependencies, transform backward dependencies into forward ones, insert tasks at the beginning or end, etc. - Occasionally, APIs like
shouldRunAfter(...)andmustRunAfter()are used for tasks without direct dependencies. - Avoid using
finalizedByas a backward dependency API; it's meant to add a finalizer task, similar to atry...catch...scenario.
Find more from below resources:
- "Task dependencies"@Gradle
- "Adding dependencies to a task"@Gradle
- "Task API"@Gradle
- "kotlin-dsl-samples/samples/task-dependencies"@Gradle
1, 2, 3 are introductions to Task dependencies and dependsOn, 4 is a practical use case. Scenarios for automatically organizing Task dependencies can refer to the use of the AGP Variant API below, based on the Provider mechanism.
AGP Variant API
Prior to version 7.0, the Android Gradle Plugin (AGP) lacked a defined API for third-party developers, necessitating direct examination of AGP's Task source code to extend its functionality. This involved identifying specific input and output interception points for manipulation. Post version 7.0, the Android team has provided standard entry points for third-party plugin developers:
- "Extend the Android Gradle plugin"@Android
- "android/gradle-recipes"@Android
- "New APIs in the Android Gradle Plugin"@Android
AGP includes plugin extension points to control build inputs and enhance its functionality through new steps that integrate with standard build tasks. Older AGP versions lacked an official API separate from its internal implementation. From version 7.0 onward, AGP will offer a set of stable official APIs that are reliable and trustworthy.
This excerpt, taken from the official documentation updated in late October 2021 (link 1), introduces the Variant API of AGP and its modular design for the first time. The document also details the Variant API's process, extension points, and how to access and modify AGP-produced Artifacts. To further understand this API's categorization and underlying design principles, refer to the "New Variant API" section in link 4, which consolidates content related to the Variant API from the 2020 DevSubmit and 2021 Google I/O. The code in link 2, maintained by the AGP official team, provides Variant API Samples for reference (and copying), such as the current process for obtaining the final output APK:
// https://github.com/android/gradle-recipes/blob/agp-7.0/Kotlin/getApksTest/app/build.gradle.kts#L26
abstract class DisplayApksTask: DefaultTask() {
@get:InputFiles
abstract val apkFolder: DirectoryProperty
@get:Internal
abstract val builtArtifactsLoader: Property<BuiltArtifactsLoader>
@TaskAction
fun taskAction() {
val builtArtifacts = builtArtifactsLoader.get().load(apkFolder.get())
?: throw RuntimeException("Cannot load APKs")
builtArtifacts.elements.forEach {
println("Got an APK at ${it.outputFile}")
}
}
}
androidComponents {
onVariants { variant ->
project.tasks.register<DisplayApksTask>("${variant.name}DisplayApks") {
apkFolder.set(variant.artifacts.get(SingleArtifact.APK))
builtArtifactsLoader.set(variant.artifacts.getBuiltArtifactsLoader())
}
}
}Summary/Extension/Considerations
- For scenarios involving the extension of existing Tasks, consider using
doLast(...)anddoFirst()combined with a Script Plugin for simple wrapping. - In Android contexts, when extending AGP with custom Tasks, prioritize methods offered by the new Variant/Artifact API. If unavailable, revert to traditional methods like hooking (reviewing source code, identifying corresponding Tasks, determining inputs and outputs, writing custom Tasks, and integrating them into the dependency graph through various means).