Gradle / Programming

ザクっと理解するbuild.gradle(.kts)の書き方 – Gradle Android Kotlin

Gradleシリーズです。
なんとなく使っているGradleをザクっと理解できるように書いています。
前回はsettings.gradle(.kts)の書き方をザクっとご紹介しました。
今回はbuild.gradle(.kts)の書き方についてご紹介します。
settings.gradle(.kts)のザクっと理解はこちら

Writing Build Scriptsより

ビルドスクリプト

すべてのGradleビルドは、ルートプロジェクトとサブプロジェクトのように1つ以上のプロジェクトで構成されています。

プロジェクトは、基本的に、ライブラリやアプリケーションなどの、ビルドが必要なソフトウェアを指します。
JARライブラリ、ウェブアプリケーション、他のプロジェクトの成果物をまとめた配布 ZIP ファイルなどをプロジェクトと呼びます。

一方、アプリケーションをステージング環境(テスト環境)や本番環境にデプロイするなど、実行する必要がある事柄を表すこともあります。

ビルドスクリプトは、Groovy DSLもしくはKotlin DSL(Domain-Specific Language)で記述します。
ビルドスクリプトはProjectオブジェクトを構成しています。

Projectオブジェクト

Projectオブジェクトは、Gradle APIの一つであり、多くのトップレベルのプロパティやブロックは、Project APIの一つです。
※Kotlin DSLのProjectオブジェクトのドキュメントはこちら
※Groovy DSLのProjectオブジェクトのドキュメントはこちら

下記は基本的なプロパティの一例です。

名前説明
nameStringプロジェクトディレクトリの名前
pathStringプロジェクトの完全修飾名
descriptionStringプロジェクトの説明
dependenciesDependencyHandlerプロジェクトのDependencyHandlerをリターン
repositoriesRepositoryHandlerプロジェクトのRepositoryHandlerをリターン
layoutProjectLayoutプロジェクトのいくつかの重要な場所へのアクセスの提供
groupObjectプロジェクトのグループ
versionObjectプロジェクトのバージョン

下記は基本的なメソッドの一例です。

名前説明
uri()プロジェクトのディレクトリを基準としたURIまでのパスの解決
task()指定された名前のタスクを作成し、プロジェクトに追加

ビルドスクリプトの構造

ビルドスクリプトは、Kotlinでは「ラムダ(lambda)」、Groovyでは「クロージャー(closure)」と呼ばれる特別な方法で呼び出すGradle APIで構成されています。

plugins { // ラムダ(クロージャー)
  ...
}
//
// ↓
//
plugins(function() { // オリジナル
  ...
})

Gradleは、設定スクリプトを上から順に1行ずつ実行していきます。

plugins { // 1
    id("org.jetbrains.kotlin.jvm") version "1.9.0"
    id("application")
}

repositories { // 2
    mavenCentral()
}

dependencies { // 3
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
    testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.3")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
    implementation("com.google.guava:guava:32.1.1-jre")
}

application { // 4
    mainClass = "com.example.Main"
}

tasks.named<Test>("test") { // 5
    useJUnitPlatform()
}

1. ビルドにプライグインを適用
プラグインは、Gradleの拡張、モジュール化して再利用のため、に使われます。
プラグインの適用は、「PluginDependenciesSpec」ブロックで行います。

例では、Kotlinのgradleプラグイン(バージョン1.9.0)とapplicationプラグインを適用しています。
KotlinのプラグインはGradleに含まれていないため、プラグインIDとバージョン情報が必要ですが、applicationはGradleに含まれているため、バージョン情報は不要です。

2. 依存関係の場所を定義
一般的に、プロジェクトは多くの依存関係を持っています。
ビルドスクリプトで、Gradleに依存関係の場所を伝える必要があります。
※依存関係とは、プラグイン、ライブラリ、ビルドを実行するためにGradleがダウンロードすべきコンポーネントを指す

例では、JetBrainsのKotlinプラグイン(org.jetbrains.kotlin.jvm)は、Maven Central Repositoryからダウンロードされます。

3. 依存関係の追加
多くの場合、ここでいう依存関係とは、プロジェクトのソースコードでインポートされている、事前にコンパイルされたライブラリのことを指します。

例では、このアプリケーションのコードで、Googleのguavaライブラリを使用しています。

4. プロパティの設定
プラグインは、拡張機能を使用することで、プロパティとメソッドの追加が可能です。
Projectオブジェクトは、プロジェクトに適用されているプラグインのすべての設定とプロパティを含んだ「ExtensionContainer」オブジェクトを持ています。

例では、applicationプラグインが、Javaアプリのメインクラスを決めるためのapplicationプロパティを追加しています。

5. タスクの登録と構成
クラスのコンパイル、単体テストの実行、WARファイルのZIP化など、タスクは基本的な作業を行います。
タスクの定義はプラグインで行いますが、タスクのプロジェクトへの追加(タスクの登録)はビルドスクリプトで行う必要があります。

タスクの登録は「TaskContainer.register(String)」で可能です。
※「TaskContainer.create(String)」の使用は非推奨です

tasks.register<Zip>("zip-reports") {
    from 'Reports/'
    include '*'
    archiveName 'Reports.zip'
    destinationDir(file('/dir'))
}

