はじめに
転職してLinuxのお仕事に戻ってきたので、Makefile等を触る機会が増えた。
ただし、ソースファイルが追加になるたびにメンテするのが煩わしいのでその対策として、
Makefileのひな型を作成した。
(2017/03/05 Makefile一部追加修正対応)
#やりたいこと
・Makefileを極力メンテしたくない
・ソースファイルを自動的に追加してビルドしたい
・CppUTestを静的ライブラリで導入する
・もちろんテストコードとターゲットコードは分離したい
・objやbinを所定のフォルダに出力したい
・release,debugも両方ビルドしたい
ツリー構成
このMakefileが対象とするツリーは、以下のような構成を想定している。
$ tree -F
.
|-- Makefile
|-- bin/
|-- header/
| `-- subdir/
| `-- submodule.hpp
|-- obj/
|-- src/
| |-- hoge.cpp
| `-- subdir/
| `-- submodule.cpp
|-- test_header/
| |-- CppUTest/
| | `-- (省略)
| |-- CppUTestExt/
| | `-- (省略)
| `-- Platforms/
| `-- c2000/
| `-- (省略)
|-- test_lib/
| |-- CppUTest/
| | `-- libCppUTest.a
| `-- CppUTestExt/
| `-- libCppUTestExt.a
|-- test_log/
|-- test_obj/
`-- test_src/
|-- subdir/
| `-- test_submodule.cpp
`-- test_hoge.cpp
#Makefile
#!/usr/bin/make
###
# Makefile
###
###
# コンパイラ設定
###
CXX = g++
CXXFLAGS = -std=c++0x -Werror -Wall -Wextra -Wfloat-equal -Wmissing-include-dirs
LDFLAGS =
LIBS =
INCLUDES = -I./header
RCXXFLAGS = $(CXXFLAGS) -O3
RLDFLAGS = $(LDFLAGS)
RLIBS = $(LIBS)
RINCLUDES = $(INCLUDES)
DCXXFLAGS = $(CXXFLAGS) -O0 -g
DLDFLAGS = $(LDFLAGS)
DLIBS = $(LIBS)
DINCLUDES = $(INCLUDES)
TCXXFLAGS = $(CXXFLAGS)
TLDFLAGS = $(LDFLAGS) -lCppUTest -lCppUTestExt
TLIBS = $(LIBS) -L./test_lib/CppUTest -L./test_lib/CppUTestExt
TINCLUDES = $(INCLUDES) -I./test_header
###
# 実行ファイル名
###
RTARGETS = hoge
DTARGETS = debug_hoge
TTARGETS = test_hoge
###
# ディレクトリ指定
###
RTARGETDIR = ./bin/release
DTARGETDIR = ./bin/debug
TTARGETDIR = ./bin/test
ROBJECTDIR = ./obj/release
DOBJECTDIR = ./obj/debug
TOBJECTDIR = ./obj/test
TLOGDIR = ./test_log
###
# ソースコードディレクトリ指定
###
SOURCEDIR = ./src
TSOURCEDIR = ./test_src
###
# テスト用除外ソース指定
###
TARGETMAINSRC = hoge.cpp
###
# 処理部
###
# 1. サブディレクトリを含むディレクトリリストの生成
SRCDIRLIST := $(shell find $(SOURCEDIR) -type d)
TSRCDIRLIST := $(shell find $(TSOURCEDIR) -type d)
# 2. 全てのcppファイルのリストの生成
SRCLIST = $(foreach srcdir, $(SRCDIRLIST), $(wildcard $(srcdir)/*.cpp))
TSRCLIST = $(foreach testsrcdir, $(TSRCDIRLIST), $(wildcard $(testsrcdir)/*.cpp))
# 3. トリミング
CUTSRCLIST = $(subst $(SOURCEDIR),.,$(SRCLIST))
CUTTSRCLIST = $(subst $(TSOURCEDIR),.,$(TSRCLIST))
# 4. オブジェクトファイル名の決定
ROBJLIST = $(addprefix $(ROBJECTDIR)/, $(CUTSRCLIST:.cpp=.o))
DOBJLIST = $(addprefix $(DOBJECTDIR)/, $(CUTSRCLIST:.cpp=.o))
TOBJLIST = $(addprefix $(TOBJECTDIR)/, $(CUTTSRCLIST:.cpp=.o))
# 5. テスト用にmainを含むファイルの除外
TEMPSRCLIST = $(filter-out %$(TARGETMAINSRC), $(CUTSRCLIST))
TMODULELIST = $(addprefix $(DOBJECTDIR)/, $(TEMPSRCLIST:.cpp=.o))
# 6. ディレクトリ構造のリスト化
ROBJDIRLIST = $(addprefix $(ROBJECTDIR)/, $(SRCDIRLIST))
DOBJDIRLIST = $(addprefix $(DOBJECTDIR)/, $(SRCDIRLIST))
TOBJDIRLIST = $(addprefix $(TOBJECTDIR)/, $(TSRCDIRLIST))
# 7. 各種ビルドターゲット設定
.PHONY: all build clean debugbuild debugclean testbuild testclean testrun testlog allbuild allclean
all: allclean allbuild
build: $(RTARGETS)
clean:
rm -f $(ROBJLIST) $(RTARGETDIR)/$(RTARGETS)
debugbuild: $(DTARGETS)
debugclean:
rm -f $(DOBJLIST) $(DTARGETDIR)/$(DTARGETS)
testbuild: $(DTARGETS) $(TTARGETS)
testclean:
rm -f $(TOBJLIST) $(DOBJLIST) $(TTARGETDIR)/$(TTARGETS)
testrun: testclean testbuild
chmod +x $(TTARGETDIR)/$(TTARGETS)
$(TTARGETDIR)/$(TTARGETS) -v
testlog: testclean testbuild
chmod +x $(TTARGETDIR)/$(TTARGETS)
$(TTARGETDIR)/$(TTARGETS) -ojunit
@if [ ! -e $(TLOGDIR) ]; then mkdir -p $(TLOGDIR); fi
mv *.xml $(TLOGDIR)
allbuild: build debugbuild testbuild
allclean: clean debugclean testclean
# 8. ターゲット実行ファイルの生成
$(RTARGETS): $(ROBJLIST)
@echo "$^"
@if [ ! -e $(RTARGETDIR) ]; then mkdir -p $(RTARGETDIR); fi
$(CXX) -o $(RTARGETDIR)/$@ $^ $(RLDFLAGS) $(RLIBS)
$(DTARGETS): $(DOBJLIST)
@echo "$^"
@if [ ! -e $(DTARGETDIR) ]; then mkdir -p $(DTARGETDIR); fi
$(CXX) -o $(DTARGETDIR)/$@ $^ $(DLDFLAGS) $(DLIBS)
$(TTARGETS): $(TOBJLIST)
@echo "$^"
@if [ ! -e $(TTARGETDIR) ]; then mkdir -p $(TTARGETDIR); fi
$(CXX) -o $(TTARGETDIR)/$@ $^ $(TMODULELIST) $(TLDFLAGS) $(TLIBS)
# 9. 中間バイナリの生成
$(ROBJECTDIR)/%.o: $(SOURCEDIR)/%.cpp
@if [ ! -e `dirname $@` ]; then mkdir -p `dirname $@`; fi
$(CXX) $(RCXXFLAGS) $(RINCLUDES) -o $@ -c $<
$(DOBJECTDIR)/%.o: $(SOURCEDIR)/%.cpp
@if [ ! -e `dirname $@` ]; then mkdir -p `dirname $@`; fi
$(CXX) $(DCXXFLAGS) $(DINCLUDES) -o $@ -c $<
$(TOBJECTDIR)/%.o: $(TSOURCEDIR)/%.cpp
@if [ ! -e `dirname $@` ]; then mkdir -p `dirname $@`; fi
$(CXX) $(TCXXFLAGS) $(TINCLUDES) -o $@ -c $<
#実行結果
$ make allbuild
$ tree -f
.
|-- Makefile
|-- bin/
| |-- debug/
| | `-- debug_hoge*
| |-- release/
| | `-- hoge*
| `-- test/
| `-- test_hoge*
|-- header/
| `-- subdir/
| `-- submodule.hpp
|-- obj/
| |-- debug/
| | |-- hoge.o
| | `-- subdir/
| | `-- submodule.o
| |-- release/
| | |-- hoge.o
| | `-- subdir/
| | `-- submodule.o
| `-- test/
| |-- subdir/
| | `-- test_submodule.o
| `-- test_hoge.o
|-- src/
| |-- hoge.cpp
| `-- subdir/
| `-- submodule.cpp
|-- test_header/
| |-- CppUTest/
| | `-- (省略)
| |-- CppUTestExt/
| | `-- (省略)
| `-- Platforms/
| `-- c2000/
| `-- (省略)
|-- test_lib/
| |-- CppUTest/
| | `-- libCppUTest.a
| `-- CppUTestExt/
| `-- libCppUTestExt.a
|-- test_log/
|-- test_obj/
`-- test_src/
|-- subdir/
| `-- test_submodule.cpp
`-- test_hoge.cpp
#解説
設定値は、以下のように記載している。
RXXXX = リリースビルド関係
DXXXX = デバッグビルド関係
TXXXX = CppUTestビルド関係
基本的に触るのは処理部より上の設定のみ。
- ディレくトリ指定を合わせる
- 必要なコンパイル/ リンカオプションを設定する
- 実行
$ make
CppUTestを実施する場合は、
$make testrun
を叩くことでクリーン、コンパイル、テストを行う。
#まとめ
ソースフォルダ中のサブディレクトリ構造を追いかけビルドしてくれるため、
Makefileのメンテをほぼ気にしなくてよく、開発に集中できるようになった。
#課題
Includeとライブラリの自動更新の検討
→課題に書いていたIncludeとリンカの自動更新は不必要なものまで
取り込んでしまう恐れがあるのでやめたほうが無難そう。