Gradleはじめました

実務でbuildはAntで行っていて、build.xmlを調べながら変更できる位の理解度の私がgradle使ってみた。
ちなみに、Mavenは使った事がある位。理解なんて微塵もしていない状態。

きっかけは「5月21日 第21回 G*ワークショップ #jggug(東京都)」でのid:nobusueさんのセッションで、「おうちかえってGradleを実行するのが宿題です。」と言われたこと。
何回か「G*ワークショップ」には参加させていただいているので知ってはいたんですけど、やらずじまいだったのでいい機会だなと。

Gradleダウンロード

Gradle公式ページから
http://www.gradle.org/

Downloadページに行って
http://www.gradle.org/downloads

3種類あるけどall.zipを落とす。
gradle-1.0-rc-3-all.zip (binaries, sources and documentation)
gradle-1.0-rc-3-bin.zip (binaries only)
gradle-1.0-rc-3-src.zip (sources only)

任意のPATHに解凍しておきましょう。

環境変数設定

「GRADLE_HOME」と「PATH」に設定。

GRADLE_HOME

C:\tools\gradle-1.0-rc-3

PATH

%GRADLE_HOME%\bin;%PATH%

インストール確認

「gradle -v」で正しくインストールされているか確認。

C:\>gradle -v

------------------------------------------------------------
Gradle 1.0-rc-3
------------------------------------------------------------

Gradle build time: 2012429235152秒 UTC
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
Ivy: 2.2.0
JVM: 1.6.0_13 (Sun Microsystems Inc. 11.3-b02)
OS: Windows XP 5.1 x86

OK!
ちなみにGroovyが入っていなくても、Gradle内に「groovy-all-1.8.6.jar」があったので大丈夫っぽい。

「Gradle User Guide 6章 ビルドスクリプトの基本」を淡々とこなす

どこかの資料 orブログでGradle User Guide 6章をやっとけというのを見たので淡々とこなしていく。
ただ、写経するだけじゃ面白く無いので適度に変更してこなしたよ。

第6章 ビルドスクリプトの基本
http://monochromeroad.com/artifacts/gradle/userguide/tutorial_using_tasks.html

Hello World

カレントディレクトリに「build.gradle」ファイルを作成して、実行すると動作する。

  • build.gradle
task hello {
	doLast {
		println "Hello World!!"
	}
}
  • 実行
C:\work\20120522>gradle hello
:hello
Hello World!!

BUILD SUCCESSFUL

Total time: 3.162 secs

おぉ!動いた動いた。

タスク定義のショートカット

「doLast」は「<<」でショートカットできる。

  • build.gradle
task hello << {
	println "Hello World!!"
}

そして、「-q」をオプションを付けるとGradleのログメッセージを抑制できる。

  • 実行
C:\work\20120522>gradle -q hello
Hello World!!

純粋に結果だけの表示になるね。

ビルドスクリプトはコードです

これはnobusueさんも言ってた。
一見Groovyのコードに見えないけど、内部DSLを利用して括弧とか省略しているだけで普通のGroovyのスクリプト

折角なので以前書いたFizzBuzzのコードを書いた。

  • build.gradle
task  fizzbuzz << {
	(1..100).each{println((s=(it%3==0?"fizz":"")+(it%5==0?"buzz":""))?s:it)}
}
  • 実行
C:\work\20120522>gradle -q fizzbuzz
1
2
fizz
4
buzz
... 中略...
98
Fizz
Buzz

普通に動いた。

タスクの依存関係

依存関係ってよくわからなかったけど、前処理みたいなものという認識でいいのかな?
そういえば実務で使っているAntでも指定してた気がする。

  • build.gradle
task hello << {
	println "Hello World!!"
}
task intro(dependsOn: hello) << {
	println "I'm Gradle!!"
}
  • 実行
C:\work\20120522>gradle -q intro
Hello World!!
I'm Gradle!!
複数タスクに依存

複数タスクに依存する場合はListで渡せばいいっぽい。

  • build.gradle
task hello1 << {
	println "Hello World!!"
}

task hello2 << {
	println "Hello Gradle!!"
}

task intro(dependsOn: [hello1, hello2]) << {
	println "I'm Gradle!!"
}
  • 実行
C:\work\20120522>gradle -q intro
Hello World!!
Hello Gradle!!
I'm Gradle!!
未定義タスクに依存

