2. OMakeクイックスタートガイド

2.1 概要

OMakeは複数のディレクトリにまたがって存在するソースファイルをビルドするために製作されたツールです。OMakeを使用したプロジェクトは通常、 OMakefile を各々のプロジェクトディレクトリに置き、 OMakeroot をプロジェクトのルートディレクトリに置きます。 OMakeroot には一般的なビルドルールを指定し、 OMakefiles にはそれぞれのサブディレクトリに固有のビルドパラメータを指定します。いったんOMakeを起動すると、OMakeはまず設定ファイルのあるディレクトリをスキャンし、すべての OMakefile を評価します。そしてプロジェクトは全体のビルドルールの集合としてビルドされます。

2.1.1 自動的な依存関係の解析

従来のmake(1)プログラムでは以前から依存関係の解析が問題となっていました。OMakeではこの問題を、依存関係を生成するコマンドを指定した .SCANNER ターゲットを追加することで解決しました。たとえば、以下のルール

.SCANNER: %.o: %.c
    $(CC) $(INCLUDE) -MM $<

.c ファイルの依存関係を生成する常套手段です。OMakeはソースファイルの依存関係を知る必要がある時点で、自動的にソースファイルをスキャンし、依存関係を特定します。

2.1.2 ファイル内容から依存関係を解析

どのファイルの依存関係が変更されたかを知るために、OMakeではMD5による要約を用いてチェックを行います。各々のディレクトリでOMakeが実行された後に、 OMakeは依存関係の情報をプロジェクトルートディレクトリの .omakedb ファイルに保存します。いったんOMakeが依存関係のルールファイルを保存したのであれば、OMakeが最後に実行された時点で、ターゲット、依存関係、ソースコードに関して変更がない場合、ビルドは実行されません。また最適化としてOMakeはMD5の再計算を、修正日時、サイズ、ノード番号に変更のないファイルに関しては実行しません。

2.2 既にmakeに慣れている人向けの注意事項

既にmakeコマンドに慣れているユーザがomakeを使う際には、以下のmakeとomakeの違いを列挙したリストについて留意しておきましょう。

  • omakeを用いると、あなたがビルドルールを自力で定義するよりもはるかに省力化できます。このシステムは多くのビルドイン関数を提供しています( StaticCLibraryCProgram など)。第13章(13. ビルド関数とユーティリティ)で説明した事項を用いると、これらのビルドはより簡単になります。
  • .SUFFIXES.suf1.suf2: などを用いた暗黙のルール(implicit rule)はサポートされていません。その代わりにあなたはワイルドカード %.suf2: %.suf1 を用いるべきです。
  • スコーピングは重要です。あなたは変数や .PHONY ターゲットを、使用する前に定義すべきです。(詳細は “8.10 .PHONY” を参照してください。)
  • サブディレクトリをプロジェクトに組み入れる際には、 .SUBDIRS: ターゲットを用います。(詳細は “8.8 .SUBDIRS” を参照してください。)

2.3 小さなCプログラムのビルド

新しいプロジェクトを作る際にもっとも簡単な方法は、カレントディレクトリをプロジェクトのルートディレクトリに変え、 omake --install とコマンドを入力してデフォルトの OMakefileOMakeroot をインストールすることです。

$ 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 という関数を使うことができます。 OMakefilehello_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 と書いてある行の前に CCCFLAGS 変数を再定義することで可能となります。たとえば、現在私たちは -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)

2.4 巨大なプロジェクト

プロジェクトが成長するにつれて、コードからライブラリをビルドしたいと思うかもしれません。ライブラリは 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)

2.5 サブディレクトリ

プロジェクトがさらに成長していく時点で、いくつかのディレクトリにコードファイルを分割することは良いアイデアです。現在私たちは libfoolibbar をサブディレクトリに置いてあるものとしましょう。

まず、 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)

変数 CCCFLAGS.SUBDIRS ターゲットの 前に 定義されてあることに注目してください。これらの変数はサブディレクトリでも保持されていますので、 libfoolibbar では 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.chello_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 ファイルの検索とリスト” を参照してください)。

2.6 その他の考慮事項

OMakeはまたリソースのあるサブディレクトリも扱うことができます。例えば、先ほどの foo/ ディレクトリの中に、さらにいくつかのサブディレクトリを保持しているような場合を考えてみましょう。 foo/OMakefilefoo/ ディレクトリ自身の .SUBDIRS ターゲットを保持しており、また各々のサブディレクトリもまたサブディレクトリ自身の OMakefile を保持しています。

2.7 OCamlプログラムのビルド

通常、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))

2.8 OMakefileとOMakeroot

プロジェクトを設定する際、OMakeは OMakefileOMakeroot の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 に設定すべきです。

2.9 複数のバージョンのサポート

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/OMakefiledebug/OMakefile としてリンクされます。

vmount 関数の動作はだいぶ透過的です。あなたは OMakefile をまるで src/ ディレクトリ内のファイルが関連付けられているかのように書くことができ、マウントについて気にする必要はありません。しかしながら、以下に示すいくつかの注意点を頭に入れておきましょう。

2.10 注意点

バージョン管理の目的で vmount 関数を用いた場合、コンパイルされたファイルをソースファイルから分離することをお勧めします。例えば、ソースディレクトリに src/foo.o ファイルが含まれている場合を考えましょう。もし src/foo.o がマウントされてしまったとすると、 foo.o ファイルはすべてのバージョンにおいて共通のファイルになってしまい、おそらくあなたが求めていた動作とは違うものになるでしょう。 src/ ディレクトリにはソースコード以外何もない状態にして、コンパイルされたコードは含めないようにしましょう。

vmount -l オプションを使用したときは、ソースファイルがプロジェクトから参照された場合のみ、バージョンディレクトリ内にリンクされます。ファイルシステムを用いる関数( $(ls ...) など)はあなたが期待していない動作を引き起こすことになります。