汎用(?)OMakefile を作ってみた

autotools(autoconf/automake/libtool) の設定のあまりの複雑さに挫折してしまった人にとって、OMake は救世主といっても過言ではないはず。
OMake とはなんぞやという人は以下のサイトを見るといいかも。

マニュアルを日本語化してくださってる方もいて、感謝感謝。


ただ、使っていて

  • ファイル間の依存関係は気にしなくてもいいけど、プログラムのビルドに必要なファイルは指定する必要がある。
  • サブディレクトリにも OMakefile がいる

という点が気になった。


このままだとファイルやディレクトリを追加するたびに OMakefile を修正・コピーする手間がかかってしまう。
というわけで、ルートディレクトリの OMakefile 1 つで上記の 2 点を無理やり解決してみた。


作成した OMakefile は次のようなディレクトリ構成のプログラムを想定している。
OMakefile, OMakeroot を含めたサンプルソース一式は こちら

 - ルートディレクトリ/
    +--- include/
    +--- src/
    |     +--- (複数階層のサブディレクトリにソースファイルを配置)
    +--- test/
    |     +--- (同じく複数階層のサブディレクトリにテストコードを配置)
    +--- OMakeroot (omake --install で作成されたまま変更なし)
    +--- OMakefile (唯一の OMakefile)


以下が作成した OMakefile の全体。

########################################################################
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this file, to deal in the File without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the File, and to permit persons to whom the
# File is furnished to do so, subject to the following condition:
#
# THE FILE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE FILE OR
# THE USE OR OTHER DEALINGS IN THE FILE.

INCLUDE_DIR = include
SRC_DIR = src
TEST_DIR = test

PROGRAM = calc
TEST_PROGRAM = $(TEST_DIR)/calc_test

CXXFLAGS += -O2 -Wall
ASFLAGS +=
LDFLAGS +=
INCLUDES += $(dir $(INCLUDE_DIR) $(SRC_DIR))

.PHONY: all clean test


########################################################################
# Target Program
#

CXXFILES[] = $(removesuffix $(filter %.cpp, $(ls R, $(SRC_DIR))))
CXXProgram($(PROGRAM), $(CXXFILES))


########################################################################
# Test Program
#

TEST_CXXFILES[] = $(filter-out %main, $(CXXFILES)) $(removesuffix $(filter %.cpp, $(ls R, $(TEST_DIR))))

section
	CXXFLAGS += -lgtest -pthread
	CXXProgram($(TEST_PROGRAM), $(TEST_CXXFILES))


########################################################################
# Subdirectories
#

foreach(d, $(subdirs $(SRC_DIR) $(TEST_DIR)))
	.SUBDIRS: $(d)
		if $(file-exists OMakefile)
			include OMakefile
		else
			clean:
				rm -f *.o


########################################################################
# Build targets
#

test: $(TEST_PROGRAM)

clean:
	rm -f $(PROGRAM) $(TEST_PROGRAM)

all: $(PROGRAM)$(EXE)

.DEFAULT: all


簡単に説明すると、

INCLUDE_DIR = include
SRC_DIR = src
TEST_DIR = test

PROGRAM = calc
TEST_PROGRAM = $(TEST_DIR)/calc_test

CXXFLAGS += -O2 -Wall
ASFLAGS +=
LDFLAGS +=
INCLUDES += $(dir $(INCLUDE_DIR) $(SRC_DIR))

ディレクトリやビルドオプションの設定部。テスト用プログラムはなんとなく test ディレクトリ内に作るようにしてみた。
この OMakefile を他のプログラムに使いまわす場合、大抵のプログラムならこの部分を変えるだけで問題ないと思う。

########################################################################
# Target Program
#

CXXFILES[] = $(removesuffix $(filter %.cpp, $(ls R, $(SRC_DIR))))
CXXProgram($(PROGRAM), $(CXXFILES))

工夫点その 1。
ビルドに必要なファイルを指定する際に、ls と filter を使うことで src ディレクトリ以下の cpp ファイルをすべて指定している。
こうすることでソースファイルが増えても設定ファイルを変更する必要がない。
しかも、隠しファイルや隠しディレクトリ内のファイルは自動的に除外される。

########################################################################
# Test Program
#

TEST_CXXFILES[] = $(filter-out %main, $(CXXFILES)) $(removesuffix $(filter %.cpp, $(ls R, $(TEST_DIR))))

section
	CXXFLAGS += -lgtest -pthread
	CXXProgram($(TEST_PROGRAM), $(TEST_CXXFILES))

テストには Google Test を使うことを想定。
テストプログラムをビルドする時だけ libgtest.so をリンクするようにする。
実際のプログラムの main 関数とテスト用の main 関数を切り替えるために、テスト時には main.cpp を除外してコンパイルするようにしている。
ファイル名決め打ちになってしまっているので、この辺はもっとスマートにしたい。

########################################################################
# Subdirectories
#

foreach(d, $(subdirs $(SRC_DIR) $(TEST_DIR)))
	.SUBDIRS: $(d)
		if $(file-exists OMakefile)
			include OMakefile
		else
			clean:
				rm -f *.o

工夫点その 2。
サブディレクトリに OMakefile を置く代わりに、.SUBDIRS の下に直接 OMakefile の中身を記述できることを利用。
サブディレクトリでは clean さえできれば事足りるとは思うけど、特定のディレクトリで何か特別なことをさせたい場合、そのディレクトリに OMakefile を置けばそちらが優先されるようにした。
ちなみに、この部分よりも上で .PHONY を設定する必要がある。

########################################################################
# Build targets
#

test: $(TEST_PROGRAM)

clean:
	rm -f $(PROGRAM) $(TEST_PROGRAM)

all: $(PROGRAM)$(EXE)

.DEFAULT: all

ビルドターゲットの設定。
特に変わったことはしていない。



今のところ、単一環境でのビルドにしか使用してないので、上記程度の OMakefile で充分間に合ってる。
でも、マニュアルを見ると autoconf 相当のこともできるようなので、また機会があったら調べてみよう。