概要
レポジトリ内にドキュメントがあるなどのケースにおいて、ドキュメントの更新時にJenkinsのJobが実行されるとビルドマシンのコストを無駄に消費してしまう。
そのためJenkins Pipelineにて特定のディレクトリに変更があった場合にのみビルドする方法が求められるが、具体的にその方法がまとまっているページが見つけられなかったためここにまとめる。
PathRestrictionについて
JenkinsPipelineのcheckout
にはPathRestriction
というクラスがあり、そこでincludedRegions
やexcludedRegions
を指定することで特定のファイルに差分があった(なかった)時のみビルドすることができる。
これはPipeline Syntax
のページでSample Step:checkout -> SCM: git -> 追加処理: Polling ignores commits in certain paths
を選択することでも確認できる。
しかしこの文法はMultibranch Pipelineにおいて使えない。
Multibranch Pipelineというものが具体的に何なのかは未調査。
少なくとも私の環境では以下の条件があるため使用不可能だと思われる。
- Jenkinsfileがあるレポジトリとビルド対象のレポジトリが別
- ジョブをトリガーするかどうかの判定はJenkinsfileのあるレポジトリにしか適応できない可能性がある。
これはジョブがトリガーされなければcheckout自体がされないためだと推測している。
- ジョブをトリガーするかどうかの判定はJenkinsfileのあるレポジトリにしか適応できない可能性がある。
- マスターマシンとスレーブマシンが別
- 以前
library
について検証した際に問題になったがJenkinsfileはマスターマシンに展開されるため、スレーブマシンからは参照できなかった。- もちろんスレーブ側で明示的にcheckoutすることで参照すること自体は可能。
- このことが影響している可能性があるが、1の問題の方が大きいと推測する。
- 以前
具体的な方法
- 前回のビルド時のコミットハッシュと今回のコミットハッシュを取得する
- checkoutの引数に各パラメータがあるためこれを利用する。
map_vars = checkout( ... ) print map_vars
- 内容は以下のものだった
{GIT_BRANCH=origin/master, GIT_COMMIT=[ハッシュ], GIT_PREVIOUS_COMMIT=[ハッシュ], GIT_PREVIOUS_SUCCESSFUL_COMMIT=[ハッシュ], GIT_URL=[url]}
- 参考 : https://issues.jenkins.io/browse/JENKINS-35230?focusedCommentId=309355&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-309355
- 内容は以下のものだった
- 以下のようにすることで環境変数にパラメータを含めることができる。
env.GIT_COMMIT = map_vars.get('GIT_COMMIT') env.GIT_PREVIOUS_COMMIT = map_vars.get('GIT_PREVIOUS_COMMIT') //前回の成功ビルドの場合は以下のものを使う //env.GIT_PREVIOUS_COMMIT = map_vars.get('GIT_PREVIOUS_SUCCESSFUL_COMMIT')
- checkoutの引数に各パラメータがあるためこれを利用する。
- 変更のあったファイルの一覧を取得する
-
git diff-tree --no-commit-id --name-only -r {ハッシュ1} {ハッシュ2}
とすることでファイルの一覧を取得できる。
(bat
でのみ確認したが、shなどでも可能であるはず)env.CHANGE_FILES = bat(returnStdout: true, script: """ @git diff-tree --no-commit-id --name-only -r ${env.GIT_PREVIOUS_COMMIT} ${env.GIT_COMMIT} """)
-
- 対象のディレクトリに変更があった場合はビルドする。
- 今回実装したのは微妙に意味が違うが、対象のディレクトリに変更がなければABORTすること。
- 以下のfunctionを呼び出すことで対象のディレクトリに変更がなければ
ABORT
できるようになる。CheckChanges = { includes = [ "hoge/", "sub", // submoduleの場合はパス区切りを入れないようにする ] for (file in env.CHANGE_FILES.split("\n")) // 1行(ファイル)毎に確認する { print "### ${file} ###" for (x in includes) { print "- ${x}" if (file.startsWith(x)) return // 対象のディレクトリに変更があったのでそのままreturn } } // returnされずに到達した == 対象のファイルに変更がなかった currentBuild.result = 'ABORTED' error('SkipBuild') }
- 補足
-
ABORTED
を選択したのはビルド失敗のFAILURE
と区別するため。 -
error
の単独呼び出しだとFAILURE
になってしまうのでABORTED
の設定は必須 -
NOT_BUILTの方が適切なように思えるが、手元で試したところ
FAILURE
になってしまったため断念。
-
- 3を呼び出す処理を挟むことで特定のディレクトリに変更があった場合にのみビルドするということが可能になる。
stage('check changes') { steps { CheckChanges() } }
今回使用したスクリプト全体
CheckChanges = {
includes = [
"hoge/",
"sub", // submoduleの場合はパス区切りを入れないようにする
]
for (file in env.CHANGE_FILES.split("\n"))
{
print "### ${file} ###"
for (x in includes) {
print "- ${x}"
if (file.startsWith(x)) return
}
}
currentBuild.result = 'ABORTED'
error('SkipBuild')
}
pipeline {
stages {
stage('Clone') { steps { script {
map_vars = checkout( ... )
env.GIT_COMMIT = map_vars.get('GIT_COMMIT')
//env.GIT_PREVIOUS_COMMIT = map_vars.get('GIT_PREVIOUS_COMMIT')
env.GIT_PREVIOUS_COMMIT = map_vars.get('GIT_PREVIOUS_SUCCESSFUL_COMMIT')
env.CHANGE_FILES = bat(returnStdout: true, script: """
@git diff-tree --no-commit-id --name-only -r ${env.GIT_PREVIOUS_COMMIT} ${env.GIT_COMMIT}
""")
} } }
stage('check changes') { steps { CheckChanges() } }
}
}