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

  • demoRelease
  • demoDebug
  • fullRelease
  • fullDebug

次のステップ

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