「依存関係にタスクを追加するときは、そのタスクがその時点で宣言されていなくてもかまいません。」との事。
ただ、↑のbuild.gradleの順番を単純に入れ替えてもエラーは出ます。
dependsOnに指定しているタスクを文字列として渡すと大丈夫!(10分位はまった。。。)
通常は文字列で記述する方が問題なくて安心かな?

  • build.gradle
task intro(dependsOn: hello) << {
	println "I'm Gradle!!"
}
task hello << {
	println "Hello World!!"
}

dependsOnに「"」をつけずに変数として指定するとエラーになります。

  • build.gradle
task intro(dependsOn: "hello") << {
	println "I'm Gradle!!"
}
task hello << {
	println "Hello World!!"
}
  • 実行
C:\work\20120522>gradle -q intro
Hello World!!
I'm Gradle!!

dependsOnに文字列として指定すると大丈夫。

未定義タスクの参照は「略記法」は使えない

ただ、
「未定義のタスクを参照する場合、略記法(「略記法」参照)は使えません」らしい。

略記法というのは「hello.name」のようにタスクのプロパティにアクセスする方法の事。
未定義タスクにして、みると例外発生。っと思ったら。。。

  • build.gradle
task intro(dependsOn: "hello") << {
	println "I'm Gradle!! [${hello.name}]"
}

task hello << {
	println "Hello World!!"
}
  • 実行
C:\work\20120522>gradle -q intro
Hello World!!
I'm Gradle!!

って思ったら正しく動作する。
まぁ、そりゃ実行時にはインスタンスはあるよね。
ここでいう「未定義タスク」というのは依存関係がない場合って事かな。

動的なタスク

これもnobusueさんの資料で見た。

  • build.gradle
4.times { counter ->
	task "task${counter}" << {
		println "I'm task number [${counter}]"
	}
}
  • 実行
C:\work\20120522>gradle -q task2
I'm task number [2]

凄いとは思うけど、数値だといまいち実際に使う例が浮かばないなぁ
OS名を詰めたリストを回すとか?

  • build.gradle
["windows","unix","mac"].each { name ->
	task "task${name}" << {
		println "os:[${name}]"
	}
}

ここでnobusueさんが発表時に「tasks」というコマンドを使っていたのを思い出す。

C:\work\20120522>gradle tasks
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Help tasks
----------
dependencies - Displays the dependencies of root project '20120522'.
help - Displays a help message
projects - Displays the sub-projects of root project '20120522'.
properties - Displays the properties of root project '20120522'.
tasks - Displays the tasks runnable from root project '20120522' (some of the displayed tasks may belong to subprojects)
.

Other tasks
-----------
taskmac
taskunix
taskwindows

To see all tasks and more detail, run with --all.

BUILD SUCCESSFUL

Total time: 2.439 secs

実行できるtaskの一覧を確認できた!
taskunixを実行

C:\work\20120522>gradle -q taskunix
os:[unix]

taskに渡されるクロージャには何が渡ってくるのか気になったので試した。

  • build.gradle
4.times { counter ->
	task "task${counter}" << {
		println "[${it}], [${it.class}]"
	}
}
  • 実行
C:\work\20120522>gradle -q task2
[task ':task2'], [class org.gradle.api.DefaultTask_Decorated]

タスククラスが渡ってくるみたい。
色々取れるんでしょう。キット。

もう一個気になった。
Groovyなんだからクロージャとして定義して渡す事も出来るってこと?

  • build.gradle
def c = {println "I'm closure"}

4.times { counter ->
	task "task${counter}" << c
}
  • 実行
C:\work\20120522>gradle -q task2
I'm closure

できた。

既存のタスクを操作する(APIからタスクにアクセスする - 依存関係の追加)

横道にそれたのでUser Guideに戻る

  • build.gradle
4.times {counter ->
	task "task${counter}" << {
		println "I'm task number ${counter}"
	}
}
task0.dependsOn task2, task3
  • 実行
C:\work\20120522>gradle -q task0
I'm task number 2
I'm task number 3
I'm task number 0

依存関係を後から追加できるよ。って事。

ちなみに、tasksで確認したらtask2とtask3は一覧から消えてた。
依存されているタスクは単体での実行対象として外れるのかな?

  • 実行
C:\work\20120522>gradle tasks
:tasks

(略)

Other tasks
-----------
task0
task1

っと思ったけど普通に動く。
tasksでの一覧からは確認できないだけみたい。

  • 実行
C:\work\20120522>gradle -q task2
I'm task number 2
APIからタスクにアクセスする - アクションの追加

User Guidは惑星名でよくわからなかったので変更。
後、doFirstがどこに入るのかも試してみた。

  • build.gradle
task hello << {
	println "寅"
}
hello.doFirst {
	println "丑"
}
hello.doLast {
	println "兎"
}
hello << {
	println "辰"
}
hello.doFirst {
	println "子"
}
  • 実行
C:\work\20120522>gradle -q hello
子
丑
寅
兎
辰

既存のタスクの前に追加、後に追加という感じに動作するみたい。

略記法

前にやったので省略

Extra task properties

何故かここだけ日本語訳されていない。

「[タスク名]ext.[任意のプロパティ]」とすれば任意のプロパティを定義できるよ。
って事かな?

  • build.gradle
task myTask
myTask.ext.tekitouProperty = "適当な値"

task showProps << {
	println myTask.tekitouProperty
}
  • 実行
C:\work\20120522>gradle -q showProps
適当な値

「ExtraPropertiesExtension」という項目も見てね。
とか、書いてあるけど、こちらも日本語訳されてなかったので対して使わない機能ということで割愛。

Antタスクの使用

AntBuilderを使ってant.loadfileターゲットを実行する

antのloadfileっというのは「テキストファイルを読み込み、一つのプロパティに設定します。 」だそうです。
http://www.jajakarta.org/ant/ant-1.6.1/docs/ja/manual/CoreTasks/loadfile.html

指定されたPATHからファイルの一覧を取ってきて、
それを一つづつAntタスクを使用して読み込んで中身を出力するという処理。

指定したPATHには以下の通りファイルを設定して実行。

  • build.gradle
task loadfile << {
	def files = file(/C:\work\20120522\resources/).listFiles().sort()
	files.each { File file ->
		if (file.isFile()) {
			ant.loadfile(srcFile: file, property: file.name)
			println (file.name.center(20,"*"))
			println "${ant.properties[file.name]}"
		}
	}
}
  • C:\work\20120522\resources
C:\work\20120522\resources
├a.txt
├b.txt
└c.txt
  • a.txt
私はファイルA。
おはようおはよう。
  • b.txt
私はファイルB。
こんにちはこんにちは。
  • c.txt
私はファイルC。
こんばんはこんばんは。
  • 実行
C:\work\20120522>gradle -q loadfile
*******a.txt********
私はファイルA。
おはようおはよう。

*******b.txt********
私はファイルB。
こんにちはこんにちは。

*******c.txt********
私はファイルC。
こんばんはこんばんは。

綺麗に出力されました。

メソッドの使用

ファイル一覧取得部をメソッドとして抽出。
GradleというかGroovyの領域。

  • build.gradle
task checksum << {
	fileList(/C:\work\20120522\resources/).each {File file ->
		ant.checksum(file: file, property: "cs_$file.name")
		println "$file.name Checksum: ${ant.properties["cs_$file.name"]}"
	}
}

task loadfile << {
	fileList(/C:\work\20120522\resources/).each {File file ->
		ant.loadfile(srcFile: file, property: file.name)
		println "I'm fond of $file.name"
	}
}

File[] fileList(String dir) {
	file(dir).listFiles({file -> file.isFile() } as FileFilter).sort()
}
  • 実行
C:\work\20120522>gradle -q loadfile
I'm fond of a.txt
I'm fond of b.txt
I'm fond of c.txt
C:\work\20120522>gradle -q checksum
a.txt Checksum: 56abfb67b6abf0bf7b0a7c20cf993f1f
b.txt Checksum: aced722a0257d9d34f73f9f2e034d2c9
c.txt Checksum: 0728926241734ed0734fbaa7ce067717

なんの問題もなく完了。

せっかくなのでメソッドをクロージャに書き直してみた

(それとクロージャのパラメータをitに。)
Groovyの勉強として。。。

  • build.gradle
def fileList = {file(it).listFiles({it.isFile()} as FileFilter).sort()}

task checksum << {
	fileList(/C:\work\20120522\resources/).each {
		ant.checksum(file: it, property: "cs_${it.name}")
		println "${it.name} Checksum: ${ant.properties["cs_$it.name"]}"
	}
}

task loadfile << {
	fileList(/C:\work\20120522\resources/).each {
		ant.loadfile(srcFile: it, property: it.name)
		println "I'm fond of ${it.name}"
	}
}
  • 実行
C:\work\20120522>gradle -q loadfile
I'm fond of a.txt
I'm fond of b.txt
I'm fond of c.txt
C:\work\20120522>gradle -q checksum
a.txt Checksum: 56abfb67b6abf0bf7b0a7c20cf993f1f
b.txt Checksum: aced722a0257d9d34f73f9f2e034d2c9
c.txt Checksum: 0728926241734ed0734fbaa7ce067717
一旦休憩

ここでUserGuide52章にリンクが貼られていたので調べてみたら、
全部で54章ある事に気づいてビックリする。
公式ドキュメントが充実っていうのも間違いないですね。
日本語訳ありがとうございます。

デフォルトタスク

タスクを指定しない状態でgradleコマンドを実行すると通常はエラーになるけど、
このデフォルトタスクを指定しておけば、指定されたタスクが動作するという事。

  • build.gradle
defaultTasks "clean", "run"

task clean << {
	println "Default Cleaning!"
}

task run << {
	println "Default Running!"
}

task other << {
	println "I'm not adefault task!"
}
  • 実行
C:\work\20120522>gradle -q
Default Cleaning!
Default Running!

「マルチプロジェクトのビルドでは、すべてのサブプロジェクトが個別にデフォルトタスクを持つことができます。
サブプロジェクトにデフォルトタスクが定義されていない時は、(定義されていれば)親プロジェクトの定義が使用されます。」
↑この意味がよくわからなかったんだけど、UserGuide内でマルチプロジェクトの説明を発見。

48.3. マルチプロジェクトのビルド
http://monochromeroad.com/artifacts/gradle/userguide/build_lifecycle.html#sec:multi_project_builds

Gradleは複数のプロジェクトを使用してビルド可能で、その際に各プロジェクトにbuild.gradleファイルが置かれている。
で、それぞれがデフォルトタスクを設定している場合の説明と理解しました。

DAGによる設定

DAGって何??

  • build.gradle
task distribution << {
	println "we build the zip with version=${version}"
}

task release(dependsOn: "distribution") << {
	println "we release now"
}

gradle.taskGraph.whenReady { taskGraph ->
	if (taskGraph.hasTask(release)) {
		version = "1.0"
	} else {
		version = "1.0-SNAPSHOT"
	}

}
  • 実行
C:\work\20120522>gradle -q distribution
we build the zip with version=1.0-SNAPSHOT
C:\work\20120522>gradle -q release
we build the zip with version=1.0

タスクが実行されるか否かを判別が可能だよって事かな?

「ここで大事なことは、releaseタスクが実行予定にあるという事実を、releaseタスクが実行される前に利用できる点です。
releaseタスクがプライマリタスク、つまりgradleコマンドに引き渡されているタスクでなくてもかまいません。」
この文章は、依存されているタスクまで見てくれるということか?

  • build.gradle
task task1 << {
	println "task1"
}
task task2 << {
	println "task2"
}

task task3 (dependsOn: task1) << {
	println "task3"
}

gradle.taskGraph.whenReady { taskGraph ->
	if (taskGraph.hasTask(task1)) {
		println "ok"
	} else {
		println "ng"
	}
}
  • 実行
C:\work\20120522>gradle -q task3
ok
task1
task3

やはり、直接task1を指定しなくても依存しているタスクとして含まれていれば判別してくれた。

以上でUserGuideの「第6章 ビルドスクリプトの基本」は完了。
Groovyがわかれば特に難しい事はなかったという印象。
そりゃ、スクリプトだから当たり前かw

とりあえず宿題はこなせたと言えるはずだけど、まだbuildすら走らせてないのでGradleの本気はこれからですね。

参考URL

Gradle User Guide
http://monochromeroad.com/artifacts/gradle/userguide/userguide.html

先日のnobusueさんの資料
http://www.slideshare.net/nobusue/gws-20120521-gradle

明日から使えるgradle
http://www.slideshare.net/kimukou/ss-8657562

インストールレスで Gradle してみる
http://d.hatena.ne.jp/bluepapa32/20110308/1299602195

[Eclipse][Groovy][Gradle]開発環境構築メモ(Gradle/Gradle Plugin on Eclipse3.6)
http://d.hatena.ne.jp/absj31/20110717/1310887985

How about Gradle?
http://www.slideshare.net/nobeans/how-about-gradle

GradleによるG*なビルドシステムの構築
http://www.slideshare.net/literalice/gradle-introduction