Extend the Android Gradle plugin

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 using get() or transformed into a new Provider<S> (where "S" means some other type) using the map(), flatMap(), and zip()methods. Note that get() should never be called during the configuration phase.
  • map(): Accepts a lambda and produces a Provider of type S, Provider<S>. The lambda argument to map() takes the value T and produces the value S. The lambda is not executed immediately; instead, its execution is deferred to the moment get() is called on the resulting Provider<S>, making the whole chain lazy.
  • flatMap(): Also accepts a lambda and produces Provider<S>, but the lambda takes a value T and produces Provider<S> (instead of producing the value S directly). Use flatMap() when S cannot be determined at configuration time and you can obtain only Provider<S>. Practically speaking, if you used map() and ended up with a Provider<Provider<S>> result type, that probably means you should have used flatMap() instead.
  • zip(): Lets you combine two Provider instances to produce a new Provider, with a value computed using a function that combines the values from the two input Providers instances.
Property<T>
Implements Provider<T>, so it also provides a value of type T. Unlike with Provider<T>, which is read-only, you can also set a value for the Property<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 the Property<T>. In this case, the value T is materialized only when Property.get() is called.
TaskProvider
Implements Provider<Task>. To generate a TaskProvider, use tasks.register() and not tasks.create(), to ensure tasks are only instantiated lazily when they're needed. You can use flatMap() to access the outputs of a Task before the Task is created, which can be useful if you want to use the outputs as inputs to other Task 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 in this document) and must run fast, deferring any complicated work to proper Task instances during the execution phase instead.

  1. 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.
  2. 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.

  3. DSL locking: DSL is now locked and changes are no longer possible.

  4. beforeVariants(): This callback can influence which components are created, and some of their properties, through VariantBuilder. It still allows modifications to the build flow and the artifacts that are produced.

  5. Variant creation: The list of components and artifacts that will be created is now finalized and cannot be changed.

  6. onVariants(): In this callback, you get access to the created Variant objects and you can set values or providers for the Property values they contain, to be computed lazily.

  7. Variant locking: Variant objects are now locked and changes are no longer possible.

  8. Tasks created: Variant objects and their Property values are used to create the Task 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("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() })
}

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 an Artifact to be used as an input to a Task that performs arbitrary transformations on it and outputs a new version of the Artifact.
  • Appendable: Applies only to artifacts that are subclasses of Artifact.Multiple. It means that the Artifact can be appended to, that is, a custom Task can create new instances of this Artifact type which will be added to the existing list.
  • Replaceable: Applies only to artifacts that are subclasses of Artifact.Single. A replaceable Artifact can be replaced by an entirely new instance, produced as an output of a Task.

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: