2017年8月9日水曜日

暗黙ルールを駆使した Makefile の雛形

Makefile には知る人ぞ知る暗黙ルールがたくさんあるので,今回はそれを紹介したいと思う.

例えば,main.c というソースをコンパイルする Makefile を普通に書くと以下のような感じだが,
main:   main.o
    gcc -o $@ $<

main.o: main.c
    gcc -c -o $@ $<

実はこんなに長ったらしく書かなくても,ただ単に
main:   main.o

と書くだけで十分である.両者の実行結果はそれぞれ以下のようになる.(それぞれ上下が対応)
$ make
gcc -c -o main.o main.c
gcc -o main main.o
$ make
cc    -c -o main.o main.c
cc   main.o   -o main

gcc と cc の違いはあるものの,下側の例では依存関係も,コンパイルのコマンドも書かずに問題なくビルドできる.これは以下の Makefile の暗黙ルールに依るものだ.

【暗黙ルールの例】
main.o が無いとき,同ディレクトリに,main.c があれば,cc で,main.cpp があれば g++ でコンパイルする.
ただし,変数 CC が指定されている場合,コンパイラには CC を用いる.

したがって,下側の Makefile を以下のように書き換えると,上側の Makefile と全く同じことができるようになる.
CC = gcc

main:   main.o

全ての暗黙ルールは Makefile が存在しない場所で,-pオプションをつけてmakeコマンドを実行すると表示できる.
数が非常に多いため,以下に,上で紹介した例に関係のある箇所を抜粋した.【2019.7.16追記】
$ make -p
# GNU Make 4.2.1
# このプログラムは x86_64-pc-linux-gnu 用にビルドされました
# Copyright (C) 1988-2016 Free Software Foundation, Inc.
# ライセンス GPLv3+: GNU GPL バージョン 3 以降 
# これはフリーソフトウェアです: 自由に変更および配布できます.
# 法律の許す限り、 無保証 です.

# Make データベース出力 Tue Jul 16 14:32:12 2019

# 変数
# デフォルト
OUTPUT_OPTION = -o $@
# デフォルト
CC = cc
# デフォルト
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
# デフォルト
LINK.o = $(CC) $(LDFLAGS) $(TARGET_ARCH)

# 暗黙ルール
%: %.o
#  実行するレシピ (ビルトイン):
 $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
%.o: %.c
#  実行するレシピ (ビルトイン):
 $(COMPILE.c) $(OUTPUT_OPTION) $<

よくCFLAGSや,LDFLAGS,LDLIBS等の変数が設定されたMakefileを見かけるのは上記のように暗黙ルールで使用されているからだ.これらの変数や暗黙ルールをうまく利用して Makefile を書くと,見やすく,かつミスの少ない Makefile を書くことができる.

個人的に最終形と言える汎用性の高い Makefile の雛形を以下に示す.【2019.7.16修正】
ディレクトリ構成
-----
├── Makefile
└── src
    ├── main.cpp
    ├── sample01.cpp
    ├── sample02.cpp
    └── sample03.cpp

Makefile
-----
# ディレクトリを変数に設定
BINDIR = ./bin/
OBJDIR = ./obj/
SRCDIR = ./src/

# ファイルを変数に設定
PROG = $(BINDIR)main
SRCS = $(wildcard $(SRCDIR)*.cpp)
OBJS = $(addprefix $(OBJDIR), $(notdir $(SRCS:.cpp=.o)))
DEPS = $(OBJS:.o=.d)
GCNO = $(OBJS:.o=.gcno)
GCDA = $(OBJS:.o=.gcda)

# 環境変数を設定
CC       = $(CXX)
LDLIBS   = -lgcov\
           -lm
LDFLAGS  = -L/usr/local/lib
CXXFLAGS = -std=c++11\
           -g\
           -O0\
           -MP -MMD\
           -coverage\
           -Wall -Wextra

.PHONY: all clean

all: $(PROG)

# make cleanが呼ばれたら,中間生成ファイル及び
# BINDIR,OBJDIRを削除する
clean:
 $(RM) $(PROG) $(OBJS) $(DEPS) $(GCNO) $(GCDA)
 @[ -d $(OBJDIR) ] && rmdir $(OBJDIR) || echo -n
 @[ -d $(BINDIR) ] && rmdir $(BINDIR) || echo -n

# 実行ファイル生成のルール
$(BINDIR)%: $(OBJDIR)%.o
 @mkdir -p $(BINDIR)
 $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

# オブジェクトファイル生成のルール
$(OBJDIR)%.o: %.cpp
 @mkdir -p $(OBJDIR)
 $(COMPILE.cpp) $(OUTPUT_OPTION) $<

# cppファイルをサーチするディレクトリを追加
vpath %.cpp $(SRCDIR)

# 依存関係ファイルのインクルード
# allよりも上に記述してはいけない
-include $(DEPS)

# PROGの依存関係は最後に記述する
$(PROG): $(OBJS)

ソースファイルが増えたら,sample03.c の下にどんどん追加していくだけで,正確にビルドできるようになっていくので,とても便利だ.ソースファイルはワイルドカードで読み込まれるため,Makefileに追加する必要はない.【2019.7.16見直し】
これを make 及び make clean すると,それぞれ以下のようになる.

$ make
g++ -std=c++11 -g -O0 -MP -MMD -coverage -Wall -Wextra   -c -o obj/main.o ./src/main.cpp
g++ -std=c++11 -g -O0 -MP -MMD -coverage -Wall -Wextra   -c -o obj/sample03.o ./src/sample03.cpp
g++ -std=c++11 -g -O0 -MP -MMD -coverage -Wall -Wextra   -c -o obj/sample01.o ./src/sample01.cpp
g++ -std=c++11 -g -O0 -MP -MMD -coverage -Wall -Wextra   -c -o obj/sample02.o ./src/sample02.cpp
g++ -L/usr/local/lib  obj/main.o obj/sample03.o obj/sample01.o obj/sample02.o  -lgcov -lm -o bin/main
$ make clean
rm -f ./bin/main ./obj/sample03.o ./obj/sample01.o ./obj/sample02.o ./obj/main.o ./obj/sample03.d ./obj/sample01.d ./obj/sample02.d ./obj/main.d
./obj/sample03.gcno ./obj/sample01.gcno ./obj/sample02.gcno ./obj/main.gcno ./obj/sample03.gcda ./obj/sample01.gcda ./obj/sample02.gcda ./obj/main.gcda


0 件のコメント:

コメントを投稿