Groovy
gradle
入門

Gradle (build.gradle) 読み書き入門

本稿の対象者

本稿は Gradle の入門記事です。

  • そもそも Gradle って?
  • apply や compile って実はメソッドなんです。
  • {} は単なるスコープではなくクロージャなんです。
  • x = '10' は書けないけど、def y = '10' は書ける。
  • でも version = '1.0' などは書ける。
  • これらは Groovy 言語の構文が基本となっている。
  • そもそも Groovy って?
  • 自身でタスクやプラグインを作ることもできる。
build.gradle
apply plugin: 'java'
apply plugin: 'application'

repositories {
    jcenter()
}

dependencies {
    compile 'com.google.guava:guava:22.0'
    testCompile 'junit:junit:4.12'
}

mainClassName = 'App'

概要

Gradle (ぐれいどる) は作成したプログラムをビルド(コンパイル/テスト/実行/パッケージング/配布)するために利用します。有名なビルドツールに Maven がありますが、Gradle は Maven のように XML で設定定義するのではなく Java 言語によく似た構文である Groovy 言語を利用して、プログラマブルにビルド定義ができプログラマと親和性が高いです。

Android 開発では標準のビルドツールですし、私も新たに作るプログラムは全て Gradle で作るようにしています。
Gradle 自身も Java と Groovy で作成され Gradle によりビルドされています。OSS として公開( https://github.com/gradle/gradle )されています。

JVM(Java 仮想マシン)上で動作するため、基本的にどのような環境でも動作させることができます。

セットアップ

前提

  • (2018/02/04 時点) Java SE 7 以上が導入されていること。 ※ 前述のとおり、Gradle 自身が Java で作成されているため。

Gradle インストール

  1. https://gradle.org/ へアクセスする
  2. 「Install Manually」を押下する

  3. 「Install Gradle」を押下する

  4. Download」リンクを押下する

  5. 「binary-only」リンクを押下する

  6. Gradle を配置し、PATH を通す

ターミナル
# ダウンロードした zip ファイルを解凍し、任意の場所へ配置する。(私は /usr/local/ に配置してシンボリックを作成しています。)
$ cd ~/Downloads
$ unzip gradle-4.5-bin.zip
$ mv gradle-4.5 /usr/local/
$ ln -snf /usr/local/gradle-4.5 /usr/local/gradle

# ${GRADLE_HOME}/bin を PATH を通す
$ vi ~/.bash_profile
...
GRADLE_HOME=/usr/local/gradle
export PATH=${GRADLE_HOME}/bin:$PATH
...

# 設定した PATH を有効化
$ . ~/.bash_profile
$ echo ${PATH}

Gradle 動作確認

gradle -v コマンドを実行し、下記のようにバージョン情報が出力されればOKです。

ターミナル
$ gradle -v

------------------------------------------------------------
Gradle 4.5
------------------------------------------------------------

Build time:   2018-01-24 17:04:52 UTC
Revision:     77d0ec90636f43669dc794ca17ef80dd65457bec

Groovy:       2.4.12
Ant:          Apache Ant(TM) version 1.9.9 compiled on February 2 2017
JVM:          9 (Oracle Corporation 9+181)
OS:           Mac OS X 10.11.6 x86_64

Gradle Wrapper

Gradle のインストール手順を紹介しましたが、プロジェクトに参加する開発者全員がこの手順を行うなると結構な手間になります。
そこで、最初の一人が Gradle のセットアップを行い、Gradle Wrapper を準備しておきます。残りのメンバーはこの Gradle Wrapper を使うことで、Gradle が自動的にダウンロードされ、Gradle のコマンドを実行できるようになります。

先程のコマンドを Gradle Wrapper で行うと下記となります。

ターミナル
# Windows の場合は gradlew -v でOK
$ ./gradlew -v

初回実行時は、Gradle をダウンロードするため時間がかかりますが、2回目以降は素早く実行可能です。
※以降の手順は基本的に Gradle Wrapper での手順を記載します。

Gradle Wrapper は下記のコマンドで生成することができます。

ターミナル
$ gradle wrapper
$ ls
gradle      gradlew     gradlew.bat

この gradlew や gradlew.bat というファイルが Gradle Wrapper です。

本稿確認用リポジトリ

事前に Gradle Wrapper を設定してあるリポジトリを用意していますので
こちらを clone してください。

ターミナル
$ mkdir ~/java
$ cd ~/java
$ git clone https://github.com/hatimiti/GradleInit samplepj
$ cd samplepj

Hello Gradle

「gradle init」コマンドを実行すると、引数で指定したタイプのプロジェクトの雛形を作成することができます。

ターミナル
# gradle init --type <TYPE>
# <TYPE>
# * 'basic'
# * 'groovy-application'
# * 'groovy-library'
# * 'java-application'
# * 'java-library'
# * 'pom'
# * 'scala-library'
$ ./gradlew init --type java-application

$ tree
~/java/samplepj
|--.gradle
|--build.gradle
|--gradle
|  |--wrapper
|  |  |--gradle-wrapper.jar
|  |  |--gradle-wrapper.properties
|--gradlew
|--gradlew.bat
|--settings.gradle
|--src
|  |--main
|  |  |--java
|  |  |  |--App.java
|  |--test
|  |  |--java
|  |  |  |--AppTest.java

「build.gradle」ファイルが Gradle によるビルド定義の中心となるファイルです。
このファイルに依存関係やタスクを定義します。
Maven でいうところの pom.xml にあたります。

build.gradle(コメント省略)
plugins {
    id 'java'
    id 'application'
}

mainClassName = 'App'

dependencies {
    compile 'com.google.guava:guava:23.0'
    testCompile 'junit:junit:4.12'
}

repositories {
    jcenter()
}

Gradle の実行は「gradle <タスク名1> <タスク名2> ...」のように、実行したいタスクを連ねます。
下記の例では clean タスク、build タスク、run タスクを実行します。

ターミナル
$ ./gradlew clean build run

> Task :run
Hello world.

Java と Groovy の比較

build.gradle ファイルは Groovy で記述します。Groovy の省略記法やクロージャについて知ることで、build.gradle の記法が理解しやすくなります。
Groovy は Java の構文を基本的に踏襲しています。
ここでは Java プログラマ目線で、Groovy と Java で異なる部分を一部紹介したいと思います。

Groovy は groovyc コマンドでコンパイルし、.class ファイルへ変換も可能ですが、スクリプト言語としてコンパイルせずに動的実行が可能です.また、動的型付言語のため、def キーワードにより型宣言の省略や、ダックタイピングも可能です。

Groovy 文法

下記のコードを build.gradle に記述してください。
実際の Groovy ファイルの拡張子は .groovy ですが、ここでは Gradle のビルドスクリプトとして実行してみます。
これにより、build.gradle が Groovy で記述できることが実感できると思います。

コード下部に Point クラスを定義しているので、そちらを先に確認すると良いと思います。

build.gradle
import groovy.transform.ToString
import groovy.transform.EqualsAndHashCode

/*: デフォルトで import 済のパッケージ
import java.io.*
import java.lang.*
import java.math.BigDecimal
import java.math.BigInteger
import java.net.*
import java.util.*
import groovy.lang.*
import groovy.util.*
*/

// 末尾のセミコロン省略可能
Point a = new Point(10, 20)
Point b = new Point(3, 4)

// 特定の名前のメソッドは演算子記号で呼び出し可能
assert a + b  == new Point(13, 24)  // a.plus(b);
assert a - b  == new Point(7, 16)   // a.minus(b);
assert a * b  == new Point(30, 80)  // a.multiply(b);
assert a / b  == new Point(3, 5)    ///a.divide(b);
assert a++    == new Point(11, 21)  // a.next();
assert b--    == new Point(2, 3)    // a.previous();
assert a << b == new Point(13, 24)  // a.leftShift(b);

// コンストラクタ呼び出しの色々
def c = [1, 2] as Point
Point d = [3, 4]
Point e = new Point(x: 5, y: 6)
assert c == new Point(1, 2)
assert d == new Point(3, 4)
assert e == new Point(5, 6)

// メソッド呼び出し時の括弧省略可能
def f = a.plus b
def g = a.next()
//def g = a.next // 引数が無い場合は省略不可

// メソッドパラメータを Map にすることで、名前付き指定のように呼び出し可能
assert Point.of(x: 10, y: 20) == new Point(10, 20)

// 宣言時にアクセス修飾子やfinalをつけていないパラメータ以外は getter/setter は自動で生成される
a.setX(15)
a.setY(30)
assert a == new Point(15, 30)

// シングルクォートは Charcter or Stirng 型に変換される
char s1 = 'x';    assert s1 instanceof Character
def  s2 = 'x';    assert s2 instanceof String
def  s3 = 'xy';   assert s3 instanceof String
// ダブルクォートは String or GString 型
def d1 = "x";     assert d1 instanceof String
def d2 = "xy";    assert d2 instanceof String
def d3 = "${a}";  assert d3 instanceof GString
// GString は ${変数} で文字列に値を埋め込み可能。 == は equals で扱われる。
assert "a = ${a}" == "a = Point(15, 30)"

// """ でマルチライン文字列を生成可能
def d4 = """
一行目
二行目
""";
final def LF = System.lineSeparator()
assert d4 == "${LF}一行目${LF}二行目${LF}"

// マルチライン文字列は \ と | と stripMargin() を使うとインデントがキレイにできる。
def d5 = """\
  |一行目
  |二行目
  |""".stripMargin()
assert d5 == "一行目${LF}二行目${LF}"

// Closure(クロージャ)
def cl1 = {
  print "${x}, ${y}"
}
a.execute(cl1)

// 括弧は省略可能
// b.execute({
//   print "${x}, ${y}"
// })
b.execute {
  print "${x}, ${y}"
}

// 最後の引数が Closure の場合 メソッド名() { ... } の形式で呼び出し可能。
// Closureへの引数が1つの場合は it で呼び出し可能。
// 省略しない場合は メソッド名() { p -> ... } の形式となる。
a.execute(b) {
  int _x = x + it.x
  int _y = y + it.y
  print "${_x}, ${_y}"
}

// 5678910
(5..10).each {
  print it
}
println "";
// 0123456789
(10).times {
  print it
}
println "";

// List や Map

def lm1 = [1, 2, 3]
assert lm1 instanceof ArrayList
assert lm1.sum() == 6

def lm2 = [x: 10, y: 11, z: 12]
assert lm2 instanceof HashMap
assert lm2['y'] == 11

// 展開ドット演算子
assert lm2*.value == [10, 11, 12]
assert lm2.collect { it.value } == [10, 11, 12]

/**
 * The 2D point at x and y.
 */
@ToString
@EqualsAndHashCode
class Point {
  int x;
  int y;

  Point(int x = 0, int y = 0) {
    this.x = x
    this.y = y
  }

  Point plus(Point p) {
    if (p == null) return this
    new Point(x + p.x, y + p.y) // return 省略可: ブロックの最後の式が return される
  }

  Point minus(Point p) {
    if (p == null) return this
    new Point(x - p.x, y - p.y)
  }

  Point multiply(Point p) {
    if (p == null) return this
    new Point(x * p.x, y * p.y)
  }

  Point div(Point p) throws Exception {
    if (p == null) return this
    try {
      new Point(x / p.x as int, y / p.y as int)
    } catch (Exception e) {
      // 検査例外を throw しても呼び出し側の catch 不要
      throw new Exception(e)
    }
  }

  Point next() {
    x++
    y++
    this
  }

  Point previous() {
    x--
    y--
    this
  }

  Point leftShift(Point p) {
    plus(p)
  }

  void execute(Closure c) {
    println "---"
    c.delegate = this
    c()
    println ""
    println "---"
  }

  void execute(Point p, Closure c) {
    println "---"
    c.delegate = this
    c(p)
    println ""
    println "---"
  }

  static Point of(Map m) {
    new Point(m['x'], m['y'])
  }

}

task hello {}

下記コマンドで実行可能です。

ターミナル
$ ./gradlew hello -q
---
15, 30
---
---
2, 3
---
---
17, 33
---
5678910
0123456789

ここで、最初の build.gradle を Java っぽく書いてみた場合の記述を紹介します。
※ Closure の部分は Groovy 構文を利用しています。

build.gradle(Java)
// apply plugin: 'java'
Map<String, String> pluginJava = new HashMap<>();
pluginJava.put("plugin", "java");
this.project.apply(pluginJava);

// apply plugin: 'application'
Map<String, String> pluginApplication = new HashMap<>();
pluginApplication.put("plugin", "application");
this.project.apply(pluginApplication);

// repositories {
//     jcenter()
// }
this.project.repositories({ ->
    jcenter();
});

// dependencies {
//     compile 'com.google.guava:guava:22.0'
//     testCompile 'junit:junit:4.12'
// }
this.project.dependencies({ ->
    compile("com.google.guava:guava:22.0");
    testCompile("junit:junit:4.12");
});

// mainClassName = 'App'
this.project.mainClassName = "App";

Closure - this/owner/delegate

Closure 内部の暗黙的な参照先として以下の3つが存在します。

  • this …… 該当スコープ定義元の参照です。 Java の this と同様です。
  • owner …… 該当 Closure 定義元の参照です。基本的に this と同様ですが、Closure の中に Closure を定義した場合に 外部 Closure が参照先となります。
  • delegate …… 該当 Closure 内の処理の委譲先です。動的に変更することが可能です。
build.gradle
class Deleg1 {
  String hello_A() {
    return "Deleg1 Say hello_A() !!"
  }
  String hello_B() {
    return "Deleg1 Say hello_B() !!"
  }
}

class Deleg2 {
  String hello_A() {
    return "Deleg2 Say hello_A() !!"
  }
  String hello_B() {
    return "Deleg2 Say hello_B() !!"
  }
}

class Main {
  void execute() {
    Closure c = {
      println "this: ${this.class}"
      println "owner: ${owner.class}"
      println "delegate: ${delegate.class}"
      println hello_A()
      println hello_B()

      Closure cc = {
        println "this: ${this.class}"
        println "owner: ${owner.class}"
        println "delegate: ${delegate.class}"
        println hello_A()
        println hello_B()
      }
      println "\n-- execute cc --"
      cc()
      println "\n-- execute cc with Deleg2 --"
      cc.delegate = new Deleg2()
      cc()
      println "\n-- execute cc by Closure.DELEGATE_FIRST --"
      cc.resolveStrategy = Closure.DELEGATE_FIRST
      cc()
    }

    println "Start Closure"
    println "\n-- execute c with Deleg1 --"
//  c() <- ここで c() を呼び出すと hello_B() メソッド参照先が見つからずにエラー
    c.delegate = new Deleg1()
    c()
    println "\nEnd Closure"
  }

  String hello_A() {
    return "Main Say hello_A() !!"
  }
}
def m = new Main()
m.execute()
ターミナル
$ ./gradlew -q
Start Closure

-- execute c with Deleg1 --
this: class Main
owner: class Main
delegate: class Deleg1
Main Say hello_A() !!
Deleg1 Say hello_B() !!

-- execute cc --
this: class Main
owner: class Main$_execute_closure1
delegate: class Main$_execute_closure1
Main Say hello_A() !!
Deleg1 Say hello_B() !!

-- execute cc with Deleg2 --
this: class Main
owner: class Main$_execute_closure1
delegate: class Deleg2
Main Say hello_A() !!
Deleg1 Say hello_B() !!

-- execute cc by Closure.DELEGATE_FIRST --
this: class Main
owner: class Main$_execute_closure1
delegate: class Deleg2
Deleg2 Say hello_A() !!
Deleg2 Say hello_B() !!

End Closure

Gradle を扱う上で最も重要なのは delegate です。この委譲先を知ることで Closure 内で呼ばれるメソッドが、どこで定義されているか分かるようになります。

コード中にあるように、これら参照先に対する探索優先度は resolveStrategy プロパティで指定することができます。
優先度については、下記記事が参考になります。

Gradle 内で利用されているクラス(抜粋)

  • Project (doc)

前述の Java ふうに記述した例では this.project というインスタンスを利用して各種プロパティへアクセスしていました。
この project インスタンスが、build.gradle ファイル中の定義とやり取りの基本となるオブジェクトです。

JavaDocより引用
このインターフェースは、ビルドファイル内でGradleとやりとりする際、メインで使用するAPIです。
Projectから、Gradleの全ての機能にプログラム的にアクセスできます。
...
Projectと"build.gradle"ファイルは、1対1の関係性を持ちます。 
JavaDocより引用
Taskはクラスのコンパイルやjavadocの生成など、ビルドに対する単一のアトミックな断片を表します。

Gradle を構成するプロパティ

プロパティは変数のようなものです。
外部(ビルドスクリプト外)から指定するもの、ビルドスクリプト内で定義するもの、があります。
外部から与えられた値で動的にビルド動作を変更したい場合や、値の共通化などに利用します。

  • システムプロパティ …… 外部から指定するプロパティです。
  • 拡張プロパティ …… ビルドスクリプト内で定義するプロパティです。
  • プロジェクトプロパティ …… 外部から指定するプロパティで、内部的には拡張プロパティと同様に扱われます。

システムプロパティ

build.gradle
task hello {
  doLast {
    println System.properties['hello.test']
  }
}

実行時に指定する

ターミナル
$ ./gradlew hello -Dhello.test="Hello Gradle 1"

> Task :hello
Hello Gradle 1
ターミナル
$ ./gradlew hello --system-prop hello.test="Hello Gradle 2"

> Task :hello
Hello Gradle 2

gradle.properties に指定する

gradle.properties に定義して利用する場合は systemProp.<システムプロパティ名>=<値>と定義します。
gradle.properties が無い場合は、プロジェクトのルートディレクトリに作成してください。

gradle.properties
systemProp.hello.test=Hello Gradle 3
ターミナル
$ ./gradlew hello

> Task :hello
Hello Gradle 3

同時に指定した場合の優先度

  • gradle.properties より実行時の引数が優先される。
  • 引数は後方に指定したものが優先される。
ターミナル
$ ./gradlew hello -Dhello.test="Hello Gradle 1" --system-prop hello.test="Hello Gradle 2"

> Task :hello
Hello Gradle 2
ターミナル
$ ./gradlew hello --system-prop hello.test="Hello Gradle 2" -Dhello.test="Hello Gradle 1"

> Task :hello
Hello Gradle 1

拡張プロパティ

ext プロパティのクロージャ内で定義します。

build.gradle
ext {
  prop1 = "Hello Ext Prop 1"
  prop2 = "Hello Ext Prop 2"
}

task hello {
  doLast {
    println prop1
    println prop2
  }
}
ターミナル
$ ./gradlew hello

> Task :hello
Hello Ext Prop 1
Hello Ext Prop 2

project インスタンス経由で直接上書きする

project.ext.(プロパティ名) で拡張プロパティを直接指定可能。

build.gradle
ext {
  prop1 = "Hello Ext Prop 1"
}

task hello {
  doLast {
    println prop1
    project.ext.prop1 = "Override prop1"
    println prop1
  }
}
ターミナル
$ ./gradlew hello

> Task :hello
Hello Ext Prop 1
Override prop1

プロジェクトプロパティ

内部的には拡張プロパティと同様に扱われる。
プロパティ名(下記の例では sample)を指定すればスクリプト内で参照可能。

build.gradle
task hello {
  doLast {
    println sample
  }
}

gradle.properties に指定する

gradle.properties
sample=Project Property 1
$ ./gradlew hello

> Task :hello
Project Property 1

実行時に指定する

$ ./gradlew hello -Psample="Project Property 2"

> Task :hello
Project Property 2
$ ./gradlew hello --project-prop sample="Project Property 3"

> Task :hello
Project Property 3

Java プロジェクト

ここでは Java を利用したプロジェクトを Gradle を使ってビルドする場合によく利用する事項を紹介します。
今までの項で build.gradle を編集した場合は下記の状態に戻してください。

build.gradle
apply plugin: 'java'
apply plugin: 'application'

repositories {
    jcenter()
}

dependencies {
    compile 'com.google.guava:guava:22.0'
    testCompile 'junit:junit:4.12'
}

mainClassName = 'App'

よく利用するプラグイン

build.gradle
apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'maven'

主なタスク

# タスク名 依存タスク 概要
1 clean - 成果物(build)を削除します。
2 build - プロジェクトをビルドします。
3 __ assemble build 成果物を生成します。
4 ____ jar assemble jar ファイルを作成します。
5 ______ classes jar main ソースセット
6 ________ compileJava classes main/java をコンパイルします。
7 ________ processResources classes main/resources をプロダクト成果物としてコピーします。
8 __ check build コードチェック系タスクを実行します。
9 ____ test check Junit などのコードテストを実行します。
10 ______ testClasses test test ソースセット
11 ________ compileTestJava testClasses test/java をコンパイルします。
12 ________ processTestResources testClasses test/resources をテスト成果物としてコピーします。

compile や testCompile

コンパイル時に必要な依存ライブラリを設定します。
compile に指定した場合は、プロダクトコードのコンパイルや実行時の依存に含まれます。war ファイルなどを作成する場合は、war ファイル内にも含まれます。
testCompile はテストコードのコンパイルやテスト実行時の依存にのみ含まれ、war ファイルなどには含まれません。

maven Central

build.gradle
repositories {
  mavenCentral();
}
  1. https://mvnrepository.com/ へアクセスする

  2. 検索窓から OSS ライブラリを検索可能
    image.png

  3. 希望のライブラリを検索し、選択する。
    image.png

  4. 希望のバージョンを選択する。(Usages列を見ると良く利用されているバージョンが分かる)
    image.png

  5. 「Gradle」タブを参照することで、Gradle の依存関係への指定方法がわかる。「License」項には OSS ライセンス種別も記載されている。
    image.png

  6. build.gradle に依存関係を記述する。

dependencies {
  ...
  compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.0'
  ...
}

jar の作り方

ターミナル
$ ./gradlew jar
$ ls build/libs/
samplepj.jar

main メソッドを持つ クラスを実行したい

main メソッドを実行する場合は application プラグインが必要となります。
application プラグインで追加される mainClassName プロパティにクラス名を指定し、run タスクを実行することでアプリケーションを実行することができます。

build.gradle
...
mainClassName = 'sample.Main'
...
ターミナル
$ ./gradlew run

コードチェックをしたい

findbugs (doc)

静的コード解析用プラグインです。

build.gradle
apply plugin: 'findbugs'
ターミナル
$ ./gradlew findBugsMain -q
$ ls build/reports/findbugs/
main.xml

checkstyle (doc)

コードスタイルのチェック用です。

build.gradle
apply plugin: 'checkstyle'
ターミナル
$ mkdir -p config/checkstyle

https://raw.githubusercontent.com/checkstyle/checkstyle/master/src/main/resources/sun_checks.xml

config/checkstyle/checkstyle.xml
<!-- sun_checks.xml の内容を貼り付ける -->
ターミナル
$ ./gradlew checkstyleMain -q
$ ls build/reports/checkstyle/
main.html main.xml

pmd (doc)

静的コードチェックのプラグインです。

build.gradle
apply plugin: 'pmd'
ターミナル
$ ./gradlew pmdMain -q
$ ls build/reports/pmd/
main.html main.xml

findbugs/checkstyle/pmd を一度に実行したい

それぞれのタスクが check タスクに依存しているため、check タスクを実行すれば良い。

ターミナル
$ ./gradlew check -q

jacoco (doc)

コードカバレッジ測定に利用します。

build.gradle
apply plugin: "jacoco"
ターミナル
$ ./gradlew test jacocoTestReport -q
$ ls build/reports/jacoco/test/html/
hello                index.html           jacoco-resources     jacoco-sessions.html

ビルドエラーの調査

--debug や --stacktrace を利用しましょう。

ターミナル
$ ./gradlew clean build --debug 
$ ./gradlew clean build --stacktrace 

タスクの作り方

ここまで見てきたように、Gradle ではデフォルトで用意されているタスクや、プラグインによって追加されるタスクがあります。
それだけでなく、自身でタスクを追加することが可能です。

Hello Task

タスクを定義するには Project クラスの task メソッドを利用します。

build.gradle
task hello {
  ...ここにタスク定義
}

タスク定義の際に注意しなくてはいけないのは、タスクのクロージャスコープは初期化時に実行されます。
タスク実行時に実行するべき処理は doLast メソッドで指定します。

build.gradle
task hello {
  doLast {
    println "Hello Task!!"
  }
}

以前のバージョンでは leftShift ( << ) を利用した定義が推奨されていましたが、現在のバージョンでは << は非推奨となり、次バージョン以降で定義できなくなる可能性があるため doLast で定義するようにしましょう。

build.gradle(非推奨)
task hello << {
  println "Hello Task!!"
}

doLast を利用した例ではインデントが1つ深く少し見づらいため、初期化が不要な場合は下記のように記述可能です。

build.gradle
task hello doLast {
  println "Hello Task!!"
}

buildscript プロパティ

プロダクトコードではなく、ビルドスクリプト( build.gradle )内で利用するプラグインや依存関係の定義については buildscript に定義します。
OSSなどを利用する場合は、プロダクトコード同様に repositories と dependencies に定義します。

下記は maven セントラルリポジトリから、JGit(GitクライアントのJava実装)を利用する場合の例です。

build.gradle
buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '4.8.0.201706111038-r'
  }
}

task 作成ハンズオン

※解答は最下部にあります。

ハンズオン1

JGit を利用して、特定の Git リポジトリを clone する cloneGitRepo タスクを作成してください。
import が必要なクラスは下記2つです。

build.gradle
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.internal.storage.file.FileRepository

clone 先のディレクトリ名と、clone するリポジトリ名、リポジトリURIは、前述した拡張プロパティに定義してください。

cloneDirName = 'jgit'
repositoryName = 'trylibs'
repositoryURI = "https://github.com/hatimiti/${repositoryName}.git"

JGit で clone する方法は下記です。

build.gradle
def gitClient = Git.cloneRepository()
  .setURI(repositoryURI)
  .setDirectory(new File("./${cloneDirName}/${repositoryName}"))
gitClient.call()
タスク実行方法
$ ./gradlew cloneGitRepo

ハンズオン2

ハンズオン1のタスクでクローンした clone 先ディレクトリを削除する cleanGitRepo タスクを作成してください。
ディレクトリ削除系のタスクは Delete クラスを利用することで簡単に実装することが可能です。

http://gradle.monochromeroad.com/docs/dsl/org.gradle.api.tasks.Delete.html

タスク実行方法
$ ./gradlew cleanGitRepo

プラグインの作り方

Gradle のプラグインは自身で作成することも可能です。プロジェクトを跨いで共通化したいタスクなどをプラグイン化しておくと、毎回 build.gradle に複製する必要がなくなるため便利になります。

Hello Plugin

プラグイン実装

通常のプロジェクトのように、ディレクトリを作成し、gradle init で初期化します。

ターミナル
$ mkdir HelloGradlePlugin
$ cd HelloGradlePlugin
$ gradle init

プラグインのソースコードを配置するディレクトリと、プラグイン公開定義用ディレクトリを作成します。

ターミナル
$ mkdir -p src/main/groovy/sample
$ mkdir -p src/main/resources/META-INF/gradle-plugins

今回は Groovy 言語を利用してプラグインを作成するため、groovy プラグインを導入します。Groovy に限らず、Java や Scala などの JVM 言語であればプラグインを作成することが可能です。Gradle 標準のプラグインの多くは Java で作られているようです。

また、Gradle プラグインを作成するためのクラス群や、Groovy 言語への依存関係を dependencies へ指定します。
プラグインは最終的に jar ファイルへ変換するため、jar ファイルの名前も定義しておきます。

build.gradle
apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}

jar.baseName = 'hello-gradle-plugin'

プラグイン本体ソースを作成します。
プラグインを作成する際のポイントは下記です。

  • org.gradle.api.Plugin インタフェースを実装する
  • Plugin#apply() メソッドをオーバライド実装する
  • apply() メソッドの引数である org.gradle.api.Project インスタンスに対して各種設定を追加する
src/main/groovy/sample/HelloPlugin.groovy
package sample

import org.gradle.api.Plugin
import org.gradle.api.Project

class HelloPlugin implements Plugin<Project> {
  @Override
  void apply(Project project) {
    project.task('hello') doLast {
      println "Hello Gradle Plugin!!"
    }
  }
}

プラグイン公開用定義を専用の properties ファイルに記述します。
implementation-class に先程作成したクラスを指定します。

src/main/resources/META-INF/gradle-plugins/com.github.hatimiti.hello.properties
implementation-class=sample.HelloPlugin

jar ファイルを作成します。
jar タスクが成功すると、build/libs 配下に jar ファイルが作成されます。

ターミナル
$ ./gradlew jar
$ ls build/libs/hello-gradle-plugin.jar
build/libs/hello-gradle-plugin.jar
tree(抜粋)
~/java/HelloGradlePlugin
|--build
|  |--classes
|  |--libs
|  |  |--hello-gradle-plugin.jar
|--build.gradle
|--src
|  |--main
|  |  |--groovy
|  |  |  |--sample
|  |  |  |  |--HelloPlugin.groovy
|  |  |--resources
|  |  |  |--META-INF
|  |  |  |  |--gradle-plugins
|  |  |  |  |  |--com.github.hatimiti.hello.properties

作成したプラグインを利用してみる

作成した プラグインの jar ファイルを、利用したい側のプロジェクト配下へコピーする。

ターミナル
$ cd ..
$ cp HelloGradlePlugin/build/libs/hello-gradle-plugin.jar samplepj/
$ cd samplepj

配置した jar ファイルを buildscript の dependencies に指定します。
そして、先程 META-INF 配下に配置した properties ファイルの '.properties' を除いたファイル名を apply メソッドに指定します。

build.gradle
buildscript {
    dependencies {
        classpath files('./hello-gradle-plugin.jar')
    }
}

apply plugin: 'com.github.hatimiti.hello'
ターミナル
$ ./gradlew hello

> Task :hello
Hello Gradle Plugin!!

プラグイン(Project)にプロパティやメソッドを追加する

先程作成したプラグインディレクトリに移動します。

ターミナル
$ pwd
~/java/HelloGradlePlugin

プロパティを追加する場合はExtensionやConfigurationクラスを追加します。

src/main/groovy/sample/HelloPluginExtension.groovy
package sample

class HelloPluginExtension {
  String value
  void extension() {
    println("Hello Extension!!")
  }
}
src/main/groovy/sample/HelloPlugin.groovy
package sample

import org.gradle.api.Plugin
import org.gradle.api.Project

class HelloPlugin implements Plugin<Project> {
  @Override
  void apply(Project project) {
    def extension = new HelloPluginExtension()
    project.extensions.hello = extension
    project.task('hello') doLast {
      println "Hello Gradle Plugin and ${extension.value}!!"
    }
  }
}
ターミナル
$ ./gradlew jar

追加したプロパティを利用してみる

ターミナル
$ cd ..
$ cp HelloGradlePlugin/build/libs/hello-gradle-plugin.jar samplepj/
$ cd samplepj
build.gradle
buildscript {
    dependencies {
        classpath files('./hello-gradle-plugin.jar')
    }
}

apply plugin: 'com.github.hatimiti.hello'

hello {
  value = "Eeeeextension"
  extension()
}
ターミナル
$ ./gradlew hello

> Configure project :
Hello Extension!!

> Task :hello
Hello Gradle Plugin and Eeeeextension!!

plugin 作成ハンズオン

「task 作成ハンズオン」で作成したタスクを持つ MyGitPlugin プラグインを作成してください。
clone 先ディレクトリや、clone するリポジトリ名は mygit { } スコープで定義できるようにしてください。

色々な Gradle プラグイン

$ gradle build taskTree

> Task :taskTree

------------------------------------------------------------
Root project
------------------------------------------------------------

:build
+--- :assemble
|    +--- :distTar
|    |    +--- :jar
|    |    |    \--- :classes
|    |    |         +--- :compileJava
|    |    |         \--- :processResources
|    |    \--- :startScripts
|    +--- :distZip
|    |    +--- :jar
|    |    |    \--- :classes
|    |    |         +--- :compileJava
|    |    |         \--- :processResources
|    |    \--- :startScripts
|    \--- :jar
|         \--- :classes
|              +--- :compileJava
|              \--- :processResources
\--- :check
     \--- :test
          +--- :classes
          |    +--- :compileJava
          |    \--- :processResources
          \--- :testClasses
               +--- :compileTestJava
               |    \--- :classes
               |         +--- :compileJava
               |         \--- :processResources
               \--- :processTestResources

ドキュメント

ハンズオン解答

1. Git リポジトリから clone するタスクと、clean するタスク定義

build.gradle
buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '4.8.0.201706111038-r'
  }
}

import org.eclipse.jgit.api.Git
import org.eclipse.jgit.internal.storage.file.FileRepository

ext {
  cloneDirName = 'jgit'
  repositoryName = 'trylibs'
  repositoryURI = "https://github.com/hatimiti/${repositoryName}.git"
}

task cloneGitRepo {
  def gitCloneClient = Git.cloneRepository()
    .setURI(repositoryURI)
    .setDirectory(new File("./${cloneDirName}/${repositoryName}"))

  doLast {
    gitCloneClient.call()
  }
}

task cleanGitRepo(type: Delete) {
  delete "./${cloneDirName}"
}

2. Git クローンプラグイン

https://github.com/hatimiti/MyGitGradlePlugin

利用側のbuild.gradle
buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath files('./mygit-gradle-plugin.jar')
    classpath group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '4.8.0.201706111038-r'
  }
}

apply plugin: 'com.github.hatimiti.mygit'

mygit {
  cloneDirName = "jgit"
  repositoryName = "trylibs"
  repositoryURI = "https://github.com/hatimiti/${repositoryName}.git"
}

参考書籍

Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築

参考サイト