Gradleでオフラインビルドの方法を考えてみた

MavenやGradleでビルド環境を構築すると悩ましいのがオフラインビルド。2012-03-02 - 新・たけぞう瀕死の日記で紹介されているように、Mavenだと「dependency:go-offline」というGoalでMavenプラグインなども含めて依存ライブラリをかき集めてくれるようです。では、Gradleだとどうすれば実現できるか考えてみた。

外部のプラグインはひとまず考えないことにして*1、コンパイル、ユニットテストを通すだけなら以下のコードで実現できそう。
例では、専用のreleaseSourceタスクを新たに定義している。このタスクでは、testCompileで使用する依存ライブラリをかき集めてrepositoryというディレクトリに格納しつつプロジェクトのソースと一緒にパッケージしている。ZIPに固める際にexcludeを使えば除外設定も可能なので、.gitとか.svnを除外することも簡単。
また、dependenciesの設定では、「repositoryディレクトリ」が存在する場合は、配下のJARをクラスパスに登録するように制御している*2。そうすることで、オフラインビルド時にはrepository配下のJARを見てビルドするようにしている。

apply plugin: 'java'

repositories {
    mavenCentral()
}

version = "1.0"

dependencies {
    if (file('repository').exists()) {
        compile fileTree(dir: 'repository', include: '*.jar')
    } else {
        compile 'commons-digester:commons-digester:2.1'
        testCompile 'junit:junit:4.10'
    }
}

task releaseSource(type: Zip) {
    classifier = 'sources'
    from(projectDir) {
        exclude '.git'
        exclude '.gradle'
        exclude '.settings'
        exclude 'bin'
        exclude 'build'
    }
    into('repository') {
        from configurations.testCompile
    }
}

作成したreleaseSourceタスクを実行する。これでbuild/distributionsディレクトリ以下にオフラインビルド可能なソース・ライブラリを一緒に格納したZIPができあがります。

gradle releaseSource

オフライン環境ではこのZIPを解凍しgradleでビルドする。Mavenリポジトリにアクセスできなくても、repositoryディレクトリに必要なライブラリがあるのでビルドができる。なお、ビルド環境にはJDKとGradle本体のインストールは必要になる。

Gradleのインストールも不要にしてみる

これだけではつまらないのでGradleの事前インストール自体も不要な形も考えてみた。やり方は、GradleのバイナリもZIPの中に含めつつGradle Wrapperで自動インストールさせるというちょっと荒技?。
Gradle Wrapperについてはインストールレスで Gradle してみる - bluepapa32’s Java Blogなどで紹介されています。通常のGradle WrapperはHTTPでサーバからGradleのバイナリを取りにいくが、この設定ファイルを相対ファイルパスで取得するように書き換えてからZIPに格納するようにする。これでJDKがインストールされている環境であれば、ZIPを解凍して「gradlew build」コマンド1発でビルドすることが可能になる。Gradleのバイナリがそれなりに大きいので、サイズが膨らむのがちょっと残念ですが。。。

apply plugin: 'java'

repositories {
    mavenCentral()
}

version = "1.0"

dependencies {
    if (file('repository').exists()) {
        compile fileTree(dir: 'repository', include: '*.jar')
    } else {
        compile 'commons-digester:commons-digester:2.1'
        testCompile 'junit:junit:4.10'
    }
}

task releaseSource(type: Zip) {
    doFirst {
        // get gradle.bin.zip for offline build.
        ant.mkdir(dir: "${projectDir}/build/offline")
        def p = new Properties()
        p.load(new FileInputStream(new File("${projectDir}/gradle/wrapper/gradle-wrapper.properties")))
        def gradleUrl = p.get('distributionUrl')
        //ant.get(src: gradleUrl, dest: "${projectDir}/build/offline")
        
        // edit properties for offline build.
        ant.copy(todir: "${projectDir}/build/offline",
                 overwrite: true,
                 preservelastmodified: true) {
            fileset (file: 'gradle/wrapper/gradle-wrapper.properties') 
        }
        ant.replaceregexp (file: "${projectDir}/build/offline/gradle-wrapper.properties",
                           match: "(distributionUrl)=.*", flags: 'g',
                           replace: "\\1=../../repository/${gradleUrl.substring(gradleUrl.lastIndexOf('/') + 1, gradleUrl.size())}")
    }
    
    classifier = 'sources'
    from(projectDir) {
        exclude '.git'
        exclude '.gradle'
        exclude '.settings'
        exclude 'bin'
        exclude 'build'
    }
    into('repository') {
        from configurations.testCompile
        from("${projectDir}/build/offline") {
            include '*.zip'
        }
    }
    into('gradle/wrapper') {
        from "${projectDir}/build/offline/gradle-wrapper.properties"
    }
}

task wrapper(type:Wrapper)

最初の例と異なる点は、releaseSource.doFirstクロージャを追加し、

  • gradle-wrapper.propertiesを読み込み、ant.getでGradle本体をダウンロードする
  • gradle-wrapper.propertiesのURLを相対パスに書き換える

を行いつつ、これらをZIP格納ファイルに含めるように書き換えています。また、Gradle Wrapperを使うためにwrapperタスクも追加しています。

使い方は、下記のようにwrapperタスクでGradle Wrapperのファイルを一度生成した後、releaseSourceタスクでZIPを作成する。

gradle wrapper
gradle releaseSource

他にも方法はある?

試してはいないけど、GradleのMaven Pluginを使って、Mavenの「dependency:go-offline」のようにMavenリポジトリを指定ディレクトリに作り、ZIPに格納するようなこともできそうな気がする。

*1:そもそもGradleはデフォルトである程度プラグインを備えているので、外部プラグインに依存しなくても大抵のことはできる。

*2:Gradleだとリポジトリ外のJARも簡単にクラスパスに登録できる。これはMavenにできない、Gradleな柔軟な点だと思う。