http://code.google.com/p/waf/wafというものを最近知り一目惚れしてしまったので、紹介記事を書きます。ユーザーが増えると嬉しいな。wafとは何か?特徴・利点・使うべき理由wafはPythonベースのビルドシステムです。同様のことを行うツールとして、Autotools、Scons、CMake、Antなどがあります。Sconsからの派生で、比較的新しいソフトウェアです。分かりやすいPythonで書かれており、スクリプトもPythonで記述します。シェルスクリプトと謎のマクロが入り混じるAutotoolsや、独自言語のCMakeなどに比べて扱い易いです。Pythonを知っていれば非常にすんなりと使いこなすことが出来ます。Pythonを知らなくても、他の独自言語を覚えるよりは実りがあるかと思います。Pythonで記述しますので、自分で機能を拡張することも非常に簡単にできます。このあたりはAutotoolsに苦しめられた経験のある方なら最も有力な乗り換え理由になると思います。配布しやすいPythonで書かれているので、Pythonのインストールされているシステムでならどこでも動作します。幅広いバージョンのPythonで動作します。今やPythonはほとんどのLinuxにデフォルトでインストールされていますし、Windows、MacなどでもPythonが入っていれば動作させることができます。wafは80KB程度の単一のPythonスクリプトで、BSDライセンスなので、プロジェクトに含めて配布することが容易です。wafを同梱して配布することによりAutotoolsなどでは起こりがちなバージョン問題が起こりません(Autotoolsではconfigureで配布するとは思いますが)。簡単に対応することができます。高速に動作するconfigureが非常に高速に行われます。ccacheのようなオブジェクトキャッシュ、並列コンパイルの標準サポートにより、コンパイルも高速です。多くの言語の組み込みサポートC、C++、D、Java、OCaml、などのプログラミング言語のコンパイル、依存解析、実行がサポートされています。新しい言語のサポートも比較的容易です。出力が分かりやすい出力がカラーで、失敗すると赤く表示されるので、コンパイル結果がわかりやすいです。コンパイルすべきファイルがいくつ有って、現在何個目のファイルをコンパイルしているかが表示されるので、コンパイルの進捗状況がわかりやすいです。コンパイルの進捗をプログレスバーで表示する機能もあって、見ていて楽しいです。uninstallパッケージ作成ユニットテスト支援ディレクトリ監視による自動再コンパイルなどが標準でサポートされています。一通り揃っていると思います。ビルドフェーズを追加することも容易です。インストールwafをインストールするのは以下の理由から推奨されません。adminが要る・めんどいバージョン問題wafを同梱しない理由がないWindowsにはインストールできないwafスクリプトをプロジェクトに含めてしまうのが一般的です。http://code.google.com/p/waf/wafのページから、最新版のwafをダウンロードして使います。distcleanwafを使うにはwscriptというファイルを書く必要があるので、今のところはエラーが出ます。wafは実行の際に自身をカレントディレクトリに展開するので、書き込み可能なディレクトリで実行しなければならないことに注意してください。プロジェクト作成の度にwafをダウンロードしたくない、実行の際に./を付けるのが嫌など、些細な点が気になるならば、上記デメリットを理解した上でインストールすることも可能です。あたりが参考になると思います。wafは単一スクリプトだけでなく、tar.bz2版も配布されています。これには、いろいろなサンプルプロジェクトや、Autotoolsからの移行ツールや、bash-completion、wafのソースなどが含まれていますので、wafを使っていこうという場合には一度確認されることをおすすめします。wscriptの基本的な書き方wafはwscriptというファイルに、ビルドに必要な情報を書きこみます。これは普通のPythonプログラムとして記述します。以下に雛形を示します。passまず、変数を四つ定義します。APPNAMEとVERSIONはプログラムの名前とバージョンを指定します。srcdirとblddirはソースの場所と、コンパイル時の一時ファイルを置くディレクトリを指定します。これらの変数は省略すると、それぞれ'noname'、'1.0'、'.'、'build'が使われます。このままでは実行しても何も起こらないので、次のように書いてみます。(0.003s)shutdownconfigure、build実行時に、それぞれ対応する関数が呼ばれているのが分かります。ここにそれぞれ必要なことを書いていくことになります。C/C++プログラムのコンパイルC++プログラムをコンパイルする例を示します。'main')まず、set_optionsで、C++コンパイラ用のオプションを使えるようにします。'compiler_cxx'はC++コンパイラ用の設定です。Cコンパイラを使うなら、'compiler_cc'を指定します。次に、configureでC++コンパイラのチェックをします。最後にbuildにビルドルールを書きます。featuresには、プログラムをどうやってコンパイルするかを書きます。ここでは、cxxとcprogramを指定しています。'cprogram']のように文字列のリストを渡してもいいです。これは以降出てくる文字列を複数している所でもすべて共通の仕様です。cxxは、C++のコンパイラでコンパイルせよと言う指定、cprogramは実行ファイルを作れと言う指定です。他にcshlib、cstaticlibなどがあります。sourceにはソースファイルを指定します。targetには生成する実行ファイルの名前を指定します。ビルドを実行すると次のようになります。world!configureで、C++コンパイラの存在がチェックされ、buildでmain.cppがコンパイルされ、build/default/mainに実行ファイルがリンクされます。ビルドの際に生成されるものはすべてbuild(blddirで指定したディレクトリ)以下に置かれます。buildのときのbuildは省略できるので、単にwafと実行すればbuildできます。ファイル複数からなるプログラムはsourceに複数ファイルを指定すればできます。'.')includesにヘッダファイルのあるディレクトリを指定します。これを書けば、そこに含まれるヘッダの依存関係を自動で解析してくれます。(0.408s)必要なファイルだけ再コンパイルされているのが分かります。ファイルの再コンパイルはmd5の比較により行われるので、内容が本当に変更されていないと再コンパイルは行われません。ライブラリの作り方ライブラリを作る場合は、featuresにcprogramの代わりにcshlibまたはcstaticlibと書くだけです。先程の例を、foo.cppをライブラリに、main.cppをそれを使うプログラムに変更してみます。'foo')featuresにcstaticlibを指定しました。foo.cppからfooという静的ライブラリを作れという指示です。mainは今度はそのfooというライブラリをリンクしなければなりません。その指定をuselib_localというところに書きます。同じプロジェクトで作られるライブラリ(ローカルなライブラリ)への参照はuselib_localに書きます。(0.426s)featuresにcstaticlibを指定したので、foo.cppがコンパイルされ、staticライブラリlibfoo.aが生成されました(targetで指定した名前にlibが付いたものが生成されますのでlibをtargetにlibをつけてはいけません。liblibfoo.aになってしまいます)。それからmain.cppとlibfoo.aがリンクされ、mainが生成されます。ここで、mainを生成する前にlibfoo.aが生成されていなければなりませんが、このビルド順は、ビルドターゲット間の依存関係から自動的に解決されます。書いてある場所の前後は関係ありません。サブディレクトリ(後述)を含む場合も全体を考慮して解決されます。-j2などとすると並列コンパイルができますが、それも依存関係に基づきます。依存関係が循環している場合、wafはそれを検知できません。エラーメッセージではなく、再帰がスタックオーバーフローしてwafが落ちます。依存関係はきちんとツリー(もしくは森)になるようにしましょう。ちなみに、wafはコマンドを並べて書くと、上のようにそれらを順に実行します。featuresにcshlibを指定すると、動的ライブラリが生成されます。その際も同様にuselib_localでリンクできます。(0.507s)サブディレクトリ先程の例を、libfooをfooというディレクトリに移動させようと思います。サブモジュールを分割するのはプロジェクトをシンプルに保つために重要です。libfooを作る方法は独立していた方が良いので、wscriptも分割します。ディレクトリ構成としては次のようになります。wscriptルートのwscriptから子のwscriptを呼び出すには次のようにします。書いてある部分がfoo/wscriptを呼び出す部分です。すると、foo/wscriptにある同じ関数が呼ばれます。この場合だとbuildなのでbuildが呼ばれます。configureからrecurseすることも可能です。recurseにはディレクトリのリストを渡せます。サブディレクトリのwscriptには、呼び出されない関数は書かなくても構いません。configureをrecurseできるので、各サブモジュールに必要なライブラリチェックを分散させることができます。Autotoolsのようにconfigure.acが肥大化したり、チェックの場所と使用の場所が離れたりすることが防げます。実行すると次のようになります。(0.535s)ライブラリ、ヘッダファイルのチェックC/C++のライブラリ、ヘッダファイルのチェックはcheck_ccまたは、check_cxxによって行うことができます。これらはconfigure時に行うので、configure()に記述します。一通りの例を以下に示します。stdio.hと関数printfの存在確認。コード片がコンパイルできるか調べる。conf.env.store('conf.log')check_cxxはC++コンパイラを用いてコンパイルを試みます。同様にcheck_ccはCコンパイラを用いてコンパイルを試みます。それぞれ、使用するためには、set_optionsでopt.tool_options('compiler_cxx')、opt.tool_options('compiler_cc')されている必要があります。libにライブラリ名を指定すると、そのライブラリがリンクできるかどうかを調べます。その際にlibpathでライブラリを探す場所を指定できます。header_nameにヘッダファイル名を指定すると、そのファイルをインクルードできるか調べます。function_nameに関数名を指定すると、関数の存在を調べられます。このときライブラリ、ヘッダファイルがそれぞれ存在するとconf.env.HAVE_TIME_H、conf.env.HAVE_PRINTFなどが1に設定されます。madantoryを指定すると、そのチェックを必須にできます。これを指定したチェックが失敗するとconfigureがこけます。check_cxxはboolを返すので、Pythonのif文などで簡単に利用できます。コード片のコードテストや、コード片の出力をconf.envに設定できます。上の例ですと、LONG_SIZEに"8"が設定されます。wafにはuselibという仕組みがあります。詳しくは後述のライブラリの参照で述べますが、ここでは、ここに指定した文字列を接尾辞に、いろいろな変数が定義されます。ここでは、LIB_M、CXXDEFINES_M、CXXFLAGS_M、が定義されます。write_config_header()を呼び出すと、集めた情報をC/C++のヘッダファイルに書きだします。上のコードなら、次のコードが生成されます。*/conf.env.store()を呼び出すと、環境を書きだすことができます。これはwafコードのデバッグにとても便利です。configureの実行結果を以下に載せておきます。(1.480s)pkg-configpkg-config、あるいは、hoge-configのような、Cのコンパイラフラグ、リンクフラグ、ライブラリパスなどを取得するためのプログラムを提供しているライブラリがあります。このようなライブラリがあるばあい、とても便利にチェックを行うことができます。uselib_store='SDL')pkg-configの場合、check_cfgで、packageにパッケージ名を渡せば、バージョンのチェックなどができます。独自のconfigプログラムの場合は、pathに指定してやります(この場合はバージョンチェックなどはできない模様)。ここで、uselib_storeを指定して、argsにコンパイラフラグを取得するための引数を書いてやると、check_cfgはその返り値を解析して、-IxxxならCPPPATH_hogeに、-DxxxならCXXDEFINES_hogeに、-LxxxならLIBPATH_hogeに、-lxxxならLIB_hogeに、その他はCXXFLAGS_hogeなどに、自動的に振り分けて追加されます。ライブラリの参照bldの引数で、ライブラリの参照を追加できます。pthreadをリンクするなら、次のようになります。['pthread'])ライブラリパスを指定したいなら、libpathを書きます。['/usr/local/lib'])その他、いろいろオプションを指定できます。['-g'])uselibuselibを用いると、上記のいろいろなコンパイルの設定を一気に設定できます。'SDL')bldの引数にuselibを指定してやると、その文字列を接尾辞とする設定がまとめてなされます。LIB_SDLがlibに、LIBPATH_SDLがlibpathに、CXXFLAGS_SDLがcxxflagsに設定されます。check_cfgもしくはcheck_cxxでのuselib_storeとuselibを組み合わせて使うと、コンパイル・リンクフラグの設定を大変簡単に行うことができます。オプションconfigure時のオプションを自由に追加することができます。プロジェクトの特定のモジュールの有効・無効を切り替えたりなどが典型的な利用例です。set_options()でadd_optionすることによりオプションの追加ができます。variants...ユニットテストwafはユニットテスト支援機能があります。先のプロジェクトで、libfooのユニットテストを作成することにします。bld.add_post_fun(UnitTest.summary)まず、set_optionsでtool_optionsにUnitTestを追加します。次に、テストプログラムを作ります。これはfeaturesにtestを追加するだけです。testフィーチャーがついているプログラムは、コンパイル後に自動で実行され、その結果が集計されるようになります。それから、bld.add_post_funでUnitTest.summaryが最後に実行されるようにします。これでテストの結果が表示されるようになります。123+123);}テストプログラムを書きます。今回はgtestを使いました。fooは二乗を返す関数だったので、これはfailするはずです。TESTコンパイルされた後、テストが実行されているのが分かります。失敗したテストがFAILと表示されています(これは実際には赤で表示されるので、失敗したことが分かりやすい)。wafはテストをコンパイルの後に実行します。コンパイルが起こらないとテストは実行されません。buildを行ってもテストは実行されません。オプションを使うと、ビルドされなかったテストを含む、全てのテストを実行することができます。先程のバグが分かったので、修正して再実行します。(0.779s)今度はokになりました(okは緑で表示されます)。通ったテストの出力は表示されません。すると、ビルドしたものをインストールできます。`/home/hideyuki/project/waf_test/build'テストでない実行ファイル、動的ライブラリは自動的にインストール対象になります。特定のファイル(例えばヘッダファイル)をインストールさせたい場合、bld.install_files()を使います。'libfoo.so.1.2.3')install_filesの第一引数には、インストールするディレクトリを指定します。文字列中の${PREFIX}は、bld.env.PREFIXで置換されます。PREFIXでなくてもbld.envにある変数なら何でも展開できます。bld.env.PREFIXには、configure時に--prefix=で指定したディレクトリが入っています。指定しなかった場合は/usr/localになっています。relative_trickを指定すると、ディレクトリ構造を保持してインストールされます。その他、別名でのインストールや、シンボリックリンク作成もできます。パッケージ作成さて、libfooもおおよそ完成したので、パッケージを作って配布しましょう。distとすると、tarballが作成されます。test-project-1.0.0/test-project-1.0.0/waftest-project-1.0.0/main.cpptest-project-1.0.0/foo/test-project-1.0.0/foo/foo.cpptest-project-1.0.0/foo/wscripttest-project-1.0.0/foo/foo.htest-project-1.0.0/wscripttest-project-1.0.0/foo_test.cpptest-project-1.0.0.tar.bz2というファイルが出来ています。デフォルトでは、bz2圧縮されます。圧縮方式を変更するには次のようにします。'gz'ところで、tarballに含めるファイルを指定した覚えはありませんでした。やってwafはtarballに含めるべきファイルを判断しているのでしょうか?wafは基本的に、ディレクトリの内容をすべてtarballに含めます。そこから、特定の接尾辞を持つファイル・ディレクトリと、特定の名前のファイル・ディレクトリを除外します。config.log'のようです。blddirは除外されます。dist_hook()という関数を作れば、distで含めるファイルをカスタマイズすることができます。dist_hook()は、tarballに含めるファイルをテンポラリディレクトリにまとめて、圧縮する直前に、そこのディレクトリがカレントディレクトリになった状態で呼ばれます。main.cppを含めたくなければこうします。test-project-1.0.0.tar.gztest-project-1.0.0/test-project-1.0.0/waftest-project-1.0.0/foo/test-project-1.0.0/foo/foo.cpptest-project-1.0.0/foo/wscripttest-project-1.0.0/foo/foo.htest-project-1.0.0/wscripttest-project-1.0.0/foo_test.cppまた、除外ルールに追加することもできます。['.cpp'].cppファイルが含まれなくなります。test-project-1.0.0.tar.gztest-project-1.0.0/test-project-1.0.0/waftest-project-1.0.0/foo/test-project-1.0.0/foo/wscripttest-project-1.0.0/foo/foo.htest-project-1.0.0/wscripttarballに含めたくないファイルをディレクトリ内に置きたい場合、特定の拡張子にして、その拡張子を除外ルールに追加するか、特定のディレクトリを除外にしてそこにまとめて置くか、あるいはdist_hook()を書いて独自の除去ルーチンを書けばなんでもできます。結びさて、wafいかがだったでしょうか。普通のPythonプログラムとしてビルドスクリプトを記述するのでとても見通しが良いのではないかと思います。どこに何が書けるのか、何を書いたらいいのかほとんど迷うことがありません。wafはコンパクトでありながら、ツボを抑えた機能をサポートしているので、普段使いではあれがないと困ることはほとんどないと思います。wafはflexibleと謳っているだけあって、機能を独自に拡張することが非常に容易にできるように設計されているので、最悪書けばなんとかなります。今回はチュートリアルということで、wafのすべてを紹介できているわけではありません。wafを知りたい、wafを是非使ってみたいという方は、下記のリンクを参照されると良いでしょう。これをWindowsで使い...2004-08-2223個の道は遥かどうも色々と考え違いが出てくるなぁ。(その2)前回の続き。今日はコードがひときわ多い。writeup月別アーカイブはてなブログをはじめよう!tanakhさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。