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な柔軟な点だと思う。

GradleとEclipseを使う場合のTipsをいくつか紹介

またまたGradleネタです。Gradleなどのツールでビルドしつつも普段の開発やユニットテストはIDEで行うというのはよくある話。GradleではIDE開発をサポートするプラグインも用意されています。

今回はこのうちEclipse Pluginに関していくつかTipsを紹介。

Eclipse Pluginを使うと何ができる?

プロジェクトからEclipseの各種設定ファイルを自動生成することができる。Mavenで言うところのMaven Eclipse Pluginと同じような物。
Eclipseのプロジェクト情報である.projectファイルや、クラスパスを定義した.classpathを生成することができる。
この時、Gradleで定義した依存関係も解決してくれるので、MavenみたいにJARを自動でダウンロードしてEclipseのクラスパスに反映させることもできる。

まずはシンプルな例:依存ライブラリをクラスパスに設定する

build.gradleを以下のように作成する。また、ソースフォルダ用にsrc/main/javaも事前に作っておく。

apply plugin: 'java'
apply plugin: 'eclipse'

repositories {
    mavenCentral()
}

dependencies {
    compile 'commons-lang:commons-lang:2.6'
}

普通のJavaプロジェクトとの違いは、apply plugin: 'eclipse'と書いただけ。これでgradle eclipseと実行する。

# gradle eclipse
:eclipseClasspath
:eclipseJdt
:eclipseProject
:eclipse

BUILD SUCCESSFUL

Total time: 5.294 secs

ビルドでは依存ライブラリのダウンロードが自動で行われ、プロジェクト直下に.projectと.classpathが生成される。.classpathを見てみると以下のように、dependenciesで依存関係として定義したcommons-langも設定されている。また、便利なことにデフォルトでsourcepathも設定されるので、依存ライブラリのソースを見たくなったときも簡単に参照できるようになっている*1。事前にsrc/main/javaフォルダを作成しておくことで、自動的にソースフォルダとしても設定もしてくれます*2

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="output" path="bin"/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
	<classpathentry kind="src" path="src/main/java"/>
	<classpathentry sourcepath="/Users/wadahiro/.gradle/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/source/67313d715fbf0ea4fd0bdb69217fb77f807a8ce5/commons-lang-2.6-sources.jar" kind="lib" path="/Users/wadahiro/.gradle/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/jar/ce1edb914c94ebc388f086c6827e8bdeec71ac2/commons-lang-2.6.jar" exported="true"/>
</classpath>

クラスパス変数を利用した.classpathを生成する

先の例では、クラスパスの設定が「/Users/wadahiro/」のように開発者環境依存のパスになっている。これだと、.classpathSVNやGitなどのソース管理で共有することができない。ここはMavenのM2_HOMEみたいにクラスパス変数を使った方式にしたいところ。
GradleでEclipseの設定ファイルを生成するときにクラスパス変数GRADLE_REPOを使う - 豆無日記でGradleでの設定方法について紹介されているが、残念ながら最新バージョン(1.0-milestone-9)だと非推奨(deprecated)になっている。

#gradle eclipse
The eclipseClasspath.variables method has been deprecated and will be removed in the next version of Gradle. Please use the eclipse.pathVariables method instead.
:eclipseClasspath
:eclipseJdt
:eclipseProject
:eclipse

BUILD SUCCESSFUL

1.0-milestone-9だと、以下のように定義する。

eclipse {
    pathVariables 'GRADLE_USER_HOME': gradle.gradleUserHomeDir
}

これでgradle eclipseを実行すると以下のように生成される。ちゃんと「GRADLE_USER_HOME」に置き換わっている。後は、Eclipseのクラスパス変数の設定でGRADLE_USER_HOMEをユーザーホームに設定すれば良い*3

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="output" path="bin"/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
	<classpathentry kind="src" path="src/main/java"/>
	<classpathentry sourcepath="GRADLE_USER_HOME/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/source/67313d715fbf0ea4fd0bdb69217fb77f807a8ce5/commons-lang-2.6-sources.jar" kind="var" path="GRADLE_USER_HOME/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/jar/ce1edb914c94ebc388f086c6827e8bdeec71ac2/commons-lang-2.6.jar" exported="true"/>
</classpath>

クラスパスの優先順位を制御する

プロジェクトによっては、あるライブラリへのクラスパスの優先度を他より高くしたいときがたま〜にある。例えば、commons-langのうちあるクラスにだけ独自の修正を入れて、そのクラスのみを含んだJARを作ったとする。この場合、オリジナルのJARより優先度を高くしないと修正したクラスが反映されない*4
独自修正のクラスはMavenリポジトリには置いていなくて、プロジェクトのlibフォルダ以下に置いていたとする。この時、build.gradleを以下のように定義したとする。dependenciesの中で上に書けば優先度も高くなるんじゃない?と期待しつつ...

dependencies {
    compile files('lib/my-custom-commons-lang.jar')
    compile 'commons-lang:commons-lang:2.6'
}

しかしこれでgradle eclipseしても、以下のように、lib/commons-lang-2.6.jarへのクラスパス設定は最後になり、優先度としては一番低くなってしまう*5。また、もう1つ残念なことにクラスパスが絶対パスになってしまっている。これではまた.classpathを共有できなくなってしまう。

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="output" path="bin"/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
	<classpathentry sourcepath="GRADLE_USER_HOME/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/source/67313d715fbf0ea4fd0bdb69217fb77f807a8ce5/commons-lang-2.6-sources.jar" kind="var" path="GRADLE_USER_HOME/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/jar/ce1edb914c94ebc388f086c6827e8bdeec71ac2/commons-lang-2.6.jar" exported="true"/>
	<classpathentry kind="lib" path="/Users/wadahiro/git/gradle-samples/eclipse-samples/classpath-order-sample/lib/commons-lang-2.6.jar" exported="true"/>
</classpath>

こういう時は、eclipse.classpath.file.whenMergedを使う。これを使うことで、.classpathの定義内容を細かく制御可能になる。以下は、kind="lib"のクラスパスは上位に移しつつ、絶対パスをやめて相対パスに変換する例。

eclipse {
    pathVariables 'GRADLE_USER_HOME': gradle.gradleUserHomeDir

    classpath {
        file {
            whenMerged { classpath ->
                def libs = classpath.entries.findAll { it.kind == 'lib' }
                libs = libs.collect { lib ->
                    def baseDir = project.projectDir.getAbsolutePath().replace('\\', '/')
                    if (lib.path.startsWith(baseDir)) {
                        lib.path = lib.path.replace(baseDir, ".")
                    }
                    return lib
                }
                def others = classpath.entries.findAll { it.kind != 'lib' }
                classpath.entries = libs + others
            }
        }
    }
}

classpath.entries に最終的に.classpathに出力するクラスパスのリストをセットすれば良い。上記では、kind="lib"とそれ以外を分けて連結しなおし、先頭に移動している。これでgradle eclipseを実行すると、以下のように望んだ形で出力されるようになる。lib/commons-lang-2.6.jarへのパスも絶対パスから相対パスに変換されているので、これで環境依存もなくなります。

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="lib" path="./lib/commons-lang-2.6.jar" exported="true"/>
	<classpathentry kind="output" path="bin"/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
	<classpathentry kind="src" path="src/main/java"/>
	<classpathentry sourcepath="GRADLE_USER_HOME/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/source/67313d715fbf0ea4fd0bdb69217fb77f807a8ce5/commons-lang-2.6-sources.jar" kind="var" path="GRADLE_USER_HOME/caches/artifacts-8/filestore/commons-lang/commons-lang/2.6/jar/ce1edb914c94ebc388f086c6827e8bdeec71ac2/commons-lang-2.6.jar" exported="true"/>
</classpath>

追記

作成したサンプルはGitHubにあげています。

*1:もちろん、*-sources.jarがリポジトリで公開されていることが前提です。

*2:フォルダがないとソースフォルダの設定はされないので注意。

*3:残念ながらこの設定は手動になってしまう。[http://maven.apache.org/plugins/maven-eclipse-plugin/configure-workspace-mojo.html:title=Maven Eclipse Pluginのeclipse:configure-workspace]と同じ機能がGradleにあるといいけど、どうせ1回しか設定しないからなくてもほとんど困らないと思う。

*4:そもそもJARを2つにせず1つにすればいいのだが、色々事情があってできないこともあるのだ!!

*5:どうもfilesで指定した場合(lib="kind"となるもの)は優先度が低くなるようだ。

GradleでWARをマージする方法

またまたGradle小ネタです。WARファイルをビルドする時に、別のWARファイルをマージしたいときがあります。
Mavenならmaven-war-pluginのオーバーレイを使えばできるのだが、Gradleならどうするか?

残念ながら、現状のGradle(1.0-milestone-9)のWAR PluginではMavenのようなオーバーレイ機能はサポートしていないようだ。ただ、そこはさすがGradle(というかGroovyのパワーを借りてだが)、以下のように比較的シンプルに書ける。

apply plugin: 'war'

war << {
	ant.zip(destfile: war.archivePath, duplicate: 'preserve', keepcompression: true, update: true) {
		zipfileset (src: project(':common').war.archivePath)
	}
}

warのアクションの最後に、antのzipタスクで別のWARを重ねているだけです。ポイントは、duplicate: 'preserve'とすることで、同名のファイルを上書きしないようにすること。

追記

サンプルをGithubにあげておきました。

Gradleのタスクの実行制御についてメモ

またまたGradleネタです。Gradleであるタスクを実行する場合のみに、依存関係にあるタスクの前に処理を割り込ませたい、というのをどう定義するか?というのでちょっと悩んでいたのでメモ。
Antなら、callタスクを使って別のタスクを呼び出すことができるので、例えば以下のように書ける。これをGradleだとどう書くのか?

  <target name="dist">
    <echo message="dist"/>
  </target>

  <target name="release">
    ... (ここで前処理)
    <antcall target="dist" />
    ... (ここで後処理)
  </target>

一応、非公開APIながらGradleではタスクに対してexecute()を呼び出せばタスクを実行できるのだが、これだとそのタスク単体しか呼ばれないようだ。コマンドラインからそのタスクを実行した時のように、そのタスクの依存関係までみて実行はしてくれない? 例えば、以下のように定義できるのだが...

task compile << {
  println "compile"
}

task dist(dependsOn: compile) << {
  println "dist"
}

task release << {
  println "Before process"
  tasks.dist.execute()
  println "After process"
}

これでreleaeタスクを実行しても、distタスクが依存するcompileタスクまで実行してくれない。

# gradle release
:release
Before process
dist
After process

BUILD SUCCESSFUL

というわけでどうするのがいいかなーとGradleのユーザガイドを眺めていると、使えそうな仕組みがあった。

以下のように、gradle.taskGraph.whenReadyのクロージャでGradleのconfiguration phase後に処理をフックすることができるのだ。これを使えば、releaesタスクが実行されるときにだけdistタスクに前処理をdoFirstクロージャで追加することができる。

task compile << {
  println "compile"
}

task dist(dependsOn: compile) << {
  println "dist"
}

task release(dependsOn: dist) << {
  println "After process"
}

gradle.taskGraph.whenReady {taskGraph ->
  if (taskGraph.hasTask(release)) {
    dist.doFirst {
      println "Before process"
    }
  }
}

実行結果は以下の通り。ちゃんと、依存関係のあるcompileも先に実行されつつ、distタスクの前に追加した処理が実行され、distタスク完了後にreleaseタスクが呼ばれるようになった。

gradle release
:compile
compile
:dist
Before process
dist
:release
After process

BUILD SUCCESSFUL

ただ、もうちょっとスマートに書く方法ないのかなぁ。。。

Gradleで複数JARをMavenリポジトリにアップロードする方法

Gradle小ネタです。オープンソースではなく社内の開発だと、独自ライブラリや商用のライブラリをプライベートなMavenリポジトリにアップロードしたくなります。AntやMavenでももちろんできるんだけど、大量にあるときはGradleが楽チンかも?

あるディレクトリ配下にあるJARファイルをまとめてアップロードしたい場合は以下のように書けばOK。例では全てgroupIdは"org.example.library"、versionは"1.0"にしているけど、別途マッピング情報を用意してファイル別に設定することもできるでしょう。
記述のポイントは以下の2点。

  • archivesタスクを使ってJARファイルをこのプロジェクトのartifactとして登録する
  • mavenDeployerのクロージャでaddFilterを使い、JARファイルごとにPOMの情報を設定する
apply plugin: 'maven'

def files = file('lib').listFiles()

artifacts {
    files.each { f ->
        archives f
    }
}

uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: "http://mycompany/content/repositories/thirdparty")
            files.each { f ->
                def id = f.name.replace(".jar", "")
                addFilter(id) { artifact, file ->
                    artifact.name == id
                }
                pom(id).groupId = 'org.example.library'
                pom(id).artifactId = id
                pom(id).version = '1.0'
            }
        }
    }
}

以下のコマンドを実行してアップロードする。libフォルダ以下のJARファイルをまとめてMavenリポジトリにアップロードしてくれます。

gradle uploadArchives

追記

サンプルはGitHubにあげておきました。

JARファイルから特定のクラスを除外するAntカスタムタグ

あるプロジェクトで、ビルド時にとあるOSSのJARファイルからクラスを除外するということをやっていたが*1、余りにも面倒なやり方で行っていたのでさくっとAntのカスタムタグを作成して自動化してみた。

これまでのやり方

  • build.propertiesに除外するクラスパターンを書く
exclude.classes=org/example/Foo.class,\
                          org/example/Bar.class
  • build.xmlで上記除外リストを利用してunjarタスクで除外しつつ解凍し、再びJARファイルにパッケージングする。
	<target name="default" depends="depends" description="description">
		<unjar src="target/base.jar" dest="work">
			<patternset>
				<exclude name="${exclude.classes}" />
			</patternset>
		</unjar>
		<jar destfile="target/result.jar" basedir="work" />
	</target>

このやり方だと、差し替えたいクラスが増える度にbuild.propertiesをメンテナンスする必要がある。面倒だし、記入漏れやミスが発生することが過去何回もあった。

Antカスタムタグでの方法

そもそも除外したいクラスは別途JARファイルとしてあるので、わざわざbuild.propertiesに書かなくてもJARファイルから除外リストを自動すれば良い。というわけでAntタスククラスを作成。

使い方

  • taskdefを定義し、classpath属性にビルドしたant-tasks-ext-1.0.jarを指定する。Ant本体のJAR(ant-1.7.0.jar)も合わせて指定する。resource属性にはantlib.xmlを指定する。
  • 実際に使うところでは、excludejarタグを記述する。
    • destfile: 編集後の出力するJARファイルを指定する。
    • basefile: 編集したいJARファイルを指定する。
    • excludefile: 除外したいクラスを持つJARファイルを指定する。
    • autoclean: workディレクトリを自動削除する場合はtrueを指定する。無指定の場合はデフォルトfalse。
    • work: 作業用ディレクトリを指定する。無指定の場合は、OSのtmpディレクトリ(java.io.tmpdir)が自動的に使われる。

記述例は以下。

<?xml version="1.0" encoding="UTF-8"?>
<project name="project" default="default">

    <taskdef classpath="lib/ant-tasks-ext-1.0.jar;lib/ant-1.7.0.jar" resource="antlib.xml" />

    <target name="default">
        <excludejar destfile="target/result.jar"
            basefile="target/base.jar"
            excludefile="target/exclude.jar"
            autoclean="true"
            work="./work" />
    </target>

</project>

これでようやく除外リストのメンテナンスから解放される!!

*1:そもそも何故除外していたかいうと、一部のクラスだけ改修して別のJARファイルに格納して利用していたのだが、同名クラスが存在すると環境によってはロード順が異なり意図しない動作になるらしいとのこと。そこでAntで一度除外したいクラスを除いて解凍し、再パッケージするという運用になっていた。

Vagrant & VeeWee を再設定してCentOS 6.2のBoxを作成してみた

以前Mac(Lion)に試しに入れたVagrantでセットアップしたVirtualBoxのイメージが/private/var/root/VirtualBox VMs以下に作られてしまうのが嫌だったので、Vagrantを入れ直した。ついでにVeeWeeも入れ直して、最新のCentOS 6.2のVagrant Boxを作ってみる。

RubyRubyGemsの詳細を押さえていないので不明だけど、Macだと最初からRubyがシステムのところに入っているのが問題? そこでRVMRubyRubyGemsを$HOME以下に全てインストールすることにした。

RVMのインストール

以下のコマンドを実行するだけ。簡単すぎる!

bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)

rvmコマンドが使えるように.bash_profileを読み直す。

source ~/.bash_profile 

Ruby 1.9.3-p0のインストール

RVMコマンドでバージョン番号を指定すれば簡単にインストールできる。すごい便利。とりあえず最新安定板の1.9.3をインストールした。rubyのコンパイルも行われるのでしばらく時間がかかります。

rvm install 1.9.3

インストールした1.9.3を使うようにセットアップ。

rvm use 1.9.3

さらに、再ログインしても維持するためにシステムのデフォルトを1.9.3にする。

rvm --default 1.9.3

Vagrantのインストール

gemコマンドでインストールするだけ。

gem install vagrant

VeeWeeのインストール

こちらもソースからインストールした。gemでもインストールできるが、用意されているテンプレートが少なかったので。

git clone https://github.com/jedi4ever/veewee.git
cd veewee
bundle install

最後にエイリアスを設定する。

alias vagrant="bundle exec vagrant"

オリジナルのVagrantBoxの作成

Vagrantで使えるベースBoxは http://www.vagrantbox.es/ で公開されているけど、欲しい物が無いときは自分で作るしかない。VeeWeeを使うとベースBoxを簡単に作ることができる。先ほど入れたVeeWeeで早速作ってみる。

今回はCentOS 6.2のBoxを作ってみる。テンプレートに「CentOS-6.2-x86_64-minimal」があるのでこれをベースに作成してみる。Box名は「centos_62_x86_64_ja」とした。なお、コマンドはVeeWeeのルートディレクトリで実行すること。

vagrant basebox define centos_62_x86_64_ja CentOS-6.2-x86_64-minimal

definitions/centos_62_x86_64_ja/以下に3ファイルができている。これらのファイルを編集すれば、作成するVMの設定(CPU、メモリ、HDDサイズなど)とかインストールされるソフトウェアをカスタマイズできる。

  • definition.rb
  • ks.cfg
  • postinstall.sh

今回はks.cfgをちょっと修正してみた。

  • インストール時に言語設定がen_US.UTF-8で行われているので、ここをja_JP.UTF-8に変更
  • キーボードをusからjp106に変更
  • タイムゾーンUTCからAsia/Tokyoに変更
iinstall
cdrom
lang ja_JP.UTF-8
keyboard jp106
network --bootproto=dhcp
rootpw --iscrypted $1$damlkd,f$UC/u5pUts5QiU3ow.CSso/
firewall --enabled --service=ssh
authconfig --enableshadow --passalgo=sha512
selinux --disabled
timezone Asia/Tokyo
...

後、definition.rbで定義されているCentOSのISOイメージ取得先も変更。デフォルトだとダウンロードが遅かったので日本のミラーサイトに変更。

ではこれでBoxをビルドしてみる。以下ののコマンドを実行する。

vagrant basebox build centos_62_x86_64_ja

CentOSのISOイメージをダウンロードするか求められるので、「Yes」とするとダウンロードが開始される。ここでしばらく待ち。もし、既に手元にISOイメージがある場合は、VeeWeeのディレクトリ以下にisoディレクトリを作り、そこに配置しておけば良い。
ダウンロードが終わると、後は勝手にVirtualBoxの起動、OSインストールが行われる。全自動でセットアップされる様子を見ているとスゲー!! とちょっと感動しちゃう。

OSが出来上がったら、あとはVagrant用のBoxとしてエクスポートする。以下のコマンドを実行する。VeeWeeのディレクトリ以下にcentos_62_x86_64_ja.boxが出来上がる。

vagrant basebox export centos_62_x86_64_ja

エクスポートしたBoxをVagrantに登録する。これで$HOME/.vagrant.d/boxes 以下に登録される。

vagrant box add 'centos_62_x86_64_ja' 'centos_62_x86_64_ja.box'

作成したVagrat Boxで環境構築してみる

適当にVagrant用のディレクトリを作成。

mkdir ~/centos_62_x86_64_ja
cd centos_62_x86_64_ja

VeeWee用にaliasを設定してしまっているので解除する。

unalias vagrant

登録した「centos_62_x86_64_ja」で初期化し、インスタンスを起動する。これで環境構築は完了。

vagrant init 'centos_62_x86_64_ja'
vagrant up

最後に

せっかくなので作成したCentOS 6.2(日本語設定版)のBoxをDropBoxで公開しておきます。
http://dl.dropbox.com/u/7023440/vagrant-boxs/centos_62_x86_64_ja.box
Vagrantがインストールされている環境であれば、以下のコマンドですぐに環境構築できます!!

vagrant box add centos_62_x86_64_ja http://dl.dropbox.com/u/7023440/vagrant-boxs/centos_62_x86_64_ja.box