.. 3-build-examples
.. index::
single: OMakefile
single: OMakeroot
single: OSTYPE
.. _label3:
3. OMakeビルドサンプル
==================================
この章ではOMakeのビルド体系をさらにもう少し解説します。この議論を決定するただ一つの結論としては、OMakeは全体のプロジェクト解析を元にしているということです。これはあなたがプロジェクト全体の設定を決めて、そしてOMakeのインスタンスを実行することを意味しています。
一つのディレクトリで構成されたプロジェクトではあまり意味がないかもしれませんが、多数のディレクトリからなるプロジェクトでは大きな意味を持ちます。 ``GNU make`` では、あなたは通常、プロジェクトの各々のディレクトリにおいて ``make`` プログラムを再帰的に呼び出すでしょう。例えば、あなたが現在サブディレクトリ ``lib`` と ``main`` が入っているソースディレクトリ ``src`` を含んでいるプロジェクトをいくつか持っているものとしましょう。具体的には、あなたのプロジェクト構成は以下のアスキーアートのようになります。 ::
my_project/
|--> Makefile
`--> src/
|---> Makefile
|---> lib/
| |---> Makefile
| `---> source files...
`---> main/
|---> Makefile
`---> source files...
一般的に ``GNU make`` では、初めに ``my_project/`` の ``make`` インスタンスを呼び出します。この ``make`` インスタンスは ``src/`` ディレクトリ内の ``make`` インスタンスを呼び出し、そして ``lib/`` と ``main/`` の新しいインスタンスを呼び出します。つまり、 ``GNU make`` では単純に、プロジェクト内の ``Makefile`` の数だけ ``make`` インスタンスが生成されることになります。
大量のプロセスを処理することは今日のコンピュータにとってさほど大きな問題ではありません(ときどき著者の意見に反対する人もいるようですが、私たちはもはや1970年代に住んでいるわけではないのです)。この構造に関する問題としては、各々の ``make`` プロセスが分けられた設定を用いており、そしてそのすべてが調和のとれたものにするには、非常に多くの負担になってしまうという点です。さらには、プログラマが ``main/`` ディレクトリで ``make`` プログラムを実行し、 ``lib/`` がもう使われていない物である場合を考えてみましょう。この場合、 ``make`` は幸せそうにあちこちに曲がりくねった挙句、恐らく ``lib/`` をリビルドしようと奮起して、恐らく諦めることになるでしょう。
OMakeではこの構造を抜本的に変更します。とは言っても実際の変更点はそれほどありません。ソース構造は非常に似通っています。私たちは単純にいくつかの"O"を以下のアスキーアートのように加えただけです。 ::
my_project/
|--> OMakeroot (or Root.om)
|--> OMakefile
`--> src/
|---> OMakefile
|---> lib/
| |---> OMakefile
| `---> source files...
`---> main/
|---> OMakefile
`---> source files...
各々の ``
/OMakefile`` の役割は各々の ``/Makefile`` の役割と同様で、どのように ```` のソースファイルをビルドするのかを設定します。 ``OMakefile`` は ``Makefile`` の構造や文法を保持しておりますが、ほとんどの場合 ``make`` よりも簡単に記述することができます。
一つ小さな違いがあるとすれば、プロジェクトのルートディレクトリに ``OMakeroot`` が存在している点です。このファイルの主要な目的は、第一にプロジェクトのルートディレクトリがどこにあるかを示すことです(omakeがサブディレクトリから呼び出されたときのためです)。 ``OMakeroot`` はブートストラップとして働きます。omakeはこのファイルを最初に読み込むことで実行されます。それ以外では、 ``OMakeroot`` の文法と機能は他の ``OMakefile`` と全く同様です。
*大きな* 違いは、OMakeは *グローバルな* 解析を行うという点です。以下はどのようにomakeが動作するのかを示します。
1. omakeはOMakerootファイルがあるディレクトリに移動し、読んでいきます。
2. 各々の ``OMakefile`` は ``.SUBDIRS`` ターゲットを使用して ``OMakefile`` があるサブディレクトリを指し示します。例えば、 ``my_project/OMakefile`` は以下のルールを持っていたとします。 ::
.SUBDIRS: src
omakeはこれらのルールを読んで、プロジェクト内のすべてのOMakefileを評価します。読み込みや評価は高速に行われるので、このプロセスは大変ではありません。
3. 全体の設定が読まれたら、omakeはどのファイルが使われていないのかを決定し(グローバルな解析を使用します)、ビルド作業を開始します。これはどのファイルのビルドが実際に必要なのかに依存した、ビルド時間がかかります。
このモデルではいくつかの利点があります。初めに、解析をグローバルにしたことで、結局たった一つの設定ファイルを用いることとなり、ビルドに関する設定の一致が保証されるという点です。別の利点はビルドに関する設定が継承され、再利用可能となり、さらに階層構造となる点です。概して、ルートの ``OMakefile`` はいくつかの標準的な決まり文句と設定を定義しており、これはサブディレクトリによって継承され、調整したり、変更することができます(全体を書き換える必要はありません)。この方法の欠点は容量が増大することで、これは結局グローバルな解析を行っているためです。が、実際にはめったに考慮する必要があるようには見えません。OMakeは大きなプロジェクトにおいてさえ、あなたが使っているウェブブラウザよりもはるかに小さい容量しか使いません。
GNU/BSDのmakeユーザは以下の点を頭に留めておいてください。
* OMakeは ``Makefile`` と同じくらい多くのファイルを作ります。文法は似ており、makeと同様に多くのビルドイン関数が用意されています。しかしながら、この2つのビルドシステムは同じではありません。OMakeではいくつかの酷い機能(これは著者の意見です)が外されており、それに代わって新しい機能が追加されています。
* OMakeはWin32を含んだ、複数のプラットフォーム上で同様に動きます。あなたは複数のプラットフォーム上で動かすためにコードを変更したり、いくつかのトリッキーなテクを使ったり、 ``$(OSTYPE)`` 変数を使ってビルド設定を調節する必要はありません。
* OMakeの依存関係の解析はMD5によるファイルの要約を元にしています。これはつまり、依存関係の解析はファイルの「修正日時」ではなくファイルの「内容」を元にしていることを表しています。さあ、ローカル日時とファイルサーバの日時から生じるミスマッチや、間違ったタイムスタンプの変更によるビルドミスにおさらばしましょう。
.. index::
single: OMakeroot
single: OMakefile
.. _label3.1:
3.1 OMakeroot vs. OMakefile
----------------------------------
さて、例を見せる前に、一つ質問をしてみましょう。それは「プロジェクトルートの ``OMakeroot`` と ``OMakefile`` の違いは何か?」というものです。その質問に関する端的な答えは「違いはないが、あなたは必ず ``OMakeroot`` ファイル(あるいは ``Root.om`` ファイル)を作らなければならない」です。
しかしながら、通常の範囲で用いるならば ``OMakeroot`` は変更する必要のない決まり文句が並んでいるファイルであり、すべてのプロジェクトの ``OMakeroot`` は多かれ少なかれ同じような内容となるでしょう。
OMakeを始めるために、あなたがこのような決まり文句を入力する必要はありません。ほとんどの場合において、あなたは以下の手順をプロジェクトのルートディレクトリで実行するだけです。
* omake --installをプロジェクトのルートで実行する。
これで最初の ``OMakeroot`` と ``OMakefile`` が生成され、編集できるようになりました。
.. index::
single: DefineCommandVars()
.. _label3.2:
3.2 Cプロジェクトのサンプル
------------------------------
OMakeを始めるためには、まず簡単なサンプルを見せるのが手っ取り早いでしょう。いま私たちは以下のファイルを含んだディレクトリツリーを持っているものとします。 ::
my_project/
|--> OMakeroot
|--> OMakefile
`--> src/
|---> OMakefile
|---> lib/
| |---> OMakefile
| |---> ouch.c
| |---> ouch.h
| `---> bandaid.c
`---> main/
|---> OMakefile
|---> horsefly.c
|---> horsefly.h
`---> main.c
以下は ``OMakeroot`` , ``OMakefile`` リストのサンプルです。 ::
my_project/OMakeroot:
# Cアプリケーションの標準的な設定をインクルード
open build/C
# コマンドライン上の変数を処理
DefineCommandVars()
# このディレクトリのOMakefileをインクルード
.SUBDIRS: .
my_project/OMakefile:
# 標準的なコンパイルオプションを設定
CFLAGS += -g
# srcディレクトリをインクルード
.SUBDIRS: src
my_project/src/OMakefile:
# あなたの好きなようにオプションを付け加えます
CFLAGS += -O2
# サブディレクトリをインクルード
.SUBDIRS: lib main
my_project/src/lib/OMakefile:
# 静的ライブラリとしてライブラリをビルドします。
# これはUnix/OSX上ではlibbug.aとして、
# Win32上ではlibbug.libとしてビルドされます。
# 引数にはソースファイルの拡張子を入れていないことに注意してください。
StaticCLibrary(libbug, ouch bandaid)
my_project/src/main/OMakefile:
# いくつかのファイルは../libディレクトリ上の
# .hファイルをインクルードしています。
INCLUDES += ../lib
# どのライブラリに対してリンクしたいのかを指定
LIBS[] +=
../lib/libbug
# プログラムをビルドします。
# Win32上ではhorsefly.exe、
# Unix上ではhorseflyとしてビルドされます。
# 最初の引数はアプリケーション名を指定します。
# 二番目の引数はソースファイルの配列を指定します(拡張子は抜いてください)。
# これらの配列はビルドするプログラムの一部となります。
CProgram(horsefly, horsefly main)
# デフォルトでこのプログラムをビルドします
# (他の引数を指定しないでomakeが実行される場合です)。
# EXE変数はWin32上では.exeとして定義されていますが、
# 他のプラットフォーム上では空の変数です。
.DEFAULT: horsefly$(EXE)
ほとんどの設定は ``build/C.om`` (これはOMakeの機能の一部です)ファイルに定義されています。このファイルはほとんどの作業の面倒を見てくれます。具体的には、
* Cライブラリやプログラムを正当な方法でビルドするための、 ``StaticCLibrary`` と ``CProgram`` 関数を定義しています。
* 依存関係を特定するために各々のソースコードを調べていくメカニズムを定義しています。これはつまり、Cソースファイルのための ``.SCANNER`` ルールを定義していることを意味しています。変数はサブディレクトリにも継承されていき、例えば、 ``src/main/OMakefile`` の ``CFLAGS`` 変数の値は ``"-g -O2"`` となります。
.. index::
single: OCaml
single: OCamlLibrary()
single: OCamlProgram()
.. _label3.3:
3.3 OCamlプロジェクトのサンプル
------------------------------------
前回のCの代わりにOCamlを使った状態で、簡単なサンプルを作ってみましょう。今回では、ディレクトリツリーは以下のようになります。 ::
my_project/
|--> OMakeroot
|--> OMakefile
`--> src/
|---> OMakefile
|---> lib/
| |---> OMakefile
| |---> ouch.ml
| |---> ouch.mli
| `---> bandaid.ml
`---> main/
|---> OMakefile
|---> horsefly.ml
|---> horsefly.mli
`---> main.ml
``OMakeroot`` , ``OMakefile`` のリストは前回と少し違います。 ::
my_project/OMakeroot:
# OCamlアプリケーションの標準的な設定をインクルード
open build/OCaml
# コマンドライン上の変数を処理
DefineCommandVars()
# このディレクトリのOMakefileをインクルード
.SUBDIRS: .
my_project/OMakefile:
# 標準的なコンパイルオプションを設定
OCAMLFLAGS += -Wa
# バイトコードのコンパイラを使いたいですか?
# それともネイティブコードのコンパイラを使いたいですか?
# 今回は両方とも使ってみましょう。
NATIVE_ENABLED = true
BYTE_ENABLED = true
# srcディレクトリをインクルード
.SUBDIRS: src
my_project/src/OMakefile:
# サブディレクトリをインクルード
.SUBDIRS: lib main
my_project/src/lib/OMakefile:
# ネイティブコードにおいて、積極的にインライン化を行う
OCAMLOPTFLAGS += -inline 10
# 静的ライブラリとしてライブラリをビルドします。
# これはUnix/OSX上ではlibbug.aとして、
# Win32上ではlibbug.libとしてビルドされます。
# 引数にはソースファイルの拡張子を入れていないことに注意してください。
OCamlLibrary(libbug, ouch bandaid)
my_project/src/main/OMakefile:
# いくつかのファイルは../libディレクトリ上の
# インターフェースに依存しています。
OCAMLINCLUDES += ../lib
# どのライブラリに対してリンクしたいのかを指定
OCAML_LIBS[] +=
../lib/libbug
# プログラムをビルドします。
# Win32上ではhorsefly.exe、
# Unix上ではhorseflyとしてビルドされます。
# 最初の引数はアプリケーション名を指定します。
# 二番目の引数はソースファイルの配列を指定します(拡張子は抜いてください)。
# これらの配列はビルドするプログラムの一部となります。
OCamlProgram(horsefly, horsefly main)
# デフォルトでこのプログラムをビルドします
# (他の引数を指定しないでomakeが実行される場合です)。
# EXE変数はWin32上では.exeとして定義されていますが、
# 他のプラットフォーム上では空の変数です。
.DEFAULT: horsefly$(EXE)
この場合、ほとんどの設定は ``build/OCaml.om`` ファイルで定義されています。今回は特に、 ``my_project/src/lib`` ファイルを ``-inline 10`` オプションを用いて積極的にコンパイルするが、 ``my_project/src/lib`` は普通にコンパイルする設定となっています。
.. _label3.4:
3.4 新しい言語を扱う
------------------------
前回の二つのサンプルは十分簡単なように見えますが、これはOMakeの標準ライブラリ( ``build/C`` と ``/build/OCaml`` ファイル)がすべての仕事を行ってしまったためです。もし私たちがOMakeの標準ライブラリでサポートされていないような言語のビルド設定を書こうとした場合、一体どうすれば良いのでしょうか?
例えば、私たちはOMakeに新しい言語を適用させているものとしましょう。この言語は標準的なコンパイル/リンクモデルを用いていますが、OMakeの標準ライブラリには存在していません。今回は問題をはっきりさせるため、以下のように動作する手順について考えてみましょう。
* ``.cat`` 拡張子(Categorical Abstract Terminology)では複数あるファイルの中のソースファイルを定義します。
* ``catc`` コンパイラを用いて ``.cat`` ファイルは ``.woof`` (Wicked Object-Oriented Format)ファイルにコンパイルされます。
* ``.woof`` ファイルは実行可能な ``.dog`` (Digital Object Group)ファイルを生成するために、 ``-c`` オプションを用いた ``catc`` コンパイラによってリンクされます。 ``catc`` はまた ``-a`` オプションで、いくつかの ``.woof`` ファイルをライブラリに結合させることができます。
* 各々の ``.cat`` ファイルは他のソースファイルに関連付けることができます。もしソースファイル ``a.cat`` が行 ``open b`` を含んでいた場合、 ``a.cat`` は ``b.woof`` ファイルに依存しており、 ``a.cat`` はもし ``b.woof`` が変更された場合再コンパイルしなければなりません。 ``catc`` 関数は ``-I`` オプションで依存関係を示した行を探索することができます。
(訳注: これはcat, woof, dogにもじって作られた仮のソースコードであり、実際に存在しているわけではありません)
ビルド設定を定義するために、私たちは以下の3つの作業を行う必要があります。
1. 依存関係の情報をソースファイルから探索するための ``.SCANNER`` ルールを定義する。
2. ``.cat`` ファイルを ``.woof`` ファイルにコンパイルするための普遍的なビルドルールを定義する。
3. 実行可能な ``.dog`` ファイルを生成するために、 ``.woof`` ファイルをリンクするためのルールを一つの関数として定義する。
初めに、これらの定義はプロジェクトルートの ``OMakefile`` に置くことになります。
.. index::
single: 遅延評価変数
single: mapprefix()
.. _label3.4.1:
3.4.1 通常の編集ルールの定義
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
さて、パート2に移って、通常の編集ルールを定義していきましょう。今回私たちはビルドルールについて、ソースコードに直接ルールを定義することにします。まずはインクルードパスを扱うために、インクルードパスを指定した変数 ``CAT_INCLUDES`` を定義します。これはディレクトリが格納されている配列です。そしてオプションを定義するために、私たちは「遅延評価変数(lazy variable)(":ref:`label7.5`"を参照)」を使用します。この場合は他にも標準的なフラグが存在していますので、 ``CAT_FLAGS`` 変数も定義することにしましょう。 ::
# 私たちは今回CATC変数ををオーバーライドしたいので、catcコマンドを定義します
CATC = catc
# 通常のフラグは空にします
CAT_FLAGS =
# インクルードパスの辞書(通常は空です)
INCLUDES[] =
# インクルードパスによるインクルードオプションを計算します
PREFIXED_INCLUDES[] = $`(mapprefix -I, $(INCLUDES))
# 通常の方法で.woofファイルをビルドします
%.woof: %.cat
$(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) -c $<
最後の部分では、インクルードパスと前に定義されている ``CAT_FLAGS`` 変数を含んだ、 ``catc`` コンパイラを呼び出すというビルドルールを定義しています。 ``$<`` 変数はソースファイル名に置き換わります。
.. index::
single: addsuffix()
.. _label3.4.2:
3.4.2 リンクするためのルールを定義
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.woofファイルをリンクするために、どのようにビルド作業を行うのかについて記述した、別のルールを記述します。ここでソースに直接ルールを定義するかわりに、リンク作業を記述した関数を定義することにします。この関数は二つの引数をとり、最初の引数は実行ファイル名(拡張子なし)で、二つ目の引数はリンクするためのファイル名(これもまた拡張子はなし)を指定します。以下はコードの断片です。 ::
# 副次的なリンクオプション
CAT_LINK_FLAGS =
# どのように.dogプログラムをビルドするのかを定義した関数
CatProgram(program, files) =
# 拡張子を追加
file_names = $(addsuffix .woof, $(files))
prog_name = $(addsuffix .dog, $(files))
# ビルドルール
$(prog_name): $(file_names)
$(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) $(CAT_LINK_FLAGS) -o $@ $+
# プログラム名を返す
value $(prog_name)
``CAT_LINK_FLAGS`` 変数はちょうど私たちがリンク作業において、追加フラグを渡したいような場合に定義される変数です。さて、新しく関数が定義されましたので、私たちがプログラムをビルドするためのルールを定義したいと思った場合はいつでも、単純にこの関数を呼ぶだけで完了します。前回のような暗黙のルールを記述する場合ですと、どのように各々のソースファイルがコンパイルされるのかについていちいち指定する必要がありましたが、 ``CatProgram`` 関数はどのように実行ファイルをビルドするのか指定するだけで完了します。 ::
# rover.dogプログラムをソースファイルneko.catとchat.catからビルドします。
# rover.dogは普通にコンパイルされます。
.DEFAULT: $(CatProgram rover, neko chat)
.. index::
single: .SCANNER
single: 暗黙的な依存関係
single: 正規表現
single: find-in-path()
single: awk()
.. _label3.4.3:
3.4.3 依存関係の解析
^^^^^^^^^^^^^^^^^^^^^^^^^
これでほとんどの作業が終わりましたが、まだ依存関係の解析を自動的に行わせる部分が残っています。これはOMakeの利点の一つであり、さらにロバストで書きやすいビルド設定を作る手助けとなっています。厳密に言うと、この箇所は必要というわけではありませんが、あなたは切実にこの機能を欲しがっているでしょう。
このメカニズムは通常のルールのように、 ``.SCANNER`` ルールを定義することで得られます。しかし ``.SCANNER`` ルールはどのように依存関係を解析するのかについて指定するのであって、ターゲット自身を指定しているわけではありません。私たちは以下のような形で ``.SCANNER`` ルールを定義したいものとします。 ::
.SCANNER: %.woof: %.cat
このルールは ```` を実行することで ``.cat`` ファイルを収集し、その収集されたファイルを展開することができるいくつかの依存関係を新たに追加することのできる ``.woof`` ファイルを指定しています???。 ```` の実行結果は、通常の端末で出力できる、OMake形式の依存関係の配列である必要があります。
心配している通り、各々の ``.cat`` ファイルは ``open`` 構文を用いて、 ``.woof`` ファイルに依存していることを指定していたとします。例えば、もし ``neko.cat`` ファイルが行 ``open chat`` コマンドを含んでいたとするならば、 ``neko.woof`` ファイルは ``chat.woof`` ファイルに依存しています。この場合、 ```` は以下の行を出力しなければなりません。 ::
neko.woof: chat.woof
この類推は、 ``.o`` ファイルが ``.c`` ファイルをコンパイルすることで生成されるC言語について考えるとより明瞭になります。もしファイル ``foo.c`` が ``#include "fum.h"`` のような行を含んでいたとすると、 ``foo.c`` は ``fum.c`` が変更されたときはいつでも再コンパイルを行う必要があります。これはつまり、ファイル ``foo.o`` がファイル ``fum.h`` に依存していることを表しています。OMakeの用語では、このことを「暗黙的な依存関係(implicit dependency)」と呼んでおり、 ``.SCANNER `` は以下のような行を出力する必要があるでしょう。 ::
foo.o: fum.h
それでは動物の世界へと戻ってみましょう。 ``neko.woof`` の依存関係を解析するために、私たちは一行一行 ``neko.cat`` ファイルをスキャンして、 ``open `` のような形の構文を含んだ行を探す必要があります。私たちはこのようなプログラムを書かなければなりませんが、OMakeはこのような作業を簡略化することができます。この例ですと、ソースファイルをスキャンする ``awk`` 関数がビルドインで用意されているので、これを使ってみましょう。一つ難しいことがあるとするならば、それは依存関係が ``INCLUDE`` パスに依存していることです。そのためにOMakeでは探索するための ``find-in-path`` 関数を用意しています。それでは以下のように書いてみます。 ::
.SCANNER: %.woof: %.cat
section
# ファイルをスキャン
deps[] =
awk($<)
case $'^open'
deps[] += $2
export
# 重複を削除し、インクルードパスのファイルを探索する
deps = $(find-in-path $(INCLUDES), $(set $(deps)))
# 依存関係を出力
println($"$@: $(deps)")
それでは上のソースコードを見てみましょう。初めに、全体の文はシェルコマンドのシーケンスとして扱われずに内部で計算されるよう、 ``section`` 文の中で定義されています。
今回私たちはすべての依存関係を集めるために、 ``deps`` 変数を用いました。 ``awk`` 関数はソースファイル ``($<)`` を一行一行スキャンしていきます。正規表現 ``^open`` (これはこの行が単語 ``open`` で始まることを表しています)が見つかった場合、 ``deps`` 変数に二番目の単語を追加します。具体的には、もし入力された行が ``open chat`` であった場合、 ``deps`` 配列に ``chat`` 文字列を追加することになります。ソースファイル中のその他すべての行は無視されます。
次に、 ``$(set $(deps))`` 文によって ``deps`` 配列の重複された文字列は削除されます(このとき、アルファベット順に配列をソートします)。 ``find-in-path`` 関数はインクルードパス中の各々のファイルの絶対パスを探索します。
最後に、文字列 ``$"$@: $(deps)"`` を結果として出力します。クオーテーションには ``deps`` 配列を単純な文字列に変換した状態で追加されます。
.. _label3.4.4:
3.4.4 まとめ
^^^^^^^^^^^^^^^^^^^
例がすべて終わったので、この成果を一つのプロジェクトにまとめてみましょう。前回の例は以下のような構成とします。 ::
my_project/
|--> OMakeroot
|--> OMakefile
`--> src/
|---> OMakefile
|---> lib/
| |---> OMakefile
| |---> neko.cat
| `---> chat.cat
`---> main/
|---> OMakefile
`---> main.cat
この全体のプロジェクトのリストは以下のようになります。私たちはまたライブラリにいくつかの ``.woof`` ファイルをリンクさせるために、 ``CatLibrary`` 関数を定義していることに注意してください。 ::
my_project/OMakeroot:
# コマンドライン上の変数を処理
DefineCommandVars()
# このディレクトリのOMakefileをインクルード
.SUBDIRS: .
my_project/OMakefile:
########################################################################
# .catファイルをコンパイルするための標準設定
#
# 私たちは今回CATC変数ををオーバーライドしたいので、catcコマンドを定義します
CATC = catc
# 通常のフラグは空にします
CAT_FLAGS =
# インクルードパスの辞書(通常は空です)
INCLUDES[] =
# インクルードパスによるインクルードオプションを計算します
PREFIXED_INCLUDES[] = $`(mapprefix -I, $(INCLUDES))
# .catファイルの依存関係を解析するスキャナ
.SCANNER: %.woof: %.cat
section
# ファイルをスキャン
deps[] =
awk($<)
case $'^open'
deps[] += $2
export
# 重複を削除し、インクルードパスのファイルを探索する
deps = $(find-in-path $(INCLUDES), $(set $(deps)))
# 依存関係を出力
println($"$@: $(deps)")
# 通常の方法で.catファイルをコンパイルする
%.woof: %.cat
$(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) -c $<
# 副次的なリンクオプション
CAT_LINK_FLAGS =
# いくつかの.woofファイルを用いてライブラリをビルド
CatLibrary(lib, files) =
# 拡張子を追加
file_names = $(addsuffix .woof, $(files))
lib_name = $(addsuffix .woof, $(lib))
# ビルドルール
$(lib_name): $(file_names)
$(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) $(CAT_LINK_FLAGS) -a $@ $+
# プログラム名を返す
value $(lib_name)
# どのように.dogプログラムをビルドするのかを定義した関数
CatProgram(program, files) =
# 拡張子を追加
file_names = $(addsuffix .woof, $(files))
prog_name = $(addsuffix .dog, $(program))
# ビルドルール
$(prog_name): $(file_names)
$(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) $(CAT_LINK_FLAGS) -o $@ $+
# プログラム名を返す
value $(prog_name)
########################################################################
# これで正しくプログラムが動きます
#
# srcサブディレクトリをインクルード
.SUBDIRS: src
my_project/src/OMakefile:
.SUBDIRS: lib main
my_project/src/lib/OMakefile:
CatLibrary(cats, neko chat)
my_project/src/main/OMakefile:
# ../libディレクトリからのインクルードを許可
INCLUDES[] += ../lib
# プログラムをビルド
.DEFAULT: $(CatProgram main, main ../cats)
注意点としては、 ``OMakeroot`` では依存関係の解析や、ソースファイルをコンパイルするための通常のルール、ライブラリやプログラムをビルドするいくつかの関数を含んだ、標準的な設定を定義しています。
これらのルールや関数はサブディレクトリに継承されていますので、 ``.SCANNER`` とビルドルールは自動的に各々のサブディレクトリに使われます。よってあなたはこれらを繰り返し記述する必要はありません。
.. index::
single: OMAKEPATH
.. _label3.4.5:
3.4.5 終わりに
^^^^^^^^^^^^^^^^^^
これで一通りの作業は終わりましたが、まだ考慮すべき点はいくつか残っています。
まず、 ``cat`` プログラムをビルドするためのルールはプロジェクトの ``OMakefile`` に定義しました。もしあなたがどこか別の ``cat`` プロジェクトを持っていた場合、 ``OMakeroot`` をコピーを(そしてもし必要ならば修正も)するかもしれません。その代わりに、あなたは設定ファイルを ``Cat.om`` のように名称を変更して、ライブラリの共有ディレクトリに移すべきです。これで、コードをコピーする代わりに、OMakeコマンド ``open Cat`` を用いてインクルードできるようになります。そのためには、あなたは共有ディレクトリを ``OMAKEPATH`` 環境変数に追加することで、omakeがどこを探せば良いのか分かるようにすべきです。
もしあなたがいい仕事をしたのなら、標準の設定となるようにあなたの設定ファイルを送ることを考えてみてください(``omake@metaprl.org`` 宛にリクエストを送ることで)。他の人の作業を省力化することになります。
.. index::
single: .SUBDIRS
single: absname()
.. _label3.5:
3.5 階層構造、.SUBDIRSの内容を並列化させる
-------------------------------------------
いくつかのプロジェクトは同一の設定を有した、数多くのディレクトリで構成されているものです。例えば、あなたは現在サブディレクトリが多数あり、その各々がウェブページの画像の集合であるというプロジェクトを持っているものとしましょう。ある特定の画像を除いて、各々のファイルの設定は同一です。
この設定をより強固に構築するため、以下のような場合を考えます。まず、このプロジェクトは4つのサブディレクトリ ``page1, page2, page3, page4`` を含んでいるものとします。また、各々のサブディレクトリは二つのファイル ``image1.jpg, image2.jpg`` を含んでおり、それらはプログラム ``genhtml`` によって生成されるウェブページの一部であるとします。
各々のディレクトリ中に ``OMakefile`` を定義する代わりに、OMakeでは ``.SUBDIRS`` コマンドの内容として定義することができます。 ::
.SUBDIRS: page1 page2 page3 page4
index.html: image1.jpg image2jpg
genhtml $+ > $@
``.SUBDIRS`` の内容は、まるで ``OMakefile`` が内部にあるかのように正確にふるまい、通常の命令を任意の数だけ実行することができます。 ``.SUBDIRS`` の内容は各々のサブディレクトリの内部で評価されます。実際に何が行われているのかについては、現在のディレクトリ名を出力する命令 ``($(CWD))`` を追加することでより分かりやすくなるでしょう。 ::
.SUBDIRS: page1 page2 page3 page4
println($(absname $(CWD)))
index.html: image1.jpg image2jpg
genhtml $+ > $@
# 出力
/home/jyh/.../page1
/home/jyh/.../page2
/home/jyh/.../page3
/home/jyh/.../page4
.. index::
single: glob()
single: ls()
.. _label3.5.1:
3.5.1 バイナリデータのパターン(blob patterns)を扱う
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
もちろん、上述した指定は非常に強固なものとなっています。実際に、各々のサブディレクトリが異なった画像の集合であり、そのすべてがウェブページに含まれているような場合でも、記述方法は似ています。この問題に対するより簡単な解法の一つは、 ``glob`` や ``ls`` のようなディレクトリのリストを出力する関数を用いることです。 ``glob`` 関数はシェルのパターンを引数に持ち、現在のディレクトリ上でマッチしているファイル名の配列を返す関数です。 ::
.SUBDIRS: page1 page2 page3 page4
IMAGES = $(glob *.jpg)
index.html: $(IMAGES)
genhtml $+ > $@
.. index::
single: include
.. _label3.5.2:
3.5.2 簡略化されたサブディレクトリの設定
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
別の方法は、各々のサブディレクトリ固有の情報を定義した設定ファイルを、それぞれのディレクトリに追加することです。例えば、私たちは現在、各々のサブディレクトリ中に、ディレクトリ内部にある画像のリストを定義した ``BuildInfo.om`` ファイルを設置しているものとします。 ``.SUBDIRS`` の行は似ていますが、 ``BuildInfo`` ファイルをインクルードしている点が異なっています。 ::
.SUBDIRS: page1 page2 page3 page4
include BuildInfo # IMAGES変数を定義
index.html: $(IMAGES)
genhtml $+ > $@
それぞれのBuildInfo.omの内容は以下のようになっています。 ::
page1/BuildInfo.om:
IMAGES[] = image.jpg
page2/BuildInfo.om:
IMAGES[] = ../common/header.jpg winlogo.jpg
page3/BuildInfo.om:
IMAGES[] = ../common/header.jpg unixlogo.jpg daemon.jpg
page4/BuildInfo.om:
IMAGES[] = fee.jpg fi.jpg foo.jpg fum.jpg
.. index::
single: subdirs()
single: find()
single: dirof()
.. _label3.5.3:
3.5.3 サブディレクトリのリストを計算
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
現在、サブディレクトリのリスト ``page1, ... , page4`` は直接指定しています。他のディレクトリが追加される度に ``OMakefile`` を編集するよりも、( ``glob`` を用いて)計算させたほうがはるかに合理的です。 ::
.SUBDIRS: $(glob page*)
index.html: $(glob *.jpg)
genhtml $+ > $@
ディレクトリ構造が階層的である場合を考えてみましょう。その場合 ``glob`` 関数を用いる代わりに、階層的に各々のディレクトリを返す ``subdirs`` 関数を使います。例えば、以下はOMakeプロジェクトのルート上で ``subdirs`` 関数を評価した結果です。最初の引数として渡したPオプションでは、OMakeのディレクトリ自身を含んでいない、「適切な」リストを返すことを指定しています。 ::
osh> subdirs(P, .)
- :
``subdirs`` を使用することで、上の例は以下のように表現できます。 ::
.SUBDIRS: $(subdirs P, .)
index.html: $(glob *.jpg)
genhtml $+ > $@
この場合ですと、プロジェクト中の *すべての* サブディレクトリが含まれることとなります。
もし私たちが ``BuildInfo.om`` オプションを使用する場合、すべてのサブディレクトリをインクルードする代わりに、 ``BuildInfo.om`` ファイルが含んであるディレクトリのみインクルードしたいと思うでしょう。これを実現するために、私たちはディレクトリを階層的に全走査し、特定の表現にマッチしたファイルを返す ``find`` 関数を使用します。この場合ですと、 ``BuildInfo.om`` という名前のファイルを探したいことになります。以下は ``find`` 関数を呼び出したサンプルです。 ::
osh> FILES = $(find . -name BuildInfo.om)
- :
osh> DIRS = $(dirof $(FILES))
- :
この例では、プロジェクト中に3つの ``BuildInfo.om`` ファイルが ``doc/html, src, tests/simple`` ディレクトリに存在しています。また、 ``dirof`` 関数は各々のファイルのディレクトリを返します。
先の例に戻って、私たちは以下のように修正することにしました。 ::
.SUBDIRS: $(dirof $(find . -name BuildInfo.om))
include BuildInfo # Defines the IMAGES variable
index.html: $(IMAGES)
genhtml $+ > $@
.. index::
single: 一時的なディレクトリ
single: CREATE_SUBDIRS
.. _label3.5.4:
3.5.4 一時的なディレクトリ
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
時々、プロジェクトでは中間ファイルを置いておくための一時的なディレクトリが必要となる場合があります。これらの一時ディレクトリはプロジェクトがクリーンアップされたときはいつでも消去されます。これは特に、ディレクトリが消去されたら同ディレクトリの ``OMakefile`` も消去されるために、 ``OMakefile`` を一時的なディレクトリに置くべきではないことを意味しています。
もしあなたがこれらのディレクトリに関する設定を行いたいのなら、あなたは ``OMakefile`` を設置する代わりに、 ``.SUBDIRS`` の内容について記述する必要があります。 ::
section
CREATE_SUBDIRS = true
.SUBDIRS: tmp
# MD5ハッシュを計算
%.digest: %.comments
echo $(digest $<) > $@
# ソースファイルからコメントを展開
%.comments: ../src/%.src
grep '^#' $< > $@
.DEFAULT: foo.digest
.PHONY: clean
clean:
rm -rf tmp
今回の例では、私たちは ``tmp`` ディレクトリが存在しない場合に新しくディレクトリを生成するため、 ``CREATE_SUBDIRS`` 変数を ``true`` に設定しました。 ``.SUBDIRS`` の内容は少々工夫してありますが、だいたいあなたが期待している通りに動作するはずです。 ``clean phony`` ターゲットでは、プロジェクトがクリーンアップされた場合は ``tmp`` ディレクトリが消去されるように指示しています。