OMakeは複数のディレクトリにまたがって存在するソースファイルをビルドするために製作されたツールです。OMakeを使用したプロジェクトは通常、 OMakefile を各々のプロジェクトディレクトリに置き、 OMakeroot をプロジェクトのルートディレクトリに置きます。 OMakeroot には全体的なビルドルールを指定し、 OMakefiles にはそれぞれのサブディレクトリに固有のビルドパラメータを指定します。いったんOMakeを起動すると、OMakeはまず設定ファイルのあるディレクトリをスキャンし、すべての OMakefile を評価します。そしてプロジェクトはビルドルールの集合としてビルドされます。
従来のmake(1)プログラムでは以前からずっと依存関係の解析が問題となっていました 。OMakeではこの問題を、依存関係を生成するコマンドを指定した .SCANNER ターゲットを追加することで解決します。たとえば、以下のルール
.SCANNER: %.o: %.c
$(CC) $(INCLUDE) -MM $<
は .c ファイルの依存関係を生成する常套手段です。OMakeはソースファイルの依存関係を知る必要がある場合、自動的にソースファイルをスキャンして依存関係を特定します。
どのファイルの依存関係が変更されたかを知るために、OMakeではMD5による要約を用いてチェックを行います。各々のディレクトリでOMakeが実行された後に、 OMakeは依存関係の情報をルートディレクトリの .omakedb ファイルに保存します。もしいったんOMakeが依存関係のルールファイルを保存したのなら、OMakeが最後に実行された後に、ターゲット、依存関係、ソースコードの変更がない場合は、ビルドは実行されません。また最適化として、OMakeは修正日時、サイズ、ノード番号に変更のないファイルについてMD5の再計算を行いません。
既にmakeコマンドに慣れているユーザがomakeを使う際には、以下のmakeとomakeの違いを列挙したリストについて留意しておきましょう。
新しいプロジェクトを作る際にもっとも簡単な方法は、カレントディレクトリをプロジェクトのルートディレクトリに変え、 omake --install とコマンドを入力してデフォルトの OMakefile と OMakeroot をインストールすることです。
$ cd ~/newproject
$ omake --install
*** omake: creating OMakeroot
*** omake: creating OMakefile
*** omake: project files OMakefile and OMakeroot have been installed
*** omake: you should edit these files before continuing
デフォルトの OMakefile はCとOCamlのプログラムをビルドするためのセクションを含んでいます。さて、それではOMakeを用いて簡単なCプロジェクトをビルドしてみましょう。
私たちは hello_code.c というCファイルを持っており、そのコードは以下であったとします。
#include <stdio.h>
int main(int argc, char **argv)
{
printf("Hello world\n");
return 0;
}
このファイルから hello というプログラムをビルドする場合、 CProgram という関数を使うことができます。 OMakefile に hello_code.c のソースコードから hello プログラムをビルドすることを指定するため、以下の一行を加えます(ファイルの拡張子はこれらの関数に渡していないことに注意してください)。
CProgram(hello, hello_code)
これで私たちはこのプロジェクトをビルドするために、OMakeを実行することができます。最初に私たちがOMakeを実行した時点で、OMakeは依存関係の解析に hello_code.c を解析し、 cc コンパイラを用いてコンパイルします。一番最後の行にはどれだけ多くのファイルが解析され、どれだけ多くビルドされ、そしてどれだけ多くのMD5が計算されたのかが表示されます。
$ omake hello
*** omake: reading OMakefiles
*** omake: finished reading OMakefiles (0.0 sec)
- scan . hello_code.o
+ cc -I. -MM hello_code.c
- build . hello_code.o
+ cc -I. -c -o hello_code.o hello_code.c
- build . hello
+ cc -o hello hello_code.o
*** omake: done (0.5 sec, 1/6 scans, 2/6 rules, 5/22 digests)
$ omake
*** omake: reading OMakefiles
*** omake: finished reading OMakefiles (0.1 sec)
*** omake: done (0.1 sec, 0/4 scans, 0/4 rules, 0/9 digests)
私たちがコンパイルオプションを変更したいと思った場合、 CProgram と書いてある行の前に CC と CFLAGS 変数を再定義することで可能となります。たとえば、現在私たちは -g オプションで gcc コンパイラを用いたいとします。加えて、デフォルトでビルドするために .DEFAULT ターゲットを指定したい、さらにWin32システムで .exe と定義された EXE 変数を用いたいとします。これらの要望は以下のコードで実現できます。
CC = gcc
CFLAGS += -g
CProgram(hello, hello_code)
.DEFAULT: hello$(EXE)
以下はomakeを実行させた場合の全体のコンソールです。
$ omake
*** omake: reading OMakefiles
*** omake: finished reading OMakefiles (0.0 sec)
- scan . hello_code.o
+ gcc -g -I. -MM hello_code.c
- build . hello_code.o
+ gcc -g -I. -c -o hello_code.o hello_code.c
- build . hello
+ gcc -g -o hello hello_code.o
*** omake: done (0.4 sec, 1/7 scans, 2/7 rules, 3/22 digests)
もちろん、プログラム中に複数のファイルをインクルードすることもできます。 hello_helper.c という新しいファイルを追加したとしましょう。以下のように Omakefile を変更することで対応できます。
CC = gcc
CFLAGS += -g
CProgram(hello, hello_code hello_helper)
.DEFAULT: hello$(EXE)
コードからライブラリをビルドしたいなどのような場合、プロジェクトは徐々に肥大化します。ライブラリは StaticCLibrary 関数を用いることでビルドすることができます。以下は二つのライブラリをビルドする際の OMakefile のサンプルです。
CC = gcc
CFLAGS += -g
FOO_FILES = foo_a foo_b
BAR_FILES = bar_a bar_b bar_c
StaticCLibrary(libfoo, $(FOO_FILES))
StaticCLibrary(libbar, $(BAR_FILES))
# helloプログラムは両方のライブラリを用いてリンクされます
LIBS = libfoo libbar
CProgram(hello, hello_code hello_helper)
.DEFAULT: hello$(EXE)
さらにプロジェクトが肥大化する場合、いくつかのディレクトリにコードファイルを分割することは良いアイデアです。現在私たちは libfoo と libbar をサブディレクトリに置いてあるものとしましょう。
まず、 OMakefile を各々のサブディレクトリ内におきます。例えば、以下は foo/ サブディレクトリ内の OMakefile のサンプルです。
INCLUDES += .. ../bar
FOO_FILES = foo_a foo_b
StaticCLibrary(libfoo, $(FOO_FILES))
INCLUDES 変数はプロジェクトの他のディレクトリをインクルードするために定義されています。
さて、次のステップはメインプロジェクトの中にサブディレクトリをリンクさせます。プロジェクトの OMakefile を .SUBDIRS プロジェクトを含めるように修正します。
# プロジェクトの設定
CC = gcc
CFLAGS += -g
# サブディレクトリ
.SUBDIRS: foo bar
# ライブラリはサブディレクトリの中に存在します
LIBS = foo/libfoo bar/libbar
CProgram(hello, hello_code hello_helper)
.DEFAULT: hello$(EXE)
変数 CC と CFLAGS は .SUBDIRS ターゲットの 前に 定義されてあることに注目してください。これらの変数はサブディレクトリでも保持されていますので、 libfoo と libbar では gcc -g が使われます。
二つのディレクトリに異なる設定を用いる必要がある場合、二つの方法があります。一つ目は各々のサブディレクトリの OMakefile にそれぞれの設定を書く方法です(普通はこのようにして問題を解決します)。二つ目は、ルートの OMakefile を以下のコードに書き換える方法です。
# 通常のプロジェクト設定
CC = gcc
CFLAGS += -g
# libfooは通常の設定を用います
.SUBDIRS: foo
# libbarは加えて最適化を行います
CFLAGS += -O3
.SUBDIRS: bar
# メインプログラム
LIBS = foo/libfoo bar/libbar
CProgram(hello, hello_code hello_helper)
.DEFAULT: hello$(EXE)
CFLAGS 変数にさらに -O3 オプションを加えることで、 hello_code.c と hello_helper.c の両方は -O3 オプションでコンパイルされます。 libbar のみにオプションを変更させたい場合、 section 文を使用することで bar/ サブディレクトリ内のみに変更を適用することができます。
# 通常のプロジェクト設定
CC = gcc
CFLAGS += -g
# libfooは通常の設定を用います
.SUBDIRS: foo
# libbarは加えて最適化を行います
section
CFLAGS += -O3
.SUBDIRS: bar
# メインプログラムでは最適化を使用しません
LIBS = foo/libfoo bar/libbar
CProgram(hello, hello_code hello_helper)
.DEFAULT: hello$(EXE)
後で、私たちがこのプロジェクトをWin32に移し、そして異なるコンパイラフラグや追加ライブラリが必要になることがわかったとしましょう。
# 通常のプロジェクト設定
if $(equal $(OSTYPE), Win32)
CC = cl /nologo
CFLAGS += /DWIN32 /MT
export
else
CC = gcc
CFLAGS += -g
export
# libfooは通常の設定を用います
.SUBDIRS: foo
# libbarは加えて最適化を行います
section
CFLAGS += $(if $(equal $(OSTYPE), Win32), $(EMPTY), -O3)
.SUBDIRS: bar
# 通常のライブラリ
LIBS = foo/libfoo bar/libbar
# Win32上のみlibwin32を必要とします
if $(equal $(OSTYPE), Win32)
LIBS += win32/libwin32
.SUBDIRS: win32
export
# メインプログラムでは最適化を使用しません
CProgram(hello, hello_code hello_helper)
.DEFAULT: hello$(EXE)
export によって if 文の中にある変数が外部にエクスポートされます。 OMakeでの変数はスコープ化 されており、ネストされたブロック内の変数は通常、外部のブロックから使用することはできません。 export はネストされたブロック内の変数を、親のブロックに移す命令です。
ノート
訳注: 今回の例では $(OSTYPE) 変数を用いて場合分けを行っていますが、 $(CC) 変数に関しては省略することができます。なぜなら、 Unix 上では $(CC) は gcc , Win32 上では cl /nologo が自動的に束縛されるからです。
大抵の場合において、このような設定変数は異なるプラットフォーム上においても正常に動作するように設計されています(詳細は “13.5.2 C/C++用の設定変数” を参照してください)。もちろん、その他になにか追加オプションを指定したい場合には、このような場合分けが有効となるでしょう。
最後に、私たちはすべてのライブラリを共通の lib/ ディレクトリにコピーしたいものとします。私たちはまずはじめにディレクトリの変数を定め、そして lib の文字列を変数で置き換えます。
# 共用のlibディレクトリ
LIB = $(dir lib)
# phonyターゲットはライブラリのみビルドを行います
.PHONY: makelibs
# 通常のプロジェクト設定
if $(equal $(OSTYPE), Win32)
CC = cl /nologo
CFLAGS += /DWIN32 /MT
export
else
CC = gcc
CFLAGS += -g
export
# libfooは通常の設定を用います
.SUBDIRS: foo
# libbarは加えて最適化を行います
section
CFLAGS += $(if $(equal $(OSTYPE), Win32), $(EMPTY), -O3)
.SUBDIRS: bar
# 通常のライブラリ
LIBS = $(LIB)/libfoo $(LIB)/libbar
# Win32上のみlibwin32を必要とします
if $(equal $(OSTYPE), Win32)
LIBS += $(LIB)/libwin32
.SUBDIRS: win32
export
# メインプログラムでは最適化を使用しません
CProgram(hello, hello_code hello_helper)
.DEFAULT: hello$(EXE)
$(LIB) ディレクトリの中にライブラリをインストールするため、ライブラリディレクトリ内の OMakefile を修正します。以下は新しく変更された foo/OMakefile です。
INCLUDES += .. ../bar
FOO_FILES = foo_a foo_b
StaticCLibraryInstall(makelib, $(LIB), libfoo, $(FOO_FILES))
ディレクトリ(そしてファイル名)は現在のパス名で評価されます。 foo/ ディレクトリ内では、 $(LIB) 変数は ../lib として評価されます。
各々のサブディレクトリ内で INCLUDES 変数を個別に定義する代わりに、以下のようにトップレベルで定義することもできます。
INCLUDES = $(ROOT) $(dir foo bar win32)
foo/ ディレクトリ内において INCLUDES 変数は文字列 .. . ../bar として評価されます。また bar/ ディレクトリ内において、 INCLUDES 変数は文字列 .. ../foo . ../win32 として評価されます。ルートディレクトリでは、 INCLUDES 変数は文字列 . foo bar win32 として評価されます。
ノート
訳注: 今までの議論の中で「 Win32プラットフォーム上でディレクトリのセパレータに/(スラッシュ)を指定していいのだろうか?\(バックスラッシュ)を使わないといけないのでは? 」と疑問に思った方も多いと思います。実は、この点に関しては心配いりません。omakeはたとえWin32上で / を使っていたとしても、実際の評価ではー非常に奇妙に思われるかもしれませんがー \ が用いられるのです。同様に、セパレータに \ を用いたとしてもUnixプラットフォーム上では / と変換されます。
このように、ディレクトリのセパレータは / , \ どちらを使っても、異なるプラットフォーム上で正常に動作します。ただし、 \ はエスケープ文字として使用されることが多いため、思わぬ誤動作を引き起こすことがあるかもしれません。大抵の場合、ディレクトリのセパレータは / を使うことをおすすめします(詳細は “10.4 ファイルの検索とリスト” を参照してください)。
OMakeはまたリソースのあるサブディレクトリも扱うことができます。例えば、先ほどの foo/ ディレクトリの中に、さらにいくつかのサブディレクトリを保持しているような場合を考えてみましょう。 foo/OMakefile は foo/ ディレクトリ自身の .SUBDIRS ターゲットを保持しており、また各々のサブディレクトリもまたサブディレクトリ自身の OMakefile を保持しています。
通常、OMakeはOCamlのプログラムをビルドするための関数群も持っています。OCamlプログラムのための関数群は接頭語として OCaml がつきます。例えば、前回のサンプルをOCamlで置き換えて、 hello_code.ml という以下のコードを含んだファイルを持っている場合を考えましょう。
open Printf
let () = printf "Hello world\n"
この簡単なプロジェクトのOMakefileのサンプルは以下になります。
# バイトコードコンパイラを使用
BYTE_ENABLED = true
NATIVE_ENABLED = false
OCAMLCFLAGS += -g
# プログラムをビルド
OCamlProgram(hello, hello_code)
.DEFAULT: hello.run
次に、二つのライブラリのサブディレクトリを持っている場合を考えましょう。 foo/ ディレクトリはCで書かれており、 bar/ ディレクトリはOCamlで書かれています。そして私たちは標準的なOCamlのUNIXモジュールを使用したいといった場合です。
# 通常のプロジェクト設定
if $(equal $(OSTYPE), Win32)
CC = cl /nologo
CFLAGS += /DWIN32 /MT
export
else
CC = gcc
CFLAGS += -g
export
# バイトコードコンパイラを使用
BYTE_ENABLED = true
NATIVE_ENABLED = false
OCAMLCFLAGS += -g
# ライブラリのサブディレクトリ
INCLUDES += $(dir foo bar)
OCAMLINCLUDES += $(dir foo bar)
.SUBDIRS: foo bar
# Cライブラリ
LIBS = foo/libfoo
# OCamlライブラリ
OCAML_LIBS = bar/libbar
# Unixモジュールも用います
OCAML_OTHER_LIBS = unix
# メインプログラム
OCamlProgram(hello, hello_code hello_helper)
.DEFAULT: hello
foo/OMakefile はCライブラリとして設定します。
FOO_FILES = foo_a foo_b
StaticCLibrary(libfoo, $(FOO_FILES))
また、 bar/OMakefile はMLライブラリとして設定します。
BAR_FILES = bar_a bar_b bar_c
OCamlLibrary(libbar, $(BAR_FILES))
プロジェクトを設定する際、OMakeは OMakefile と OMakeroot の2つのファイルを使用します。これらのファイルの文法は同じですが、役割は全く異なります。さらに付け加えると、すべてのプロジェクトは OMakeroot ファイルをプロジェクトのルートディレクトリに必ず置かなければなりません。このファイルはこのディレクトリがプロジェクトのルートディレクトリであることを決め、さらにプロジェクトをセットアップするためのコードを含んでいます。対照的に、複数のディレクトリが存在するプロジェクトは、どのようにサブディレクトリ内のファイルをビルドするのかを設定した OMakefile を、各々のプロジェクトのサブディレクトリに置くことになります。
通常、 OMakeroot ファイルはほとんど変更する必要のない決まり文句です。以下のリストは OMakeroot ファイルの一部です。
include $(STDLIB)/build/Common
include $(STDLIB)/build/C
include $(STDLIB)/build/OCaml
include $(STDLIB)/build/LaTeX
# コマンドライン変数を再定義
DefineCommandVars(.)
# 現在のディレクトリをプロジェクトの一部として設定
.SUBDIRS: .
include が書かれている行では、プロジェクトに必要な、標準的な設定ファイルをインクルードしています。 $(STDLIB) 変数はOMakeライブラリのディレクトリを表します。OCamlが動作するために必須となる設定ファイルは Common だけで、その他の設定ファイルはなくても構いません。 $(STDLIB)/build/OCaml ファイルはプロジェクトがOCamlで書かれたプログラムを含んでいる場合のみ必要となります。
DefineCommandVars 関数は( VAR=<value> のような形で)コマンドライン上から指定された、いくつかの変数を定義します。 .SUBDIRS が書かれている行では現在のディレクトリがプロジェクトの一部であることを指定しています(よって同ディレクトリの OMakefile が読み込まれます)。
通常、 OMakeroot ファイルはサイズが小さく、かつプロジェクトから独立しています。プロジェクト特有の設定はすべてプロジェクト上の OMakefile に設定すべきです。
OMake バージョン 0.9.6では、複数の同じバージョンのプロジェクトや、予備として用いる複数のプロジェクトに関するサポートを導入しました。 vmount(dir1, dir2) 関数を用いることで、 dir1/ ディレクトリを dir2/ ディレクトリに『仮想的にマウント』することができます。『仮想的なマウント』はUnixにて、 dir1/ ディレクトリ内のファイルを dir2/ ディレクトリにマウントするが、新しいファイルは dir2/ ディレクトリに作られるようなものです。さらに具体的には、ファイル dir2/foo は、 dir1/foo が存在している場合は dir1/foo に置き換わりますが、存在していない場合は dir2/foo が用いられます。
vmount 関数によってプロジェクト内の複数のバージョンをビルドすることが容易になりました。 src/ ディレクトリ内にいくつかのソースファイルが入っており、これをデバッグサポートがついているバージョンと、最適化されたバージョンの2つにコンパイルする場合を考えてみましょう。まず debug/ と opt/ の2つのディレクトリを作成して、 src/ ディレクトリをこれらのディレクトリにマウントします。
section
CFLAGS += -g
vmount(-l, src, debug)
.SUBDIRS: debug
section
CFLAGS += -O3
vmount(-l, src, opt)
.SUBDIRS: opt
ここで、 vmount 関数を必要としていないプロジェクトに適用させないために、私たちは section ブロックを用いて vmount のスコープ範囲を定義しました。
-l オプションはなくても構いません。それを指定することで src/ ディレクトリのファイルは、ターゲットとなるディレクトリにリンクされます(システムがWin32の場合、ファイルはコピーされます)。ファイルが関連付けられるようにファイルへのリンクが追加されます。何もオプションが与えられていない場合、ファイル名は直接 src/ ディレクトリのファイル名に変換されます。
debug/ ディレクトリ内のファイルが参照された時点で、もし src/ ディレクトリにそのファイルが存在するならば、 debug/ ディレクトリのファイルは src/ のファイルにリンクされます。例えば、 debug/OMakefile が参照された場合、 src/OMakefile が debug/OMakefile としてリンクされます。
vmount 関数の動作はだいぶ透過的です。あなたは OMakefile をまるで src/ ディレクトリ内のファイルが関連付けられているかのように書くことができ、マウントについて気にする必要はありません。しかしながら、以下に示すいくつかの注意点を頭に入れておいてください。
バージョン管理の目的で vmount 関数を用いた場合、コンパイルされたファイルをソースファイルから分離することをお勧めします。例えば、ソースディレクトリに src/foo.o ファイルが含まれている場合を考えましょう。もし src/foo.o がマウントされてしまったとすると、 foo.o ファイルはすべてのバージョンにおいて共通のファイルになってしまい、おそらくあなたが求めていた動作とは違うものになるでしょう。 src/ ディレクトリにはソースコード以外何もない状態にして、コンパイルされたコードは含めないようにしましょう。
vmount -l オプションを使用したときは、ソースファイルがプロジェクトから参照された場合のみ、バージョンディレクトリ内にリンクされます。ファイルシステムを用いる関数( $(ls ...) など)はあなたが期待していない動作を引き起こすことになります。