先日のJenkinsユーザ・カンファレンスの講演を受けて、Jenkins Workflow Pluginがすごく便利そうだったので、早速試してみました。
想定するプロジェクト構成
基本的にはGradleの階層型マルチプロジェクト構成で、その他にドキュメントのビルド(Sphinx)とCoffeeScript, Sassのコンパイル用のプロジェクトがあります。
ファイル構成を簡略化するとこんな感じです。
root/ build.gradle settings.gradle common/ build.gradle sub1/ build.gradle sub2/ build.gradle doc/ Makefile ui/ Gruntfile.coffee package.json
各プロジェクトの概要は、
- root
- rootプロジェクト
- common
- sub1, sub2の共通クラス
- sub1
- サブプロジェクトその1(commonに依存)
- sub2
- サブプロジェクトその2(commonに依存)
- doc
- ui
パッケージのための成果物はcommon+sub2で1つで1つ, common+sub2+doc+uiで計2つ。
現行のジョブの流れと課題
というわけで、それぞれのプロジェクトのビルドを1つのジョブにマッピングさせて、以下のようなパイプラインを組んでいます。
チェックアウトはパイプラインの最初の1回だけ行います。
そのため、パイプラインの先頭でチェックアウトしてきたソースコード類を他のジョブに引き継ぐために、Clone Workspace SCM Pluginを利用しています。
これはこれで運用は周りますが、設定のメンテナンスという観点で、以下のような課題がありました(一部は対策済み)。
- 似たようなジョブがたくさんできる(common1-build, sub1-build, sub2-build)
- Jenkinsのビューでジョブ一覧を見た時にパイプラインの状況が分かりにくい。
- (対処例)Build Pipeline Pluginを使ってパイプラインビューを参照するようにする、など。
- ジョブ間にまたがった同じ処理の設定が面倒。
- (対処例)Configuration Slicing Pluginを使う、など。
- 【Gradle特有の問題】sub1, sub2のビルドの際に、依存プロジェクト(common)に変更がなくても常に再ビルドされてしまう。
- (原因)UP-TO-DATEとするかどうかは、デフォルトではファイルのフルパスで判断しているから、らしい(どこかで見た気がするけど曖昧…)。
- 依存プロジェクトのビルドがすぐ終わるような場合はあまり気になりませんが、やはり同じプロジェクトのビルドはパイプライン中で1回だけで済ませるべきなので、
-a
オプションをつけて依存プロジェクトのビルドは後続ジョブでスキップするような回避をしています。
Jenkins Workflow Plugin
そこでJenkins Workflow Pluginを試しました。といっても、今は、このパイプラインを置き換えるというよりも、このプラグインで同等のことが実現できるかどうかの実験段階ですが。
インストール
いつも通りのプラグインの管理画面で「Workflow: Aggregator」をチェックしてインストールします。"Jenkins Workflow"でページ検索してもhitしないので注意。 その後Jenkinsを再起動してインストール完了。
Workflow Jobを作成
試しに以下のようなワークフロースクリプトを組んでみて、(とりあえずパッケージングの手前まで)動くことが確認できました。
なお、上記のパイプラインと完全に一致しているわけではないので注意。
node('java') { git url: "http://gitbucket-server:8080/git/hoge/root.git", branch: "develop" buildSubproject('common') archive excludes: '.git/**/*', includes: '**/*' parallel(sub1: { buildSubproject('sub1') }, sub2: { buildSubproject('sub2') }, doc: { dir('doc') { sh "make html" } }) step([$class: 'ArtifactArchiver', artifacts: 'doc/_build/html/**/*']) step([$class: 'JUnitResultArchiver', testResults: '**/build/test-results/TEST-*.xml']) } node('nodejs') { unarchive mapping: ['ui/' : '.'] dir('ui') { sh 'npm install' sh 'grunt ci' } step([$class: 'ArtifactArchiver', artifacts: 'ui/build/**/*']) } def buildSubproject(name) { sh "chmod +x ./gradlew" sh "./gradlew --daemon ${name}:clean ${name}:build" }
ちょっと補足。
- GitリポジトリはGitBucketを使っています。
- 開発環境の都合で、Java用プロジェクトと、Coffee/Sassビルド用プロジェクトはそれぞれ別スレーブ(
java
ラベル付きスレーブとnodejs
ラベル付きスレーブ)になっています。 - Coffee/SassのビルドはタスクランナーとしてGruntを使っています。
grunt ci
はCI用のカスタムタスクの実行です。
所感&分かったこと
- プラグインで拡張されているようなビルド処理やビルド後の処理などがどうなるのか気になっていたのですが、現状では完全には対応できるわけではないみたいですね。 ※ 公式のCOMPATIBILITY参照。
- ビルド手順は
sh
を使えば大体のものが対応できそうだけど、ビルド後の処理(通知や静的解析結果の集計など)は苦しい気がしました。 - スクリプト中で
build(jobname)
とすると別のジョブのビルドができるので、うまく組み合わせるとか、になりそう。
- ビルド手順は
- ビルドでの成果物のコピーは
archive
とunarchive
で簡単にできる。unarchive
でのマッチングルールは、archive
のものと一致していなくてもよい。archive includes: '*.txt'
に対してunarchive mapping: ['a.txt' : '.']
とやってもコピー可。archive excludes: '.git/**/*', includes: '**/*'
のようにワークスペース全体を成果物としてしまえば、Clone Workspace SCMプラグイン相当のことができる。- 異なるスレーブ間でも
archive
とunarchive
でコピー可。 build(jobname)
でビルドしたジョブの成果物をワークフロージョブ内でunarchive
でコピーすることはさすがにできない模様。
- GitHubやGitBucketのHookを使ってこのワークフロージョブを走らせる、といったことはできない?
- ワークフロージョブのトリガールールの設定はかなりシンプルでした。
- ただ、ワークフロージョブとしてできなくても、トリガー専用ジョブ(フリースタイル・ジョブ)とワークフロージョブの2つに分けてつなげば回避できそうです。
- Snippet Generatorがとても便利でした。
- stageとかはチェックポイントは使いこなしていないので、要調査。
- 昨日のレポートでも書いたのですが、解消できなかった疑問として、あるリビジョンでワークフローを流している途中に、別のリビジョンでワークフローを並行して流すことはできるのかどうか、検証する必要がありそうです。
- ワークスペースが共通なので、Jenkinsの仕組み的に多分できなそうな気はしますが。
というわけで、カンファレンスで受けた印象通りに、すばらしいプラグインだと感じました。
このプラグインの適用事例が増えて、色んなワークフロースクリプト(のテンプレート or サンプル)が公開されてくることに期待したいです。