Jenkins Workflow Pluginを使ってGradleマルチプロジェクトのワークフローを組んでみた(WIP)

先日の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
    • ドキュメントビルド用プロジェクト(Sphinx関連のファイルが置いてある)
    • Javaプロジェクトではない。
  • ui

パッケージのための成果物はcommon+sub2で1つで1つ, common+sub2+doc+uiで計2つ。

現行のジョブの流れと課題

というわけで、それぞれのプロジェクトのビルドを1つのジョブにマッピングさせて、以下のようなパイプラインを組んでいます。

f:id:jappy:20150112230334p:plain

チェックアウトはパイプラインの最初の1回だけ行います。
そのため、パイプラインの先頭でチェックアウトしてきたソースコード類を他のジョブに引き継ぐために、Clone Workspace SCM Pluginを利用しています。

これはこれで運用は周りますが、設定のメンテナンスという観点で、以下のような課題がありました(一部は対策済み)。

  • 似たようなジョブがたくさんできる(common1-build, sub1-build, sub2-build)
  • Jenkinsのビューでジョブ一覧を見た時にパイプラインの状況が分かりにくい。
  • ジョブ間にまたがった同じ処理の設定が面倒。
  • 【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) とすると別のジョブのビルドができるので、うまく組み合わせるとか、になりそう。
  • ビルドでの成果物のコピーは archiveunarchive で簡単にできる。
    • unarchive でのマッチングルールは、 archive のものと一致していなくてもよい。 archive includes: '*.txt' に対して unarchive mapping: ['a.txt' : '.'] とやってもコピー可。
    • archive excludes: '.git/**/*', includes: '**/*' のようにワークスペース全体を成果物としてしまえば、Clone Workspace SCMプラグイン相当のことができる。
    • 異なるスレーブ間でも archiveunarchive でコピー可。
    • build(jobname) でビルドしたジョブの成果物をワークフロージョブ内で unarchive でコピーすることはさすがにできない模様。
  • GitHubやGitBucketのHookを使ってこのワークフロージョブを走らせる、といったことはできない?
    • ワークフロージョブのトリガールールの設定はかなりシンプルでした。
    • ただ、ワークフロージョブとしてできなくても、トリガー専用ジョブ(フリースタイル・ジョブ)とワークフロージョブの2つに分けてつなげば回避できそうです。
  • Snippet Generatorがとても便利でした。
  • stageとかはチェックポイントは使いこなしていないので、要調査。
  • 昨日のレポートでも書いたのですが、解消できなかった疑問として、あるリビジョンでワークフローを流している途中に、別のリビジョンでワークフローを並行して流すことはできるのかどうか、検証する必要がありそうです。
    • ワークスペースが共通なので、Jenkinsの仕組み的に多分できなそうな気はしますが。

というわけで、カンファレンスで受けた印象通りに、すばらしいプラグインだと感じました。
このプラグインの適用事例が増えて、色んなワークフロースクリプト(のテンプレート or サンプル)が公開されてくることに期待したいです。