例では、「TaskCollection.named(String)」を使用し、タスクの発見と構成を行っています。

下記は、Javaコードから自動的にHTMLのドキュメントを作成する、Javadocタスクを構成しています。

tasks.named("javadoc").configure {
    exclude 'app/Internal*.java'
    exclude 'app/internal/*'
    exclude 'app/internal/*'
}

ビルドスクリプト記述の注意点

これまでの内容に加えて、変数の宣言、オブジェクトの定義、クロージャ委譲など、が可能です。

変数の宣言
変数には、ローカル変数と追加プロパティの2種類があります。

ローカル変数
ローカル変数の宣言は、valを用いて行い、可視性は宣言したスコープ内のみです。

val dest = "dest"

tasks.register<Copy>("copy") {
    from("source")
    into(dest)
}
def dest = 'dest'

tasks.register('copy', Copy) {
    from 'source'
    into dest
}

追加プロパティ
プロジェクト、タスク、ソースセットなどのGradleの拡張オブジェクトは、ユーザーが定義したプロパティを保持することができます。
オブジェクトのextraプロパティを介して、追加プロパティの追加、読み取り、設定を行います。もしくはKotlinの委任プロパティである「by extra」を使用することで、アクセス可能です。

可視性は、ローカル変数より広く、サブプロジェクトから親プロジェクトの追加プロパティにアクセス可能です。

plugins {
    id("java-library")
}

val springVersion by extra("3.1.0.RELEASE")
val emailNotification by extra { "build@master.org" }

sourceSets.all { extra["purpose"] = null }

sourceSets {
    main {
        extra["purpose"] = "production"
    }
    test {
        extra["purpose"] = "test"
    }
    create("plugin") {
        extra["purpose"] = "production"
    }
}

tasks.register("printProperties") {
    val springVersion = springVersion
    val emailNotification = emailNotification
    val productionSourceSets = provider {
        sourceSets.matching {
          it.extra["purpose"] == "production"
        }.map {
          it.name
        }
    }
    doLast {
        println(springVersion)
        println(emailNotification)
        productionSourceSets.get().forEach { println(it) }
    }
}
plugins {
    id 'java-library'
}

ext {
    springVersion = "3.1.0.RELEASE"
    emailNotification = "build@master.org"
}

sourceSets.all { ext.purpose = null }

sourceSets {
    main {
        purpose = "production"
    }
    test {
        purpose = "test"
    }
    plugin {
        purpose = "production"
    }
}

tasks.register('printProperties') {
    def springVersion = springVersion
    def emailNotification = emailNotification
    def productionSourceSets = provider {
        sourceSets.matching {
          it.purpose == "production"
        }.collect {
          it.name
        }
    }
    doLast {
        println springVersion
        println emailNotification
        productionSourceSets
          .get()
          .each {
            println it
          }
    }
}

上記の例では、5行目と6行目で「extra {…}」を用いて、追加プロパティをProjectオブジェクトに設定しています。

8行目で、purposeにnullを代入することで、すべてのソースセットにpurposeという追加プロパティを設定しています。

10行目から20行目で、mainとtestソースセットのpurposeにそれぞれ「production」と「test」を設定しています。さらに、新しい「plugin」という名前のソースセットを作成し、その purposeに「production」を設定しています。

22行目から37行目で、プロジェクトにprintPropertiesタスクを登録しています。

実行結果は下記のとおりです。

$ gradle -q printProperties
3.1.0.RELEASE
build@master.org
main
plugin

オブジェクトの定義
下記の例のようにオブジェクトの定義も可能です。

class UserInfo(
    var name: String? = null, 
    var email: String? = null
)

tasks.register("configure") {
    val user = UserInfo().apply {
        name = "Isaac Newton"
        email = "isaac@newton.me"
    }
    doLast {
        println(user.name)
        println(user.email)
    }
}
$ gradle -q configure
Isaac Newton
isaac@newton.me

クロージャ委譲
クロージャには、そのクロージャが定義された環境を保持するデリゲートオブジェクトが存在します。Groovyでは、このデリゲートオブジェクトを使用して、非ローカル変数やクロージャパラメータへの参照を解決します。Gradleでは、このデリゲートオブジェクトを構成クロージャに使用し、構成対象のオブジェクトを参照します。

dependencies {
    assert delegate == project.dependencies
    testImplementation('junit:junit:4.13')
    delegate.testImplementation('junit:junit:4.13')
}

この例では、2行目でdelegateプロパティとproject.dependencieオブジェクトが同じであることを確認しています。

4行目で、project.dependenciesオブジェクトのtestImplementationプロパティに「junit:junit:4.13」を追加しています。

このように、delegateプロパティを使用して、dependenciesブロックの内部で project.dependenciesオブジェクトにアクセスすることができます。

初期インポート

「throw new org.gradle.api.tasks.StopExecutionException()」ではなく「throw new StopExecutionException()」と記述できるように、自動的に、Gradleは一連のインポートをスクリプトに追加します。

インポート対象の詳細はこちらから確認をお願いします。

おわりに

次回はタスクの使用について解説します。
ご要望等がありましたらお気軽にコメントまでお願いします。

参考文献

Writing Build Scripts

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です