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 移行タイムラインをご覧ください。

この移行やその他の方法で AGP API のサポートが終了した場合、現在のメジャー リリースでは引き続き利用できますが、警告が表示されます。サポートが終了した API は、今後のメジャー リリースで AGP から完全に削除されます。たとえば、API が AGP 7.0 で非推奨になっている場合は、そのバージョンで使用することはできますが、警告が生成されます。この API は AGP 8.0 で使用できなくなります。

一般的なビルドのカスタマイズで使用される新しい API の例を確認するには、Android Gradle プラグインのレシピをご覧ください。一般的なビルドのカスタマイズ例を示しています。新しい API について詳しくは、リファレンス ドキュメントをご覧ください。

Gradle ビルドの基本

このガイドでは、Gradle ビルドシステム全体について説明するわけではありません。Google の API との統合に役立つ、最低限必要な一連のコンセプトについて説明します。また、より詳細な Gradle メイン ドキュメントへのリンクも記載しています。

プロジェクトの構成、ビルドファイルの編集、プラグインの適用、タスクの実行など、Gradle の動作について基本的な知識をお持ちであることを前提としています。AGP に関する Gradle の基本については、ビルドを構成するを確認することをおすすめします。Gradle プラグインのカスタマイズの一般的なフレームワークについては、カスタムの Gradle プラグインを開発するをご覧ください。

Gradle の遅延型の用語集

Gradle には、いくつかの型の「遅延」動作、すなわち大量の計算や Task の作成をビルドの後のフェーズで行うよう遅延させる機能が用意されています。これらの型は、多くの Gradle API と AGP API の中核を担うものです。遅延実行に関連する主な Gradle の型と重要なメソッドのリストを以下に示します。

Provider<T>
T 型の値を指定します(「T」は任意の型です)。これは、実行フェーズ中に get() を使用して読み取る、または map()flatMap()zip() のメソッドを使用して新しい Provider<S>(「S」は他の型です)に変換することができます。構成フェーズ中は get() を呼び出さないでください。
  • map(): ラムダを受け入れ、S 型の ProviderProvider<S> を生成します。map() のラムダ引数は、値 T を受け取って、値 S を生成します。ラムダは直ちに実行されません。その代わりに、生成される Provider<S> に対して get() が呼び出された時点まで実行が延期され、チェーン全体が遅延します。
  • flatMap(): ラムダを受け入れて Provider<S> を生成しますが、ラムダは値 T を受け取って Provider<S> を生成(値 S を直接生成するのではなく)します。構成時に S を決定できず、Provider<S> のみを取得できた場合は、flatMap() を使用します。実際には、map() を使用してから Provider<Provider<S>> の結果の型が返された場合は、flatMap() を使用したものと考えられます。
  • zip(): 2 つの Provider インスタンスを組み合わせて、2 つの入力 Providers インスタンスから取得した値を結合する関数を使用して計算された値を含む、新しい Provider を生成します。
Property<T>
Provider<T> を実装するため、T 型の値も指定します。読み取り専用の Provider<T> とは異なり、Property<T> の値も設定できます。これには、次の 2 つの方法があります。
  • T 型の値が使用可能な場合は、値を直接設定します。演算の遅延は必要ありません。
  • Property<T> の値のソースとして、別の Provider<T> を設定します。この場合は、Property.get() が呼び出された場合にのみ、値 T が実体化されます。
TaskProvider
Provider<Task> を実装します。TaskProvider を生成するには、tasks.create() ではなく tasks.register() を使用して、必要な場合にのみタスクがインスタンス化されるようにします。Task が作成される前に flatMap() を使用して Task の出力にアクセスできます。これは、出力を他の Task インスタンスへの入力として使用する場合に有効です。

プロバイダとその変換メソッドは、タスクの入力と出力を遅延的に、つまりすべてのタスクを事前に作成し、値を解決することを必要とせずに設定するために不可欠です。

プロバイダはタスクの依存関係についての情報も提供します。Task 出力を変換して Provider を作成すると、その TaskProvider の暗黙的な依存関係になり、Provider の値が解決される(別の Task で必要な場合など)たびに作成されて実行されます。

次の例では、GitVersionTaskManifestProducerTask の 2 つのタスクを登録し、実際に必要になるまで Task インスタンスの作成を遅延させています。ManifestProducerTask 入力値は、GitVersionTask の出力から取得した Provider に設定されるため、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))
    }

これら 2 つのタスクは、明示的にリクエストされた場合にのみ実行されます。これは、たとえば、./gradlew debugManifestProducer を実行する場合や、ManifestProducerTask の出力が他のタスクに接続されてその値が必要な場合に、Gradle 呼び出しの一部として行われます。

入力を使用する、または出力を生成するカスタムタスクを記述しますが、AGP は独自のタスクへの公開アクセス権を直接付与しません。これらは実装の詳細であり、バージョンによって変更される場合があります。代わりに、AGP は Variant API を備えており、読み取りと変換が可能なタスク(ビルド アーティファクト)の出力へのアクセス権を付与します。詳細については、このドキュメントの Variant API、アーティファクト、タスクをご覧ください。

Gradle ビルドフェーズ

プロジェクトのビルドは、本質的に複雑でリソースを必要とするプロセスであり、タスク構成の回避、最新のチェック、構成のキャッシュ機能などにより、再現可能な、または不要な計算に費やす作業時間を最小限に抑えることができます。

これらの最適化機能の一部について、適用するには Gradle のスクリプトとプラグインで、初期化、構成、実行という Gradle の異なるビルドフェーズごとに厳格なルールに従う必要があります。このガイドでは、構成と実行のフェーズを中心に説明します。すべてのフェーズについて詳しくは、Gradle ビルドのライフサイクル ガイドをご覧ください。

構成フェーズ

構成フェーズでは、ビルドに含まれるすべてのプロジェクトのビルド スクリプトが評価され、プラグインが適用されて、ビルドの依存関係が解決されます。このフェーズは、DSL オブジェクトを使用してビルドを構成する場合や、タスクとその入力を必要に応じて登録する場合に使用します。

実行がリクエストされるタスクに関係なく、構成フェーズは常に実行されるため、演算がビルド スクリプト自体以外の入力に依存しないように制限することが特に重要です。つまり、適切な Task インスタンスとして、外部プログラムを実行する、ネットワークから読み取る、または実行フェーズまで遅延させることができる長時間の演算を実行することは避けてください。

実行フェーズ

実行フェーズでは、リクエストされたタスクとそれらの依存タスクが実行されます。具体的には、@TaskAction とマークされた Task クラスメソッドが実行されます。タスク実行中は、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 ブロックの Android DSL オブジェクトのさまざまなプロパティを作成して設定します。以降のセクションで説明する Variant API コールバックの登録も、このフェーズで実施されます。
  2. finalizeDsl(): コンポーネント(バリアント)の作成のためにロックされる前に DSL オブジェクトを変更できるコールバック。VariantBuilder オブジェクトは、DSL オブジェクトに含まれるデータに基づいて作成されます。

  3. DSL ロック: DSL がロックされ、変更できなくなります。

  4. beforeVariants(): このコールバックは、VariantBuilder を通じて、作成されるコンポーネントとそれらのプロパティの一部に影響を与える可能性があります。ビルドフローと生成されるアーティファクトは引き続き変更可能です。

  5. バリアントの作成: 作成されるコンポーネントとアーティファクトのリストが確定され、変更できなくなります。

  6. onVariants(): このコールバックでは、作成された Variant オブジェクトへのアクセス権を取得し、オブジェクトに含まれる値または Property 値のプロバイダを、遅延的に演算が行われるように設定できます。

  7. バリアントのロック: バリアントのオブジェクトがロックされ、変更できなくなります。

  8. 作成されたタスク: Variant オブジェクトとその Property 値は、ビルドの実行に必要な Task インスタンスの作成に使用されます。

AGP では、finalizeDsl()beforeVariants()onVariants() のコールバックを登録できる AndroidComponentsExtension が導入されています。この拡張機能は、ビルド スクリプトの androidComponents ブロックで使用できます。

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

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

ただし、Android ブロックの DSL を使用して宣言型の構成のビルド スクリプトのみを保持し、カスタムの命令ロジックを buildSrc または外部プラグインに移動することをおすすめします。プロジェクトでプラグインを作成する方法については、Gradle レシピ GitHub リポジトリの buildSrc サンプルをご覧ください。プラグイン コードからコールバックを登録する例を次に示します。

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)

このコールバックでは、ビルドファイル内の android ブロックから情報を解析して、作成された DSL オブジェクトにアクセスし、変更できます。これらの 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() と同様に、指定するすべての値は構成時に解決され、外部入力に依存しないことが必要です。beforeVariants() コールバックの実行が完了した後は、VariantBuilder オブジェクトを変更しないでください。

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

beforeVariants() コールバックは、必要に応じて androidComponentsExtensionselector() メソッドで取得できる VariantSelector を取ります。これを使用して、名前、ビルドタイプ、プロダクト フレーバーに基づいて、コールバック呼び出しに参加するコンポーネントをフィルタできます。

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

onVariants()

onVariants() が呼び出されるまでに、AGP によって作成されるすべてのアーティファクトはすでに決定されているため、無効にすることはできません。ただし、Variant オブジェクトで Property 属性に値を設定することで、タスクに使用する値の一部を変更できます。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 をご覧ください。

次のコード スニペットは、addStaticSourceDirectory() 関数を使用して ${variant.name} というカスタム ソースフォルダを Java ソースセットに追加する方法を示しています。追加すると、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 のレシピをご覧ください。

アーティファクトにアクセスして変更する

AGP には、Variant オブジェクトの単純なプロパティの変更に加えて、ビルド中に生成された中間アーティファクトと最終アーティファクトの読み取りまたは変換を可能にする拡張メカニズムも用意されています。たとえば、結合された最終的な AndroidManifest.xml ファイルのコンテンツをカスタム Task で読み取って分析する、またはそのコンテンツをカスタム Task によって生成されたマニフェスト ファイルのコンテンツと完全に置き換えることができます。

現在サポートされているアーティファクトのリストについては、Artifact クラスのリファレンス ドキュメントをご覧ください。アーティファクト タイプにはそれぞれ、次のような有用なプロパティがあります。

カーディナリティ

Artifact のカーディナリティは、FileSystemLocation インスタンスの数、またはアーティファクト タイプのファイルまたはディレクトリの数を表します。アーティファクトのカーディナリティに関する情報を取得するには、親クラスを確認します。FileSystemLocation が 1 つのアーティファクトは、Artifact.Single のサブクラスになります。複数の FileSystemLocation インスタンスを持つアーティファクトは、Artifact.Multiple のサブクラスになります。

FileSystemLocation の型

Artifact がファイルまたはディレクトリを表すかどうかを確認するには、パラメータ化された FileSystemLocation の型(RegularFile または Directory)に着目します。

サポートされている操作

すべての Artifact クラスは、次のいずれかのインターフェースを実装して、サポートする操作を示すことができます。

  • Transformable: Artifact を、任意の変換を実行して Artifact の新しいバージョンを出力する Task の入力として使用できるようにします。
  • Appendable: Artifact.Multiple のサブクラスであるアーティファクトにのみ適用されます。これは、Artifact を追加できることを意味します。つまり、カスタム Task で、この Artifact タイプの新しいインスタンスを作成し、既存のリストに追加できます。
  • Replaceable: Artifact.Single のサブクラスであるアーティファクトにのみ適用されます。置換可能な Artifact は、Task の出力として生成される、まったく新しいインスタンスに置き換えることができます。

3 つのアーティファクト変更オペレーションに加えて、すべてのアーティファクトは get()(または getAll())オペレーションをサポートしています。これは、アーティファクトの最終バージョンを含む Provider を返します(すべてのオペレーションが終了した後)。

複数のプラグインが、アーティファクトへの任意の数のオペレーションを onVariants() コールバックからパイプラインに追加できます。これにより、AGP によってチェーンが適切に連結され、すべてのタスクが適切なタイミングで実行されて、アーティファクトが正しく生成、更新されます。つまり、オペレーションが出力を追加、置換、変換して変更を行った場合、次のオペレーションではこれらのアーティファクトの更新版が入力として表示されます。

オペレーションを登録するためのエントリ ポイントは Artifacts クラスです。次のコード スニペットは、onVariants() コールバックで Variant オブジェクトのプロパティから Artifacts のインスタンスにアクセスする方法を示しています。

続いて、カスタムの TaskProvider を渡して TaskBasedOperation オブジェクト(1)を取得し、取得したオブジェクトを使用して、wiredWith* メソッド(2)のいずれかにより入力と出力を接続します。

実際に選択する必要があるメソッドは厳密には、変換する Artifact によって実装されるカーディナリティと FileSystemLocation 型によって異なります。

最後に、返される *OperationRequest オブジェクトで選択したオペレーションを表すメソッドに Artifact 型を渡します。たとえば、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_MANIFESTSingleArtifact、すなわち RegularFile です。そのため、入力に単一の RegularFileProperty の参照を受け入れ、出力に単一の RegularFileProperty を受け入れる wiredWithFiles メソッドを使用する必要があります。TaskBasedOperation クラスには、その他にも Artifact カーディナリティと FileSystemLocation 型の他の組み合わせに使用できる wiredWith* メソッドがあります。

AGP の拡張について詳しくは、Gradle ビルドシステムのマニュアルで次のセクションを読むことをおすすめします。