背景
お仕事でJenkins Pipelineを書くことになりました。早速サンプルをコピーし、Hello Worldが表示されてうれしい一方、こんな疑問が。
この、pipeline
と{...}
とは何なのか?どういう文法規則なのか?
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'echo "Hello World"'
}
}
}
}
Jenkinsfile自体は単なるデータ構造(JSONやYAMLの仲間)に見えます。ということは何か規則があるはずです。しかしそれが書かれているページが見つからない。
職場のつよつよエンジニアに聞いたところ、JenkinsfileはGroovyのプログラムということが分かりました。
その後自分で調べたことと、他の言語との対比を書いておきます。
"pipeline"と"{...}"は何かの答え
pipeline
はメソッド名
{...}
はクロージャ
pipeline {...}
は、pipeline
メソッドに、{...}
というクロージャを引数として渡す、という構文です。
pipeline
はメソッドなので、Jenkinsfileの実行時に
def pipeline(Closure cl) {
// 前処理
// クロージャを呼び出す
cl();
// 後処理
}
のように定義されたメソッドが(どこかから)読み込まれています。(上記コードは正確には少し違います。補足を参照してください)
もっと詳しく
groovyのメソッド呼び出し
上記のpipeline
の呼び出しを冗長に書くと、
pipeline({ ->
// 処理
})
です。
groovyのクロージャは{x, y -> x + y}
のように、{引数 -> 処理}
のように書きます。引数がない場合は、引数 ->
の部分は省略可能です1。引数を省略すると、
pipeline({
// 処理
})
の形になります。
また、メソッド呼び出しのカッコは、引数がある場合は省略可能なので、
pipeline {
// 処理
}
になります。Jenkinsfileと同じ形になりました。
クロージャ
クロージャとは、メソッドの呼び出し側で動いているような振る舞いをする無名関数です。
こちらの説明が分かりやすいです。
クロージャ - Apache Groovyチュートリアル
他の言語で書いてみると
Groovyのクロージャを他の言語と対比します。(ここに書ききれない言語につきましてはコメントで補足お願いします)
Ruby
Groovyの
pipeline {
// 処理
}
という記法は、Rubyの
pipeline {
# 処理
}
という記法と対応します。見た目は同じですね。GroovyはRubyに強い影響を受けていることがわかります。
Rubyのブロックを、Groovyではクロージャと言います。ブロックとクロージャの違いは、こちらのページが分かりやすいです。
Rubyist のための他言語探訪 【第 5 回】 Groovy
JavaScript
Groovyの
pipeline {
// 処理
}
という記法は、JavaScriptの
pipeline(() => {
// 処理
})
という記法に対応します。JavaScriptではメソッド呼び出しのカッコは省略できず、引数がない場合も引数を囲むカッコを省略できないですが、Groovyでは省略が可能です。
Java
Groovyの
pipeline {
// 処理
}
という記法は、Javaの
pipeline(() -> {
// 処理
})
に対応します。JavaScriptと同様Javaでもメソッド呼び出しのカッコと、引数がない場合は引数を囲むカッコを省略できません。Groovyではこの2つは省略可能です。
Python
Groovyの
pipeline {
// 処理
}
という記法は、Pythonで対応する記法がないのですが、あえて書くとしたら
pipeline(lambda: 処理)
です。Pythonの場合、lambda式は複数行にまたがることができませんが、Groovyでは可能です。
補足
上で、pipeline
メソッドは
def pipeline(Closure cl) {
// 前処理
// クロージャを呼び出す
cl();
// 後処理
}
のようになっていると書きましたが、正確にいうと少し違います。クロージャ内で使用できる関数を制限するため、delegate
(移譲)を行なっています。pipeline
メソッドのソースが見つからなかったのですが、より実情に沿ったコードは以下です。
def pipeline(@DelegatesTo(PipelineSpec) Closure cl) {
def pipelineSpec = new PipelineSpec()
def code = cl.rehydrate(pipelineSpec, this, this)
code.resolveStrategy = Closure.DELEGATE_ONLY
code()
}
本文中では分かりやすさを優先するため、クロージャをそのまま呼び出す書き方にしています。
参考資料
あとがき
Jenkinsfileを作成している最中、「コピペで動いてはいる、ただ、なんで動いているのか分からない」という状態でしたが、Groovyのプログラムということを理解し、実態がつかめました。
Jenkins Pipelineの記事がまだまだ少ないので、分かったことを今後も書けたらと思います。
-
正確に言うと、
{-> 処理}
と{ 処理 }
では挙動が変わります。 ↩