LoginSignup
10
11

More than 5 years have passed since last update.

メンテの少ないMakefile

Last updated at Posted at 2017-02-28

はじめに

転職してLinuxのお仕事に戻ってきたので、Makefile等を触る機会が増えた。
ただし、ソースファイルが追加になるたびにメンテするのが煩わしいのでその対策として、
Makefileのひな型を作成した。

(2017/03/05 Makefile一部追加修正対応)

やりたいこと

・Makefileを極力メンテしたくない
・ソースファイルを自動的に追加してビルドしたい
・CppUTestを静的ライブラリで導入する
・もちろんテストコードとターゲットコードは分離したい
・objやbinを所定のフォルダに出力したい
・release,debugも両方ビルドしたい

ツリー構成

このMakefileが対象とするツリーは、以下のような構成を想定している。

pre_tree.log
$ 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 $<

実行結果

end_tree.log
$ 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ビルド関係
基本的に触るのは処理部より上の設定のみ。
1. ディレくトリ指定を合わせる
2. 必要なコンパイル/ リンカオプションを設定する
3. 実行
$ make

CppUTestを実施する場合は、
$make testrun
を叩くことでクリーン、コンパイル、テストを行う。

まとめ

ソースフォルダ中のサブディレクトリ構造を追いかけビルドしてくれるため、
Makefileのメンテをほぼ気にしなくてよく、開発に集中できるようになった。

課題

Includeとライブラリの自動更新の検討
→課題に書いていたIncludeとリンカの自動更新は不必要なものまで
 取り込んでしまう恐れがあるのでやめたほうが無難そう。

10
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
11