android/gradle-recipes全部読む


- GoogleのAndroidチームには`Gradle職人`がいる（はず）
- Android Gradle Pluginを身体に馴染ませるために職人が用意してくれたサンプルを全部読む
- 各READMEに全部書いていると言えば、それはそう
- 基本は抑えている前提でこんな書き方もありなのか、という観点で読む

## agp-8.9

[https://github.com/android/gradle-recipes/tree/dd275c878633c4ad272883d4bc70f1ab3c8ed0fa](https://github.com/android/gradle-recipes/tree/dd275c878633c4ad272883d4bc70f1ab3c8ed0fa)

https://github.com/android/gradle-recipes/tree/dd275c878633c4ad272883d4bc70f1ab3c8ed0fa


### addBuildTypeUsingDslFinalize

- [AndroidComponentsExtension](https://developer.android.com/reference/tools/gradle-api/8.9/com/android/build/api/variant/AndroidComponentsExtension)を使って、`buildTypes`を追加するサンプル
  - AndroidComponentsExtensionは実行順序を持っているので、それに合わせて実装してねというもの
  - 実行順序については公式の[Android build flow and extension points](https://developer.android.com/build/extend-agp#build-flow-extension-points)に書いてある

- `build-login`でAGP全部(`com.android.tools.build:gradle`)をimplementationせずに`com.android.tools.build:gradle-api`のみをimplementationしている
- 依存解決の量が少ないので初期化が若干早いかも（最終的にビルドするときには本体自体も必要になるので、最終的には同じ）

<pre>
com.android.tools.build:gradle-api
    └── com.android.tools.build:builder-test-api
        ├── com.android.tools:annotations
        ├── com.android.tools:common
        └── com.android.tools.ddms:ddmlib
</pre>



### addCustomBuildConfigFields

- AndroidComponentsExtensionを使って`BuildConfigField`を追加するサンプル
- `buildTypes`や`productFlavors`で追加するコードのほうが良く見かけるがAndroidComponentsExtension登場移行のAGP7系以降のbuild type, product flavor依存のものはAndroidComponentsExtensionに寄せるほうが思想とマッチしているかもしれない
- **AndroidComponentsExtensionを使うと生成コードにコメントを追加できる**
- Gradleの[Provider API](https://docs.gradle.org/current/userguide/lazy_configuration.html#lazy_configuration_faqs)を使って、`buildConfigField`の値を動的に変更することができる

```groovy:build.gradle
import com.android.build.api.variant.BuildConfigField

// よくみるやつ：コメント追加不可
android {
    buildTypes {
        release {
            buildConfigField("String", "BUILD_TIME", "\"${minutesSinceEpoch}\"")
        }
        debug {
            buildConfigField("String", "BUILD_TIME", "\"0\"")
        }
    }
}

// AndroidComponentsExtension：コメント追加可
androidComponents.onVariants { variant ->
    variant.buildConfigFields.put(
        "FloatValue",
        new BuildConfigField(
            "Float",
            "12.0f",
            "Float Value Comment"
        )
    )
}
```


```java:BuildConfig.java
public final class BuildConfig {
  // Float Value Comment
  public static final Float FloatValue = 12.0f;
}
```



### addCustomSourceType

- AndroidComponentsExtensionで`registerSourceType`を使って追加するsource typeを宣言する
- AGPでは認識されないがAndroidComponentsExtensionで認識されるsource typeを追加することができる
- addStaticSourceDirectory, addGeneratedSourceDirectoryを使って、source typeに追加するsource folderを追加する
- アプリに梱包しないファイル類を管理する際に便利（だが標準のGradle APIを使ってもそんな変わらない気がする）

```kotlin:build.gradle.kts
androidComponents {
    registerSourceType("toml")
    onVariants { variant ->
        variant.sources.getByName("toml").also {
            // adding custom folders (static and generated) to `toml` source type
            it.addStaticSourceDirectory("third_party/${variant.name}/toml")
            it.addGeneratedSourceDirectory(addSourceTaskProvider, AddCustomSources::outputFolder)
        }
    }
}
```

- Google Play Services Pluginでも使われている


https://github.com/google/play-services-plugins/blob/0c2a0720fcf98ca3f90417a2fdb2528d21e361e5/google-services-plugin/src/main/kotlin/com/google/gms/googleservices/GoogleServicesPlugin.kt#L56-L69



### addGeneratedSourceFolder

- AndroidComponentsExtensionで自動生成ファイルを`assets`に追加するサンプル
- `addGeneratedSourceDirectory`を使って、source folderを追加する
- サンプルではassetsに追加しているが、`java`や`kotlin`のsource folderにも追加できる
  - [com.android.build.api.variant.Sources](https://developer.android.com/reference/tools/gradle-api/8.9/com/android/build/api/variant/Sources)
  - 既存のAndroidSourceSetでも同様にできるっちゃできる



### addMultipleArtifact

- [com.android.build.api.artifact.MultipleArtifact](https://developer.android.com/reference/tools/gradle-api/8.9/com/android/build/api/artifact/MultipleArtifact)のサンプル（≒AABにファイルを追加する）
  - MultipleArtifact.MULTIDEX_KEEP_PROGUARD: proguardルールを追加する
  - MultipleArtifact.NATIVE_DEBUG_METADATA: debug metadata(.dbg)を追加する
  - MultipleArtifact.NATIVE_SYMBOL_TABLES: debug symbol tables(.sym)を追加する

- CheckBundleTask, outputを設定しないとup-to-dateにならないので`使わなくとも宣言している`
- `variant.artifacts.**`
  - `variant.artifacts.get`: SingleArtifact(apk, aab...etc)を取得する
  - `variant.artifacts.getAll`, `variant.artifacts.addStaticDirectory`: MultipleArtifact(debug metadata, symbol tables...etc)を取得、追加する MultipleArtifactはReplaceable, Transformableなartifact
  - `variant.artifacts.forScope`: Gradle Taskを使って汎用的にScopedArtifactをartifactを操作する
  - `variant.artifacts.use`: Gradle Taskを使って汎用的にartifactを操作する


https://developer.android.com/reference/tools/gradle-api/8.9/com/android/build/api/artifact/Artifact


### allProjectsApkAction

- apkファイルを収集するtop levelのタスクを作るサンプル
- Variant.artifactsをタスクのinputに設定しているので暗黙的にタスクの依存関係が構築される（≒トップレベルのタスクを実行するとapkを生成するタスクが実行される）

```kotlin
var taskProvider: TaskProvider<AllProjectsApkTask>? = null
taskProvider = project.tasks.register<AllProjectsApkTask>("allProjectsAction") {
    outputFile.set(project.layout.buildDirectory.file("list_of_apks.txt"))
}

androidComponents.onVariants(selector().withBuildType("release")) { variant ->
    taskProvider?.configure { task ->
        task.inputDirectories.add(
            variant.artifacts.get(SingleArtifact.APK)
        )
    }
}
```



### appendToMultipleArtifact

- [addMultipleArtifact](#addmultipleartifact)と同じようなことをするがこちらは自動生成ファイルを追加する
- `com.android.build.api.artifact.Artifacts#use`を使うと設定したタスクが`MultipleArtifact.NATIVE_DEBUG_METADATA`を収集するタスクの依存関係に組み込まれる


```kotlin
variant.artifacts.use(generateNativeDebugMetadataTask)
    .wiredWith(GenerateNativeDebugMetadataTask::output)
    .toAppendTo(MultipleArtifact.NATIVE_DEBUG_METADATA)
```



### appendToScopedArtifacts

- ScopedArtifactsを追加する
- [ScopedArtifacts.Scope](https://developer.android.com/reference/tools/gradle-api/8.9/com/android/build/api/variant/ScopedArtifacts.Scope)
  - `ALL`: インポートされたプロジェクト、外部依存込み
  - `PROJECT`: プロジェクトのみ
- [ScopedArtifact](https://developer.android.com/reference/tools/gradle-api/8.9/com/android/build/api/artifact/ScopedArtifact)
  - `CLASSES`: classファイル、Jacocoを除くASMの結果も含む
  - `JAVA_RES`: 所謂普通のjavaプログラムにおけるresouceファイル e.g.)kotlin-tooling-metadata.json



### applyFusedLibraryPlugin

- 複数のartifactを単一のaarにまとめる
- `gradle.properties`に`android.experimental.fusedLibrarySupport=true`を追加することで、AGPがFusedLibraryPluginを適用する
- [FusedLibraryPlugin](https://developer.android.com/reference/tools/gradle-api/8.4/com/android/build/gradle/api/FusedLibraryPlugin)はAGP8.5以降、Android Developerからは消えているっぽい
- [AOSP](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/plugins/FusedLibraryPlugin.kt)にはまだ実装が存在しているので表にでないだけでAGP8.5以降も使用可能
- `dependencies`句に`include`を追加することでaarに含める内容をコントロールするが、aar or jar以外はコンパイル済みのclassファイルが梱包される（イメージとしてはstatic library）
  - fused libraryをpublishした際の`pom.xml`を見る限り内部で使用しているライブラリの依存が表示されない



### asmTransformClasses

- compile後に[ASM](https://asm.ow2.io/)を使ってclassファイルを変換するサンプル
- `instrumentation#setAsmFramesComputationMode`でASMのスタックフレームと最大スタックサイズの扱いを指定する
  - `FramesComputationMode.COMPUTE_FRAMES_FOR_ALL_CLASSES`: オリジナルをスキップして計算もしない
  - `FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_CLASSES`: オリジナルをスキップしてゼロから計算する
  - `FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS`: 変更もしくは追加されたメソッドを計算する
  - `FramesComputationMode.COPY_FRAMES`: オリジナルをそのまま使用する


- 始めは[TraceClassVisitor](https://asm.ow2.io/javadoc/org/objectweb/asm/util/TraceClassVisitor.html)を使いながら実装するとよい



```log:TraceClassVisitorのoutput
// class version 61.0 (61)
// access flags 0x31
public final class com/example/android/recipes/sample/SomeSource {

  // compiled from: SomeSource.kt

  @Lkotlin/Metadata;(mv={1, 9, 0}, k=1, xi=48, d1={"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0008\u0002\n\u0002\u0010\u000e\n\u0000\u0018\u00002\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0002J\u0008\u0010\u0003\u001a\u00020\u0004H\u0002\u00a8\u0006\u0005"}, d2={"Lcom/example/android/recipes/sample/SomeSource;", "", "()V", "someMethod", "", "app_debug"})

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 18 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/example/android/recipes/sample/SomeSource; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x12
  private final someMethod()Ljava/lang/String;
   L0
    LINENUMBER 20 L0
    LDC "something"
    ARETURN
   L1
    LOCALVARIABLE this Lcom/example/android/recipes/sample/SomeSource; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
}
```



### createSingleArtifact

- AndroidComponentsExtensionを使ってビルド時に`AndroidManifest.xml`を生成するサンプル
- サンプルでは丸っと新規で`AndroidManifest.xml`を生成している（toCreate）が、単に記載をちょっと変える（toTransform）こともできる


```kotlin
androidComponents.onVariants { variant ->
    // create new manifest
    variant.artifacts.use(produceManifestTask)
        .wiredWith(ProduceAndroidManifestTask::outputManifest)
        .toCreate(SingleArtifact.MERGED_MANIFEST)

    // modify existing manifest
    variant.artifacts.use(produceManifestTask)
        .wiredWithFiles(
            ProduceAndroidManifestTask::mergedManifest,
            ProduceAndroidManifestTask::outputManifest
        )
        .toTransform(SingleArtifact.MERGED_MANIFEST)
}

```



### disableTests

- `AndroidComponentsExtension#beforeVariants`を使って、`test`や`androidTest`を無効化するサンプル
- テストコードがないモジュールなどそもそもテストする気がないvariantやmoduleに対して使うと有効
  - そもそもKotlin Compileなどが実行されなくなる
  - androidTestにコードがない場合に`./gradlew connectedAndroidTest`を実行するとハングしたりすることが稀にある

- HostTestBuilder(unit test, screenshot test), HasDeviceTests(instrumented test)それぞれを柔軟に有効・無効化できる



### extendingAgp

- `AndroidComponentsExtension#registerExtension`を使ってbuild type、product flavorを慮った独自Extensionを作成するサンプル
- サンプルではVerifierTaskで独自Extensionの値を参照しているだけだが、実際には独自Extensionの値を使ってAndroidManifestやresourceをごにょったっていい



### getMultipleArtifact

- [addMultipleArtifact](#addmultipleartifact)と似たような話



### getScopedArtifacts

- [appendToScopedArtifacts](#appendToScopedArtifacts)と似たような話



### getSingleArtifact

- [addMultipleArtifact](#addmultipleartifact)と似たような話
- aabを取得しているサンプル



### legacyTaskBridging

- 通常のGradle TaskをAndroidComponentsExtensionに組み込むサンプル
- Copyタスクそのままだと依存を組み込めないのでサブクラスを作成して組み込んでいる



### listenToArtifacts

- artifactを生成するTaskを改変せずにartifactを取得するサンプル
- toListenTo: artifactを生成するTaskをHookする
- artifactのパスをそのままTaskに渡さず、**BuiltArtifactsLoaderをTaskに渡して取得する部分は通常のGradle Taskとは違う点**になる


```groovy
// 👇みたいに自前でTaskのoutputに対して依存を構築しなくてよい
def assembleTask = project.tasks.named("assembleDebug")
tasks.register("printArtifact") {
    doLast {
        println assembleTask.get().outputs.files.singleFile
    }
}

```



### listenToMultipleArtifact

- [listenToArtifacts](#listentoartifacts)のMultipleArtifact版



### onVariants

- `AndroidComponentsExtension#onVariants`を使うとapplicationIdやもろもろを変更できるよ、というサンプル
- [extend-agp#build-flow-extension-points](https://developer.android.com/build/extend-agp#build-flow-extension-points)にも書かれている



### perVariantManifestPlaceholder

- [onVariants](#onvariants)と似たような話
- `AndroidComponentsExtension#onVariants`を使って、variantごとにmanifest placeholderを変更するサンプル
- 詳細は[manage-manifests](https://developer.android.com/build/manage-manifests)
- `app/build.gradle`にごちゃごちゃ書くより、プラグインとして別出しした方が管理しやすくなるかもしれない



### registerPreBuild

- `variant.lifecycleTasks.registerPreBuild`を使うと任意のタスクをbuildの前に実行することができる
- AGP8.10には`variant.lifecycleTasks.registerPreInstallation`が追加されている
  - 任意のタスクをアプリをインストールする前に実行することができる（たぶん）



### selectVariants

- variant filterはdeprecatedになったので、`AndroidComponentsExtension#beforeVariants`を使ってねというサンプル
- [build-variants#filter-variants](https://developer.android.com/build/build-variants#filter-variants)にも同じ話が書かれている



### testFixtures

- testFixturesを有効化するサンプル
- [HasTestFixturesBuilder](https://developer.android.com/reference/tools/gradle-api/8.9/com/android/build/api/variant/HasTestFixturesBuilder)を使ってvariantごとに有効無効も切り替えられる
- AGP自体は`android.experimental.enableTestFixturesKotlinSupport=true`を設定することでKotlinをTestFixturesで使用することができるがKotlinとGradleの問題でGradle Hostedなテストではまだ動作しない
  - https://medium.com/@theilacker/implementing-test-fixtures-in-kotlin-android-project-say-goobdye-to-sharedtest-folder-ec51f396f56f
  - https://issuetracker.google.com/issues/219687418


https://medium.com/@theilacker/implementing-test-fixtures-in-kotlin-android-project-say-goobdye-to-sharedtest-folder-ec51f396f56f


https://issuetracker.google.com/issues/219687418



### transformAllClasses

- [asmTransformClasses](#asmtransformclasses)とは別アプローチでclassファイルを変換するサンプル
- ASMを使用せずにjarファイルからclassファイルを読み取り、[Javassist](https://www.javassist.org/)を使ってclassファイルを変換している



### transformDirectory

- [createSingleArtifact](#createsingleartifact)と似たような話
- `toTransform`を使って、assetの中身をビルド時に変換するサンプル



### transformManifest

- [createSingleArtifact](#createsingleartifact)と似たような話
- `toTransform`を使って、AndroidManifest.xmlをビルド時に変換するサンプル



### transformMultiple

- [addMultipleArtifact](#addmultipleartifact)と似たような話



### variantDependencySubstitutionTest

- [Variant.components](https://developer.android.com/reference/tools/gradle-api/8.9/com/android/build/api/variant/Variant#components())を使って、variantごとに依存関係を変更するサンプル
- [Variant.nestedComponents](https://developer.android.com/reference/tools/gradle-api/8.9/com/android/build/api/variant/Variant#nestedComponents())でunit test, instrumented test, test fixturesの依存関係を変更することができる



### variantOutput

- [onVariants](#onvariants)と似たような話



### workerEnabledTransformation

- `ArtifactTransformationRequest#submit`を使うと[Gradle Worker API](https://docs.gradle.org/current/userguide/worker_api.html)を使用できるよというサンプル
- **通常、Gradle Workerを使う場合はinjectしたWorkerExecutorを使用して[WorkQueue](https://docs.gradle.org/current/javadoc/org/gradle/workers/WorkQueue.html)を取得し、Taskをsubmitするがその辺りがAGP側に委譲する必要があるので注意が必要**
- 似たような形で[Gradle Build Services](https://docs.gradle.org/current/userguide/build_services.html)を使うこともできる（はず）