Как писать плагины Gradle

Плагин Android Gradle (AGP) – это официальная система сборки для приложений Android. Он поддерживает компиляцию многих типов источников и их связывание в приложение, которое можно запустить на физическом устройстве Android или в эмуляторе.

AGP содержит точки расширения для плагинов, позволяющие управлять входными данными сборки и расширять ее функциональность за счет новых шагов, которые можно интегрировать со стандартными задачами сборки. В предыдущих версиях AGP официальные API не были четко отделены от внутренних реализаций. Начиная с версии 7.0, AGP имеет набор официальных стабильных API, на которые вы можете положиться.

Жизненный цикл AGP API

AGP использует жизненный цикл функций Gradle для обозначения состояния своих API:

  • Внутренний. Не предназначен для публичного использования.
  • Инкубационные. Доступны для общего пользования, но не являются окончательными. Это означает, что они могут быть несовместимы с предыдущими версиями в окончательном варианте.
  • Общедоступная. Стабильная версия, доступная для всех.
  • Устаревшие. Больше не поддерживаются и заменены новыми API.

Правила прекращения поддержки

AGP развивается, старые API заменяются новыми, стабильными, и новым предметно-ориентированным языком (DSL). Этот процесс затронет несколько выпусков AGP. Подробную информацию вы найдете в графике перехода на AGP API/DSL.

Когда поддержка API AGP прекращается (в связи с этим переносом или по другой причине), они остаются доступными в текущем основном выпуске, но генерируют предупреждения. Устаревшие API будут полностью удалены из AGP в следующем основном выпуске. Например, если API объявлен устаревшим в AGP 7.0, он будет доступен в этой версии и будет генерировать предупреждения. Этот API больше не будет доступен в AGP 8.0.

Примеры использования новых API в распространенных настройках сборки можно найти в рецептах плагина Android Gradle. В них приведены примеры распространенных настроек сборки. Дополнительную информацию о новых API можно найти в справочной документации.

Компиляция с использованием артефакта AGP API

Чтобы написать собственный плагин Gradle или логику сборки (например, в каталоге buildSrc, модуле плагина соглашения или проекте отдельного плагина), расширяющие AGP, выполните компиляцию с артефактом gradle-api AGP, а не с полным артефактом реализации плагина.

Зачем компилировать с артефактом gradle-api?

AGP публикует два основных артефакта:

  • com.android.tools.build:gradle-api – содержит официальные API AGP, в том числе стабильные API (например, AndroidComponentsExtension, Variant и общедоступные интерфейсы DSL), инкубационные API (с пометкой @Incubating) и устаревшие API (с пометкой @Deprecated).
  • com.android.tools.build:gradle содержит полную реализацию среды выполнения AGP, включая внутренние классы, задачи сборки и интеграцию компилятора.

Компиляция с артефактом gradle-api дает несколько ключевых преимуществ:

  • Не используйте внутренние API. Код плагина должен обращаться только к официальным API. Это предотвращает случайные зависимости от внутренних классов реализации (com.android.build.gradle.internal.*), которые могут изменяться или удаляться в разных версиях AGP без предупреждения.
  • Более быстрая сборка и меньший размер. У артефакта gradle-api дерево зависимостей намного меньше, чем у полного артефакта gradle. Это уменьшает размер скачиваемых файлов, предотвращает загрязнение пути к классам и ускоряет компиляцию логики сборки.
  • Изоляция среды выполнения. Для собственных плагинов требуются только определения API во время компиляции. Во время выполнения полная реализация AGP обеспечивается сборкой Android, когда применяется плагин приложения для Android или библиотеки.

Добавьте зависимость

Добавьте артефакт gradle-api в качестве зависимости в файл сборки проекта плагина или каталог buildSrc. Артефакт gradle-api опубликован в репозитории Google Maven. Чтобы устранить проблему, убедитесь, что в сборку включен репозиторий google(). Например, настройте его в блоке dependencyResolutionManagement.repositories файла settings.gradle или settings.gradle.kts, в блоке repositories файла сборки плагина или в файле сборки каталога buildSrc.

С помощью каталога версий

Если в вашем проекте используется каталог версий, определите зависимость gradle-api в файле gradle/libs.versions.toml:

[versions]
androidGradlePlugin = "9.4.0"

[libraries]
android-gradle-plugin-api = { group = "com.android.tools.build", name = "gradle-api", version.ref = "androidGradlePlugin" }

Псевдоним каталога android-gradle-plugin-api генерирует типобезопасный метод доступа libs.android.gradle.plugin.api.

Затем добавьте libs.android.gradle.plugin.api в блок dependencies файла сборки плагина:

Kotlin

dependencies {
    compileOnly(libs.android.gradle.plugin.api)
}

Яркий

dependencies {
    compileOnly libs.android.gradle.plugin.api
}

Без каталога версий

Если в вашем проекте не используется каталог версий, объявите зависимость непосредственно в файле сборки плагина:

Kotlin

dependencies {
    compileOnly("com.android.tools.build:gradle-api:9.4.0")
}

Яркий

dependencies {
    compileOnly 'com.android.tools.build:gradle-api:9.4.0'
}

Используйте параметр конфигурации compileOnly по умолчанию для отдельных плагинов, предназначенных для публикации. Потребляющий проект применяет AGP и предоставляет свои классы среды выполнения в пути к классам buildscript. Использование конфигурации compileOnly предотвращает утечку зависимости среды выполнения от определенной версии AGP из опубликованного плагина и позволяет избежать конфликтов версий для пользователей. Если вы разрабатываете внутреннюю логику сборки, например плагины в каталоге buildSrc или плагины соглашений в составных сборках, используйте вместо этого параметр конфигурации implementation. Эти плагины запускаются в сборке и требуют AGP в пути к классам среды выполнения.

Основы сборки Gradle

В этом руководстве не рассматривается вся система сборки Gradle. Однако в ней рассматривается минимальный набор понятий, необходимых для интеграции с нашими API, и приводятся ссылки на основную документацию по Gradle.

Мы предполагаем, что вы обладаете базовыми знаниями о работе Gradle, в том числе о том, как настраивать проекты, редактировать файлы сборки, применять плагины и выполнять задачи. Чтобы узнать об основах Gradle в отношении AGP, рекомендуем ознакомиться со статьей Настройка сборки. Общие сведения о настройке плагинов Gradle можно найти в статье Разработка пользовательских плагинов Gradle.

Глоссарий ленивых типов Gradle

Gradle предлагает ряд типов, которые ведут себя "лениво" или помогают отложить тяжелые вычисления или создание Task на более поздние этапы сборки. Эти типы лежат в основе многих API Gradle и AGP. В списке ниже приведены основные типы Gradle, задействованные в отложенном выполнении, и их ключевые методы.

Provider<T>
Предоставляет значение типа T (где T – любой тип), которое можно прочитать на этапе выполнения с помощью get() или преобразовать в новый тип Provider<S> (где S – любой другой тип) с помощью методов map(), flatMap() и zip(). Обратите внимание, что метод get() не должен вызываться на этапе настройки.
  • map(): принимает лямбда-функцию и создает Provider типа S, Provider<S>. Аргумент лямбда-функции для map() принимает значение T и выдает значение S. Лямбда-выражение не выполняется сразу. Вместо этого его выполнение откладывается до момента вызова get() для результирующего Provider<S>, что делает всю цепочку ленивой.
  • flatMap(). Также принимает лямбда-функцию и возвращает Provider<S>, но лямбда-функция принимает значение T и возвращает Provider<S> (вместо того, чтобы напрямую возвращать значение S). Используйте flatMap(), если S нельзя определить во время настройки и вы можете получить только Provider<S>. Если вы использовали map() и получили результат типа Provider<Provider<S>>, то, скорее всего, вам нужно было использовать flatMap().
  • zip() позволяет объединить два экземпляра Provider, чтобы создать новый экземпляр Provider со значением, вычисленным с помощью функции, которая объединяет значения из двух входных экземпляров Providers.
Property<T>
Реализует Provider<T>, поэтому также предоставляет значение типа T. В отличие от Provider<T>, которое доступно только для чтения, вы также можете задать значение для Property<T>. Это можно сделать двумя способами:
  • Задайте значение типа T напрямую, если оно доступно, без необходимости отложенных вычислений.
  • Укажите другой Provider<T> в качестве источника значения Property<T>. В этом случае значение T материализуется только при вызове Property.get().
TaskProvider
Реализация Provider<Task>. Чтобы сгенерировать TaskProvider, используйте tasks.register(), а не tasks.create(). Это позволит создавать экземпляры задач только по мере необходимости. Вы можете использовать flatMap(), чтобы получить доступ к выходным данным Task до создания Task. Это может быть полезно, если вы хотите использовать выходные данные в качестве входных данных для других экземпляров Task.

Поставщики и их методы преобразования необходимы для настройки входных и выходных данных задач в ленивом режиме, то есть без необходимости создавать все задачи заранее и разрешать значения.

