Gradleビルドスクリプトの基本

先週はGradleプラグインを自作していたが、そもそもGradleスクリプトの基本がよくわからない。

kdnakt.hatenablog.com

 

ということでこちらの公式ドキュメントを読みながら、手を動かしてみた。

docs.gradle.org

 

 

[タスクの基本]

Gradleのビルドは1つ以上のプロジェクトから構成される。プロジェクトはjarやwarをビルドしたり、デプロイジョブを記述することもできる。

そうしたGradleの仕事はタスクの形で定義される。タスクがビルドで実行される仕事の最小単位となる。クラスをコンパイルしたり、jarをビルドしたり、javadocを生成したり。

 

これらのタスクは通常プラグインで定義されているが、自分で実装することもできる。

 

gradleコマンドはカレントディレクトリのbuild.gradleファイルまたはbuild.gradle.ktsファイルを探して、実行する。

新しいディレクトリに以下の内容のbuild.gradle.ktsファイルを作る。

tasks.register("hello") {
    doLast {
        println("Hello world!")
    }
}

 

実行すると以下のようになる。

$ gradle -q hello
Hello world!

 

なるほど、これはシンプルで分かりやすい。

 

build.gradle.ktsファイルがGradleプロジェクトに相当する。ProjectのAPIをみると、確かにtasksプロパティが確認できる。

docs.gradle.org

 

tasksプロパティはTaskContainer型で、TaskContainerには5つのregister()メソッドが定義されている。

  • register(String name)
  • register(String name, Class<T> type)
  • register(String name, Class<T> type, Object... constructorArgs)
  • register(String name, Class<T> type, Action<? super T> configurationAction)
  • register(String name, Action<? super T> configurationAction)

 

クラスとしてタスクを定義して、それを登録することもできるようだ。

Hello worldの例は、{ ... }を第2引数にしているので、最後のregister(String name, Action<? super T> configurationAction)のメソッドを利用していることがわかる。

 

登録したタスクの内容を後から追加することもできる。

tasks.register("hello") {
    doLast {
        println("Hello Earth")
    }
}
tasks.named("hello") {
    doFirst {
        println("Hello Venus")
    }
}
tasks.named("hello") {
    doLast {
        println("Hello Mars")
    }
}
tasks.named("hello") {
    doLast {
        println("Hello Jupiter")
    }
}

 

上記のタスクhelloを実行すると以下のようになる。

$ gradle -q hello
Hello Venus
Hello Earth
Hello Mars
Hello Jupiter

 

[Kotlinのコードを利用する]

先のHello worldの例でもわかるように、Gradleのビルドスクリプト内ではGroovyやKotlinのコードを記述することができる。

 

シンプルな例としては、以下のように変数を利用できる。

tasks.register("upper") {
    doLast {
        val someString = "mY_nAmE"
        println("Original: $someString") // Original: mY_nAmE
        println("Upper case: ${someString.toUpperCase()}") // Upper case: MY_NAME
    }
}

 

以下のように、関数を定義して利用することもできる。

tasks.register("checksum") {
    doLast {
        fileList("./resources").forEach { file ->
            ant.withGroovyBuilder {
                "checksum"("file" to file, "property" to "cs_${file.name}")
            }
            println("$file.name Checksum: ${ant.properties["cs_${file.name}"]}")
        }
    }
}

fun fileList(dir: String): List<File> =
    file(dir).listFiles { file: File -> file.isFile }.sorted()

 

以下のフォルダ構造となっているときに、上記のchecksumを実行する。

$ tree
.
├── build.gradle.kts
└── resources
    ├── agile.manifesto.txt
    └── gradle.manifesto.txt

 

結果は以下のようになる。

$ gradle -q checksum
/(省略)/resources/agile.manifesto.txt.name checksum: faa3983e3f8ad2a53ff9cfd746db6499
/(省略)/resources/gradle.manifesto.txt.name checksum: e4f048aef3c397d4ddaf835b090b8910

 

buildscript()メソッドを利用すると、外部ライブラリを利用できる。

import org.apache.commons.codec.binary.Base64

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        "classpath"(group = "commons-codec", name = "commons-codec", version = "1.2")
    }
}

tasks.register("encode") {
    doLast {
        val encodedString = Base64().encode("hello world\n".toByteArray())
        println(String(encodedString)) // aGVsbG8gd29ybGQK
    }
}

 

[タスクの依存関係]

タスクとタスクの間には依存関係を定義することができる。

tasks.register("hello") {
    doLast {
        println("Hello world!")
    }
}
tasks.register("intro") {
    dependsOn("hello")
    doLast {
        println("I'm Gradle")
    }
}

 

このビルドスクリプトに対して、introタスクを実行すると、以下のようにhelloタスクが先に実行される。

$ gradle -q intro
Hello world!
I'm Gradle

 

タスクの依存関係を定義する際に、タスクを定義する順序は関係なく、依存される側のタスクを後に記述してもよい。

tasks.register("taskX") {
    dependsOn("taskY")
    doLast {
        println("taskX")
    }
}
tasks.register("taskY") {
    doLast {
        println("taskY")
    }
}

 

ここでtaskXを実行すると以下のようになる。

$ gradle -q taskX
taskY
taskX

 

また、依存関係は後から追加することもできる。

repeat(4) { counter ->
    tasks.register("task$counter") { // task0〜task3を定義
        doLast {
            println("I'm task number $counter")
        }
    }
}
tasks.named("task0") {
    dependsOn("task2", "task3") // task0の依存関係にtask2、taks3を追加
}

 

ここでtask0を実行すると、先にtask2とtask3が実行される。

$ gradle -q task0
I'm task number 2
I'm task number 3
I'm task number 0

 

[デフォルトタスク]

defaultTasks()メソッドでデフォルトタスクを登録すると、タスクの指定なしでgradleコマンドを呼び出した場合の処理を定義できる。

defaultTasks("clean", "run")

task("clean") {
    doLast {
        println("Default Cleaning!")
    }
}

tasks.register("run") {
    doLast {
        println("Default Running!")
    }
}

tasks.register("other") {
    doLast {
        println("I'm not a default task!")
    }
}

 

ここでgradleコマンドを実行すると、以下のようになる。

$ gradle -q
Default Cleaning!
Default Running!

 

[まとめ]

  • Gradleプロジェクトを構成するタスクはtasks.register()で登録できる
  • Gradleプロジェクト内でGroovyやKotlinのコードを記述できる
  • タスク間の依存関係やデフォルトで実行されるタスクを定義できる
  • コードは以下のリポジトリにまとめてある

github.com