Maven を Gradle 上で実行 - SVNKit によるソースのチェックアウト付き

前回 は Groovy スクリプトで Maven を実行してみましたが、今回は Gradle 上で実行してみる事にします。

ソースは http://github.com/fits/try_samples/tree/master/blog/20130731_2/

既存資産の pom.xml を Gradle に移行するのが困難なケース(数が多すぎるとか)で活用できるかもしれません。

Gradle 上で Maven 実行1

Gradle のビルドスクリプト内で MavenCli を使うには下記の設定が必要になります。

ここで 1つ注意点があります。

前回のように MavenCli.main(String[]) を使うと System.exit() が呼び出され Gradle 上で実行するには不都合が生じるので、System.exit() を呼び出さない下記のメソッドを使います。(どちらでも可)

  • MavenCli.main(String[], ClassWorld)
  • MavenCli.doMain(String[], ClassWorld)
gradle_mvn/build.gradle
import org.apache.maven.cli.MavenCli

// ビルドスクリプト内で MavenCli を使うための設定
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.apache.maven:maven-embedder:3.0.5'
    }
}

task mvn << {
    def argsLine = "-f work/sample-app/pom.xml clean package -Dmaven.test.skip=true"
    // Maven の実行
    MavenCli.doMain(argsLine.split(' '), null)
}

上記では mvn タスクに Maven の実行を割り当てているので gradle mvn で実行します。

実行結果
> gradle mvn

:mvn
[INFO] Scanning for projects...
・・・
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
・・・
BUILD SUCCESSFUL

Total time: 11.482 secs

特に問題無く Maven が Gradle 上で実行されると思います。

Gradle 上で Maven 実行2

次に SVNKit を使えば、Subversion からチェックアウトしたソースを Maven を使ってビルドするような処理を Gradle 上で比較的容易に実現できます。

ついでに Maven の処理が失敗した場合に RuntimeException を throw する処理を追加しました。

gradle_mvn+svn/build.gradle
import org.tmatesoft.svn.core.*
import org.tmatesoft.svn.core.wc.*
import org.apache.maven.cli.MavenCli

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.tmatesoft.svnkit:svnkit:1.7.8'
        classpath 'org.apache.maven:maven-embedder:3.0.5'
    }
}

// Subversion のリポジトリURL
def repoUrl = 'http://localhost/svn/sample-app'
def workDir = 'work'

task svnco << {
    def manager = SVNClientManager.newInstance()
    def client = manager.updateClient

    // Subversion のリポジトリからソースをチェックアウト
    def res = client.doCheckout(
        SVNURL.parseURIDecoded(repoUrl), 
        new File(workDir), 
        SVNRevision.HEAD, 
        SVNRevision.HEAD, 
        SVNDepth.INFINITY, 
        true
    )

    println "checkout revision = ${res}"
}

task mvn << {
    def cmd = "-f ${workDir}/pom.xml package -Dmaven.test.skip=true"

    def res = MavenCli.doMain(cmd.split(' '), null)

    if (res != 0) {
        // Maven 処理の失敗時
        throw new RuntimeException()
    }
}
// タスクの依存関係を設定(mvn は svnco に依存)
mvn.dependsOn svnco

task clean << {
    delete(workDir)
}
実行結果
> gradle mvn

:svnco
checkout revision = 1901
:mvn
[INFO] Scanning for projects...
・・・
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
・・・
BUILD SUCCESSFUL
Total time: 1 mins 25.088 secs

Gradle 上で Maven 実行3

更に、Maven のプロファイル毎(今回は prd, stg, dev の 3種)にチェックアウト・ビルドして、ビルド結果の war ファイルをリネームして同じディレクトリにまとめるような処理も Gradle なら比較的簡単に実装できます。

gradle_mvn+svn_profiles/build.gradle
import org.tmatesoft.svn.core.*
import org.tmatesoft.svn.core.wc.*
import org.apache.maven.cli.MavenCli

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.tmatesoft.svnkit:svnkit:1.7.8'
        classpath 'org.apache.maven:maven-embedder:3.0.5'
    }
}

// Subversion のリポジトリURL
def repoUrl = 'http://localhost/svn/sample-app'
def workDir = 'work'
def destDir = 'dest'
def profiles = ['prd', 'stg', 'dev']

task svnco << {
    def manager = SVNClientManager.newInstance()
    def client = manager.updateClient

    // プロファイル毎にループ
    profiles.each { profile ->
        // Subversion のリポジトリからソースをチェックアウト
        def res = client.doCheckout(
            SVNURL.parseURIDecoded(repoUrl), 
            new File("${workDir}/${profile}"), 
            SVNRevision.HEAD, 
            SVNRevision.HEAD, 
            SVNDepth.INFINITY, 
            true
        )

        println "${profile} checkout revision = ${res}"
    }
}

task mvn << {
    // プロファイル毎にループ
    profiles.each { profile ->
        def cmd = "-f ${workDir}/${profile}/pom.xml package -Dmaven.test.skip=true -P ${profile}"

        def res = MavenCli.doMain(cmd.split(' '), null)

        if (res != 0) {
            throw new RuntimeException()
        }
    }
}

task dest << {
    mkdir(destDir)

    // プロファイル毎にループ
    profiles.each { profile ->
        // 生成された war ファイルの末尾に .<profile名> を付けて
        // dest ディレクトリへフラットにコピー
        copy {
            from fileTree("${workDir}/${profile}") {
                include '**/target/*.war'
            }.files

            rename {
                "${it}.${profile}"
            }

            into destDir
        }
    }
}

mvn.dependsOn svnco
dest.dependsOn mvn

task clean << {
    delete(workDir)
    delete(destDir)
}

なお、下記のような copy の仕方にするとディレクトリ階層を保ったまま dest にコピーされるので (例 dest/target/sample-app.war.prd にコピー)、上記では fileTree().files を使ってフラットにファイルをコピー (dest 直下にファイルを配置) するようにしています。

ディレクトリ階層を保ったままコピーする例

copy {
    // 下記のようにすると target ディレクトリごとコピーされてしまう
    from("${workDir}/${profile}") {
        include '**/target/*.war'
    }

    rename {
        "${it}.${profile}"
    }

    into destDir
    // 空ディレクトリをコピーさせないための設定
    includeEmptyDirs = false
}
実行結果
> gradle dest

:svnco
prd checkout revision = 1901
stg checkout revision = 1901
dev checkout revision = 1901
:mvn
[INFO] Scanning for projects...
・・・
:dest

BUILD SUCCESSFUL

出力されたファイルは下記のようになります。

出力ファイル
  • dest ディレクトリ
    • sample-app.war.dev
    • sample-app.war.prd
    • sample-app.war.stg