Rake ってのは Ruby で書かれた単純なビルド・プログラム。ぶっちゃけいえ ば make のパクリ。
『make のパクリ』ってだけじゃピンと来ない? ふーん。そういう人もいるん だね、世の中。じゃあ、ちょっと説明しとく。実際にブツ見てもらったほうが 早そうだから、いきなり Makefile を見せる: makefile-1.make make で使うファイルはフツーは "Makefile" っていう名前にするんだけど。 ここじゃオトナの事情で "makefile-1.make" ってなってる。もし、これが "Makefile" っていう名前だったら、そのファイルが置かれてるディレクトリ で "make" ってコマンドを実行すると、"Makefile" の中身が読み込まれるこ とになってる。今はオトナの事情で "Makefile" じゃないから、わざわざそれ を教えてやんないといけない。すると: $ make -f makefile-1.make echo hello hello みたいに表示される。 これだけ見ると、sh に毛が生えた程度なんだけどね。さすがにもうちょっと ビルドに特化した機能を持ってる。make でピンと来ないくらいだから、『ビ ルド』もチンプンカンプンだろうね。『ビルド』っていうのは、コンパイルと かインストールとかドキュメント生成とかそういうことにまつわる事柄ってこ と。オレも今わかったんだけど、結構曖昧なんだよね。 とはいっても、ビルドの主役はコンパイル。それも C 言語で書かれたソース・ ファイルのコンパイルね。これも例を見てもらったほうが早そう: makefile-2.make hello.c main.c で、同じように実行してみる: $ make -f makefile-2.make gcc -Wall -c -o hello.o hello.c gcc -Wall -c -o main.o main.c gcc -o makesample -Wall hello.o main.o どう? 何かいろいろやってそうでしょ? まぁ、正直、こんなチンケな例じゃ: $ gcc -o makesample -Wall hello.c main.c ってやっちゃったほうが早いんだけどね。そこは、あくまでも例ってヤツだか ら。 だから、結局のところビルドっていうのは、ソース・ファイルを加工すること で、make っていうのはその作業を自動化するツールっていうことでいいんじゃ ない? その加工っていうのはコンパイルが多いんだけど、他にもいろいろあ ると。
オレは make 捨てたくなったから rake 使いだしたんだよね。だから、make の話はもう終わり。 # Ant もねぇ、あんまし便利じゃない。やっぱね、XML がね、面倒なのよね。 さて、make が "Makefile" っていう名前のファイルをデフォにしてたみたい に、rake でも "Rakefile" っていう名前のファイルがデフォになってる。も ちろん、ここでもオトナの事情は有効なわけです。いきなりその Rakefile を 見てもらう: rake-1.rake rake の場合も同じように実行する: $ rake -f rake-1.rake (in /home/mcd/doc/rake) hello, world くどいようだけど、"Rakefile" っていう名前にしとけば、"rake" ってタイプ するだけでいい。 ほんじゃ、いよいよ Rakefile の中身の説明に入ってくぜ。1 行目は問題ない だろ。いわゆる shebang というヤツ。あ、いっとくけど、rake では Ruby の コメントは有効だから。っていうか、ほとんど Ruby と変わらないと思ってく れていい。カッチョよくいえば言語内 DSL ってヤツなんだけど、知らなかっ たらこれは忘れてくれていい。 最初にやんなきゃいけないのは、デフォルトのタスクの指定。まぁ、絶対やん んなきゃいけないわけじゃないんだけど、これがないと rake を実行するとき にわざわざタスクを指定しないといけない。そんな面倒はヤだから、ここでやっ とく。 『タスクってなぁに?』グッドクゥェスチョン! でも、Rakefile 見れば何と なくわかるでしょ? 上では world っていうタスクが定義されてる。それでもっ て、その world っていうのがデフォルトのタスクになってる。 で、world タスクの中身はというと、"hello, world" を印字することなわけ。 タスクをわかりやすくするために、タスクをもう 1 つ増やしてみよう: rake-2.rake これで、world と universe っていう 2 つのタスクができた。でも、デフォ のタスクは world のまま。だから: $ rake -f rake-2.rake (in /home/mcd/doc/rake) hello, world ってやっても何も変わらないアル。ところがところが、ここで universe を指 定すると、アラ不思議: $ rake -f rake-2.rake universe (in /home/mcd/doc/rake) hello, universe ホレこのとおり。"hello, universe" と印字されたアル。 これでタスクは理解できたと思う。どんなことかをクチで説明できるよりも、 肌で理解できてればそれで OK よ! しょせんこの世界、コードでしか語れな いんだから。なんつってな。
タスクの中身のことをアクションという。見てのとおり、アクションの中身は Ruby のスクリプト。そもそも、Rake 自体が Ruby の言語内 DSL だから、フ ツーに Ruby のスクリプトが書ける: rake-3.rake この柔軟性が Rake のウリでもある。前に『ビルドの定義は曖昧だ』って書い たけど、そんだけビルドでやんなきゃいけないことは多様だってこと。コンパ イルだけじゃないし、インストールとか配備 (デプロイ、deployment) とか、 ドキュメントの生成とかもある。そうなると、なかなか『型にハメる』っての が難しくなってくる。そうなってくると、ビルド・ツールが柔軟でないと使い にくくなる。そこで Rake は Ruby の柔軟性をできるだけ活かせるようになっ てるというわけ。 もちろん、柔軟性にはトレードオフがある。Rakefile は Makefile を書くよ りちょっと手間がかかる。Ruby の知識もあったほうがいいしね。でも、今と なっては、そうした犠牲を払ってでも柔軟性がほしいということ。アンダスタ ン? グーッド。
ビルドっていうのはソース・ファイルを加工することだと書いたけど。その典 型がコンパイル。コンパイルっていうのは、ソース・ファイルからオブジェク ト・ファイルを吐き出すよね。これを逆に見れば、オブジェクト・ファイルを 吐き出すためにはソース・ファイルが必要だということになる。つまり、オブ ジェクト・ファイルはソース・ファイルに依存してるわけだ。 hello の例でいえば、hello を作るには hello.o と main.o がいるわけだ (もちろんキミはオトナの事情をわかっているよね?)。で、hello.o を作るに は hello.c がいるし、main.o を作るには main.c がいるわけだ。 んじゃ、そうした依存関係を Rakefile で実際に書いてみよう: rake-4.rake 前の Rakefile と比べると変わったところがいくつかあるけど。まず、デフォ ルト・タスクの指定がシンボルから文字列になったこと。タスクの名前はシン ボルでも文字列でもいいんだけど、シンボルだと表現できないものもある。た とえばピリオドを含むファイル名とかね。だから慣例として、ファイルを作る タスクの場合はタスク名を文字列にする。 で、次にタスクの定義なんだけど: task "hello" => ["hello.o", "main.o"] っていうのがある。この配列の中身が hello を作るのにいるもの、つまり hello が依存しているものってことになる。この配列の中身は、基本的にはファ イル名じゃなくってタスク名ね。たまたまタスク名とファイル名がおんなじだ ということ。 そうすると、hello タスクを実行する前に hello.o と main.o っていうタス クをやんなきゃなんないってことになる。で、その 2 つが下に書いてある。 hello.o タスクを見ると、単純に hello.c をコンパイルしてるだけ。main.o も同様。で、hello.o と main.o っていうファイルが作られる。すると、 hello タスクのとこに戻ってくる。ああ、書き忘れたけど、sh っていうのは、 system 関数とほぼ同じ。ブロック取れたりするみたいだけど。 んで、ブロック引数の "t" はタスク・オブジェクトね。それを文字列の中に 埋め込むともちろん "hello" になる。で、"prerequisites" っていう舌噛ん じまいそうなのが配列で並べた名前たち。ここでは "hello.o" と "main.o"。 これが文字列の中に埋め込まれると、名前たちが空白でつなげられる。じゃあ、 ちょっと動かしてみよっか: $ rake -f rake-4.rake (in /home/mcd/doc/rake) gcc -c -o hello.o hello.c gcc -c -o main.o main.c gcc -o hello hello.o main.o 出力をじっくり見れば、何がどう置き換わってるかわかると思う。そんなに難 しくないでしょ?
この前の Rakefile には不満な点がある。それは、rake を呼び出すたんびに オブジェクト・ファイルを全部作り直しちゃうこと。 $ rake -f rake-4.rake (in /home/mcd/doc/rake) gcc -c -o hello.o hello.c gcc -c -o main.o main.c gcc -o hello hello.o main.o $ rake -f rake-4.rake (in /home/mcd/doc/rake) gcc -c -o hello.o hello.c gcc -c -o main.o main.c gcc -o hello hello.o main.o ソース・ファイルいじってないのに、そのオブジェクト・ファイルを作り直し ちゃう。これだと分割コンパイルしてる意味なくなっちゃう。 もちろん、Rake 作ってる人もそんなことはわかっててね。そういうのにはス ペシャルなタスクが用意されてる: rake-5.rake rake-4.rake と見比べてみると、いくつか違いがある。まず 3 つの task が file に変わってる。これがスペサルなタスク、ファイル・タスクと呼ばれる もの。 で、今度は、hello.o と main.o がそれぞれ hello.c と main.c に依存して いることをコードに書いてる。でも、hello.c と main.c っていうタスクは見 当たらないよね? こういうとき、Rake はその依存物がファイル名だと見なす んだねぇ。 じゃ、動かしてみよっか: $ rm *.o hello $ rake -f rake-5.rake (in /home/mcd/doc/rake) gcc -c -o hello.o hello.c gcc -c -o main.o main.c gcc -o hello hello.o main.o $ rake -f rake-5.rake (in /home/mcd/doc/rake) ね? 作り直さないでしょ? ここで hello.c をおさわりして (イヤン)、もう 1 回試してみる: $ touch hello.c $ rake -f rake-5.rake (in /home/mcd/doc/rake) gcc -c -o hello.o hello.c gcc -o hello hello.o main.o すると、hello.c がコンパイルされ直されて、でも main.c はコンパイルされ なくて、でも hello は作り直されてる。hello.c はおさわりされたから作り 直されるのは当然。で、それに依存してる hello のほうも作り直される。で も、main.o はおさわりされてないからシカトされるというわけ。スキンシッ プ重要! でもセクハラは厳禁! ファイル・タスクっていうのは、それが依存してるものが変わってなければ何 もしないということ。これさえ押さえときゃ、まず赤点はもらわない。
マナーからルールへ! というわけで、ルールの話。
もう一度 rake-5.rake を見てもらいましょう:
rake-5.rake
ここで注目すべきは:
file "hello.o" => "hello.c" do |t|
sh "gcc -c -o #{t} #{t.prerequisites}"
end
file "main.o" => "main.c" do |t|
sh "gcc -c -o #{t} #{t.prerequisites}"
end
の部分。これでピンと来たら、あなたも達人の仲間入り!
そう DRY の原則。これをどうにかしたい。そんなときにルールを持ち出すわ
けです:
rake-6.rake
ハイ、消えたぁ (by キンキン)。
あるタスクの依存物がファイル名で、なおかつ、そのファイル名に対応するタ
スクが定義されていないとき、そのファイル名を持つファイルがルールによっ
て作成されます。っていってもよくわかんないか。
『あるタスクの依存物がファイル名で』っていうのは、hello タスクの依存関
係に並んでいる hello.o と main.o のこと。で、『そのファイル名に対応す
るタスクが定義されていない』というのは、hello.o と main.o がタスクとし
て定義されていないということ。実際、今 Rakefile にあるタスクは hello
だけだよね。で、定義されてなきゃルールが適用されるってわけだ。
で、そのルールというのは、.o という拡張子を持つファイルは、.c という拡
張子を持つファイルから生成されると。で、その生成方法が do...end の中で
書かれてるわけ。とどのつまり、*.c というソース・ファイルから *.o とい
うオブジェクト・ファイルの作り方が書かかれているわけだ。t.source って
いうのがそのソース・ファイルの名前になりそうのはすぐわかるべ。
さて、ルールが理解できたとこで、もうちょっとだけ現実に近いソース・ファ イルを扱ってみようか。 ここでヘッダ・ファイルに登場してもらう。だから、他のファイルも書き換え なきゃいけない。その結果が次: hello2.h hello2.c main2.c 見てわかるとおり、hello2.c も main2.c も hello2.h をインクルードしてい る。言い替えるなら、hello2.c も main2.c も hello2.h に依存してるわけだ。 今度はこれをビルドしてみよう。もちろん、ルールを使ってだ: rake-7.rake 実質的には下の 2 行が追加された感じ。hello2.o は hello2.c と hello2.h とに依存してる。main2.o は main2.c と hello2.h とに依存してる。 実行してみよう: $ rake -f rake-7.rake (in /home/mcd/doc/rake) gcc -c -o hello2.o hello2.c gcc -c -o main2.o main2.c gcc -o hello2 hello2.o main2.o ここで、ルールと依存関係をゴッチャにしないように。あそこで書いたルール は『.o は .c から生成されて、その生成方法はこうです』ってこと。そのルー ルとタスクの依存関係は別物。だから、hello2.o のタスクには、実際に依存 するものを書かなきゃいけない。ためしに rake-7.rake を: file "hello2.o" => ["hello2.h"] みたいに書きなおして、hello2.c をおさわりして何が起こるか見てみよう: $ touch hello2.c $ rake -f rake-7.rake (in /home/mcd/doc/rake) 何も起こらない。前みたく暗黙のタスクだったら、hello2.o と hello2.c と の依存関係を rake が解決してくれたんだけど、今は hello2.h のために明示 的にタスクを書かなきゃいけなくなった。だから、hello2.o と hello2.c と の依存関係も書いとかなきゃいけないんだね。その代わり、hello2.o は hello2.c から生成されるっていうのは、前もってルールで決めたからもう一 度書く必要はないってわけ。
ババンバ・バン・バン・バン 風呂入れよ! とカトちゃんもいうように、きれ にすることは大事なことなんだよな。そんなわけで、clean だ。 みんなは良い子だから Ruby をソースからビルドしたことがあると思う。そん なとき、『いらなくなったオブジェクト・ファイルは make clean で消したほ うがいいよ』っていわれなかったかなぁ? それとおんなじことを手軽にやっ ちまおうっていうのが CLEAN なわけだ。早速見てもらおう: rake-clean.rake しょっぱなに 'rake/clean' ってのを require してる。で、デフォルト・タ スクの次に CLEAN.include ウンチャラってのをやってる。見ればやってるこ とは大体わかるだろう。んじゃ実行してみよう: $ rm -f *.o hello $ rake -f rake-clean.rake (in /home/mcd/doc/rake) gcc -c -o hello.o hello.c gcc -c -o main.o main.c gcc -o hello hello.o main.o $ rake -f rake-clean.rake clean (in /home/mcd/doc/rake) rm -r hello.o rm -r main.o rm -r hello 3 番目のコマンド入力に注目。clean っていうタスクを指定してるよね。これ が clean の効果なわけ。clean を require して、CLEAN に消したいファイル を include すれば勝手に clean っていうタスクが追加されるわけ。 ちなみに、CLEAN が消すファイル・パターンはデフォルトだと: CLEAN = Rae::FileList["**/*~", "**/*.bak", "**/core"] ってなってるらしい。.bak が消されちゃヤバイときは注意が必要だ。
これを書いてて、rake-6.rake と rake-7.rake をいっぺんにビルドできて、 なおかつ clean で全部きれいにしたくなったんだ。それで書いてみたのが: all.rake こういうこともできるんだと軽く読み流してほしい。
そう、これでおしまいなんだ。もちろん、オチもない。あとは各自いろいろと 工夫して使ってほしい。そして、素晴らしい使い方を見つけたら、こっそり教 えてほしい。そんなところ。いざさらば。
2007-01-08
2010-03-09
Copyright © 1906 tko at jitu.org