moshはout-of-tree buildできる

out-of-tree buildは、オブジェクトファイルの生成をソースツリー内部ではなく、適当なサブディレクトリに行うことを指す。
例えば、現在のmoshは次のようにしてビルドできる。

tar zxvf mosh-0.2.2.tar.gz
mkdir build
cd build
../mosh-0.2.2/configure
make

(0.2.2はSnowLeopardやMinGWをサポートしていないことに注意)
これは現代的なプロジェクトの多くが採用しているビルド習慣で(Linux,gcc,LLVM,CMakeを使うもの全て)、複雑なプログラムで無い限りはautotoolsで作ったconfigureでも自然に実装できる。
メリットは多い。

  • ソースツリーにファイルを生成しなくなるので、git cleanのような操作で対象となるファイルを減らすことができ、事故が減る。
  • 一箇所のツリーを更新すれば、configureしたツリー全てで変更が有効になる(しかし、この特徴はmoshでは当てはまらない)
  • 一つのソースツリーを複数のアーキテクチャでconfigureできる。個人的にはmoshを非常に多くのOS/CPUで使っているので、いままではアーキテクチャの数(7つ!)だけgitツリーをcheckoutしていた。。これは不要になる。
  • make distcleanを行わずに、生成ファイルをツリーから消去することができる。mosh(のGC-7.1)は一般的なmake cleanを提供していないが、単にビルドツリーを消去すれば目的を達成することができる。

しかし、mosh固有のデメリットがいくつか有る。

テストできない

autotoolsのout-of-tree buildは、in-treeに一度configureしたツリー(= ./configure でconfigureしたツリー)に対して行うことはできない。(何故?)
よってテスト用のディレクトリは依然必要になる。moshのテストは、$top_srcdirのようなパスを使っていないのでout-of-treeでは実行できない。また、多くのテストで、外部ファイル(や/tmpのようなディレクトリ)を暗黙に使用している。

ツリーを生成できない

moshMakefile.amには、ツリーの生成、つまり1) bison、re2c、moshGaucheによって生成されるソースコードのための生成ルールと、2) 実際のプログラム生成ルールの両方が混在して記述されている。
mosh-0.2.2.tar.gzのような一般的な配布では、既に1)は実行された状態でパッケージされているので重要な問題では無いが、gitからツリーをチェックアウトしてmoshをビルドするには1と2の両方を行う必要がある。(これはgen-git-build.shによって行える)
このとき、1も2もMakefile.amに記述されているので、一度configureしなければ1のルールを使用することはできない。よって、gitからツリーをチェックアウトしてout-of-tree buildする正確な手順は、

  1. git clone git://github.com/higepon/mosh
  2. cd mosh && ./gen-git-build.sh
  3. make src/Scanner.cpp src/NumberScanner.cpp src/all-tests.scm
  4. make distclean
  5. cd .. && mkdir build && cd build
  6. ../mosh/configure
  7. make

となる。

生成ルールの記述

最初のout-of-tree buildに複雑な手順が必要なのは、ツリーの生成が自動で行われないことに起因する。
一つは、1で生成されるようなファイルもout-of-tree buildの対象とすることで、これは多分機能する。しかし、これを行うと、全てのディレクトリで--enable-developerを与えてconfigureしなければ、何か間違ったことが起るという問題がある(moshで1のルールを可視にするには--enable-developerしなければならない)。
本来は、configure一発で必要なコードが生成され、ビルドできるようになることが好ましい。そのためには、1に相当するオブジェクト(= --enable-developerでなければビルドできないオブジェクト)を可能な限り減らす必要がある。
1に相当するオブジェクトには4つの側面がある。

  • bisonやre2cのような一般的なツールによって生成されるコード。これは本来2に含まれるべきで、何らかの理由で分離されている(チェック中)。
  • Gauchemoshによって生成される、Scheme側の手続き定義(cprocedures等)。例えば、C++側のコードによって実装されている手続きのリストをSchemeコードで持っていて、序数等の情報をSchemeコードによって生成している。ここは難しいポイントで、1に存在する理由が有るとも言える。可能ならばこれはCプリプロセサやsedのような一般的なUNIXツールで処理できるようにすべき。しかし、現状のスタイルもメンテナンス性を考えるとメリットが多い。
  • baseランタイム。(これにはmapやfor-eachのようなSchemeで実装された手続きと、VM命令へのコンパイラを含む。) ここも難しい。現状のmosh VMに対するコンパイラを生成するためにはGaucheが必ず必要なので、ソースツリーにある道具だけではbaseランタイムを生成することは不可能。
  • R6RSランタイム。(これにはR6RSマクロ展開器やライブラリマネージャを含む。) psyntaxランタイムはそもそもユーザによるconfigureを想定していない(外部からダウンロードする)のでルールとして記述するのは好ましくない。nmoshは(原理的には)*1vanilla mosh(R6RSランタイムを含まないmosh)でビルドできるため、2に組込むことができる(bootstrap用のmoshをランタイム無しで生成し、それを使ってnmoshを生成できる)。

*1:今のところ展開したコードがmoshで動作しないので、Gaucheを経由する必要がある