読者です 読者をやめる 読者になる 読者になる

GeekFactory

int128.hatenablog.com

Gradleプラグインの作り方

Gradle G* Advent Calendar 2013

G* Advent Calendar 2013の19日目です。

Gradleでビルドスクリプトを書いていると、他でも再利用できるタスクや関数が出てくることがあります。ビルドスクリプトの一部をプラグインに切り出すことで、他のGradleプロジェクトで再利用できます。

bluepapa32さんのG* Advent Calendar 12日目の記事でGradleプラグインの作り方が紹介されています。ネタがちょっとかぶってしまいましたが、本稿では違う視点から説明してみたいと思います。

Gradleプラグインの作り方

Gradleプラグインの実体はクラスファイルや設定ファイルを固めたJARです。Gradleプラグインを作るには、Gradleでプロジェクトを作って成果物としてJARを生成するのが簡単です。JARはMaven CentralやGitHubなどで配布します。

例えば、HelloPluginを作る手順は下記になります。

  1. build.gradle を書く。
  2. src/main/groovy/HelloPlugin.groovy にプラグインコードを書く。
  3. /src/main/resources/META-INF/gradle-plugins/hello.properties に設定を書く。これにより apply plugin: 'hello'プラグインを適用できるようになります。
  4. src/test/groovy/HelloPluginSpec.groovy にテストコードを書く。必要に応じて。
  5. gradle build でビルドする。

他にもGitリポジトリTravis CIの設定など面倒ですね。そこで、Gradleプラグインのブランクプロジェクトを作りました。

ブランクプロジェクトの使い方
git clone https://github.com/int128/gradle-plugin-blank.git gradle-hello-plugin
cd gradle-hello-plugin
./gradlew build
主な特徴
  • プラグインの空実装が入っています (see HelloPlugin.groovy)
  • Spockベースのテストコードが入っています (see HelloPluginSpec.groovy)
  • プラグイン名の設定ファイルも入ってます (see hello.properties)
  • GroovyDoc JARとsources JARを生成するタスクが入っています。
  • Maven Central RepositoryにJARを発行するタスクが入っています。
  • Travis CIに対応しています。
  • Gradle Wrapperが入っているので、すぐに開発を始められます。
  • Gradle、IDEA、Eclipse向けの.gitignoreが入っています。


Gradleプラグインの作り方が分かったところで、何を実装しましょうか?何かお題があってプラグインを実装するより、すでにあるビルドスクリプトから再利用できる部分を抜き出してプラグイン化することが多いと思います。

まずは、プロジェクト内のタスクや関数を共通化する方法から説明します。

apply fromを使う方法

プロジェクト内のタスクや関数を共通化するのであれば、別のスクリプトに切り出して apply from で読み込むだけで十分です。マルチプロジェクトの場合はルートプロジェクトに共通の定義を書けます。

// hoge.gradle
def checkHoge() {
    println 'ほげほげ'
}
// build.gradle
apply from: 'hoge.gradle'

task SomeTask << {
    checkHoge()
}

この方法はとても簡単なので機動力が高い利点がありますが、スクリプトが肥大化するとビルド時間が長くなる欠点もあります。

buildSrcにコードを配置する方法

プロジェクトのbuildSrcに共通的なコードを配置して、プロジェクト内で利用する方法もあります。buildSrc内は特別なプロジェクトになっており、buildSrcの成果物はビルドスクリプトのクラスパスに配置されます。

例で説明しましょう。下記のようにコードを配置します。

  • project/
    • build.gradle
    • buildSrc/
      • src/main/groovy/

ここでgradleを実行すると、まずbuildSrcがビルドされて、その後projectがビルドされます。

$ ./gradlew
:buildSrc:compileJava
:buildSrc:compileGroovy
:buildSrc:processResources
:buildSrc:classes
:buildSrc:jar
:buildSrc:assemble
:buildSrc:compileTestJava
:buildSrc:compileTestGroovy
:buildSrc:processTestResources
:buildSrc:testClasses
:buildSrc:test
:buildSrc:check
:buildSrc:build
:help

Welcome to Gradle 1.9.
(以下略)

projectのビルド時にはbuildSrcの成果物がクラスパスに配置されるため、build.gradleからHogeクラスを使うことができます。

// buildSrc/src/main/groovy/Hoge.groovy
class Hoge {
    static checkHoge() {
        println 'ほげほげ'
    }
}
// build.gradle
import static Hoge.*

task SomeTask << {
    checkHoge()
}

buildSrc内は一つのプロジェクトなので、プロダクトコードだけでなくテストコードも配置できます。

  • project/
    • build.gradle
    • buildSrc/
      • build.gradle
      • src/
        • main/groovy/
        • test/groovy/
          • HogeSpec.groovy

ユーティリティメソッドをbuildSrcに移動することでビルドスクリプトの見通しがよくなり、しかもビルドが早くなります。この方法は簡単な上に利点が多いので、知っておくと便利です。

buildSrcにプラグインクラスを配置してapply pluginする方法

前項の方法では、buildSrc内のクラスからビルドスクリプトのプロジェクトにアクセスすることができません。そこで、buildSrcにプラグインクラスを配置してapply pluginする方法があります。

Pluginインタフェースを実装したクラスをbuildSrcに配置します。Pluginインタフェースのapplyメソッドにはprojectインスタンスが渡されるため、これを使って新しいタスクを定義したりGradle DSLを拡張したりできます。

// buildSrc/src/main/groovy/HogePlugin.groovy
import org.gradle.api.Plugin
import org.gradle.api.Project

class HogePlugin implements Plugin<Project> {
    void apply(Project project) {
        project.extensions.checkHoge = { -> println 'ほげほげ' }
    }
}
// build.gradle
apply plugin: HogePlugin

ここでは project.extensions を使ってGradle DSLを拡張していますが、他にも project.container や project.convention.plugins を使う方法があります。Gradle DSLの仕様を読みながら進めるとよいでしょう。

JARをapply pluginする方法

前述のプラグインクラスをJARに固めて配布すると、他のプロジェクトでもプラグインを使えます。

// build.gradle
buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'org.hidetake:gradle-ssh-plugin:0.2.0'
  }
}

apply plugin: 'ssh'

まとめ

ビルドスクリプトから再利用できる部分を抜き出してプラグイン化しておくと、他のプロジェクトで役に立ちます。プラグインの実体はJARで、多くのプラグインMaven CentralやGitHubなどで配布されています。自分でプラグインを作る場合はブランクプロジェクトから作ると簡単です。