Common Lisp クックブックもぼちぼちやってます。
2011-01-02
■[OMake] しばらく取り組んだ感想
去年は OMake を本格的に使い始めました。もしかすると今年からは多少離れるかもしれませんが、しばらく取り組んだ上での感想をまとめておきます。
パフォーマンス
遅いと感じることはありません。 OCaml で実装されているんで起動も実行も速いです。
ソースコードが数百万行に達するような大規模なプロジェクトには縁がないので、大量のソースコードと OMakefile を相手にするときのパフォーマンスはわかりません。聞いた話ではそれなりに早いようですが、動的に生成していた依存関係を静的に直す (直接依存関係を書く) など、パフォーマンスに気を使っているようです。
とっつきやすいがなじみにくい文法 (と言語仕様)
OMakefile は make とシェルスクリプトを足したような文法です。ぱっと見て「あ、これならわかるかも」と思うと思います、最初は。しかしねえこれ、やってみると第一印象ほど楽じゃありません。 make を知っているなら、次の make のコードが何をするのか想像つくでしょう。もちろん OMake でもまったく同じコードを書けます。
$(PROG): $(OBJS) ... コマンドつらつら
PROG と OBJS はマクロで、マクロが展開されたコードがルールとして make に認識されます。 Makefile で使われるデータは文字列だけ、他の文法はルール定義くらいですから簡単ですね。でも OMake はこれ自体一つのプログラミング言語で、オブジェクト指向とか匿名関数とか遅延評価とか、なかなかの記述力があります。オブジェクト指向と言うからには、 OMakefile 中に現れるデータは文字列だけではないわけです。例えばファイルを開いて書き込むなんて操作もできます。以下。
f = $(fopen myname, w) myname = John Marston write($(f), $(myname)) close($(f))
fopen, write, close は関数です。関数ですが $() で囲んだり囲まなかったりします。いつ囲むかは置いといて、最初の fopen でファイルを開いて、ファイルオブジェクトを変数 f に代入します。変数の参照は必ず $() で囲むので、 write, close では $(f) でファイルオブジェクトを参照します。さて、この前のルール定義のコードと見比べて、どういう順序で $() が評価されるのかわかりますか? make ではこんなコードも通りますよ?
OBJ = a.o: $(OBJ) a.c
いつ変数に代入されて、いつ $() が展開されて、いつ関数が評価されるのか?いえ、 OMake の仕様は決していい加減ではありません。ただ、 make とは違うだけです。でも、 make と同じ見た目です。結構質の悪いギャップです。
わざと make のマクロを例に出してひっかけようとしましたが、実は OMake の $() はマクロではなく変数参照です。基本的に構文解析後に上から順に実行されるため、上の make の例のように変数に構文自体を含めることはできません。 OMake ではエラーになります。
案外充実しているプログラミング言語、でも...
オブジェクト指向とか匿名関数とか遅延評価とか、 OMake はプログラミング言語としてなかなかの記述力があると書きました。確かにこの記述力のおかげで、新しい言語やツールに対応させられます。...でもねえ、最近はどの言語にも専用のビルドツールがあるよね。 Python なら setup.py 、 Ruby なら Rake 、 Java なら Ant なり Maven なり Eclipse なり、 Erlang なら rebar など。そりゃーそれぞれビルド対象言語で設計・実装されてんだから、 OMake でなんとか対応するよりそっち使ったほうが楽よね。 OMake はそれなりに記述力があるけど、記述しやすいかというとそうでもありません。それに OMake のユーザ数より各言語のユーザ数のほうが圧倒的に多いから、 OMake 向けのモジュールを実装・保守してくれるユーザ数なんて微々たるもの。だいたい OMake に相当習熟しないといけないわけですよ。
それでも OMake でなんとかしようとする場合、まず OMake でビルドするために、新しい言語用のルールを動的に定義する関数が要ります。これは少し慣れればそう難しくはありません。ただ OMake の恩恵を受けるにはファイル間の依存関係を把握できなければいけないので、依存関係を調べるスキャナも要ります。スキャナは次のようなコードを書きます。
.SCANNER: $(src_path) section deps[] = awk($<) case $'^import' deps[] += $(PackageToPath $2)$(JAVA_CLASS_EXT) export deps = $(find-in-path-optional $(JAVACLASSPATH), $(set $(deps))) # 標準出力に依存関係を出力する println($"$@: $(deps)")
これは Java のソースの依存関係を調べるスキャナで、私的にちょろっと書いてみたものです。 import 文しかチェックしないので不完全も不完全、 Java (に限らず最近の言語) は名前空間がありますから、パターンマッチでハイ終了とはいきません。 C のように #include のある部分をチェックする程度なら簡単なんですが。
それに加えて、スキャナで調べた依存関係を「標準出力に出力」して OMake に知らせなければならないのが困っちゃいます。おかげでスキャナでは print デバッグができません。標準エラー出力に出力してもだめ。ファイルに出力してデバッグしましたが、何か他にやりやすい方法あるのかなあ。
強力な依存関係の分析
make と同様に、サブディレクトリごとに OMakefile を置く慣習があります。ただし make ではサブディレクトリの Makefile との依存関係は断絶していますが (cd subdir; make all などとして再帰的に make を実行しますね) 、 OMake では正確に依存関係を構築できます。 -P オプションで継続監視ビルドを行えば、どんな複雑なディレクトリ構造だろうが、ファイルの変更が瞬時に再ビルドとして反映されます。楽です。便利です。
ただし翻せば、ディレクトリにまたがる依存関係を正確に作らなければならないということでもあります。これがまた OMake の仕様をよく知らないと面倒で、 make のつもりで使っていると思わぬ落とし穴にはまる可能性があります。 make では (依存関係の解析が不十分になる分) ルール内で make を呼ぶ使い方はよくあることで、「こまけぇこたぁいいんだよ!」よろしく実行順序を強引に指定できます。 OMake ではハリウッドの法則よろしく "Don't fire rules. We fire rules." とでも言いますか、我々が勝手にルールを実行したり再帰的な OMake の呼び出しは許されません (※呼ぶには呼べます) 。とにかくさっさと強引に進めたいときにも完全な依存関係を考えなければならず、まどろっこしいかもしれません。
C 用の関数がいまいち?
デフォルトの C 用ビルド関数には、どうもいまいちカスタマイズできない部分があります。わかりやすいものだと yacc, lex のフラグ用の変数 YACCFLAGS, LEXFLAGS がありません。一応 YACC, LEX にコマンドのパスとフラグを一緒に入れておくことはできます。もう一つは説明が面倒臭いんで省きますが、言語バインディングなどの凝った処理をする場合に困るかもしれない部分があります。まあオリジナルのソースを元に自分で C 用のビルド関数を書き直せばいい話ですが、ビルドツールにそこまで労力を割かなきゃならないのもねえ。
まとめ
長々と愚痴ってきましたが、 OMake は make の代替として非常に便利で楽です。ただし必要な関数が揃った環境に限ります。 C には前述の問題がありますから、今のところ何の苦もなく OMake で扱える言語は OCaml くらいではないでしょうか OCaml でもいろいろがんばらないといけないらしいです (C++ は未評価) 。これ以外の言語に対応させようとすると、急に難しくなります。難しいというか、ドキュメント読んでもよくわかんねーというか。いくらプログラミングができても、これなら Python で書ける SCons のほうがましだと思わなくもない。
まとめがてら「入門 OMake」を書き始めましたけど、 OMake の複雑さに紹介する気力が削られ中。疲れた。
■[料理] シュー・ア・ラ・クレーム
=カスタードシュークリーム。シュー生地も結構な理屈の塊のようで、いかに (小麦粉に含まれる) でんぷんを糊化させるか、がポイントらしい。クレーム・パティシエールも含めて鍋やらボウルやらをガコンガコンと勢いよくかき混ぜ続けたもんで、汗かいた。
シュー生地を絞り出したところ。ちょっと間隔を詰め過ぎた。各生地は直径3cmくらい。
焼き上がったところ。素晴らしい。これまでは失敗続きだったけど、やっぱり理屈を知ってから作るのではだいぶ違うなあ。つやが出ているのはつや出しの漉した溶き卵を塗ったから。それとフォークで軽く格子状の模様をつけたけど、どう成果が出てるのかよくわからん。
今回の生地は固めで、パイシューくらいの固さ。フランスでは固めが基本のようで、配合が固めになってた。フランス行ったことないけど。今回は焼き上がったらオーブンから出して冷ました。ふわふわやわらか生地にするならば、あまり蒸気を逃さないようにオープンに入れたまま冷ますのかもしれない。焼成途中で蒸気を逃がすのを忘れた (これは好みで、忘れても問題ない) 。
近影。
縦の断面。空洞ができていて、きちんと膨らんでる...と思う。
パットで冷やして固まったクレーム・パティシエール。ここから木べらでガコンガコンかき混ぜてなめらかな状態に戻す。黒い粒はバニラの種。写真が暗かったので明るさだけ加工した。
戻した状態。写真が (ry
ふたを切ってクレーム・パティシエールを詰めたもの。シューが小さめだったから、横に穴を開けてクリームを詰めるようにはしなかった。写真が (ry
全部クリームを詰めたところ。あれ、こうして見るとあまり詰め具合がきれいじゃないな。でも多めに詰めないとシュークリームを食べている感じがしなかった。写真が (ry
シュー生地のでんぷんが完全に糊化した状態、クレーム・パティシエールのでんぷんに完全に火が通った状態がどのくらいなのか、まだいまいちピンとこないな。今回はうまくいったけど、次回はわからない。










もっとコメントを書こうかと思いましたが上のシューの写真が邪魔をします!じゅるり。