The Android Gradle plugin (AGP) is the official build system for Android applications. It includes support for compiling many different types of sources and linking them together into an application that you can run on a physical Android device or an emulator.
AGP contains extension points for plugins to control build inputs and extend its functionality through new steps that can be integrated with standard build tasks. Previous versions of AGP did not have official APIs clearly separated from internal implementations. Starting in version 7.0, AGP has a set of official, stable APIs that you can rely on.
AGP API lifecycle
AGP follows the Gradle feature lifecycle to designate the state of its APIs:
- Internal: Not intended for public use
- Incubating: Available for public use but not final, which means that they may not be backward compatible in the final version
- Public: Available for public use and stable
- Deprecated: No longer supported, and replaced with new APIs
Deprecation policy
AGP is evolving with the deprecation of old APIs and their replacement with new, stable APIs and a new Domain Specific Language (DSL). This evolution will span multiple AGP releases, and you can learn more about it at the AGP API/DSL migration timeline.
When AGP APIs are deprecated, for this migration or otherwise, they will continue to be available in the current major release but will generate warnings. Deprecated APIs will be fully removed from AGP in the subsequent major release. For example, if an API is deprecated in AGP 7.0, it will be available in that version and generate warnings. That API will no longer be available in AGP 8.0.
To see examples of new APIs used in common build customizations, take a look at the Android Gradle plugin recipes. They provide examples of common build customizations. You can also find more details about the new APIs in our reference documentation.
Gradle build basics
This guide doesn't cover the entire Gradle build system. However, it does cover the minimum necessary set of concepts to help you integrate with our APIs, and links out to the main Gradle documentation for further reading.
We do assume basic knowledge about how Gradle works, including how to configure projects, edit build files, apply plugins, and run tasks. To learn about the basics of Gradle with respect to AGP, we recommend reviewing Configure your build. To learn about the general framework for customizing Gradle plugins, see Developing Custom Gradle Plugins.
Gradle lazy types glossary
Gradle offers a number of types that behave "lazily," or help defer heavy
computations or
Task
creation to later phases of the build. These types are at the core of many
Gradle and AGP APIs. The following list includes the main Gradle types involved
in lazy execution, and their key methods.
Provider<T>
- Provides a value of type
T
(where "T" means any type), which can be read during the execution phase usingget()
or transformed into a newProvider<S>
(where "S" means some other type) using themap()
,flatMap()
, andzip()
methods. Note thatget()
should never be called during the configuration phase.map()
: Accepts a lambda and produces aProvider
of typeS
,Provider<S>
. The lambda argument tomap()
takes the valueT
and produces the valueS
. The lambda is not executed immediately; instead, its execution is deferred to the momentget()
is called on the resultingProvider<S>
, making the whole chain lazy.flatMap()
: Also accepts a lambda and producesProvider<S>
, but the lambda takes a valueT
and producesProvider<S>
(instead of producing the valueS
directly). Use flatMap() when S cannot be determined at configuration time and you can obtain onlyProvider<S>
. Practically speaking, if you usedmap()
and ended up with aProvider<Provider<S>>
result type, that probably means you should have usedflatMap()
instead.zip()
: Lets you combine twoProvider
instances to produce a newProvider
, with a value computed using a function that combines the values from the two inputProviders
instances.
Property<T>
- Implements
Provider<T>
, so it also provides a value of typeT
. Unlike withProvider<T>
, which is read-only, you can also set a value for theProperty<T>
. There are two ways to do so:- Set a value of type
T
directly when it's available, without the need for deferred computations. - Set another
Provider<T>
as the source of the value of theProperty<T>
. In this case, the valueT
is materialized only whenProperty.get()
is called.
- Set a value of type
TaskProvider
- Implements
Provider<Task>
. To generate aTaskProvider
, usetasks.register()
and nottasks.create()
, to ensure tasks are only instantiated lazily when they're needed. You can useflatMap()
to access the outputs of aTask
before theTask
is created, which can be useful if you want to use the outputs as inputs to otherTask
instances.
Providers and their transformation methods are essential for setting up inputs and outputs of tasks in a lazy way, that is, without the need to create all tasks up front and resolve the values.
Providers also carry task dependency information. When you create a Provider
by
transforming a Task
output, that Task
becomes an implicit dependency of the
Provider
and will be created and run whenever the value of the Provider
is
resolved, such as when another Task
requires it.
Here's an example of registering two tasks, GitVersionTask
and
ManifestProducerTask
, while deferring creation of the Task
instances until
they are actually required. The ManifestProducerTask
input value is set to a
Provider
obtained from the output of GitVersionTask
, so
ManifestProducerTask
implicitly depends on GitVersionTask
.
// Register a task lazily to get its TaskProvider.
val gitVersionProvider: TaskProvider =
project.tasks.register("gitVersionProvider", GitVersionTask::class.java) {
it.gitVersionOutputFile.set(
File(project.buildDir, "intermediates/gitVersionProvider/output")
)
}
...
/**
* Register another task in the configuration block (also executed lazily,
* only if the task is required).
*/
val manifestProducer =
project.tasks.register(variant.name + "ManifestProducer", ManifestProducerTask::class.java) {
/**
* Connect this task's input (gitInfoFile) to the output of
* gitVersionProvider.
*/
it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
}
These two tasks will only execute if they are explicitly requested. This can
happen as part of a Gradle invocation, for example, if you run ./gradlew
debugManifestProducer
, or if the output of ManifestProducerTask
is connected
to some other task and its value becomes required.
While you will write custom tasks that consume inputs and/or produce outputs, AGP doesn't offer public access to its own tasks directly. They are an implementation detail subject to change from version to version. Instead, AGP offers the Variant API and access to the output of its tasks, or build artifacts, that you can read and transform. See Variant API, Artifacts, and Tasks in this document for more information.
Gradle build phases
Building a project is inherently a complicated and resource demanding process, and there are various features such as task configuration avoidance, up-to-date checks, and the configuration caching feature that help minimize the time spent on reproducible or unnecessary computations.
To apply some of these optimizations, Gradle scripts and plugins must obey strict rules during each of the distinct Gradle build phases: initialization, configuration, and execution. In this guide, we will focus on the configuration and execution phases. You can find more information about all the phases in the Gradle build lifecycle guide.
Configuration phase
During the configuration phase, the build scripts for all projects that are part of the build are evaluated, the plugins are applied, and build dependencies are resolved. This phase should be used to configure the build using DSL objects and for registering tasks and their inputs lazily.
Because the configuration phase always runs, regardless of which task is
requested to run, it is especially important to keep it lean and restrict any
computations from depending on inputs other than the build scripts themselves.
That is, you shouldn't execute external programs or read from the network, or
perform long computations that can be deferred to the execution phase as proper
Task
instances.
Execution phase
In the execution phase, the requested tasks and their dependent tasks are
executed. Specifically, the Task
class method(s) marked with @TaskAction
are
executed. During task execution, you are allowed to read from inputs (such as
files) and resolve lazy providers by calling Provider<T>.get()
. Resolving lazy
providers this way kicks off a sequence of map()
or flatMap()
calls that follow
the task dependency information contained within the provider. Tasks are run
lazily to materialize the required values.
Variant API, Artifacts, and Tasks
The Variant API is an extension mechanism in the Android Gradle plugin that lets you manipulate the various options, normally set using the DSL in build configuration files, that influence the Android build. The Variant API also gives you access to intermediate and final artifacts that are created by the build, such as class files, the merged manifest, or APK/AAB files.
Android build flow and extension points
When interacting with AGP, use specially made extension points instead
of registering the typical Gradle lifecycle callbacks (such as afterEvaluate()
) or
setting up explicit Task
dependencies. Tasks created by AGP are considered
implementation details and are not exposed as a public API. You must avoid
trying to get instances of the Task
objects or guessing the Task
names and
adding callbacks or dependencies to those Task
objects directly.
AGP completes the following steps to create and execute its Task
instances,
which in turn produce the build artifacts. The main steps involved in
Variant
object creation are followed by callbacks that let you make changes to certain
objects created as part of a build. It's important to note that all of the
callbacks happen during the configuration phase
(described on this page) and must run fast, deferring any complicated work
to proper Task
instances during the execution phase instead.
- DSL parsing: This is when build scripts are evaluated, and when the
various properties of the Android DSL objects from the
android
block are created and set. The Variant API callbacks described in the following sections are also registered during this phase. finalizeDsl()
: Callback that lets you change DSL objects before they are locked for component (variant) creation.VariantBuilder
objects are created based on data contained in the DSL objects.DSL locking: DSL is now locked and changes are no longer possible.
beforeVariants()
: This callback can influence which components are created, and some of their properties, throughVariantBuilder
. It still allows modifications to the build flow and the artifacts that are produced.Variant creation: The list of components and artifacts that will be created is now finalized and cannot be changed.
onVariants()
: In this callback, you get access to the createdVariant
objects and you can set values or providers for theProperty
values they contain, to be computed lazily.Variant locking: Variant objects are now locked and changes are no longer possible.
Tasks created:
Variant
objects and theirProperty
values are used to create theTask
instances that are necessary to perform the build.
AGP introduces an
AndroidComponentsExtension
that lets
you register callbacks for finalizeDsl()
, beforeVariants()
and onVariants()
.
The extension is available in build scripts through the androidComponents
block:
// This is used only for configuring the Android build through DSL.
android { ... }
// The androidComponents block is separate from the DSL.
androidComponents {
finalizeDsl { extension ->
...
}
}
However, our recommendation is to keep build scripts only for declarative
configuration using the android block's DSL and
move any custom imperative logic to buildSrc
or external plugins. You can also take a look at the buildSrc
samples in our Gradle recipes GitHub repository to learn how to create a plugin in your project. Here is an example of registering the callbacks from plugin code:
abstract class ExamplePlugin: Plugin<Project> {
override fun apply(project: Project) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.finalizeDsl { extension ->
...
}
}
}
Let's take a closer look at the available callbacks and the type of use cases that your plugin can support in each of them:
finalizeDsl(callback: (DslExtensionT) -> Unit)
In this callback, you are able to access and modify the DSL objects that were
created by parsing the information from the android
block in the build files.
These DSL objects will be used to initialize and configure variants in later
phases of the build. For example, you can programmatically create new
configurations or override properties—but keep in mind that all values must be
resolved at configuration time, so they must not rely on any external inputs.
After this callback finishes executing, the DSL objects are no longer useful and
you should no longer hold references to them or modify their values.
abstract class ExamplePlugin: Plugin<Project> {
override fun apply(project: Project) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.finalizeDsl { extension ->
extension.buildTypes.create("extra").let {
it.isJniDebuggable = true
}
}
}
}
beforeVariants()
At this stage of the build, you get access to VariantBuilder
objects, which
determine the variants that will be created and their properties. For example,
you can programmatically disable certain variants, their tests, or change a
property's value (for example, minSdk
) only for a chosen variant. Similar to
finalizeDsl()
, all of the values you provide must be resolved at configuration
time and not depend on external inputs. The VariantBuilder
objects must not be
modified once execution of the beforeVariants()
callback finishes.
androidComponents {
beforeVariants { variantBuilder ->
variantBuilder.minSdk = 23
}
}
The beforeVariants()
callback optionally takes a VariantSelector
, which you can
obtain through the selector()
method on the androidComponentsExtension
. You can
use it to filter components participating in the callback invocation based on
their name, build type, or product flavor.
androidComponents {
beforeVariants(selector().withName("adfree")) { variantBuilder ->
variantBuilder.minSdk = 23
}
}
onVariants()
By the time onVariants()
is called, all the artifacts that will be created by
AGP are already decided so you can no longer disable them. You can, however,
modify some of the values used for the tasks by setting them for
Property
attributes in the Variant
objects. Because the Property
values will
only be resolved when AGP's tasks are executed, you can safely wire them up to
providers from your own custom tasks that will perform any required
computations, including reading from external inputs such as files or the network.
// onVariants also supports VariantSelectors:
onVariants(selector().withBuildType("release")) { variant ->
// Gather the output when we are in single mode (no multi-apk).
val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }
// Create version code generating task
val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
it.outputFile.set(project.layout.buildDirectory.file("${variant.name}/versionCode.txt"))
}
/**
* Wire version code from the task output.
* map() will create a lazy provider that:
* 1. Runs just before the consumer(s), ensuring that the producer
* (VersionCodeTask) has run and therefore the file is created.
* 2. Contains task dependency information so that the consumer(s) run after
* the producer.
*/
mainOutput.versionCode.set(versionCodeTask.map { it.outputFile.get().asFile.readText().toInt() })
}
Contribute generated sources to the build
Your plugin can contribute a few types of generated sources, such as:
- Application code in the
java
directory - Android resources in the
res
directory - Java resources
in the
resources
directory - Android assets in the
assets
directory
For the full list of sources you can add, see the Sources API.
This code snippet shows how to add a custom source folder called
${variant.name}
to the Java source set using the addStaticSourceDirectory()
function. The Android toolchain then processes this folder.
onVariants { variant ->
variant.sources.java?.let { java ->
java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
}
}
See the addJavaSource recipe for more details.
This code snippet shows how to add a directory with Android resources
generated from a custom task to the res
source set. The process is similar for other
source types.
onVariants(selector().withBuildType("release")) { variant ->
// Step 1. Register the task.
val resCreationTask =
project.tasks.register<ResCreatorTask>("create${variant.name}Res")
// Step 2. Register the task output to the variant-generated source directory.
variant.sources.res?.addGeneratedSourceDirectory(
resCreationTask,
ResCreatorTask::outputDirectory)
}
...
// Step 3. Define the task.
abstract class ResCreatorTask: DefaultTask() {
@get:OutputFiles
abstract val outputDirectory: DirectoryProperty
@TaskAction
fun taskAction() {
// Step 4. Generate your resources.
...
}
}
See the addCustomAsset recipe for more details.
Access and modify artifacts
In addition to letting you modify simple properties on the Variant
objects, AGP
also contains an extension mechanism that allows you to read or transform
intermediate and final artifacts produced during the build. For example, you can
read the final, merged AndroidManifest.xml
file contents in a custom Task
to
analyze it, or you can replace its content entirely with that of a manifest file
generated by your custom Task
.
You can find the list of artifacts currently supported in the reference
documentation for the Artifact
class. Every artifact type has certain properties that are useful to know:
Cardinality
The cardinality of an Artifact
represents its number of FileSystemLocation
instances, or the number of files or directories of the artifact type. You can
get information about the cardinality of an artifact by checking its parent
class: Artifacts with a single FileSystemLocation
will be a subclass of
Artifact.Single
; artifacts with multiple FileSystemLocation
instances will
be a subclass of Artifact.Multiple
.
FileSystemLocation
type
You can check if an Artifact
represents files or directories by looking at its
parameterized FileSystemLocation
type, which can be either a RegularFile
or a
Directory
.
Supported operations
Every Artifact
class can implement any of the following interfaces to indicate
which operations it supports:
Transformable
: Allows anArtifact
to be used as an input to aTask
that performs arbitrary transformations on it and outputs a new version of theArtifact
.Appendable
: Applies only to artifacts that are subclasses ofArtifact.Multiple
. It means that theArtifact
can be appended to, that is, a customTask
can create new instances of thisArtifact
type which will be added to the existing list.Replaceable
: Applies only to artifacts that are subclasses ofArtifact.Single
. A replaceableArtifact
can be replaced by an entirely new instance, produced as an output of aTask
.
In addition to the three artifact-modifying operations, every artifact supports
a get()
(or getAll()
)
operation, which returns a Provider
with the final version of the artifact
(after all operations on it are finished).
Multiple plugins can add any number of operations on artifacts into the pipeline
from the onVariants()
callback, and AGP will ensure they are chained properly so
that all tasks run at the right time and artifacts are correctly produced and
updated. This means that when an operation changes any outputs by appending,
replacing, or transforming them, the next operation will see the updated version
of these artifacts as inputs, and so on.
The entry point into registering operations is the Artifacts
class.
The following code snippet shows how you can get access to an instance of
Artifacts
from a property on the Variant
object in the onVariants()
callback.
You can then pass in your custom TaskProvider
to get a
TaskBasedOperation
object (1), and use it to connect its inputs and outputs using one of the
wiredWith*
methods (2).
The exact method you need to choose depends on the cardinality and
FileSystemLocation
type implemented by the Artifact
that you want to transform.
And finally, you pass in the Artifact
type to a method representing the chosen
operation on the *OperationRequest
object that you get in return, for example,
toAppendTo()
,
toTransform()
, or toCreate()
(3).
androidComponents.onVariants { variant ->
val manifestUpdater = // Custom task that will be used for the transform.
project.tasks.register(variant.name + "ManifestUpdater", ManifestTransformerTask::class.java) {
it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
}
// (1) Register the TaskProvider w.
val variant.artifacts.use(manifestUpdater)
// (2) Connect the input and output files.
.wiredWithFiles(
ManifestTransformerTask::mergedManifest,
ManifestTransformerTask::updatedManifest)
// (3) Indicate the artifact and operation type.
.toTransform(SingleArtifact.MERGED_MANIFEST)
}
In this example, MERGED_MANIFEST
is a SingleArtifact
, and it is a
RegularFile
. Because of that, we need to use the wiredWithFiles
method, which
accepts a single RegularFileProperty
reference for the input, and a single
RegularFileProperty
for the output. There are other wiredWith*
methods on
the TaskBasedOperation
class that will work for other combinations of Artifact
cardinality and FileSystemLocation
types.
To learn more about extending AGP, we recommend reading the following sections from the Gradle build system manual:
- Developing Custom Gradle Plugins
- Implementing Gradle plugins
- Developing Custom Gradle Task Types
- Lazy Configuration
- Task Configuration Avoidance