簡易的にコンパイル型言語でプログラムを作ることがちょくちょくある。まあライブラリの性能評価とか、小さなカーネルモジュールとか、要するにCやC++で書いてソースが1ファイルで済むようなもの。
でもコンパイルオプションが面倒だったりするわけだ。Makefileとか別ファイルにコンパイル方法を書くのは面倒(要するにプログラムの規模にそぐわない手間がかかる)。しかもこのコンパイルオプションというやつ、とてもじゃないけど覚え切れないので、日をまたいだりシェルのヒストリから消えたりすると最悪です。
これまではMakefileが面倒なときはbuild.shみたいなスクリプトにコンパイルするためのスクリプトを書いていたのだけど、最近やっと画期的に便利な方法を発見した。
それはソースにコンパイル方法を書いておいて、ソースごと小さなスクリプトに食わせる、というもの。
例えばEmacsでこういうものがありますね、以下のような記述をファイルの末尾に書いておくという方法。末尾1KBしか読まないとか、^Lがあれば^Lの後ろだけ有効とか、ルールがいろいろあるようですが。
/* * Local Variables: * c-file-style: "gnu" * compile-command: "g++ -I/usr/include/python2.6 -shared -o hogehoge.so hogehoge.C -lboost_python -fPIC -lhogehoge" * End: */
まあ考え方は同じなんですが、Emacsの場合はオプションを変えたり複数のコマンドを書くのが実は面倒だったりします。オプションの行を変えてもLocal Variablesは勝手に更新されたりしないので、ファイルを再読み込みしたりといった無駄な動作が必要でした。これは使いづらい。
でも、例えば以下のようなスクリプト(compile.py)を用意して、compile-commandを"compile.py filename.C | sh -x"みたいな文字列にしておきます。これは共通の.emacsに設定しておいてもいいでしょう。これで、M-x compileやM-x recompileでは^Lで始まる行よりも後ろにあるコマンドとして通用する書式が実行されます。Python使ってますが、awkはbashでも書けるような内容です。
#! /usr/bin/python import os,sys import re def cmd_in_path(cmd, path=os.getenv("PATH")): for p in path.split(":"): if os.path.isdir(os.path.join(p, cmd)): continue if os.path.exists(os.path.join(p, cmd)): return True return False def process_file(fn): flag=False for i in file(fn): if i.startswith("\x0c"): flag=True elif flag: n=i.strip() if n.startswith("//"): n=n[2:] elif n.startswith("*"): n=n[1:] n=n.strip() if n.startswith("compile-command"): n=n.split('"')[1] cmd=n.split()[0] if cmd_in_path(cmd): print n if __name__=="__main__": for i in sys.argv[1:]: process_file(i)
この方法であれば再読み込みをしなくても変更が反映されますし、複数のコマンドも簡単に実行できる。#defineで動作を切り替えるとかの複数のバイナリを作るときや、テスト用に引数をつけて実行したい場合なんかも楽勝ですね。
しかも、人にソースファイルを渡すときのことを考えても、たとえ相手がcompile.pyを持っていない人であっても(今のところこのスクリプトは私しか持ってない)、渡した相手が話が通じる人であれば、コンパイル方法に迷うこともありません。なんて快適なんだ~!
しかしこの方法を編み出すまでに、何年かかったことやら…。どうやら僕は今まで、長い時間を無駄に過ごしてしまったようです。
C/C++が#!(shebangと呼ばれます)を理解するか、せめてコメントとして扱ってくれればもっと楽なんですけどね。