Pisanie wtyczek Gradle

Wtyczka Androida do obsługi Gradle (AGP) to oficjalny system kompilacji 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. Od wersji 7.0 AGP zawiera zestaw oficjalnych, stabilnych interfejsów API, na których możesz polegać.

Cykl życia interfejsu AGP API

AGP stosuje cykl życia funkcji Gradle, aby określić stan interfejsów API:

  • Do użytku wewnętrznego: nie jest przeznaczony do użytku publicznego.
  • W fazie testów: dostępne dla użytkowników, ale nie są wersjami ostatecznymi, co oznacza, że w ostatecznej wersji mogą nie być zgodne z wcześniejszymi wersjami.
  • Publiczna: stabilna wersja publiczna.
  • 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 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 dostosowań kompilacji. Więcej informacji o nowych interfejsach API znajdziesz w naszej dokumentacji referencyjnej.

Podstawy kompilacji Gradle

Ten przewodnik nie obejmuje całego systemu kompilacji Gradle. Zawiera on jednak minimalny zestaw pojęć, który pomoże Ci w integracji z naszych interfejsami API, oraz linki do głównej dokumentacji Gradle, w której znajdziesz więcej informacji.

Zakładamy, że znasz podstawy działania Gradle, w tym konfigurowanie projektów, edytowanie plików kompilacji, stosowanie wtyczek i uruchamianie zadań. Podstawowe informacje o Gradle w odniesieniu do AGP znajdziesz w artykule Konfigurowanie kompilacji. Informacje o ogólnej strukturze dostosowywania wtyczek Gradle znajdziesz w artykule Tworzenie niestandardowych wtyczek Gradle.

Słownik typów nieaktywnych w 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. Poniższa lista zawiera główne typy Gradle używane w opóźnionym wykonywaniu oraz ich kluczowe metody.

Provider<T>
Dostarcza wartość typu T (gdzie „T” oznacza dowolny typ), którą można odczytać podczas fazy wykonywania za pomocą metody get() lub przekształcić w nową wartość Provider<S> (gdzie „S” oznacza inny typ) za pomocą metod map(), flatMap() i zip(). Pamiętaj, że metoda get() nigdy nie powinna być 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 odroczone do momentu wywołania funkcji get() w wynikającym z niej obiekcie Provider<S>, dzięki czemu cały łańcuch jest opóźniony.
  • flatMap(): przyjmuje funkcję lambda i zwraca wartość Provider<S>, ale funkcja lambda przyjmuje wartość T i zwraca wartość Provider<S> (zamiast bezpośrednio wartości S). Użyj flatMap(), gdy S nie może zostać określony w czasie konfiguracji i możesz uzyskać tylko Provider<S>. Praktycznie rzecz biorąc, jeśli użyjesz funkcji map(), a w efekcie otrzymasz typ wyniku Provider<Provider<S>>, prawdopodobnie powinieneś użyć funkcji flatMap().
  • zip(): umożliwia połączenie 2 instancji Provider w celu utworzenia nowej Provider z wartością obliczoną za pomocą funkcji łączącej wartości z 2 instancji wejściowych 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 przeznaczony tylko do odczytu, parametr Property<T> może też mieć ustawioną wartość. Możesz to zrobić na 2 sposoby:
  • Ustaw wartość typu T bezpośrednio, gdy jest dostępna, bez potrzeby odroczonego przetwarzania.
  • Jako źródła wartości Property<T> użyj innego parametru Provider<T>. W tym przypadku wartość T jest materializowana tylko wtedy, gdy wywołana jest funkcja 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ą flatMap() możesz uzyskać dostęp do danych wyjściowych elementu Task przed utworzeniem Task. Może to być przydatne, jeśli chcesz używać danych wyjściowych jako danych wejściowych dla innych instancji Task.

Dostawcy i ich metody transformacji są niezbędne do konfigurowania danych wejściowych i wyjściowych zadań w sposób leniwy, czyli bez konieczności tworzenia wszystkich zadań z wyprzedzeniem i rozwiązywania wartości.

Dostawcy przekazują też informacje o zależnościach zadań. Gdy utworzysz funkcję Provider, przekształcając dane wyjściowe funkcji Task, ta pierwsza stanie się jej domyślną zależnością i będzie tworzona oraz uruchamiana zawsze, gdy zostanie rozwiązana wartość funkcji Task, np. gdy będzie to wymagane przez inną funkcję Task.ProviderProvider

Oto przykład zarejestrowania 2 zadań: GitVersionTask i ManifestProducerTask, z odroczeniem utworzenia instancji Task do momentu, gdy będą one faktycznie wymagane. Wartość wejściowa ManifestProducerTask jest ustawiona na Provider uzyskaną z danych wyjściowych GitVersionTask, więc ManifestProducerTask domyślnie zależy od wartości 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ń. Zawierają one szczegóły implementacji, które mogą się zmieniać w zależności od wersji. Zamiast tego AGP oferuje interfejs Variant API i dostęp do wyników jego zadań, czyli elementów buildu, które możesz odczytać i przekształcić. 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 zastosować niektóre z tych optymalizacji, skrypty i wtyczki Gradle muszą przestrzegać ścisłych reguł na każdym z etapów kompilacji Gradle: inicjalizacji, 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

W trakcie konfiguracji są oceniane skrypty kompilacji wszystkich projektów wchodzących w jej skład, stosowane są wtyczki, a zależności kompilacji są rozwiązywane. Ta faza służy do konfigurowania kompilacji za pomocą obiektów DSL oraz do leniwego rejestrowania zadań i ich danych wejściowych.

Etap konfiguracji jest zawsze uruchamiany niezależnie od tego, jakie zadanie ma zostać uruchomione, dlatego szczególnie ważne jest, aby nie przeprowadzał żadnych obliczeń i ogranicz wykonywanie obliczeń na podstawie danych wejściowych innych niż same 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. Konkretnie uruchomione są metody klasy Task oznaczone symbolem @TaskAction. Podczas wykonywania zadania możesz odczytywać dane wejściowe (np. pliki) i rozwiązywać 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 na Androida i punkty rozszerzeń

Podczas interakcji z AGP używaj specjalnie utworzonych punktów rozszerzenia zamiast rejestrować typowe wywołania cyklu życia Gradle (takie jak afterEvaluate()) lub konfigurować 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 te czynności, aby utworzyć i wykonać 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. Analizowanie DSL: w tym momencie są oceniane skrypty kompilacji oraz tworzone i ustawiane są różne właściwości obiektów DSL Androida z bloku android. Na tym etapie rejestrowane są też wywołania zwrotne interfejsu API wariantów opisane w poniższych sekcjach.
  2. finalizeDsl(): Funkcja wywołania zwrotnego, która umożliwia zmianę obiektów DSL, zanim zostaną zablokowane na potrzeby tworzenia komponentu (wariantu). Obiekty VariantBuilder są tworzone na podstawie danych zawartych w obiektach DSL.

  3. Blokowanie DSL: usługa DSL jest teraz zablokowana i nie można już wprowadzać zmian.

  4. beforeVariants(): to wywołanie zwrotne może wpływać na tworzone komponenty i niektóre z ich właściwości w VariantBuilder. Nadal umożliwia wprowadzanie zmian w procesie kompilacji i w wygenerowanych artefaktach.

  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 za pomocą bloku 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. Aby dowiedzieć się, jak utworzyć w projekcie wtyczkę, możesz też przejrzeć przykłady buildSrc w repozytorium z przepisami Gradle w GitHub. 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 późniejszych fazach kompilacji. Możesz na przykład automatycznie tworzyć nowe konfiguracje lub zastępować właściwości, ale pamiętaj, że wszystkie wartości muszą zostać potwierdzone na etapie konfiguracji, więc nie mogą opierać się na żadnych zewnętrznych danych wejściowych. 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żywać do filtrowania komponentów korzystających z wywołania zwrotnego na podstawie nazwy, typu kompilacji lub rodzaju usługi.

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

onVariants()

Przed wywołaniem funkcji onVariants() wszystkie artefakty utworzone przez interfejs AGP są już ustawione, więc nie można ich już 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() })
}

Przekaż wygenerowane źródła 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 zbioru źródeł Javy niestandardowy folder źródeł 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 zbioru źródłowego res katalog z zasobami Androida wygenerowanymi na podstawie zadania niestandardowego. W przypadku innych typów źródeł proces jest podobny.

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.

Dostęp do artefaktów i ich modyfikowanie

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

Listę elementów obecnie obsługiwanych 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ę instancji FileSystemLocation, czyli liczbę plików lub katalogów typu artefaktu. Informacje o kardinalności artefaktu możesz uzyskać, sprawdzając jego klasę nadrzędną: artefakty z jednym FileSystemLocation będą podklasą 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ć, które operacje obsługuje:

  • 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: dotyczy tylko artefaktów, które są podklasami Artifact.Multiple. Oznacza to, że do Artifact można dołączać elementy, czyli niestandardowy element Task może tworzyć nowe wystąpienia tego typu Artifact, które zostaną dodane do istniejącej listy.
  • Replaceable: dotyczy tylko artefaktów, które są podklasami Artifact.Single. Zastępowalny element Artifact może zostać zastąpiony przez zupełnie nowy egzemplarz wygenerowany jako dane wyjściowe elementu 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ć do potoku dowolną liczbę operacji na artefaktach z wywołania zwrotnego onVariants(), a architektura AGP zapewnia, że są one prawidłowo powiązane w łańcuchu, tak aby wszystkie zadania były wykonywane w odpowiednim czasie, a artefakty były prawidłowo tworzone i aktualizowane. Oznacza to, że gdy operacja zmieni dane wyjściowe przez ich dodanie, zastąpienie lub przekształcenie, w następnej operacji zostanie przedstawiona zaktualizowana wersja artefaktów jako dane wejściowe itd.

Punkt wejścia do operacji rejestrowania to 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ć niestandardową zmienną TaskProvider, aby uzyskać obiekt TaskBasedOperation (1), i używać go do łączenia wejść i wyjść 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óra zwraca 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 to SingleArtifact i RegularFile. Z tego powodu musimy użyć metody wiredWithFiles, która przyjmuje jako dane wejściowe pojedyncze odwołanie RegularFileProperty, a jako dane wyjściowe pojedynczą wartość RegularFileProperty. 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 instrukcji systemu kompilacji Gradle: