DSL(Domain Specific Language)
Gradleで使用するスクリプトはDSL(Domain Specific Language)というGroovyを基礎にした、Gradle専用の言語、文法が使用される。
Groovyで記述されていると思ってGradleの学習を進めていくと、???となる時がある。
GroovyはJavaと同様にJVMによって実行される。
Groovyの実行
$ cd ファイルを配置したディレクトリのパス
$ groovy ファイル名
ファイルのあるフォルダまで移動後、拡張子を含むファイル名をコマンドの引数に指定することで、スクリプトとして実行できる。
$ groovysh
コマンドライン上で対話型のスクリプトを実行できる。
$ groovyConsole
Groovy実行用のシェルアプリが起動する。
Groovyの型宣言
String a = 'hello';
String b = "good bye" // Groovyでは ; は省略できる
String c = "${a} world"
String d = "$b world"
def e = 'hello'
def
を使用した場合は、Object
型になる。
このように、型を宣言するかしないかを選択できる型づけをオプショナルな型づけという。
ただし、明示的に型を宣言して、異なるデータ型が代入された場合でも、コンパイルエラーは発生しない。
型エラーは実行時に発生する。
また、メソッドのオーバーライド時には、明示的に型を宣言する必要がある。
String hello(String name) {
return "hello $name"
}
def bye(def name) {
"bye $name" // Groovyではreturnが省略できる
}
return
の省略には条件があるので、基本的には省略しないほうが良いらしい。
Groovyのメソッド
println('hello')
println 'hello' // Groovyでは()が省略できる
println hello('taro') // 省略できない場合もある
String greet = hello('taro') // 省略できない場合もある
void helloWorld(){
println 'hello world'
}
helloWorld() // 引数がないメソッドでも省略できない
Gradleでも省略できない()
は度々見かける。
repositories {
mavenCentral()
}
メソッドにMap型の仮引数を用意することで、呼び出し時の実引数に対して、名前付き引数を使用できる。
名前付き引数はコンストラクタに対しても使用できる。
void hello2(Map params) {
println "I'm ${params.name}. $params.age years old."
}
hello2(name: 'taro', age: 10) // I'm taro. 10 years old.
hello2 name: 'hanako', age: 5 // I'm hanako. 5 years old.
class Person {
int age
String name
}
Person taro = new Person(age: 10, name: 'taro')
Gradleでは、次のように使用されている。
apply plugin: 'java'
メソッドの引数のデフォルト値を設定しておくと、実引数が渡されなくてもエラーが起きない。
void hello3(String name = 'world') {
println "hello $name"
}
hello3()
引数のdefキーワードも省略することができる。defを省略するとObject型として扱われる。
void hello4(name){
println "hello $name"
}
Groovyの変数
int x = 10
def y = 20
z = 30
変数の宣言をせずに使用できる変数をバインディング変数と言う。型を指定する、もしくはdefを使用して宣言した変数はローカル変数と言う。
バインディング変数は常にObject型として扱われる。
ローカル変数はメソッドからアクセスすることができない。
def localVariable = 40 // ローカル変数
bindingVariable = 50 // バインディング変数
void compareScope() {
println localVariable // エラー
println bindingVariable // 実行できる
}
compareScope()
Groovyはスクリプトとして実行することができる言語であり、クラスを宣言しなくてもコードのトップレベルに処理を記述できる。ただし、Groovyをスクリプトとして実行させる場合、コンパイル時にファイル名と同じ名前のクラスがコンパイラによって作成されている。
$ groovyc Sample.groovy
$ javap Sample
Compiled from "Sample.groovy"
public class Sample extends groovy.lang.Script {
public static transient boolean __$stMC;
public sample();
public sample(groovy.lang.Binding);
public static void main(java.lang.String...);
public java.lang.Object run();
protected groovy.lang.MetaClass $getStaticMetaClass();
public static java.lang.invoke.MethodHandles$Lookup $getLookup();
}
defで宣言した変数はコンパイラによって作成されるメソッドのローカル変数になるため、別のメソッドからアクセスができないものとなる。
ただし、ローカル変数はクロージャーからはアクセスが可能。
def localVariable2 = 'hello'
callback = {
println localVariable2
}
Groovyのクロージャー
宣言された場所のコンテキストを保持した実行可能な関数オブジェクト。
{}
を使用して宣言する。
メソッドの引数として渡したり、動的な関数を生成できる。
Closure saySomething = { word -> println word }
saySomething 'Hi!'
saySomething.call('Thank you!')
Closure incrementFactory() {
int i = 0
return { println i++ }
}
Closure increment = incrementFactory()
increment() // 0
increment() // 1
increment() // 2
Closure greetFactory(greet) {
return { name -> println "$greet $name"}
}
Closure goodMorning = greetFactory 'Good mornig'
goodMorning 'taro'
Closure goodAfternoon = greetFactory 'Good Afternoon'
goodAfternoon 'taro'
クロージャーを引数に受け取るメソッドを実行する際、メソッドの最後の引数がクロージャーであれば、引数に渡すクロージャーを()
の外に記述することができる。
GradleをGroovyの文法から理解しようとした時に、非常に重要な記法。
void method(Closure closure) {
println 'The closure will be executed.'
closure()
println 'The closure has been executed.'
}
method({ println 'hi' })
method() { println 'hi' } // ()の外に出せる
method { println 'hi' } // ()を省略する
Groovyのオブジェクトのメンバへのアクセス方法
class Product {
String name
int price
}
Product pen = new Product(name: 'pencile', price: 100)
println pen.name // pencile
println pen.'name' // pencile
println pen."name" // pencile
String fieldName = 'name'
println pen."$fieldName" // pencile
class Human {
void sayHello() {
println 'hello'
}
}
Human man = new Human()
man.sayHello() // hello
man.'sayHello'() // hello
String say = 'say'
String hello = 'Hello'
man."$say$hello"() // hello
コレクション
List list = [1, 2, 3, 4]
println list[0] // 1
String key2 = 'key2'
Map map1 = [('key1'): 10, (key2):20, ('key3'): 30] // ()内では変数や式を使用できる
println map1['key1'] // 10
Map map2 = [key1: 40, key2:50, key3:60] // キーが文字列の場合、()が省略できる
println map2['key1'] // 40
println map2.key2 // 50 オブジェクトのフィールドのようにアクセスできる
String key = 'key3'
println map2[key] // 60
println map2."$key" // 60
Gradleに登場するGroovyの演算子
<<
メソッドleftShift()
は演算子<<
で表現することができる。
コレクションに対して、要素を追加する働きがある。
List words = ['hi', 'hey']
words.leftShift('hi there')
words << 'hello'
println words // [hi, hey, hi there, hello]
Gradleではタスクの定義時に目にする。
Gradle5.0で廃止になったらしい。
task hi << {
println 'hi'
}
*
演算子*
は、リストを展開する。
別のリストのリストリテラル[]
内、メソッドの引数などに利用できる。
List parts = [0, 1, 2, 3, 4]
List whole = [*parts, 5, 6, 7, 8, 9]
println whole // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
*:
演算子*:
は、マップを展開する。
別のマップのマップリテラル[:]
内で利用できる。
Map m = [a: 10, b: 20]
Map n = [c: 30, d: 40]
Map mAndN = [*:m, *:n]
println mAndN // [a:10, b:20, c:30, d:40]
*.
演算子*.
は、リストの各要素に対して指定した処理を実行し、新たなリストを返す。
List fruits = ['apple', 'orange']
println fruits*.toUpperCase() // [APPLE, ORANGE]
.&
演算子.&
は、Closure
クラスのメソッドを、Closure
型ではないインスタンスメソッドが使用できるようにすることができる。例えばClosure#curry()
を使用する際に利用する。
Closure#curry()
- クラス
Clusure
にはcurry()
メソッドが存在する -
curry()
は引数を束縛した状態で、クロージャを生成する(戻り値として返却する)メソッド
class Member {
void introduce(String name, int age) {
println "My name is ${name}.I'm $age years old."
}
}
Member taro = new Member()
Closure introduceTaro = taro.&introduce.curry('taro')
introduceTaro(10) // My name is taro.I'm 10 years old.
変数taro
はMember
型のインスタンスであり、Closure
型ではないが、演算子.&
の働きによってClosure
クラスのcurry()
が使用できるようになっている。これを利用して、第一引数を'taro'
に束縛した、新たなクロージャーであるintroduceTaro
を生成している。
参考
・プログラミングGROOVY 関谷 和愛 (著), 上原 潤二 (著), 須江 信洋 (著), 中野 靖治 (著) 技術評論社
・Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築 綿引 琢磨 (著), 須江 信洋 (著), 林 政利 (著), 今井 勝信 (著) 翔泳社