[ C++で開発 ]

GNU makeの使い方

更新日:

C++のビルドをGNU makeで行います。

Makefile記述テンプレート

単一ディレクトリで1つの実行ファイル作成用

Hello makefile(Ver.1)

一つのsrcディレクトリ、一つのincludeディレクトリからなるソースファイルをmakeし、一つの実行ファイルを作成します。

PROGRAM = hello.exe
SRCS = Hello.cc Main.cc
OBJS = $(subst .cc,.o,$(SRCS))

RM := rm
CXX := g++
CC := g++

CPPFLAGS = -I../include
LDFLAGS = -mno-cygwin

$(PROGRAM): $(OBJS)
        $(LINK.o) $^ $(LOADLIBES) -o $@

.PHONY: clean

clean:
        $(RM) $(OBJS) $(PROGRAM)

.ccファイルから.oファイルへコンパイルするルールは、GNU makeの暗黙のルールが使われています。

makeを実行すると、

  1. まずデフォルト(最初に登場する)ターゲットである$(PROGRAM)、つまりhello.exeターゲットが評価される。ターゲットが実行される条件は次のとおり。 評価されるマクロ
    $(CC) $(LDFLAGS) $(TARGET_ARCH) $^ $(LOADLIBES) -o $@
    マクロが展開され実行される例
    g++ -mno-cygwin Hello.o Main.o -o hello.exe
  2. $(OBJS)ターゲットつまりHello.oとMain.oが存在しなければ、Hello.oおよびMain.oをターゲットとして評価する。ここで、ターゲットがMakefileに定義されていないため、暗黙のルールが適用される。 評価されるマクロ
    $(COMPILE.cc) $(OUTPUT_OPTION) $<
    展開される中間マクロ
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<
    マクロが展開され実行される例
    g++ -mno-cygwin -I../include -c -o Hello.o Hello.cc

make中で使用するコマンド名は、移植性を考慮して変数で表すのがよい習慣です。

RM := rm
CXX := g++
CC := g++

再帰展開する必要がないので、単純展開変数として代入(:=)を使用しています。

ファイルが存在しないターゲット(擬似ターゲット)は、.PHONYで指定します。偶然擬似ターゲットと同じ名前のファイルが存在してしまった場合の誤動作を防ぎます。

.PHONY: clean 

ソースファイルをMakefile中で指定するのではなく、その場でカレントディレクトリにあるものを対象とすることもできます。

SRCS := $(wildcard *.cc)
OBJS = $(subst .cc,.o,$(SRCS))

Hello makefile(Ver.2)

Ver.1では、ヘッダーファイルの変更はmakeに検知されません。そこで、ヘッダーファイルの変更を検知できるように依存関係の記述を追加します。

PROGRAM = hello.exe
SRCS = Hello.cc Main.cc
OBJS = $(subst .cc,.o,$(SRCS))

RM := rm
MV := mv
CXX := g++
CC := g++
SED := sed

CPPFLAGS = -I../include
LDFLAGS = -mno-cygwin

dependencies = $(subst .o,.d,$(OBJS))

$(PROGRAM): $(OBJS)
        $(LINK.o) $^ $(LOADLIBES) -o $@

.PHONY: clean

clean:
        $(RM) $(OBJS) $(PROGRAM) $(dependencies)

ifneq "$(MAKECMDGOALS)" "clean"
include $(dependencies)
endif

%.d: %.cc
        $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M $< | \
        $(SED) 's,\($(notdir $*)\.o\) *:,$(dir $@)\1 $@: ,' >$@.tmp
        $(MV) $@.tmp $@

ソースファイル(.cc)とヘッダーファイル(.hh)の依存関係は、.dファイルに記述します。.dファイルは、gccの-Mオプションで生成される依存関係ファイルからsedコマンドを使って抽出します。(他のコンパイラではオプションと出力形式が違うので調整が必要)

.dファイルは都度修正されるので、Makefileからincludeするようにしています。

複数ディレクトリで複数ライブラリ・実行ファイル作成用

ディレクトリに対して再帰的に実行するMakefileを記述するやり方は、書籍「GNU Make 第3版」では推奨されていません。代わりに、各ディレクトリの設定を記述した個別ファイルをインクルードした1つのMakefileで全体を制御する方法を推奨しています。

[To Be Description]


GNU makeの設定

自動変数

変数名 内容
$@ ターゲットのファイル名
$% ライブラリの構成指定中の要素
$< 最初の必須項目のファイル名
$? ターゲットより最新の必須項目をスペースで区切ったリスト
$^ すべての必須項目をスペースで区切ったリスト(重複は除く)
$+ すべての必須項目をスペースで区切ったリスト(重複を含む)
$* ターゲットファイル名のサフィックスを除いたファイル名(basename)

GNU makeのデフォルトシンボル

シンボル 内容 デフォルトの定義
CXX C++コンパイルコマンド名 g++
CXXFLAGS C++コンパイルオプション 未定義
CPPFLAGS プリプロセッサ用オプション 未定義
TARGET_ARCH 未定義
LDFLAGS リンカ用オプション 未定義
COMPILE.cc C++のコンパイル実行 $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
COMPILE.C $(COMILE.cc)
COMPILE.cpp $(COMILE.cc)
LINK.cc C++のリンク実行
(ソースから一気に実行体を作る)
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
LINK.C $(LINK.cc)
LINK.cpp $(LINK.cc)
LINK.o オブジェクトファイルのリンク $(CC) $(LDFLAGS) $(TARGET_ARCH)
OUTPUT_OPTION -o $@

GNU makeのデフォルト定義暗黙のルール

C/C++言語用のルールを抜粋しました。全てを見る場合、--print-data-base (-p)オプションを使います。

ルール 実行
%: %.o $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
%: %.cc $(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@
%.o: %.cc $(COMPILE.cc) $(OUTPUT_OPTION) $<
%: %.C $(LINK.C) $^ $(LOADLIBES) $(LDLIBS) -o $@
%.o: %.C $(COMPILE.C) $(OUTPUT_OPTION) $<
%: %.cpp $(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@
%.o: %.cpp $(COMPILE.cpp) $(OUTPUT_OPTION) $<
.cc.o: $(COMPILE.cc) $(OUTPUT_OPTION) $<
.cc: $(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@
.C.o: $(COMPILE.C) $(OUTPUT_OPTION) $<
.C: $(LINK.C) $^ $(LOADLIBES) $(LDLIBS) -o $@
.cpp.o: $(COMPIILE.cpp) $(OUTPUT_OPTIONS) $<
.cpp: $(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@

暗黙ルールを削除するならば、空のルールを記述します。

%.o: %.l

パターンルールとサフィックスルール

パターンルールは、ファイル名本体(basename)を'%'で示し、ルールを記述します。

%.o: %.c
        $(COMPILE.c) $(OUTPUT_OPTION) $<

パターンルール中ではファイル名の中で%は1度しか使えません。

特定のターゲットに対してのみパターンルールを適用させることができます。

$(OBJECTS): %.o: %.c
        $(CC) -c $(CPPFLAGS) $< -o $@

$(OBJECTS)に記述されたファイルを構築するときにのみパターンルールを適用します。

サフィックスルールは、旧来のmakeとの互換性のために残されています。GNU makeで記述する場合はパターンルールを使うのがベターです。

変数定義の優先順序

デフォルトでは以下の順序で変数定義の優先度が決まります。

  1. コマンドラインで指定: makeのコマンドラインオプションで指定した定義
  2. ファイル中で指定: makefileやインクルードされたファイルの中での定義
  3. 環境変数で指定

2.を最優先にするには、変数を定義する際にoverride命令を使います。

3.を最優先にするには、makeのコマンドラインオプションで--environment-overrides (-e)を指定します。

組み込み関数

関数名 内容
$(filter パターン...,テキスト) テキストを空白で区切ったリストとみなし、パターンと一致したものを返す(完全一致)
$(filterout パターン..., テキスト) パターンに一致しないものを返す
$(findstring 文字列,テキスト) テキスト中に文字列を探し、見つかれば文字列自身、見つからねば何も返さない
$(subst 検索文字列,置換文字列,テキスト) 単純置換。空白に注意
$(patsubst 検索パターン,置換パターン,テキスト) ワイルドカード(%)が使える置換
$(変数:検索文字列=置換文字列) 参照置換。単語末尾に存在する検索文字列を置換
$(words テキスト) テキスト中の単語数を返す
$(word n, テキスト) テキスト中のn番目の単語を返す(n>=1)
$(firstword テキスト) テキスト中の最初の単語を返す
$(wordlist 開始,終了,テキスト) テキスト中の開始番目〜終了番目までの単語列を返す
$(sort リスト) 重複を除去し辞書順にソート
$(shell コマンド)
$(wildcard パターン...)
$(dir リスト...) ディレクトリ部分だけを返す
$(notdir リスト...) ファイル名部分だけを返す
$(suffix 名前...) サフィックスのリストを作って返す
$(basename 名前...) サフィックスを取り除いた部分を返す
$(addsuffix サフィックス,名前...) 名前にサフィックスを追加
$(addprefix プレフィックス,名前...) 名前にプレフィックスを追加
$(join プレフィックスリスト,サフィックスリスト) dirとnotdirを補完する役割、それぞれのリストの1番目同士、2番目同士、‥を連結
$(if 条件,真のときに実行,偽のときに実行)
$(error テキスト) テキスト表示後、makeは終了ステータス2で終了する
$(foreach 変数,リスト,実行部)
$(strip テキスト) 前後の空白削除、途中の空白列を1つの空白に置換
$(origin 変数) 変数の出自を返却。undefined | default | environment | environment-override | file | command-line | override | automatic
$(worning テキスト) makeは終了しない

VPATH

makeコマンドに、カレントディレクトリ以外からファイルを探させるための仕組みです。2つの設定方法があります。

  1. VPATH = src
    ディレクトリのリストを指定します。
  2. vpath %.c src
    パターンに合致するファイルを探すディレクトリのリストを指定します。

代入演算子

:=
単純展開変数を設定します。代入行はmakefileから読み込まれると右辺が即時評価されます。
=
再帰展開変数を設定します。代入行はmakefileから読み込まれた時点では評価されず、使用されるたびに右辺が再評価されます。
?=
条件付代入を行います。変数が値を持っていない場合に代入が実行されます。これは環境変数と組み合わせて使用されることが多いです。
+=
アペンドを行います。通常再帰展開変数に自己参照が含まれると無限ループになります。そのようなときはこれを使います。

マクロ

define文を使って複数行のコマンド実行をマクロ定義することができます。

define create-jar
  @echo Creating $@...
  $(RM) $(TMP_JAR_DIR)
  $(MKDIR) $(TMP_JAR_DIR)
  $(CP) -r $^ $(TMP_JAR_DIR)
  cd $(TMP_JAR_DIR) && $(JAR) $(JARFLAGS) $@ .
  $(JAR) -ufm $@ $(MANIFEST)
  $(RM) $(TMP_JAR_DIR)
endef

このマクロを実行するには、通常の変数と同様に呼び出します。

$(UI-JAR): $(UI_CLASSES)
        $(create-jar)

マクロは引数を伴うこともできます。define句の中では、$1, $2, ...のように参照します。

条件判断

ifdef, ifndef

ifdef COMSPEC
  PATH_SEP := ;
else
  PATH_SEP := :
endif

ifeq, ifneq

参考書籍・URL

書籍

URL