2010年2月23日火曜日

RakeとAntを一緒に

原文: トム=エネボ

JRuby 1.5リリースに含まれる新しいライブラリをレポジトリーに先日コミットしました。これについて述べる前にこの下にでている写真を見て色々と思いを巡らして下さい。そう!私たち皆の模範となるミスター・ポテトヘッドです。ミスター・ポテトヘッドは私たちを数時間に渡って楽しませる(少なくともおそらく楽しませてくれていたであろう)、柔軟なただのオモチャではなくて、澱粉質を多く含む食べ物でもあるのです。

ミスター・ポテトヘッド

(MyMollyPopさんの写真)



ちょっとトンチンカンな事を言っているな、と思う前にちょっと考えてみて下さい。実際のところ、私たちがプログラマとして日常やっているのは、ミスター・ポテトヘッドを造り上げているようなものではないですか。ソフトウェアを造り上げるという問題に私たちが直面した時、求められているのは、さまざまな部品をくっつけるだけではなく、部品を最も綺麗に組み合わせる事です。ソフトウェアデザインとは正しくジャガイモを飾り付けるようなものです。この記事の「ジャガイモ」はソフトウェアです。




ビルドのためのツール


Javaにおいて、Antは800ポンド(360㌔強)のジャガイモと言える位のビルドのためのツールの「重鎮」です。AntはJavaをビルドする環境の殆ど全てにあると言っていいでしょう。私は、Antを「心の底から好きだ」と言ってのける人には今までにたった一人しか出会っていません。

大抵は、Antは構文的には見苦しいが与えられた仕事をきちんと成し遂げてくれる信頼出来るツールとして一目置いてるというところでしょう。命令型プログラミングにあまり適さないのにも不平があると思いますが、これは元来の仕様としてそうであるように思われますが、これを歓迎するプログラマはそう多くはないでしょう。

RubyではRakeを使います。Antとは対照的に、Rakeの構文は綺麗です。RubyでDSLのような物を書く為のAPIであるRakeにおいてはRubyで使える物ならば何でも使えます。その一方で、Javaの世界ではよくやるような作業をするのにはAntにあるようなプラットフォームに依存しないタスクを持ち合わせていません。その為、シェルを使ったりする羽目になってしまって、これもある程度は動くのですが、Windowsで動かそうとすると悲しい目に遭ってしまいます。

現実的に言うと、Javaをよく使っている人たちは他のビルド法に関心を示しはするものの、挙って数多くのプロジェクトをAnt以外のツールに移行はしないでしょう。仮にRakeに心惹かれていても、Antで当然の事としてやっているような事をRakeで成す為に四苦八苦する事になるのです。…今までは。

ユースケース


JRubyにおけるRakeとAntの統合では以下のユースケースを扱います。

  • RakeでAntで定義されているタスクとタイプを呼ぶ。

  • AntからRakeを呼べるようにする。

  • RakeのタスクをAntのターゲットとしてインポート出来るようにする。

  • RakeからAntを呼べるようにする。

  • AntのターゲットをRakeのタスクとして呼べるようにする。



一つずつ見ていきましょう。

RakeでAntで定義されているタスクとタイプを呼ぶ


実を言うと、AntはJRubyでは組み込みのライブラリに過ぎません。Rakeで使わないで、スクリプトを書いてしまう事も可能です。
require 'ant'

ant do
build_dir = "java_build" # Regular Ruby variables interact fine

# But defining and consuming Ant properties is fine
property :name => "src.dir", :value => "java_src"

path(:id => "project.class.path") do
pathelement :location => "classes"
end

mkdir :dir => build_dir

javac(:destdir => build_dir) do
classpath :refid => "project.class.path"
src { pathelement :location => "${src.dir}" }
end

jar :destfile => "simple_compile.jar", :basedir => build_dir
end


この例では、Antクラスのオブジェクトを生成して、ディレクトリを作り、Javaのソースコードをコンパイルし、最後にその結果をjarに入れます。これは皆Antのタスクで、全てのプラットフォームで動きます。凄いでしょう。しかしまだ依存関係の管理が欠けています。これはRakeにやらせましょう。

require 'ant'

build_dir = "java_build"
file build_dir

task :setup => build_dir do
ant.property :name => "src.dir", :value => "java_src"
ant.path(:id => "project.class.path") do
pathelement :location => "classes"
end
end

task :compile => :setup do
ant.javac(:destdir => build_dir) do
classpath :refid => "project.class.path"
src { pathelement :location => "${src.dir}" }
end
end

task :jar => :compile do
ant.jar :destfile => "simple_compile.jar", :basedir => build_dir
end

task :default => :jar


(ここで一つ注意。RakeとAntを両方使える訳ですからAntのプロパティを設定する必要はありません。Rubyの変数や定数を使えばいいんです。十人十色ですね。)

Rakefileの中でAntのタスクを使用するのが如何に簡単かが判ると思います。JRubyのAntライブラリは、Antの元の構文に素直に対応するAPIの集合です。何をどうすればいいのかは直ぐに理解出来るでしょう。

