グレインの備忘録

プログラミング関係とかをつらつらと。

C++用のMakefileを半自動で書く

Makefileはうまく書けばコンパイルが自動化できて楽だが、ソースの登録が面倒くさい。

そこで、シェルスクリプトなどを活用して自動でMakefileを書いてみる。

(autoconfとかを使ってもいいのだけど、小規模なプログラムでいちいちconfigureするのも面倒だし。)

まずはベタ書き

以下のようなシチュエーションを考える。(treeで出力したディレクトリ構造)

.
├── Makefile
├── src
│   ├── main.cpp (test1_1.h,test2.hをインクルード)
│   ├── test1.cpp (test1_1.h,test1_2.hをインクルード)
│   ├── test1_1.h
│   ├── test1_2.h
│   ├── test2.cpp (test2.hをインクルード)
│   └── test2.h
└── [test]を出力(バイナリ)

とりあえず手動で全部書いてみる。

CXX := clang
CXXFLAGS := -Wall -std=c++11
LD := clang
LDFLAGS := -lstdc++

TARGET := test
OUTS := main.o test1.o test2.o
SRCDIR := src


#本体生成規則
$(TARGET) : $(OUTS:%=$(SRCDIR)/%)
  $(LD) $^ -o $@ $(LDFLAGS)

#依存関係
main.o : $(SRCDIR)/main.cpp $(SRCDIR)/test1_1.h $(SRCDIR)/test2.h
test1.o : $(SRCDIR)/test1.cpp $(SRCDIR)/test1_1.h $(SRCDIR)/test1_2.h
test2.o : $(SRCDIR)/test2.cpp $(SRCDIR)/test2.h

#サフィックスルール
.cpp.o :
  $(CXX) -c $< -o $@ $(CXXFLAGS)

ヘッダの依存関係などを考慮すればこういう風に書くことになるだろうか。(流石に少し醜い部分もあるが・・・)

サフィックスルールなどを使って楽してはいるが、依存関係を自分で調べないといけないのがだるい。

ソース数が少ないうちはこれでも我慢できるが、やはり数が増えるとつらい。

ヘッダ依存関係の自動検出

実はgccやclangにはMakefile用の便利な機能がある。

ソースファイルを与えるとそれが依存しているソースとヘッダをmakefile形式で列挙してくれるのだ。

例えば、

clang -MM src/test1.cpp

と実行すると、

test1.o: src/test1.cpp src/test1_1.h src/test1_2.h

と言った具合に出力され、非常に便利である。

ヘッダファイルの中で更にインクルードがあってもきちんと追跡してくれる。

これをうまく活用してみることにする。

シェルスクリプトによるソースの列挙

ソースごとの依存関係が自動で検出できるなら後は簡単で、

シェルスクリプトで1ファイルずつ出力してしまえばよい。

ついでにソースコードの一覧も生成してしまおう。

#!/bin/bash

#出力ファイル:
#    Makefile.1 : 依存関係一覧
#    Makefile.2 : ソース一覧

export MK_DEPENDS=Makefile.1
export MK_SOURCES=Makefile.2
export SRCDIR=src
export SUFFIX="*.cpp"
export CXX=clang

#Makefileの初期化
echo -n > $MK_DEPENDS
echo -n "SRCS := " > $MK_SOURCES

#SRCDIR直下の全cppファイルに対して操作する
find $SRCDIR -name $SUFFIX | while read -r f; do
    #依存関係を出力
    $CXX -MM $f >> $MK_DEPENDS
    #ソース一覧に追加
    echo -n "$f " >> $MK_SOURCES
done

#ソース一覧に改行を追加
echo >> $MK_SOURCES

とりあえず「makedepends.sh」として保存しておく。

これを実行すると、

[Makefile.1]
main.o : src/main.cpp src/test1_1.h src/test2.h
test1.o : src/test1.cpp src/test1_1.h src/test1_2.h
test2.o : src/test2.cpp src/test2.h
[Makefile.2]
SRCS := src/main.cpp src/test1.cpp src/test2.cpp

といった具合になる。(順番は異なる可能性がある)

リストをMakefileに取り込む

リストが生成できたので、あとはMakefileに取り込めば終わりだ。

CXX := clang
CXXFLAGS := -Wall -std=c++11
LD := clang
LDFLAGS := -lstdc++

MK_DEPENDS := Makefile.1
MK_SOURCES := Makefile.2

-include $(MK_SOURCES)

TARGET := test
#$(SRCS)の.cppを.oに変換して格納
OUTS := $(SRCS:%.cpp=%.o)

#本体生成規則
$(TARGET) : $(OUTS)
  $(LD) $^ -o $@ $(LDFLAGS)

#依存関係
-include $(MK_DEPENDS)

#サフィックスルール
.cpp.o :
  $(CXX) -c $< -o $@ $(CXXFLAGS)

これを保存してmakeすると、最初のMakefileと同様に正しくコンパイルされる。

makedepends.shも自動で実行

今のままだと、コンパイル前にmakedepends.shも実行しないといけないので何かと面倒だ。

そこで、makedepends.shも自動実行するようにしてみよう。

インクルードされたファイルを更新してから再実行する必要があるので少し工夫が必要だが、再帰をうまく使えばできる。

Makefileを以下のように書き換える。

CXX := clang
CXXFLAGS := -Wall -std=c++11
LD := clang
LDFLAGS := -lstdc++

MK_DEPENDS := Makefile.1
MK_SOURCES := Makefile.2
SH_MKDEPENDS := makedepends.sh

-include $(MK_SOURCES)

TARGET := test
#$(SRCS)の.cppを.oに変換して格納
OUTS := $(SRCS:%.cpp=%.o)
SRCDIR := src


#デフォルト生成規則
#makedependを実行し、test_implを生成する
$(TARGET) : $(OUTS) mkdep
  $(MAKE) target_impl

#実際のバイナリ生成
target_impl : $(OUTS)
  $(LD) $(OUTS) -o $(TARGET) $(LDFLAGS)

#依存関係
-include $(MK_DEPENDS)

#サフィックスルール
.cpp.o :
  $(CXX) -c $< -o $@ $(CXXFLAGS)

#コマンド一覧
.PHONY : clean mkdep

clean :
  find $(SRCDIR) -name "*.o" | while read -r f; do rm -f $$f; done
  rm -f 

mkdep :
  bash $(SH_MKDEPENDS)

これで最終形態になるのでついでにcleanも付けておいた。

※ここのMakefileをコピペするときには生成規則行頭のスペースをタブに変換すること

これでかなり楽にコンパイルができそうだ。

(プロジェクト依存の部分のみを別ファイルに切り出すと更に便利かもしれない。)