ソースが複階層化された場合のMakefile(第五回)
まだバグがあるかもしれない。後日追って詳細を追記したい。
ソースファイルが複数階層に分かれた場合に対応したMakefileが完成!
試行錯誤の末、以下に示すMakefileが完成した。このMakefileは以下のことが可能
- 依存関係の自動解決(ヘッダファイルも含む)
- 中間生成ファイルの別ディレクトリ化(OBJDIRで指定)
- 下位階層も含めたソースファイルの検索(拡張子がcppのものを検索して追加)
- コンパイルしたくないディレクトリの除外(NOMAKEDIRで指定)
前回の問題点
前回は複階層化されたものに完全に対応していなかった。原因はOBJDIR内に中間ファイルを置くためのサブディレクトリをMakefileが自動で作成しないからだった(詳しくは実行してみて)。
今回の改善点
まず問題だった手動ディレクトリ生成を自動化した。これによりソースファイルが複階層化されていても問題なくコンパイルできるようになった。また、前回はwildcard関数によりcppファイルを検索していたが、UNIXのfindコマンドを使うことにより下位階層も検索可能になった。さらに参考にしたサイトよりコンパイルが不要なディレクトリを追加できるようになった。コンパイルしたくない(Makefileのルートパス+)除外ディレクトリ名+%で除外できる。
プログラムを新しく作る度にMakefileの変更点が多い、またどこを変えるんだっけ?と時間の無駄を省くために主要な変更点をUser settingにまとめた。基本的にここを適切なものに変更するだけで階層が分かれていようがコンパイルできる...はず。
今後の課題
プリコンパイルヘッダの実装である。試験的に実装してみたところ、基本的には正常動作するが大量のファイルを変更した際になぜかセグる(Segmentation error)問題があった。C++で外部ライブラリをバンバン使う身においてはプリコンパイルヘッダによるコンパイル速度の高速化もできれば達成したい。
################################################################ # # Makefile # # - search recursively the source file # # Time-stamp: "2013-12-18 15:53:50 Gmaj7sus4" # ################################################################ #--------------------------------------------------------------- # User setting #--------------------------------------------------------------- TARGET = main INCLUDES = -I./include LDFLAGS = -lyaml-cpp NOMAKEDIR= .git% data% doc% src/bin% OBJDIR = objs #--------------------------------------------------------------- # Don't change the following #--------------------------------------------------------------- GCC = g++ CFLAGS = -O2 -MMD -MP -Wall -Wextra CPPS = $(shell find * -name *.cpp) SRCS = $(filter-out $(NOMAKEDIR), $(CPPS)) DIRS = $(dir $(SRCS)) BINDIRS = $(addprefix $(OBJDIR)/, $(DIRS)) OBJS = $(addprefix $(OBJDIR)/, $(patsubst %.cpp, %.o, $(SRCS))) # patsubstは空白で区切られたものか指定の文字列に置換 DEPS = $(OBJS:.o=.d) TILS = $(patsubst %.cpp, %.cpp~, $(SRCS)) ifeq "$(strip $(OBJDIR))" "" OBJDIR = . endif ifeq "$(strip $(DIRS))" "" OBJDIR = . endif default: @[ -d $(OBJDIR) ] || mkdir -p $(OBJDIR) @[ -d "$(BINDIRS)" ] || mkdir -p $(BINDIRS) @make all --no-print-directory ./$(TARGET) all : $(OBJS) $(TARGET) $(TARGET): $(OBJS) $(LIBS) $(GCC) -o $@ $^ $(LDFLAGS) $(OBJDIR)/%.o: %.cpp $(GCC) $(CFLAGS) $(INCLUDES) -o $@ -c $< clean: @rm -rf $(TARGET) $(TILS) $(OBJDIR) -include $(DEPS)
- 参考サイト
raspbianの上にXBMCをインストールしてiPhoneからAirplay
(書きかけ)
raspberrypiという小型の教育用LinuxPCを持て余してたのでタイトルの通りにシステムを構築した。
具体的なシステムの仕様は
- RaspXbmc(OS)をインストールしない
- Raspbianの上にxbmcをインストール
- iPhoneのコントローラAppで操作可能
- AirPlayでiPhone内の動画をTV画面で再生
- SSH, SMBによりデータサーバ化(将来的には外付けHDD)
ハードウェア仕様
今回、構築したハードウェアの環境はこんな感じ
構築手順
ほとんど参考URLの通りです。
xbmcのインストール
パッケージのダウンロード先の追加
$ sudo emacs /etc/apt/sources.list.d/mene.list
パッケージの更新
$ sudo apt-get update
xbmcのインストール
$ sudo apt-get install xbmc
Ariplayデーモンのインストール
$ sudo apt-get install avahi-daemon
xbmcの起動
$ xbmc-standalone
xbmcの設定
xbmcが起動したらシステム、設定のメニュー画面を開く。iPhoneのコントローラAppで操作するために「外部機器から操作可能」と「Airplayを有効」にチェックを入れる(詳細は割愛)。
運用テスト
iPhoneに「Official XBMC Remote」をインストール。IPアドレスを入れて難なく接続。
次にAirplay。カメラロール内にある動画を再生...できない。なぜ?ちなみにiPodの曲は再生できた。また、動画も音だけは流れる。動画が再生されない。
メモリ割当の増加
raspbianのGPUのメモリ割当を増やす。そのために設定画面を立ち上げて
$ sudo raspi-config
memory_splitを64MB→128MBまたは256MBへ増やす。一応、これで再生できるようにはなった。
高負荷CPUの改善
xbmcの設定、システム情報でCPUの使用率が常に85〜95%あった。これを下げるために一つはVertical blank syncをalways enabledに設定。何度か再起動したが変化がなかった。
二つ目はスクリーンセイバーを黒んk設定。これでアイドル状態時にば40%くらいまで落ちた。
動作確認
現在、sambaとSSHサーバの二つが動いてるためWindowsからLinuxからもiPhoneからもファイルの転送が楽。xbmcはraspberry内のストレージにアクセスできるから便利。外付けのHDD付ければ一括管理できるデータサーバになるね!
参考:
RaspbianにXBMCをインストールしてみる:とりあえずやってみる:So-netブログ
Raspbian + XBMC + Airplay ~ Dr.Palaniraja
RaspbianにXBMCを導入したときの設定まとめ - @slaypniのまとめも
バイナリファイルの整理とワイルドカードの利用(第四回)
前回までに
- 依存関係の自動解決
- 多段make
について示した。今回はバイナリファイルの整理と関数を利用した方法を紹介する。
wildcard関数の利用
カレントディレクトリのMakefile
Makefileで一番面倒臭く、エラーの原因になるのはファイルの追加とファイル面の変更だろう。同じ階層内であれば拡張子を基にコンパイルするリストに自動追加できる。今までソースファイルを
SRCS = main.cpp dirA/sample1.cpp dirB/sample2.cpp dirC/sample3.cpp dirC/dirD/sample4.cpp
としていたところを$(wildcard *.<拡張子>)とする。上の例なら
SRCS = $(wildcard *.cpp)\ $(wildcard dirA/*.cpp)\ $(wildcard dirB/*.cpp)\ $(wildcard dirC/*.cpp)\ $(wildcard dirC/dirD/*.cpp)
とする。これでソースファイルの名前を変更してもファイルを削除、追加しても自動的にリストを更新してくれる。
オブジェクトファイル、依存リストが散らかる
次にコンパイル後の中間ファイル(オブジェクトファイル.o、依存関係リスト.d)の整理を考える。これまではソースファイルのある階層にそのままオブジェクトファイルを作成して最後にリンクしていた。しかし、ファイル数が増えてくると一つのファイルに対して似た名前のファイルが二つ作られるため、コンパイル後には同じ階層に拡張子だけが異なる同じ名前のファイルが乱立して見にくくなる。そこでオブジェクトファイルや依存リストをobjというディレクトリにまとめる。
ここで前提条件が一つ。objディレクトリ内にdirA、dirBなどとディレクトリを作ってあげてから実行していただきたい。
今回の最終的なMakefileは以下のようになる。
GCC = g++ CFLAGS = -O2 -MMD -Wall -Wextra INCLUDE= -I./include SUBDIR = dirA dirB dirC dirC/dirD OBJDIR = obj ifeq "$(strip $(OBJDIR))" "" OBJDIR = . endif SRCS = $(wildcard *.cpp)\ $(wildcard dirA/*.cpp)\ $(wildcard dirB/*.cpp)\ $(wildcard dirC/*.cpp)\ $(wildcard dirC/dirD/*.cpp) OBJS = $(addprefix $(OBJDIR)/, $(SRCS:.cpp=.o)) DEPS = $(OBJS:.o=.d) TILS = $(SRCS:.cpp=.cpp~) TARGET = main $(OBJDIR)/%.o: %.cpp @[ -d $(OBJDIR) ] || mkdir -p $(OBJDIR) $(GCC) $(CFLAGS) -o $@ -c $< $(INCLUDE) $(TARGET): $(OBJS) $(GCC) $(CFLAGS) -o $@ $+ default: $(OBJS) $(TARGET) clean: $(RM) $(OBJS) $(DEPS) $(TARGET) -include $(DEPS)
主な変更点は3箇所である。まず、objディレクトリが存在するかどうかチェックするために
ifeq "$(strip $(OBJDIR))" "" OBJDIR = . endif
というものを書いている。もし存在しなければカレントディレクトリ(.)にobjというディレクトリを作成する。
次に、
$(OBJDIR)/%.o: %.cpp @[ -d $(OBJDIR) ] || mkdir -p $(OBJDIR) $(GCC) $(CFLAGS) -o $@ -c $< $(INCLUDE)
とオブジェクトファイルの生成ルールを変更する。ディレクトリ/%.oとするとそのディレクトリ内にファイルが生成される。次に、OBJS = $(addprefix $(OBJDIR)/, $(SRCS:.cpp=.o))とするとaddprefixを使うと、変数の先頭に「/」前の任意文字列を追加できる。最後に、 @[ -d $(OBJDIR) ] || mkdir -p $(OBJDIR)の[]はtestコマンドである。test -dとすることでディレクトリが存在するかどうか判定できる。もしなければmkdir -p によりディレクトリを作成する。その後コンパイルを行う。
これでobjというディレクトリ内に中間ファイルが生成される。
さらなる課題
一応これでカレントディレクトリのMakefileでコンパイルしたいディレクトリを指定することにより、自動的にソースファイルリストが作成される。重要な問題点は、コンパイル前にobjディレクトリ内にdirAやdirB、dirCなどのディレクトリ構造を作ってあげなければいけない。もちろんディレクトリがないとエラーが発生する。これを判定するコマンドが必要である。
また、SRCSにディレクトリリストを作る場合も変更するのはディレクトリ名だけにしたい。wildcardを含む長い文を並べるのはよろしくない。このあたりの整理も行いたい。
と要望はどんどん細かく複雑になるが、一般性のあるMakefileを目指して頑張ります。
cmakeは絶対に使わない!ことを目標にして
多段makeの利用(第三回)
ソースファイルが別階層にある場合
前回の記事ではMMDオプションにより依存関係を自動解決する方法を示した。しかし、「ソースファイルは同じ階層(ディレクトリ)に存在する」ことを仮定していた。Makefileで違う階層にあるファイル達をコンパイルするにはどうすればいいのか。以下のツリー構造を考えてみる。
.
├── Makefile
├── dirA
│ └── sample1.cpp
├── dirB
│ └── sample2.cpp
├── dirC
│ ├── dirD
│ │ └── sample4.cpp
│ └── sample3.cpp
├── include
│ ├── Test.hpp
│ └── header.h
└── main.cpp
ちなみにこの階層図はtreeというコマンドで作成した(参照:[Linux] ディレクトリ構成図作るのに便利だよ tree コマンド | バシャログ。)。各ファイルの中身は"sample○ is executed."と画面表示する関数が書いてある。main.cpp内でsample1()~sample4()まで順に呼び出している。各ソースファイルと実行結果は一番下に示す。
このときのMakefileを手動で書いてあげると、
GCC = g++ CFLAGS = -O2 -MMD -Wall -Wextra INCLUDE= ./include SRCS = main.cpp dirA/sample1.cpp dirB/sample2.cpp dirC/sample3.cpp dirC/dirD/sample4.cpp TARGET = main OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) TILDE = $(SRCS:.cpp=.cpp~) .cpp.o: $(GCC) $(CFLAGS) -c $< -o $@ -I$(INCLUDE) $(TARGET): $(OBJS) $(GCC) $(CFLAGS) -o $@ $+ default: $(OBJS) $(TARGET) clean: $(RM) $(OBJS) $(DEPS) $(TILDE) $(TARGET) -include $(DEPS)
となる。前回のMakefilのSRCSという変数にディレクトリ名を含めて直接追加すればよい。しかしファイルの移動、名前変更、ファイルの増加でここに全て書くのは限界がある。そこで、
各階層に階層内にあるファイルリストが書かれたMakefileを置く
これが多段makeと呼ばれるものである。各階層にMakefileを配置してカレントにあるMakefileが最終的にそれらを取り込む。ファイルのリストが分散することで管理が容易になる。
カレントディレクトリのMakefileの変更点
以下の項目を変更する。
である。これらを踏まえてSRCSの部分を以下のように変更
カレントディレクトリのMakefileのSRCS部分(他は変更なし)
SRCS := REL := dirA/ include $(REL)Makefile REL := dirB/ include $(REL)Makefile REL := dirC/ include $(REL)Makefile SRCS+=main.cpp
変数:=にしないとうまくいかない(純拡張変数と呼ぶらしい:GNU make 日本語訳(Coop編) - makeの実行方法)。グローバル変数になるのか?どうしてできなくなるかまだ分かってない(判明したら追加修正)。変数RELにより配置先を指定し、includeする。
配置先のMakefileの作成
配置先のMakefileは変数SRCSにファイルの一覧を+=で追加していけばいい(override変数と呼ぶらしい:GNU make 日本語訳(Coop編) - makeの実行方法)。以下のようになる。DirAのMakefileはこれだけ書いてあるだけ(DirB、DirDも同様)
DirAのMakefile
SRCS+=\ $(REL)sample1.cpp\
「SRCS+=$(REL)ファイル名」とすればいい。今回は一個しかないが複数個存在する場合は下に足していけばいい。また、DirCはDirD内のMakefileを取り込むようにもう一段階includeを書く
DirCのMakefile
SRCS+=\ $(REL)sample3.cpp\ REL:=$(REL)dirD/ include $(REL)Makefile
注意するのは1行目の「$(REL)ディレクトリ名」であること。カレントディレクトリからのパスにならないといけない。
多段makeについて補足「-C」オプション
今回の多段make、includeによりサブディレクトリのMakefileを取り込んで行ったが
make -C dirA
というように「-C」オプションを使って実行する方法もある。includeと-Cオプションの違いは大きく二つある。
このオプションを使うと、別階層のMakefileを動かすことになり相対パスが変わるということ。includeした場合、「includeしているMakfileの階層から見たパス」になるのに対して、make -Cは「-Cで移動した先にあるMakefileの階層から見たパス」になる。私はmake -Cを使うと相対パスが変わってしまうのが面倒なのでincludeを使った。また、-Cで指定されるMakefileは完全に独立していなければいけない。つまりmakeのルールや変数、ターゲットをまた別に書かなければいけない。階層ごとに独立したプログラムであればこの方式を取ったほうがいい。私は各階層に置くMakefileはファイルリストだけを並べた最小限のものにしたかったためincludeを使った手法を紹介した。
残りの問題点
このMakefileはまだ改善の余地がある。それは
- 中間ファイルが散らかる(*.dとか*.oとかファイル数が多くなるとコンパイル後すごくみにくい)
- 複数のMakefileを書き換える必要がある(できるだけ変更箇所は少なくしたい)
このうち次回は関数を使って、自動的にファイルの追加を検知するMakefileを作る。目標はいかにMakefileを変更せずに自動化できるか、コンパイル後の中間ファイルの整理である。
ソースファイルと実行結果
実行結果
sample1 is executed. sample2 is executed. sample3 is executed. sample4 is executed.
main.cpp
#include "header.h" #include "Test.hpp" int main(void){ Test obj; obj.sample1(); obj.sample2(); obj.sample3(); obj.sample4(); return 0; }
GCC = g++ CFLAGS = -O2 -MMD -Wall -Wextra INCLUDE= ./include SRCS := REL := dirA/ include $(REL)Makefile REL := dirB/ include $(REL)Makefile REL := dirC/ include $(REL)Makefile SRCS+=main.cpp TARGET = main OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) TILDE = $(SRCS:.cpp=.cpp~) .cpp.o: $(GCC) $(CFLAGS) -c $< -o $@ -I$(INCLUDE) $(TARGET): $(OBJS) $(GCC) $(CFLAGS) -o $@ $+ default: $(OBJS) $(TARGET) clean: $(RM) $(OBJS) $(DEPS) $(TILDE) $(TARGET) -include $(DEPS)
header.h
#include <iostream>
Test.hpp
#include "header.h" class Test{ public: void sample1(); void sample2(); void sample3(); void sample4(); };
dirA/sample1.cpp
#include "Test.hpp" void Test::sample1(void){ std::cout<<"sample1 is executed."<<std::endl; }
(sample2~sample4も表示が違うだけで中身は同様)
依存関係の自動解決(第二回)
前回の記事で書いたようにMakefile依存関係の自動解決について書いていきます。
必要最小限なソースファイルだけ再コンパイルすればいい
sample0.cpp、sample1.cpp、sample2.cpp、sample3.cppの4つのソースファイルがあるとします。そしてこれらの依存関係が
- sample0: sample1 sample2
- sample1: sample2
であるとします。このときsample1.cppを書き換えたとき、sample2.cppのみ再コンパイルが必要であってsample0およびsample1は再コンパイルの必要はありません。しかしこれらの依存関係を全てMakefile中に記述・反映するのは結構大変です。
コンパイラオプション「-MMD」により依存関係を記述したファイルを作成
詳細はMakeでヘッダファイルの依存関係に対応する - wagavulinの日記に書かれています。こちらにある通り最終的に
$ g++ -O2 -MMD -c sample0.cpp -o sample0.o
とオプションに「-MMD」をつければいい。このオプションは#includeしているヘッダーファイルの依存関係も解決してくれる。コンパイルが無事に終了すると同じ階層にsample0.dという依存関係が記述されたファイルが作成される。
参照ブログと内容が重複するので、簡単に説明するとこのオプションには「-M」「-MM」「-MD」「-MMD」があり、Mはシステムディレクリ(stdio.hなど)にあるヘッダーファイルの依存関係もチェックし(出力結果2参照)、MMはシステムディレクトリを除外してチェックする(出力結果1参照)。またDを使うと依存関係を記述したファイルを作成ししてくれる。システムディレクトリを含む依存ファイルを作る必要はなく、依存関係を記述したファイルの出力もして欲しいことから「-MMD」を使えばいいということになる。
ソースファイル:C++でhallo worldを表示
main.cpp
#include <iostream> #include "header.h" int main(void){ std::cout<<"hallo world\n"; return 0; }
出力結果1:-MMDオプション
コンパイルコマンド
$ g++ -O2 -MMD -c main.cpp -o main.o
main.dの中身
main.o: main.cpp header.h
出力結果2:-MDオプション
コンパイルコマンド
$ g++ -O2 -MD -c main.cpp -o main.o
main.dの中身
main.o: main.cpp /usr/include/c++/4.6/iostream \ /usr/include/c++/4.6/i686-linux-gnu/./bits/c++config.h \ /usr/include/c++/4.6/i686-linux-gnu/./bits/os_defines.h \ (省略) /usr/include/c++/4.6/bits/istream.tcc header.h
作成した依存関係ファイルを使ってmakeする
次回のmake時にはこの依存関係ファイルを利用する。そのためには
-include main.d
として依存関係ファイルを取り込む。これはmakefile中のどこかに書いておけばよい。
MMDコマンドを導入したmakefile
GCC = g++ CFLAGS = -O2 -MMD -Wall -Wextra SRCS = main.cpp sample.cpp TARGET = main OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) .cpp.o: $(GCC) $(CFLAGS) -c $< -o $@ $(TARGET): $(OBJS) $(GCC) $(CFLAGS) -o $@ $+ default: $(OBJS) $(TARGET) clean: $(RM) $(OBJS) $(TARGET) -include $(DEPS)
まずコンパイルオプションを表す変数CFLAGSに「-MMD」を追加。その後、依存関係がかかれたファイル*.dをインクルードするために末行に「-include $(DEPS)」を追加。変数DEPSの中身はmain.d sample.dを表すように上部に「DEPS=$(SRCS:.cpp=.d)」を定義する。次回は多段Makefileについて書きます。
Makefileの改造計画(第一回)
Makefileで苦労した人は多いはず
Linuxにおいてプログラムをコンパイルする際に必要となるMakefile。階層と依存関係を自動的に解決するようなMakefileを作るのは以外に難しいです。何年もこいつを書いてますが未だに苦戦します。ただただコンパイルルールを書くだけなのに以外にクセが強く奥深いです。まわりの人間もMakefileが嫌いとか言ってるやつ結構います。
それでも長い時間(数ヶ月とか)かけて自分の理想に近いMakefileを作りあげることができました。しかしこれを1コマで説明してしまうと複雑だと思われるので、複数回に分けて順に紹介していきます。
今後の掲載予定とソフトウェア環境
現在考えているのは、
- Makefileの改造計画(今回)
- 依存関係の自動解決(ヘッダーファイルの依存関係も考慮
- 多段Makefileの活用(ソースファイルが複数ディレクトリに分かれてる場合)
- バイナリファイルの整理(odjsディレクトリにまとまる)
- (番外編)プリコンパイルヘッダによるコンパイルの高速化
です。ただし、多段Makefileやバイナリファイルの整理あたりは説明が多くなりそうなのでさらにページを分割するかもしれません。
Makefileの基本事項を一通り説明する前に私の開発環境(ソフトウェア)を晒しておきます。
ハードウェアは仮想環境(VMware)でも動作しているのでそれほど関係ないでしょう。計算速度を図ることが目的でもないためCPUとかメモリ容量等も省きます。
さて、ベースとなるMakefileは以下の通り
main.cppとsample.cppをコンパイルするMakefile
GCC = g++ CFLAGS = -O2 -Wall -Wextra SRCS = main.cpp sample.cpp TARGET = main OBJS = $(SRCS:.cpp=.o) .cpp.o: $(GCC) $(CFLAGS) -c $< -o $@ $(TARGET): $(OBJS) $(GCC) $(CFLAGS) -o $@ $+ default: $(OBJS) $(TARGET) clean: $(RM) $(OBJS) $(TARGET)
私が使っている基本点なMakefileの雛形です。コメントいただければ各部の動作の詳細をお答えしますが、多くのサイトで解説されているので割愛します。このMakefile中のSRCSにソースファイルを手動で追加していけば基本的にコンパイルできます。
しかし、ファイル数が10,20,100...となってそんな面倒なことやってられません・・・ってことでwildcardを使ったりするわけです。さらにヘッダーファイルのインクルードや自作ライブラリのリンク、ソースファイルが階層ごとに散らかっていたり、もうとにかく大変です。
それらを次回以降、順次解決していきます。