GNU Make の使い方
GNU Make 第3版では、大きなプロジェクトで良くある、複数ディレクトリにまたがったプロジェクトを管理する場合に、ディレクトリ毎のMakeを再帰的に呼出す方法は推奨してません。また、.ccファイルの#include 依存関係をまずスクリプトで調査してMakefileを生成する方法も、かなりスマートでないということを知りました。ほんと、あたしってば物をしらない。
この無知さかげんは、Makefileを人の読むモノだと思っていなかったのが原因です。読まないで、自動でツールに作らせるから、とりあえず動けばOK。エレガントな makefileを作ろうと全然努力してないのだから、無知なのは当たり前よね。
さて、以下は本日の作業メモ。勉強のための模擬プロジェクトを作って GNU Make 第3版 を見ながらいろいろやってみました。こんなディレクトリ構成でビルドを行うmakefileです。
project + src + app + module.mk #コンポーネントの設定ファイル + main.cc + main.h + cmdanalizer + module.mk #コンポーネントの設定ファイル + cmdHoge.cc + cmdHoge.h + makefile
まずは project/makefile のコード
#---------------------------------------------------------- # User function #---------------------------------------------------------- # $(call source-to-object, source-file-list) source-to-object = $(subst .cc,.o,$(filter % .cc,$1)) # $(subdirectory) subdirectory = $(patsubst %/module.mk, %, \ $(word \ $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))) # $(call make-library, library-name, source-file-liset) define make-library libraries += $1 sources += $2 $1: $(call source-to-object,$2) $(AR) $(ARFLAGS) $$@ $$^ endef #---------------------------------------------------------- # Initialize variables #---------------------------------------------------------- # プロジェクトを構成するモジュールへの # 相対パスを列挙する modules := src/cmdanalizer src/app # 各モジュールの情報を、次の3つの変数に収集する # ここでは、単純変数として初期化する programs := sources := libraries := objects = $(subst .cc,.o,$(sources)) dependencies = $(subst .cc,.d,$(sources)) # 検索パスを生成 # (Make用(->vpath) と Cコンパイラ(-I option)用) include_dirs := $(modules) CPPFLAGS += $(addprefix -I ,$(include_dirs)) vpath %.h &(include_dirs) # デフォルトパラメータ CC := g++ MV := mv -f RM := rm -f SED := sed #---------------------------------------------------------- # Collect modules infomation #---------------------------------------------------------- # defaut target 問題の回避 # (includeの中でも targetが居る) && # (all: は include してからでないと駄目) # → 先に all: を空で定義し、後で上書き all: # 全てのモジュール中の module.mk ファイルをインクルードする。 # (この時、モジュール情報が 上記3変数に追加されていく) include $(addsuffix /module.mk,$(modules)) #---------------------------------------------------------- # Rules #---------------------------------------------------------- .PHONY: all all: $(programs) .PHONY: libraries libraries: $(libraries) .PHONY: clean clean: $(RM) $(objects) $(programs) $(libraries) $(dependencies) ifneq "$(MAKECMDGOALS)" "clean" include $(dependencies) endif # 依存関係の自動生成 # gcc -MM オプションを利用 %.d: %.cc $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -MM $< | \ $(SED) 's,\($(notdir $*)\.o\) *:,$(dir $@)\1 $@: ,' > $@.tmp $(MV) $@.tmp $@
次に、各モジュールの設定ファイル(project/src/cmdanalizer/module.mk)。
親のmakefile で定義したユーザ定義関数を使ってます。
# ディレクトリ直下の .cc ファイルは勝手に対象にする local_src := $(wildcard $(subdirectory)/*.cc) # このアーカイブのルールを生成 $(eval $(call make-library, \ $(subdirectory)/libcmdanalizer.a, $(local_src)))
以上、GNU Make 第3版の 6章を見ながら作成しました。ハッキリいってほぼ一緒ですが、部分的に自プロジェクトへの要求を反映した形にカスタマイズしています。
肝は、.ccファイル中の #include ディレクティブで発生する依存関係の自動解決と、各モジュール設定ファイルに情報を設定させておいて、ルートのmakefileで一気にmake するという構造です。
gcc を -M オプションで実行すると、Makeスタイルの依存関係リストを作ってくれます。(ここではシステムファイルは含めない-MMオプションを使います)。これをPerlとかでかき集めてMakefileを生成のも一つの手ですが、一つの.ccファイルにつき 一つの.d ファイルを作り、これを Make ルールで依存関係管理をさせましょう、というアプローチ。-Mオプションで出来るパス文字の関係で sedで編集している部分で見通しが悪くなっていますが、やってることは単純です。
一方全体の構築ですが、makefile 中の変数(programs, objects, sources)に対し、各モジュールの設定ファイルが、自身のソース、オブジェクト、ライブラリを追加していき、それを元に一気にメイクする構造です。各モジュールの設定ファイルは、
local_dir := rlsp/cmdanalizer local_lib := $(local_dir)/libcmdanalizer.a local_src := $(addprefix $(local_dir)/,cmdHoge.cc) local_objs := $(subst .cc,.o,$(local_src)) libraries += $(local_lib) sources += $(local_src) $(local_lib): $(local_objs) $(AR) $(ARFLAGS) $@ $^
の様にパリッと書いても良いのですが、一寸したユーザ定義関数を書くことで2行になっちゃいます。(これも GNU Make 第3版の受け売り)
さて、このサンプルmakefileはコメント多めでも90行くらい。あとやりたいことはソースツリーとバイナリツリーの分離くらいで、正直今まで 某IDE を使ってmakefile生成してたり、他部門から流れてきていた makefile 生成 Perl を使ったりしていたことが、この行数で Make だけで出来てしまうのだから、今までどれだけMakeを使いこなせなかったのか、という事になると思います。