Rakeでは命令型プログラミングが出来るという長所があると既に述べました。このようなAntのコード片について考察してみましょう。

      <java classname="${mainclass}">
<arg value="--command"/>
<arg value="maybe_install_gems"/>

<arg value="--no-ri"/>
<arg value="--no-rdoc"/>
%lt;arg value="--env-shebang"/>
</java>


これを命令型に直すとこんな感じ

command = "--command may_install_gems --no-ri --no-rdoc --env-shebang"
ant.java :classname => "${mainclass}" do
command.split(/\s+/).each { |value| arg :value => value }
end


もしもあなたがRakeのユーザであるならば、こちらの方を使おうと決断するのは用意でしょう。その他のAntのタスクの中にはJavaらしい事をするのでなくたって便利な物というのもあります。

AntからRakeを呼べるようにする


或るAntプロジェクトと同等の物をRakeを使う事で書けるとしたら、そのプロジェクトをRake主導でやりたいと思うかもしれません。ところが、それでも尚AntからRakeを呼ぶ必要があるかも知れません。そういう時はこのRakeという名前のタスクで切り抜けましょう。

上に挙げたRakefileとbuild.xmlとが共存するという事にすると、そのbuild.xmlの中ではこのようなコード片を使う事が出来ます。

<?xml version="1.0" encoding="UTF-8"?>
<project name="foobar" default="default" basedir=".">
<description>Builds, tests, and runs the project foobar.</description>

<target name="load-rake-task">
<taskdef name="rake" classname="org.jruby.ant.Rake"/>

</target>

<target name="default" depends="load-rake-task">
<rake task="jar"/>
</target>

...
</project>


このAntスクリプトでdefaultターゲットを呼ぶと、まず始めにRakeのタスクが読み込まれ、それがRakeを呼び(ここではデフォルトであるRakefileをインプットとして使います)、それがRakeのデフォルトであるdefault、即ちjarタスクを呼ぶ訳です。これを元にしたシナリオは二つ考えられます。

1. Rakeを少しずつ試用する事が出来る

これは、あなたはRakeが好きだけれども、あなたの所属する開発チーム全体でRakeに移行するよう説得するのは難しいと思われる場合に有効です。何か新しい機能を入れる際にRakefileをプロジェクトの一部として忍ばせておいて、チームの他のメンバーに評価をしてもらうのです。もし気に入ってもらえれば残りを導入すればいいです…よね。Javaを主に使用している部署がビルドのシステムを一気に変えてしまうというのはとても考えられませんから、このように少しずつ導入していくのがよいでしょう。

2. 他のJavaのツールとうまく統合する

もしもあなたがRakeに惚れ込んでいたとしても、NetBeans等のソフトウェアは、あなたのプロジェクトとうまくやって行くためにはbuild.xmlがある事を大前提としています。このような繋ぎがあれば、あなたのプロジェクトとAntを前提としているツールとの相性はばっちりです。

RakeのタスクをAntのターゲットとしてインポート出来るようにする


上のRakeのタスクの大きな欠点は、それが一方通行であるという点です。Rakeを呼ぶ事は出来ますが、Rakeの側からはAntとは大した接触が有りません。Antのタスクを呼べはしますが、プロパティや他にどのようなAntのターゲットがbuild.xmlに定義されているのか等はさっぱり判りません。

よりよい相互運用の為にはRakeImportというもう一つのAntタスクを使います。RakeImportを使うには、定められたRakefileと、それにより定められているタスクをAntの依存管理システムに登録する必要が有ります。例を見てみましょう。

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

<project name="foobar" default="top-level" basedir=".">
<description>Builds, tests, and runs the project foobar.</description>

<taskdef name="rakeimport" classname="org.jruby.ant.RakeImport"/>

<rakeimport/>

<target name="top-level" depends="its_in_rake" />

<target name="its_in_ant">
<echo message="ant: its_in_ant"/>
</target>

</project>


このファイルでRakeImportを用いる事を明示して、その後直ぐにこのタスクを呼びます。これにより以下に示すRakefileを読み込むと、そこに書かれているタスクの全てがAntに登録されます。

task :its_in_rake => [:setup, :its_in_ant]  do
puts "it's in Rake"
end

task :setup do
puts "setup in Rake"
end


ここでant top-levelを呼ぶと、以下のような結果が画面に出ます。

Buildfile: build.xml
[rakeimport] (in /Users/enebo/work/akakamiari/samples/rake_import_example2)

setup:
setup in Rake

its_in_ant:
[echo] ant: its_in_ant

its_in_rake:
it's in Rake

top-level:

BUILD SUCCESSFUL
Total time: 7 seconds


これを見ると、AntのターゲットとRakeのタスクの両方が好ましい順序で実行されているのが解ります。its_in_antはits_in_rakeの従属性として実行され、そしてits_in_rakeはAntのターゲットであるtop-levelの従属性として実行されているという具合です。

このレベルでの統合におけるシナリオは

1. 最適のツールを選べる

Rakeは命令型プログラミング環境を与えてくれますから、Antでは煩わしい(または自分でAntタスクをカスタマイズしなければ不可能な)事が非常に簡単に出来る事があります。そうしたものはRakeに移管しつつ、Antは残りの物に使う事が出来ます。

2. Rakeに完全に移行する

前の節で、開発チームにRakeの有用性を示す為にRakeタスクを使いました。これにより、Antの依存グラフにRakeの機能を頼みとするタスクを滑り込ませていく事が出来ているでしょう。あなたのグループは主なビルドのツールとしてAntを使っているのでしょうが、Rakeに任せる部分が大きくなってきています。

RakeからAntを呼べるようにする



ではここで反対側から見てみましょう。もしもあなたがすでにRakeのユーザであって、既存のAntファイルと拘わらなければならないならば、ここに良い解決策が有ります!先ず第一の方法ではAntをRakeから直接呼び出せます。

task :call_ant do
ant '-f my_build.xml my_target1'
end


もう一つの方法では引数を渡す事も出来ます。

args = ['-f', 'my_build.xml', 'my_target1']
task :call_ant do
ant args
end


信じられないかも知れませんが、ここでは`ant`を実行しているだけではありません。antは実際にJRubyの環境内部へと読み込まれているのです。JVMをもう一つ起動させる手間が要らないのが素晴らしいです。

この節の始めに書いた通り、Antを呼び出すだけでいいとあなたが思うのならばそれでも構いません。でももっと深く拘わりたいのなら…

AntのターゲットをRakeのタスクとして呼べるようにする



ant_importは、Rankeの依存管理システムにAntの最上層のターゲットを登録するという点に於いて、antに優っています。一度AntのファイルをRakeにインポートしてしまえば、Antのタスクを単なるRakeのタスクであるかのように呼び出す事が出来ます。簡単な例を挙げます。

task :ant_import do
ant_import
end

task :compile => [:ant_import, :its_in_ant_setup] do
# Do some compilation
end


この例ではcompileというRakeのタスクが実行されるとant_importというタスクを読み込み、それが今度はディレクトリにあるbuild.xmlを読み込み、そしてAntのターゲット全てを読み込んでいます。ant_importを最上部で使わないのは、Antを使う必要がない時にはAntのターゲットを読み込むような無駄を避ける為です。

細かい所を…



このコードはJRubyのトランクにコミットされたばかりです。この記事に書いてある例は全て動く筈ですが、何しろ新しいのでJRuby 1.5がリリースされるまでは多少の仕様変更があるかも知れません。ですから以下の二点を心に留めておいて下さい。

1. バグを発見して、このライブラリを改善する手助けをして下さい

もしこのライブラリに興味があるのなら、早いうちからプロジェクトに関わって、問題点を正したりライブラリを向上させる事が出来ます。この統合が凄いものになるか、ただ「良い」ものになるかはあなたの参加に掛かっているのかも知れません。

始める際の注意点を挙げます。現在の仕様ではjruby-complete.jarを$ANT_HOME/libにコピーするのが最善だと思います。jruby-complete.jarを含むようにクラスパスを設定する事でも可能ですが、コピーした方が手っ取り早く、間違える危険を低く抑える事が出来るでしょう。

2. 作りかけのソフトウェアを好まないのならJRuby 1.5をリリースを待って下さい

作りかけのソフトウェアを煩わしく思ったり、いじくったりする時間のない人たちも少なくありません。そうだとしても、ご心配は無用。1.5リリースまでには上手く動くようにします。中途半端な物をリリースして後味が悪くなったりしないように努力します。

最後に


始めにミスターポテトヘッドの話をしたのは、色々な部品を組み合わせるのが時として最善の策だという事を示す為でした。理論的には、一からやり直して一貫性のある純粋な物を造り上げる事が出来れば良いのでしょう。でも実際的には、実生活での細かい事が常に邪魔をするもの。そんな時、全部書き直してしまうよりは、新しい技術を噛み合わせるという手があります。古い物を長期的な視野で置換するのなら、少しずつ変えていくのも良いでしょう。私が過去に携わった大規模な書換えプロジェクトは悉く失敗しました。部品を一つ一つ変えていくほうが成功の確率は高いのです。

このRakeとAntとを統合するプロジェクトは、こうした小規模な変更を(或いは、二つのツールのそれぞれの良い部分を掻い摘んで使う事を)可能にします。また、この二つのツールはどちらも欠く事が出来ないのかも知れない、或いは、二つのツールのうちの一つをもう一つで完全に置き換えてしまうのは100%良い解決策ではないのかも知れない、という現実を具現化しています。

質問、コメントはいつも通り大歓迎します。

3 件のコメント:

  1. 「十人十色ですね。」の前に「変数定数で」が抜けてるので、唐突に見えました。あと、jruby-comple.jar → jruby-complete.jarですね。

    返信削除
  2. http://twitter.com/nahi/status/9518085673 を転載: 「この記事の「ジャガイモ」はソフトウェア」だと、末尾の組み合わせ話とつながらないような。「ビルド」が抜けてそう。続く「the 800 pound potato of build tools.」もゴリラ?

    返信削除
  3. NaHiさん、コメント有り難うございます。直しました。

    返信削除