Поставщики также содержат информацию о зависимостях задач. Когда вы создаете Provider, преобразуя выходные данные Task, этот Task становится неявной зависимостью Provider и будет создаваться и запускаться всякий раз, когда разрешается значение Provider, например когда этого требует другой Task.

Ниже приведен пример регистрации двух задач, GitVersionTask и ManifestProducerTask, при которой создание экземпляров Task откладывается до тех пор, пока они не потребуются. Входное значение ManifestProducerTask задается как Provider, полученное из выходного значения GitVersionTask, поэтому ManifestProducerTask неявно зависит от 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))
    }

Эти две задачи будут выполнены, только если они явно запрошены. Это может произойти при вызове Gradle, например если вы запустите ./gradlew debugManifestProducer или если выходные данные ManifestProducerTask связаны с другой задачей и их значение становится обязательным.

Хотя вы будете писать собственные задачи, которые используют входные данные и/или создают выходные данные, AGP не предлагает публичный доступ к своим собственным задачам напрямую. Они являются деталью реализации, которая может меняться от версии к версии. Вместо этого AGP предлагает Variant API и доступ к выходным данным задач, или артефактам сборки, которые вы можете читать и преобразовывать. Подробную информацию вы найдете в разделе Variant API, Artifacts и Tasks.

Этапы сборки Gradle

Сборка проекта – это сложный и ресурсоемкий процесс. Чтобы сократить время, затрачиваемое на воспроизводимые или ненужные вычисления, можно использовать различные функции, такие как отказ от настройки задач, актуальные проверки и кеширование конфигурации.

Чтобы применить некоторые из этих оптимизаций, скрипты и плагины Gradle должны соблюдать строгие правила на каждом из отдельных этапов сборки Gradle: инициализации, настройки и выполнения. В этом руководстве мы сосредоточимся на этапах настройки и выполнения. Подробную информацию обо всех этапах можно найти в руководстве по жизненному циклу сборки Gradle.

Этап настройки

На этапе настройки оцениваются скрипты сборки для всех проектов, входящих в сборку, применяются плагины и разрешаются зависимости сборки. На этом этапе настраивается сборка с использованием объектов DSL и регистрируются задачи и их входные данные.

Поскольку этап настройки выполняется всегда, независимо от того, какую задачу требуется выполнить, особенно важно, чтобы он был простым и не зависел от входных данных, кроме самих скриптов сборки. То есть не следует запускать внешние программы, считывать данные из сети или выполнять длительные вычисления, которые можно отложить до фазы выполнения в виде экземпляров Task.

Этап выполнения

На этапе выполнения выполняются запрошенные задачи и задачи, зависящие от них. В частности, выполняются методы класса Task, помеченные как @TaskAction. Во время выполнения задачи вы можете читать входные данные (например, файлы) и разрешать отложенные поставщики, вызывая Provider<T>.get(). Разрешение отложенных поставщиков таким образом запускает последовательность вызовов map() или flatMap(), которые следуют за информацией о зависимости задач, содержащейся в поставщике. Задачи выполняются лениво, чтобы материализовать необходимые значения.

Variant API, артефакты и задачи

Variant API – это механизм расширения в плагине Android Gradle, который позволяет управлять различными параметрами, обычно задаваемыми с помощью DSL в файлах конфигурации сборки, которые влияют на сборку Android. Variant API также предоставляет доступ к промежуточным и конечным артефактам, созданным в процессе сборки, например к файлам классов, объединенному манифесту или файлам APK/AAB.

Процесс сборки Android и точки расширения

При взаимодействии с AGP используйте специально созданные точки расширения вместо регистрации типичных обратных вызовов жизненного цикла Gradle (таких как afterEvaluate()) или настройки явных зависимостей Task. Задачи, созданные AGP, считаются деталями реализации и не предоставляются в качестве общедоступного API. Не пытайтесь получить экземпляры объектов Task, угадать названия Task или добавить обратные вызовы или зависимости непосредственно в эти объекты Task.

