ページ

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 の雛形を以下に示す.【2021.2.8修正】
ディレクトリ構成
-----
├── Makefile
└── src
    ├── main.cpp
    ├── sample01.cpp
    ├── sample02.cpp
    ├── sample03.cpp
    └── dir
        ├── sample04.cpp
        ├── sample05.cpp
        └── sample06.cpp

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

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

# (環境変数を設定するための)変数を設定
OPTIONS  := std=c++17\
            O0\
            g\
            MP\
            MMD\
            coverage
WARNINGS := all\
            extra
INCLUDE  := $(sort $(dir $(SRCS)))
LIBDIRS  := /usr/local/lib
LIBS     := gcov\
            m
            
# 環境変数を設定
CC       := $(CXX)
CXXFLAGS := $(addprefix -,  $(OPTIONS))
CXXFLAGS += $(addprefix -W, $(WARNINGS))
CXXFLAGS += $(addprefix -I, $(INCLUDE))
LDFLAGS  := $(addprefix -L, $(LIBDIRS))
LDLIBS   := $(addprefix -l, $(LIBS))

.PHONY: all clean

all: $(PROG)

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

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

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

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

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

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

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

$ make
mkdir: ディレクトリ './obj/' を作成しました
g++ -std=c++17 -O0 -g -MP -MMD -coverage -Wall -Wextra -I./src/ -I./src/dir/   -c -o obj/main.o ./src/main.cpp
g++ -std=c++17 -O0 -g -MP -MMD -coverage -Wall -Wextra -I./src/ -I./src/dir/   -c -o obj/sample01.o ./src/sample01.cpp
g++ -std=c++17 -O0 -g -MP -MMD -coverage -Wall -Wextra -I./src/ -I./src/dir/   -c -o obj/sample02.o ./src/sample02.cpp
g++ -std=c++17 -O0 -g -MP -MMD -coverage -Wall -Wextra -I./src/ -I./src/dir/   -c -o obj/sample03.o ./src/sample03.cpp
g++ -std=c++17 -O0 -g -MP -MMD -coverage -Wall -Wextra -I./src/ -I./src/dir/   -c -o obj/sample04.o ./src/dir/sample04.cpp
g++ -std=c++17 -O0 -g -MP -MMD -coverage -Wall -Wextra -I./src/ -I./src/dir/   -c -o obj/sample05.o ./src/dir/sample05.cpp
g++ -std=c++17 -O0 -g -MP -MMD -coverage -Wall -Wextra -I./src/ -I./src/dir/   -c -o obj/sample06.o ./src/dir/sample06.cpp
mkdir: ディレクトリ './bin/' を作成しました
g++ -L/usr/local/lib  obj/main.o obj/sample01.o obj/sample02.o obj/sample03.o obj/sample04.o obj/sample05.o obj/sample06.o  -lgcov -lm -o bin/main
$ make clean
rm -f ./bin/main ./obj/main.o ./obj/sample01.o ./obj/sample02.o ./obj/sample03.o ./obj/sample04.o ./obj/sample05.o ./obj/sample06.o ./obj/main.d ./obj/sample01.d ./obj/sample02.d ./obj/sample03.d ./obj/sample04.d ./obj/sample05.d ./obj/sample06.d ./obj/main.gcno ./obj/sample01.gcno ./obj/sample02.gcno ./obj/sample03.gcno ./obj/sample04.gcno ./obj/sample05.gcno ./obj/sample06.gcno ./obj/main.gcda ./obj/sample01.gcda ./obj/sample02.gcda ./obj/sample03.gcda ./obj/sample04.gcda ./obj/sample05.gcda ./obj/sample06.gcda
rmdir: ディレクトリ './obj/' を削除しています
rmdir: ディレクトリ './bin/' を削除しています


0 件のコメント:

コメントを投稿