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 {
        version = release(36) {
            minorApiLevel = 1
        }
    }
    // ...

    defaultConfig {
        applicationId = "com.example.app"
        minSdk {
            version = release(23)
        }
        targetSdk {
            version = release(36)
        }
        // ...
    }
}

Groovy

android {
    namespace = 'com.example.app'
    compileSdk {
        version = release(36) {
            minorApiLevel = 1
        }
    }
    // ...

    defaultConfig {
        applicationId = 'com.example.app'
        minSdk {
            version = release(23)
        }
        targetSdk {
            version = release(36)
        }
        // ...
    }
}

舞台裏では、DSL コードは次のようになります。

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

interface ApplicationExtension {
    var namespace: String?

    fun compileSdk(configure: CompileSdkSpec.() -> Unit) {
        ...
    }

    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> になります。たとえば、ビルドタイプが release と debug で、フレーバーが demo と full の場合、AGP は次のバリアントを作成します。

  • demoRelease
  • demoDebug
  • fullRelease
  • fullDebug

次のステップ

ビルドのコンセプトを理解したら、プロジェクトの Android ビルド構造を確認します。