AGP выполняет следующие шаги для создания и выполнения своих экземпляров Task, которые, в свою очередь, создают артефакты сборки. Основные этапы создания объекта Variant сопровождаются обратными вызовами, которые позволяют вносить изменения в определенные объекты, созданные в рамках сборки. Важно отметить, что все обратные вызовы происходят на этапе настройки (описанном на этой странице) и должны выполняться быстро, откладывая любую сложную работу до соответствующих экземпляров Task на этапе выполнения.

  1. Синтаксический анализ DSL. На этом этапе оцениваются скрипты сборки, а также создаются и настраиваются различные свойства объектов Android DSL из блока android. Обратные вызовы Variant API, описанные в следующих разделах, также регистрируются на этом этапе.
  2. finalizeDsl() – обратный вызов, позволяющий изменять объекты DSL до того, как они будут заблокированы для создания компонента (варианта). Объекты VariantBuilder создаются на основе данных, содержащихся в объектах DSL.

  3. Блокировка DSL. DSL заблокирован, и изменения больше невозможны.

  4. beforeVariants() – этот обратный вызов может влиять на то, какие компоненты создаются, и на некоторые их свойства через VariantBuilder. Она по-прежнему позволяет вносить изменения в процесс сборки и создаваемые артефакты.

  5. Создание вариантов. Список компонентов и артефактов, которые будут созданы, теперь окончательный и не может быть изменен.

  6. onVariants(). В этом обратном вызове вы получаете доступ к созданным объектам Variant и можете задавать значения или поставщиков для содержащихся в них значений Property, которые будут вычисляться по мере необходимости.

  7. Блокировка вариантов. Объекты вариантов заблокированы, и их больше нельзя изменить.

  8. Создание задач. Объекты Variant и их значения Property используются для создания экземпляров Task, необходимых для выполнения сборки.

В AGP появился AndroidComponentsExtension, который позволяет регистрировать обратные вызовы для finalizeDsl(), beforeVariants() и onVariants(). Расширение доступно в скриптах сборки через блок androidComponents:

// This is used only for configuring the Android build through DSL.
android { ... }

// The androidComponents block is separate from the DSL.
androidComponents {
   finalizeDsl { extension ->
      ...
   }
}

Однако мы рекомендуем использовать скрипты сборки только для декларативной конфигурации с помощью DSL блока android и переместить любую пользовательскую императивную логику в каталог buildSrc или внешние плагины. Вы также можете посмотреть примеры buildSrc в нашем репозитории рецептов Gradle на GitHub, чтобы узнать, как создать плагин в своем проекте. Убедитесь, что ваш плагин компилируется с артефактом gradle-api в файле сборки. Ниже приведен пример регистрации обратных вызовов из кода плагина.

abstract class ExamplePlugin: Plugin<Project> {

    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.finalizeDsl { extension ->
            ...
        }
    }
}

Давайте подробнее рассмотрим доступные обратные вызовы и типы вариантов использования, которые ваш плагин может поддерживать в каждом из них:

finalizeDsl(callback: (DslExtensionT) -> Unit)

В этом обратном вызове вы можете получить доступ к объектам DSL, созданным в результате синтаксического анализа информации из блока android в файлах сборки, и изменить их. Эти объекты DSL будут использоваться для инициализации и настройки вариантов на более поздних этапах сборки. Например, вы можете программно создавать новые конфигурации или переопределять свойства. Однако помните, что все значения должны быть разрешены во время настройки, поэтому они не должны зависеть от каких-либо внешних входных данных. После завершения выполнения этого обратного вызова объекты DSL больше не нужны, и вы больше не должны хранить ссылки на них или изменять их значения.

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

На этом этапе сборки вы получаете доступ к объектам VariantBuilder, которые определяют, какие варианты будут созданы и с какими свойствами. Например, вы можете программно отключить определенные варианты, их тесты или изменить значение свойства (например, minSdk) только для выбранного варианта. Как и в случае с finalizeDsl(), все значения, которые вы указываете, должны быть определены во время настройки и не зависеть от внешних входных данных. Объекты VariantBuilder нельзя изменять после завершения обратного вызова beforeVariants().

androidComponents {
    beforeVariants { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

Обратный вызов beforeVariants() может принимать VariantSelector, который можно получить с помощью метода selector() в androidComponentsExtension. С его помощью можно фильтровать компоненты, участвующие в вызове обратного вызова, по их названию, типу сборки или версии продукта.

androidComponents {
    beforeVariants(selector().withName("adfree")) { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

onVariants()

К моменту вызова onVariants() все артефакты, которые будут созданы AGP, уже определены, поэтому вы больше не можете их отключить. Однако вы можете изменить некоторые значения, используемые для задач, задав их для атрибутов Property в объектах Variant. Поскольку значения Property будут разрешены только при выполнении задач AGP, вы можете безопасно подключить их к поставщикам из ваших собственных пользовательских задач, которые будут выполнять любые необходимые вычисления, включая чтение из внешних входных данных, таких как файлы или сеть.

// 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() })
}

Добавление сгенерированных источников в сборку

Плагин может добавлять несколько типов сгенерированных источников, например:

Полный список источников, которые можно добавить, приведен в документации по Sources API.

В этом фрагменте кода показано, как добавить папку с собственным исходным кодом ${variant.name} в набор исходных файлов Java с помощью функции addStaticSourceDirectory(). Затем набор инструментов Android обрабатывает эту папку.

onVariants { variant ->
    variant.sources.java?.let { java ->
        java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
    }
}

Подробную информацию можно найти в рецепте addJavaSource.

В этом фрагменте кода показано, как добавить каталог с ресурсами Android, сгенерированными из пользовательской задачи, в набор исходных файлов res. Для других типов источников процесс аналогичен.

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

Подробную информацию вы найдете в рецепте addCustomAsset.

Как получить доступ к объектам и изменить их

Помимо изменения простых свойств объектов Variant, AGP также содержит механизм расширения, который позволяет читать или преобразовывать промежуточные и конечные артефакты, созданные во время сборки. Например, вы можете прочитать содержимое итогового объединенного файла AndroidManifest.xml в пользовательском Task, чтобы проанализировать его, или полностью заменить его содержимое содержимым файла манифеста, созданного вашим пользовательским Task.

Список поддерживаемых артефактов можно найти в справочной документации по классу Artifact. У каждого типа артефактов есть определенные свойства, о которых полезно знать:

Количество элементов

Количество элементов Artifact представляет собой количество экземпляров FileSystemLocation или количество файлов или каталогов типа артефакта. Информацию о количестве элементов в артефакте можно получить, проверив его родительский класс. Артефакты с одним экземпляром FileSystemLocation будут подклассом Artifact.Single, а артефакты с несколькими экземплярами FileSystemLocation – подклассом Artifact.Multiple.

Тип FileSystemLocation

Чтобы узнать, представляет ли Artifact файлы или каталоги, посмотрите на его параметризованный тип FileSystemLocation, который может быть RegularFile или Directory.

Поддерживаемые операции

Каждый класс Artifact может реализовывать любой из следующих интерфейсов, чтобы указать, какие операции он поддерживает:

  • Transformable позволяет использовать Artifact в качестве входных данных для Task, которая выполняет произвольные преобразования и выводит новую версию Artifact.
  • Appendable: применяется только к артефактам, которые являются подклассами Artifact.Multiple. Это означает, что в Artifact можно добавлять новые элементы, то есть пользовательский Task может создавать новые экземпляры этого типа Artifact, которые будут добавлены в существующий список.
  • Replaceable: применяется только к артефактам, которые являются подклассами Artifact.Single. Заменяемый объект Artifact можно заменить полностью новым экземпляром, созданным в результате выполнения Task.

Помимо трех операций, изменяющих артефакт, каждый артефакт поддерживает операцию get() (или getAll()), которая возвращает Provider с окончательной версией артефакта (после завершения всех операций над ним).

Несколько плагинов могут добавлять любое количество операций с артефактами в конвейер из обратного вызова onVariants(), а AGP обеспечит их правильную цепочку, чтобы все задачи выполнялись в нужное время, а артефакты создавались и обновлялись правильно. Это означает, что когда операция изменяет какие-либо выходные данные путем их добавления, замены или преобразования, следующая операция будет видеть обновленную версию этих артефактов в качестве входных данных и так далее.

Точкой входа для регистрации операций является класс Artifacts. В следующем фрагменте кода показано, как получить доступ к экземпляру Artifacts из свойства объекта Variant в обратном вызове onVariants().

Затем вы можете передать собственный объект TaskProvider, чтобы получить объект TaskBasedOperation (1), и использовать его для подключения входов и выходов с помощью одного из методов wiredWith* (2).

Выбор метода зависит от количества элементов и типа FileSystemLocation, реализованного в Artifact, который вы хотите преобразовать.

Наконец, вы передаете тип Artifact методу, представляющему выбранную операцию с объектом *OperationRequest, который вы получаете в ответ, например toAppendTo(), toTransform() или 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)
}

В этом примере MERGED_MANIFEST – это SingleArtifact, а также RegularFile. Поэтому нам нужно использовать метод wiredWithFiles, который принимает одну ссылку RegularFileProperty для входных данных и одну ссылку RegularFileProperty для выходных данных. В классе TaskBasedOperation есть и другие методы wiredWith*, которые будут работать для других комбинаций количества элементов Artifact и типов FileSystemLocation.

Чтобы узнать больше о расширении AGP, рекомендуем прочитать следующие разделы руководства по системе сборки Gradle: