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

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

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

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

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

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

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

AGP развивается, и старые API заменяются новыми, стабильными 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 и предоставляет свои классы времени выполнения в classpath скрипта сборки. Использование конфигурации 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>. Аргумент lambda для функции map() принимает значение T и возвращает значение S. Лямбда-функция не выполняется сразу. Ее выполнение откладывается до момента, когда для полученного объекта Provider<S> вызывается метод get(). Таким образом, вся цепочка становится ленивой.
  • 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

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

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

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

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

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

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

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

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

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")) { varian>tBuilder -
        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.task<s.registerResC>reatorTask("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: