Gradle ビルドの概要

Android アプリは通常、Gradle ビルドシステムを使用してビルドされます。ビルドの構成方法の詳細に入る前に、ビルドの背後にあるコンセプトについて説明します。これにより、システム全体を把握できます。

ビルドとは

ビルドシステムは、ソースコードを実行可能なアプリケーションに変換します。ビルドには、アプリケーションやライブラリの分析、コンパイル、リンク、パッケージ化を行う複数のツールが関与することがよくあります。Gradle は、タスクベースのアプローチを使用してこれらのコマンドを整理して実行します。

タスクは、入力を出力に変換するコマンドをカプセル化します。プラグインは、タスクとその構成を定義します。ビルドにプラグインを適用すると、タスクが登録され、入力と出力を使用してタスクが接続されます。たとえば、Android Gradle プラグイン(AGP)をビルドファイルに適用すると、APK または Android ライブラリのビルドに必要なすべてのタスクが登録されます。java-library プラグインを使用すると、Java ソースコードから jar をビルドできます。Kotlin や他の言語にも同様のプラグインがありますが、他のプラグインはプラグインを拡張することを目的としています。たとえば、protobuf プラグインは、AGP や java-library などの既存のプラグインに protobuf サポートを追加することを目的としています。

Gradle では構成よりも規則が優先されるため、プラグインには適切なデフォルト値が用意されていますが、宣言型のドメイン固有言語(DSL)を使用してビルドをさらに構成できます。DSL は、ビルド方法ではなく、ビルドする内容を指定できるように設計されています。プラグインのロジックが「どのように」を管理します。この構成は、プロジェクト(およびサブプロジェクト)の複数のビルドファイルで指定します。

タスク入力には、ファイルやディレクトリのほか、Java 型(整数、文字列、カスタムクラス)としてエンコードされたその他の情報も使用できます。出力はディスクに書き込む必要があるため、ディレクトリまたはファイルにする必要があります。タスクの出力を別のタスクの入力に接続すると、タスクがリンクされ、一方が他方の前に実行されるようになります。

Gradle では、ビルドファイルに任意のコードとタスク宣言を記述できますが、これにより、ツールがビルドを理解し、ビルドを維持することが難しくなる可能性があります。たとえば、プラグイン内のコードのテストは作成できますが、ビルドファイル内のコードのテストは作成できません。代わりに、ビルドロジックとタスク宣言を(自分または他のユーザーが定義する)プラグインに制限し、そのロジックをビルドファイルでどのように使用するかを宣言する必要があります。

Gradle ビルドを実行するとどうなりますか?

Gradle ビルドは 3 つのフェーズで実行されます。これらの各フェーズでは、ビルドファイルで定義したコードの異なる部分が実行されます。

  • 初期化では、ビルドに含まれるプロジェクトとサブプロジェクトが決定され、ビルドファイルと適用されたプラグインを含むクラスパスが設定されます。このフェーズでは、ビルドするプロジェクトと、プラグインとライブラリを取得する場所を宣言する設定ファイルについて説明します。
  • 構成は、プロジェクトごとにタスクを登録し、ビルドファイルを実行してユーザーのビルド仕様を適用します。構成コードは、実行中に生成されたデータやファイルにアクセスできないことを理解することが重要です。
  • 実行: アプリケーションの実際の「ビルド」を行います。構成の出力は、タスクの有向非巡回グラフ(DAG)です。これは、ユーザーがリクエストしたすべての必要なビルドステップ(コマンドラインまたはビルドファイルのデフォルトとして指定されたタスク)を表します。このグラフは、タスク間の依存関係を表します。これは、タスクの宣言で明示的に指定されている場合もあれば、入力と出力に基づいて指定されている場合もあります。タスクの入力が別のタスクの出力である場合、そのタスクは他のタスクの後に実行する必要があります。このフェーズでは、グラフで定義された順序で古いタスクを実行します。タスクの入力が前回の実行から変更されていない場合、Gradle はタスクをスキップします。

詳細については、Gradle のビルド ライフサイクルをご覧ください。

構成 DSL

Gradle は、ドメイン固有言語(DSL)を使用してビルドを構成します。この宣言型のアプローチでは、手順(命令型)の指示を記述するのではなく、データを指定することに重点を置いています。ビルドファイルは Kotlin または Groovy を使用して記述できますが、Kotlin を使用することを強くおすすめします。

DSL は、ドメイン エキスパートやプログラマなど、すべてのユーザーがプロジェクトに簡単に貢献できるように、データをより自然な方法で表す小さな言語を定義します。Gradle プラグインは DSL を拡張して、タスクに必要なデータを構成できます。

たとえば、ビルドの Android 部分の構成は次のようになります。

Kotlin

android {
    namespace = "com.example.app"
    compileSdk = 34
    // ...

    defaultConfig {
        applicationId = "com.example.app"
        minSdk = 34
        // ...
    }
}

Groovy

android {
    namespace 'com.example.myapplication'
    compileSdk 34
    // ...

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdk 24
        // ...
    }
}

背後では、DSL コードは次のようになります。

fun Project.android(configure: ApplicationExtension.() -> Unit) {
    ...
}

interface ApplicationExtension {
    var compileSdk: Int
    var namespace: String?

    val defaultConfig: DefaultConfig

    fun defaultConfig(configure: DefaultConfig.() -> Unit) {
        ...
    }
}

DSL 内の各ブロックは、ラムダを受け取って構成し、同じ名前のプロパティを使用してアクセスする関数で表されます。これにより、ビルドファイル内のコードがデータ仕様のように見えます。

外部依存関係

Maven ビルドシステムでは、依存関係の仕様、ストレージ、管理システムが導入されています。ライブラリはリポジトリ(サーバーまたはディレクトリ)に保存され、バージョンや他のライブラリへの依存関係などのメタデータが含まれます。検索するリポジトリと使用する依存関係のバージョンを指定し、ビルドシステムがビルド中にダウンロードします。

Maven アーティファクトは、グループ名(会社、デベロッパーなど)、アーティファクト名(ライブラリの名前)、そのアーティファクトのバージョンで識別されます。これは通常、group:artifact:version として表されます。

このアプローチにより、ビルド管理が大幅に改善されます。このようなリポジトリは「Maven リポジトリ」と呼ばれることがよくありますが、これはアーティファクトのパッケージ化と公開方法に関係しています。これらのリポジトリとメタデータは、Gradle を含む複数のビルドシステムで再利用されています(Gradle はこれらのリポジトリに公開できます)。公開リポジトリはすべてのユーザーが使用できる共有リポジトリで、社内リポジトリは社内の依存関係を社内で保持します。

プロジェクトをサブプロジェクト(Android Studio では「モジュール」とも呼ばれます)にモジュラ化することもできます。サブプロジェクトは依存関係としても使用できます。各サブプロジェクトは、サブプロジェクトまたは最上位プロジェクトで使用できる出力(jar など)を生成します。これにより、再ビルドが必要な部分を分離し、アプリケーション内の責任をより明確に分離することで、ビルド時間を短縮できます。

依存関係を指定する方法について詳しくは、ビルド依存関係を追加するをご覧ください。

ビルド バリアント

Android アプリケーションを作成する場合は、通常、複数のバリアントをビルドします。バリアントには異なるコードが含まれているか、異なるオプションでビルドされており、ビルドタイプとプロダクト フレーバーで構成されています。

ビルドタイプは、宣言されたビルドオプションによって異なります。デフォルトでは、AGP は「release」ビルドタイプと「debug」ビルドタイプを設定しますが、これらのビルドタイプを調整したり、追加したりできます(ステージングや内部テスト用に追加する場合など)。

デバッグビルドでは、アプリを圧縮したり難読化したりしないため、ビルドが高速化され、すべてのシンボルがそのまま保持されます。また、アプリを「デバッグ可能」としてマークし、汎用デバッグキーで署名して、デバイスにインストールされているアプリファイルへのアクセスを有効にします。これにより、アプリケーションの実行中にファイルとデータベースに保存されているデータを探索できます。

リリースビルドでは、アプリが最適化され、リリースキーで署名され、インストールされたアプリファイルが保護されます。

プロダクト フレーバーを使用すると、アプリケーションに含まれるソースと依存関係のバリエーションを変更できます。たとえば、アプリケーションに「デモ」と「完全版」のフレーバーを作成したり、「無料」と「有料」のフレーバーを作成したりできます。共通のソースは「main」ソースセット ディレクトリに記述し、フレーバーの名前が付けられたソースセットでソースをオーバーライドまたは追加します。

AGP は、ビルドタイプとプロダクト フレーバーの組み合わせごとにバリアントを作成します。フレーバーを定義しない場合、バリアントはビルドタイプの名前が付けられます。両方を定義すると、パターンの名前は <flavor><Buildtype> になります。たとえば、ビルドタイプが releasedebug、フレーバーが demofull の場合、AGP は次のようにバリエーションを作成します。

  • demoRelease
  • demoDebug
  • fullRelease
  • fullDebug

次のステップ

ビルドのコンセプトを確認したので、プロジェクトの Android ビルド構造について見てみましょう。