Pisanie wtyczek Gradle

Wtyczka Android Gradle (AGP) to oficjalny system kompilacji dla aplikacji na Androida. Obejmuje ona kompilowanie wielu różnych typów źródeł i łączenie ich w aplikację, którą można uruchomić na fizycznym urządzeniu z Androidem lub w emulatorze.

Interfejs AGP zawiera punkty rozszerzeń umożliwiające wtyczce kontrolowanie danych wejściowych kompilacji i rozszerzenie jej funkcjonalności za pomocą nowych kroków, które można zintegrować ze standardowymi zadaniami kompilacji. Poprzednie wersje AGP nie miały oficjalnych interfejsów API wyraźnie oddzielonych od implementacji wewnętrznych. Począwszy od wersji 7.0 platforma AGP udostępnia zestaw oficjalnych, stabilnych interfejsów API, na których można polegać.

Cykl życia interfejsu AGP API

AGP stosuje się do cyklu życia funkcji Gradle, aby określić stan swoich interfejsów API:

  • Do użytku wewnętrznego: nie jest przeznaczony do użytku publicznego.
  • Inkubacja: rozwiązania dostępne do użytku publicznego, ale nie ostateczne, co oznacza, że w ostatecznej wersji mogą nie być zgodne wstecznie.
  • Publiczny: dostępny do użytku publicznego, stabilny.
  • Wycofany: nie jest już obsługiwany i został zastąpiony nowym interfejsem API.

Zasady wycofywania

AGP ewoluuje wraz z wycofaniem starych interfejsów API i zastąpieniem ich nowymi, stabilnymi interfejsami API oraz nowym językiem do zastosowań w domenie (DSL). Ta ewolucja obejmie wiele wersji interfejsu AGP. Więcej informacji znajdziesz w harmonogramie migracji interfejsów API i interfejsów DSL AGP.

Gdy wycofamy interfejsy AGP (np. w ramach tej migracji), nadal będą one dostępne w obecnej głównej wersji, ale będą generować ostrzeżenia. Wycofane interfejsy API zostaną całkowicie usunięte z pakietu AGP w kolejnej wersji głównej. Jeśli na przykład interfejs API został wycofany w wersji AGP 7.0, będzie on dostępny w tej wersji i wygeneruje ostrzeżenia. Ten interfejs API nie będzie już dostępny w wersji AGP 8.0.

Przykłady nowych interfejsów API używanych w ramach typowych dostosowań kompilacji znajdziesz w przepisach wtyczki Gradle na Androida. Znajdziesz w nich przykłady typowych modyfikacji kompilacji. Więcej informacji o nowych interfejsach API znajdziesz w naszej dokumentacji referencyjnej.

Podstawy kompilacji w Gradle

Ten przewodnik nie obejmuje całego systemu kompilacji Gradle. Omawiamy w nim jednak minimalny zestaw koncepcji, które ułatwiają integrację z naszymi interfejsami API, oraz linki do głównej dokumentacji Gradle, gdzie można się z nimi zapoznać.

Zakładamy, że znasz podstawy działania Gradle, w tym konfigurowanie projektów, edytowanie plików kompilacji, stosowanie wtyczek i uruchamianie zadań. Aby dowiedzieć się więcej o podstawach Gradle w związku z AGP, przeczytaj artykuł Konfigurowanie procesu kompilacji. Informacje o ogólnej platformie dostosowywania wtyczek Gradle znajdziesz w artykule Tworzenie niestandardowych wtyczek Gradle.

Glosariusz leniwego typu Gradle

Gradle oferuje kilka typów działania, które działają „leniwie” lub pomagają opóźnić wykonywanie złożonych obliczeń bądź tworzenie Task do późniejszych faz kompilacji. Te typy są podstawą wielu interfejsów API Gradle i AGP. Na liście poniżej znajdziesz główne typy Gradle używane w leniwym wykonaniu i ich kluczowe metody.

Provider<T>
Udostępnia wartość typu T (gdzie „T” oznacza dowolny typ), którą można odczytać w trakcie fazy wykonywania za pomocą get() lub przekształcić w nowy Provider<S> (gdzie „S” oznacza inny typ) przy użyciu metod map(), flatMap() i zip(). Pamiętaj, że metoda get() nie powinna być nigdy wywoływana na etapie konfiguracji.
  • map(): przyjmuje lambda i tworzy Provider typu S, Provider<S>. Argument lambda funkcji map() przyjmuje wartość T i zwraca wartość S. Funkcja lambda nie jest wykonywana od razu. Jej wykonanie jest odraczane do momentu wywołania funkcji get() w wyniku Provider<S>, przez co cały łańcuch staje się leniwy.
  • flatMap(): akceptuje również lambdę i zwraca wartość Provider<S>, ale lambda przyjmuje wartość T i zwraca wartość Provider<S> (zamiast bezpośrednio generować wartość S). Użyj flatMap(), gdy S nie może zostać określony w czasie konfiguracji i możesz uzyskać tylko Provider<S>. Ogólnie rzecz biorąc, jeśli po użyciu typu wyniku map() otrzymujesz typ wyniku Provider<Provider<S>>, prawdopodobnie musisz użyć parametru flatMap().
  • zip(): umożliwia połączenie 2 wystąpienia funkcji Provider w celu utworzenia nowej wartości Provider, obliczonej za pomocą funkcji, która łączy wartości z 2 wejść Providers.
Property<T>
Wdraża Provider<T>, więc również udostępnia wartość typu T. W przeciwieństwie do parametru Provider<T>, który jest tylko do odczytu, możesz też ustawić wartość parametru Property<T>. Możesz to zrobić na 2 sposoby:
  • Ustaw wartość typu T bezpośrednio, gdy jest dostępna, bez konieczności odroczonych obliczeń.
  • Ustaw inny element Provider<T> jako źródło wartości parametru Property<T>. W tym przypadku wartość T jest zmaterializowany tylko wtedy, gdy wywołana jest Property.get().
TaskProvider
Wdroż Provider<Task>. Do wygenerowania TaskProvider użyj tasks.register() zamiast tasks.create(). Dzięki temu zadania będą tworzone leniwie tylko wtedy, gdy są potrzebne. Za pomocą funkcji flatMap() możesz uzyskać dostęp do danych wyjściowych funkcji Task przed jej utworzeniem. Może to być przydatne, jeśli chcesz użyć tych danych jako danych wejściowych dla innych instancji funkcji Task.

Dostawcy i ich metody przekształcania są niezbędne do leniwego konfigurowania danych wejściowych i wyjściowych zadań, czyli bez konieczności wcześniejszego tworzenia wszystkich zadań i usuwania wartości.

Dostawcy mogą też przekazywać informacje o zależności między zadaniami. Gdy utworzysz Provider przez przekształcenie danych wyjściowych Task, ta funkcja Task stanie się zależność domniemaną parametru Provider i będzie tworzona i uruchamiana za każdym razem, gdy zostanie ustalona wartość obiektu Provider, np. gdy wymaga tego inny element Task.

Oto przykład rejestrowania 2 zadań GitVersionTaskManifestProducerTask, przy czym tworzenie instancji Task jest odkładane do momentu, gdy są one rzeczywiście potrzebne. Wartość wejściowa ManifestProducerTask jest ustawiana na Provider uzyskaną z wyjścia funkcji GitVersionTask, więc ManifestProducerTask pośrednio zależy od 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))
    }

Te 2 zadania będą wykonywane tylko wtedy, gdy zostanie wysłane odpowiednie żądanie. Może się to zdarzyć w ramach wywołania Gradle, na przykład gdy uruchomisz ./gradlew debugManifestProducer, lub gdy dane wyjściowe ManifestProducerTask są połączone z jakimś innym zadaniem i ich wartość staje się wymagana.

Podczas gdy będziesz pisać niestandardowe zadania, które wykorzystują dane wejściowe lub generują dane wyjściowe, usługa AGPC nie udostępnia publicznie własnych zadań. Są to szczegóły implementacji, które mogą się zmieniać w kolejnych wersjach. Zamiast tego udostępnia interfejs API wariantu i dostęp do danych wyjściowych jego zadań, czyli artefaktów tworzenia, które można odczytywać i przekształcać. Więcej informacji znajdziesz w opisie interfejsu API wariantu, artefaktów i zadań w tym dokumencie.

Fazy kompilacji Gradle

Tworzenie projektu to z zasady skomplikowany proces, który wymaga wielu zasobów. Dostępne są różne funkcje, takie jak unikanie konfiguracji zadań, aktualne kontrole i funkcja buforowania konfiguracji, które pomagają zminimalizować czas poświęcany na powtarzalne lub niepotrzebne obliczenia.

Aby możliwe było zastosowanie niektórych z tych optymalizacji, skrypty i wtyczki Gradle muszą przestrzegać rygorystycznych reguł na każdym z różnych faz kompilacji Gradle: inicjowania, konfiguracji i wykonania. W tym przewodniku skupimy się na konfiguracji i wykonaniu. Więcej informacji o wszystkich fazach znajdziesz w przewodniku po cyklu życia kompilacji Gradle.

Etap konfiguracji

Na etapie konfiguracji oceniane są skrypty kompilacji, które wchodzą w skład kompilacji, są stosowane wtyczki i usuwane są zależności kompilacji. Ta faza służy do konfigurowania kompilacji za pomocą obiektów DSL oraz do leniwego rejestrowania zadań i ich danych wejściowych.

Faza konfiguracji jest zawsze wykonywana niezależnie od tego, jakie zadanie zostało uruchomione, dlatego ważne jest, aby była jak najbardziej zwięzła i niezależna od danych wejściowych innych niż skrypty kompilacji. Oznacza to, że nie należy uruchamiać programów zewnętrznych ani odczytywać danych z sieci ani wykonywać długich obliczeń, które można odłożyć do fazy wykonywania jako odpowiednie instancje Task.

Faza wykonania

Na etapie wykonywania są wykonywane żądane zadania i zadania zależne. W szczególności wykonywane są metody klasy Task oznaczone jako @TaskAction. Podczas wykonywania zadania możesz odczytywać dane z danych wejściowych (takich jak pliki) i rozwiązywać problemy leniwych dostawców, wywołując funkcję Provider<T>.get(). Rozwiązanie problemu leniwego dostawcy w ten sposób uruchamia sekwencję wywołań map() lub flatMap(), które są zgodne z informacjami o zależności zadań podanymi w dostawcy. Zadania są wykonywane leniwie w celu realizacji wymaganych wartości.

Interfejs API wariantów, artefakty i zadania

Interfejs Variant API to mechanizm rozszerzeń w pliku Android Gradle, który umożliwia manipulowanie różnymi opcjami, zwykle ustawianymi za pomocą języka DSL w plikach konfiguracji kompilacji, które wpływają na kompilację Androida. Interfejs Variant API zapewnia też dostęp do pośrednich i ostatecznych artefaktów tworzonych przez kompilację, takich jak pliki klasy, scalony plik manifestu czy pliki APK/AAB.

Proces kompilacji aplikacji na Androida i punkty rozszerzenia

Podczas interakcji z interfejsem AGP używaj specjalnie utworzonych punktów rozszerzenia zamiast rejestrować typowe wywołania zwrotne cyklu życia Gradle (np. afterEvaluate()) lub konfigurując jawne zależności Task. Zadania tworzone przez AGP są uważane za szczegóły implementacji i nie są udostępniane jako publiczny interfejs API. Nie próbuj uzyskiwać instancji obiektów Task, nie zgaduj nazw Task ani nie dodawaj wywołań zwrotnych lub zależności bezpośrednio do tych obiektów Task.

AGP wykonuje poniższe kroki, aby utworzyć i wykonać swoje instancje Task, które z kolei generują artefakty kompilacji. Po głównych czynnościach związanych z tworzeniem obiektu Variant następują wywołania zwrotne umożliwiające wprowadzenie zmian do określonych obiektów tworzonych w ramach kompilacji. Warto pamiętać, że wszystkie wywołania zwrotne występują podczas fazy konfiguracji (opisanej na tej stronie) i muszą działać szybko, co oznacza odroczenie wszelkich skomplikowanych zadań do odpowiednich instancji Task w fazie wykonywania.

  1. Analiza DSL: następuje ocena skryptów kompilacji oraz utworzenie i ustalenie różnych właściwości obiektów DSL Androida z bloku android. W tej fazie są też rejestrowane wywołania zwrotne interfejsu Variant API opisane w następnych sekcjach.
  2. finalizeDsl(): wywołanie zwrotne, które umożliwia zmianę obiektów DSL, zanim zostaną zablokowane na potrzeby utworzenia komponentu (wariant). Obiekty VariantBuilder są tworzone na podstawie danych zawartych w obiektach DSL.

  3. Blokowanie DSL: DSL jest teraz zablokowany i nie można już wprowadzać zmian.

  4. beforeVariants(): ten wywołanie zwrotne może wpływać na to, które komponenty są tworzone, a także na niektóre ich właściwości za pomocą VariantBuilder. Nadal umożliwia modyfikowanie procesu kompilacji i tworzonych artefaktów.

  5. Tworzenie wariantów: lista komponentów i elementów, które zostaną utworzone, jest już sfinalizowana i nie można jej zmienić.

  6. onVariants(): W tym wywołaniu zwrotnym uzyskujesz dostęp do utworzonych obiektów Variant i możesz dla zawartych w nich wartości Property ustawić wartości lub dostawców, aby były obliczane z opóźnieniem.

  7. Blokowanie wariantów: obiekty wariantów są teraz zablokowane i nie można ich już zmieniać.

  8. Utworzone zadania: obiekty (Variant) i ich wartości Property są używane do tworzenia instancji Task niezbędnych do wykonania kompilacji.

AGP wprowadza AndroidComponentsExtension, który umożliwia rejestrowanie wywołań zwrotnych finalizeDsl(), beforeVariants() i onVariants(). Rozszerzenie jest dostępne w skryptach kompilacji poprzez blok androidComponents:

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

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

Zalecamy jednak, aby skrypty kompilacji służyły tylko do deklaratywnej konfiguracji za pomocą języka DSL bloku Androida, a dowolną niestandardową logikę imperatywną przenieść do buildSrc lub zewnętrznych wtyczek. Możesz też zapoznać się z przykładami buildSrc w naszym repozytorium przepisów na Gradle na GitHubie, aby dowiedzieć się, jak utworzyć wtyczkę w swoim projekcie. Oto przykład rejestrowania wywołań zwrotnych z kodu wtyczki:

abstract class ExamplePlugin: Plugin<Project> {

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

Przyjrzyjmy się bliżej dostępnym funkcjom zwracania wartości i przypadkom użycia, które może obsługiwać Twój wtyczka:

finalizeDsl(callback: (DslExtensionT) -> Unit)

To wywołanie zwrotne umożliwia dostęp do utworzonych obiektów DSL i modyfikowanie ich przez analizowanie informacji z bloku android w plikach kompilacji. Te obiekty DSL będą używane do inicjowania i konfigurowania wariantów w kolejnych fazach kompilacji. Możesz na przykład tworzyć nowe konfiguracje za pomocą programów lub zastępować właściwości. Pamiętaj jednak, że wszystkie wartości muszą być rozwiązywane w momencie konfiguracji, więc nie mogą zależeć od żadnych danych zewnętrznych. Po zakończeniu tego wywołania zwrotnego obiekty DSL nie są już przydatne i nie należy już przechowywać do nich odwołań ani modyfikować ich wartości.

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

Na tym etapie kompilacji uzyskujesz dostęp do obiektów VariantBuilder, które określają tworzone warianty i ich właściwości. Możesz na przykład za pomocą kodu wyłączyć określone warianty lub ich testy albo zmienić wartość właściwości (np. minSdk) tylko w przypadku wybranego wariantu. Podobnie jak w przypadku parametru finalizeDsl() wszystkie podane przez Ciebie wartości muszą zostać rozwiązane w momencie konfiguracji i nie mogą zależeć od zewnętrznych danych wejściowych. Po zakończeniu wykonywania wywołania zwrotnego beforeVariants() obiektów VariantBuilder nie można modyfikować.

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

Opcjonalnie wywołanie zwrotne beforeVariants() może przyjmować parametr VariantSelector, który możesz uzyskać za pomocą metody selector() obiektu androidComponentsExtension. Możesz go użyć do filtrowania komponentów biorących udział w wywoływaniu funkcji zwrotnej na podstawie ich nazwy, typu kompilacji lub wersji produktu.

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

onVariants()

W momencie wywołania funkcji onVariants() wszystkie artefakty, które zostaną utworzone przez AGPL, są już określone, więc nie można ich wyłączyć. Możesz jednak zmodyfikować niektóre wartości używane do zadań, ustawiając je w atrybutach Property w obiektach Variant. Wartości Property są rozpoznawane tylko podczas wykonywania zadań AGP, więc możesz bezpiecznie połączyć je z dostawcami z własnych niestandardowych zadań, które będą wykonywać wymagane obliczenia, w tym odczyty z zewnętrznych danych wejściowych, takich jak pliki lub sieć.

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

Dodawanie wygenerowanych źródeł do kompilacji

Twój wtyczek może udostępniać kilka typów generowanych źródeł, takich jak:

Pełną listę źródeł, które możesz dodać, znajdziesz w interfejsie Sources API.

Ten fragment kodu pokazuje, jak za pomocą funkcji addStaticSourceDirectory() dodać do zestawu źródłowego Java niestandardowy folder źródłowy o nazwie ${variant.name}. Następnie narzędzia Androida przetwarzają ten folder.

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

Więcej informacji znajdziesz w przepisie addJavaSource.

Ten fragment kodu pokazuje, jak dodać do zestawu źródeł res katalog z zasobami Androida wygenerowanymi na podstawie niestandardowego zadania. Ten proces jest podobny w przypadku innych typów źródeł.

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

Więcej informacji znajdziesz w przepisie addCustomAsset.

Uzyskiwanie dostępu do artefaktów i ich modyfikowanie

Oprócz możliwości modyfikowania prostych właściwości obiektów Variant interfejs AGP zawiera też mechanizm rozszerzenia, który umożliwia odczytywanie lub przekształcanie pośrednich i ostatecznych artefaktów tworzonych podczas kompilacji. Możesz na przykład odczytać ostateczną, scaloną zawartość pliku AndroidManifest.xml w niestandardowym elemencie Task, aby ją przeanalizować, lub zastąpić jego zawartość całkowicie zawartością pliku manifestu wygenerowanego przez niestandardowy Task.

Listę artefaktów, które są obecnie obsługiwane, znajdziesz w dokumentacji referencyjnej klasy Artifact. Każdy typ artefaktu ma określone właściwości, które warto poznać:

Moc zbioru

Moc zbioru Artifact reprezentuje liczbę wystąpień FileSystemLocation lub liczbę plików albo katalogów typu artefaktu. Aby uzyskać informacje o mocy zbioru artefaktu, sprawdź jego klasę nadrzędną: artefakty z pojedynczym elementem FileSystemLocation będą podklasą klasy Artifact.Single, a artefakty z wieloma instancjami FileSystemLocation będą podklasą Artifact.Multiple.

FileSystemLocation typ

Aby sprawdzić, czy Artifact reprezentuje pliki czy katalogi, sprawdź parametryzowany typ FileSystemLocation, który może być albo RegularFile, albo Directory.

Obsługiwane operacje

Każda klasa Artifact może implementować dowolny z tych interfejsów, aby wskazać obsługiwane operacje:

  • Transformable: umożliwia użycie Artifact jako wejścia dla funkcji Task, która wykonuje na nim dowolne przekształcenia i wyprowadza nową wersję Artifact.
  • Appendable: ma zastosowanie tylko do artefaktów, które są podklasami Artifact.Multiple. Oznacza to, że element Artifact może zostać dołączony do, co oznacza, że niestandardowy element Task może utworzyć nowe instancje tego typu Artifact, które zostaną dodane do istniejącej listy.
  • Replaceable: ma zastosowanie tylko do artefaktów, które są podklasami Artifact.Single. Wymienny element Artifact można zastąpić całkowicie nowym wystąpieniem, wygenerowanym jako dane wyjściowe funkcji Task.

Oprócz 3 operacji modyfikujących artefakty każdy artefakt obsługuje operację get() (lub getAll()), która zwraca Provider z ostateczną wersją artefaktu (po zakończeniu wszystkich operacji na nim).

Wiele wtyczek może dodawać dowolną liczbę operacji na artefaktach do potoku z poziomu wywołania zwrotnego onVariants(), a AGP zadba o to, aby były one odpowiednio połączone, aby wszystkie zadania były wykonywane we właściwym czasie, a artefakty były prawidłowo tworzone i aktualizowane. Oznacza to, że gdy operacja zmienia jakiekolwiek dane wyjściowe przez ich dodanie, zastąpienie lub przekształcenie, następna operacja będzie traktować zaktualizowaną wersję tych artefaktów jako dane wejściowe i tak dalej.

Punktem wejścia do operacji rejestracji jest klasa Artifacts. Ten fragment kodu pokazuje, jak uzyskać dostęp do instancji Artifacts z właściwości obiektu Variant w wywołaniu zwrotnym onVariants().

Następnie możesz przekazać niestandardowy obiekt TaskProvider, aby uzyskać obiekt TaskBasedOperation (1), a następnie użyć go do połączenia jego danych wejściowych i wyjściowych za pomocą jednej z metod wiredWith* (2).

Dokładna metoda wyboru zależy od mocy zbioru i typu FileSystemLocation zaimplementowanego przez obiekt Artifact, który chcesz przekształcić.

Na koniec przekazujesz typ Artifact do metody reprezentującej wybraną operację na obiekcie *OperationRequest, który otrzymasz w zamian, np. toAppendTo(), toTransform() lub 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)
}

W tym przykładzie MERGED_MANIFEST jest SingleArtifact i jest RegularFile. Z tego względu musimy użyć metody wiredWithFiles, która akceptuje pojedyncze odwołanie RegularFileProperty dla danych wejściowych i pojedynczą wartość RegularFileProperty dla danych wyjściowych. W klasie TaskBasedOperation są też inne metody wiredWith*, które działają w przypadku innych kombinacji mocy zbioru Artifact i typów FileSystemLocation.

Aby dowiedzieć się więcej o rozszerzaniu AGP, przeczytaj te sekcje z podręcznika systemu kompilacji Gradle: