Makeファイルによるコンパイルの自動化(Webな方々でいう所のデプロイ)
GNUMakeは歴史が古く、確固たる地位を築いているシステム。 ゆえに、様々な後継プログラミング言語用にツールが開発されている。 GNUMakeのMakefileが書けると、JavaではAntやMaven、RubyではRakeなどで応用がきく?!・・・事にしておこう。
- Makeファイルによるコンパイルの自動化(Webな方々でいう所のデプロイ)
- 昔あった記事がない・・・・
- Make で Hello World!
- Makefileの基本文法: 依存関係
- マクロ
- サフィックスルール
- さらなる応用
昔あった記事がない・・・・
昔参考にしていたサイトがいつの間にやらなくなっていた。 ネットを覗けば何時でも読めると思っていたら・・悲しい。 ので、Webアーカイバを漁って見つけてきたものをここに残す。
よって、この文章は私オリジナルの文章ではなく、昔学習させていただいたサイトの情報をHatenaに残したものである。 http://web.archive.org/web/20060320022409/http://www.c.csce.kyushu-u.ac.jp/~seiichirou/wiki/index.php?Makefile%A4%CE%BD%F1%A4%AD%CA%FD
Make で Hello World!
それではメイクファイルを書いてみましょう。ここではソースファイルとしてコンパイル方法でも使ったC言語のHello World!を使用します。
/* hello.c */ #include <stdio.h> int main(int argc, char* argv[]) { printf("Hello World!\r\n"); return 0; }
同じディレクトリに“Makefile”というファイルを作成して、以下の内容を記述してください。
# Makefile hello: hello.c gcc -o hello hello.c
三行目の先頭は空白文字ではなくてタブ文字(Tab)なので注意してください! 一行目はコメントです。“#”と書くとその行の“#”以降の文字列はコメントとなります。 コマンドからmakeを実行すると以下のようにコンパイルしてくれます。
pi@raspberrypi:~ $ make gcc -o hello hello.c
このあともう一度makeを実行してみましょう。
$ make make: `hello' は更新済みです
hello.cが更新されていない(helloの方がhello.cよりも日付が新しい)のでコンパイルされません。 こんな感じで、必要な部分のみコンパイルしてくれます。
Makefileのファイル名を指定する場合は
pi@raspberrypi:~ $ make -f Makefile
とします。 ファイル名を指定しない場合は、“GNUmakefile”、“makefile”、“Makefile”の順に検索します。
Makefileの基本文法: 依存関係
Makefileの基本的な構文は依存関係を表す依存関係です こんな感じ。
ターゲット名: 依存ファイル名1 依存ファイル名2 依存ファイル名3 コマンド行1 コマンド行2 コマンド行3
ターゲット名は一般的に生成されるファイルのファイル名にします(そうでない場合については後述します)。
ターゲット名の後に“:”を書いて、その後にスペース区切りで依存するファイルのファイル名を記述します。 これらのファイルのうちどれか一つでも更新されるとコマンドが実行されます。
ターゲット名を指定してmakeを実行する場合は
pi@raspberrypi:~ $ make ターゲット名
とします。 ターゲット名を省略すると、Mkefileの中で先頭のターゲットが実行されます。
ターゲット名から始まる行の次の行から実行するコマンドを記述します。 コマンドを記述する場合は必ず先頭にタブ文字を入れる必要があります。
例として、C言語の分割コンパイルをしてみましょう。分割コンパイル用に以下のファイルを用意します。
#include <stdio.h> void edajima(void); int main(int argc, char* argv[]) { edajima(); return 0; }
/* edajima.c */ #include <stdio.h> void edajima(void); void edajima(void) { printf("わしが男塾塾長 江田島平八である!!\r\n"); }
メイクファイルはこんな感じです。
# Makefile hello: hello.c edajima.c gcc -o hello hello.c edajima.c
hello.cまたはedajami.cのいずれかを更新するとコンパイルし直してくれます。 しかし、このままでは更新されてないファイルもコンパイルし直されてしまうので、少し変更します。
# Makefile hello: hello.o edajima.o gcc -o hello hello.o edajima.o hello.o: hello.c gcc -c hello.c edajima.o: edajima.c gcc -c edajima.c
こうしてmakeを実行すると、
pi@raspberrypi:~ $ make gcc -c hello.c gcc -c edajima.c gcc -o hello hello.o edajima.o
となります。 ここで、edajima.cを書き換えます。
/* edajima.c */ #include <stdio.h> void edajima(void); void edajima(void) { printf("わしが男塾塾長 江田島平八である!!\r\n"); printf("以上!\r\n"); }
そしてmakeを実行すると、
pi@raspberrypi:~ $ make gcc -c edajima.c gcc -o hello hello.o edajima.o
となって、edajima.oだけが更新されます。
依存関係行の応用その一
依存関係行を使った応用について説明します。 プログラムをコンパイルすると中間ファイルなどができていちいち削除するのが面倒です。 そこで、Makefileに以下の行をつけたします。
# Makefile hello: hello.o edajima.o gcc -o hello hello.o edajima.o hello.o: hello.c gcc -c hello.c edajima.o: edajima.c gcc -c edajima.c clean: rm -f hello hello.o edajima.o
こうしてコマンドで以下のように実行します。
pi@raspberrypi:~ $ make clean rm -f hello hello.o edajima.o
不要なファイルをすべて削除してくれます。 “clean”は依存するファイルがなく、cleanというファイルを生成するわけでもなく、コマンドを実行するだけです。 このようなターゲットのことを“phony target”と呼びます。 phonyターゲットを使用する場合、ターゲット名と同じ名前のファイルがあると変なことになります。
pi@raspberrypi:~ $ touch clean pi@raspberrypi:~ $ make clean make: `clean' は更新済みです
これをさけるためにはMakefileのcleanターゲット部分を以下のように書き換えます。
.PHONY: clean clean: rm -f hello hello.o edajima.o
こうするとcleanというファイルが存在していても問題ありません。
依存関係行の応用その二
もう一つの応用は、複数のプログラムを作成するときに役に立ちます。 ここでは以下のソースファイルを追加します。
/* raiden.c */ #include <stdio.h> int main(int argc, char* argv) { printf("男の勝負に言葉はいらん\r\n"); printf("ただそれだけのこと…………!!\r\n"); return 0; }
そしてMakefileを以下のようにします。
# Makefile hello: hello.o edajima.o gcc -o hello hello.o edajima.o hello.o: hello.c gcc -c hello.c edajima.o: edajima.c gcc -c edajima.c raiden: raiden.o gcc -o raiden raiden.o raiden.o: raiden.c gcc -c raiden.c .PHONY: clean clean: rm -f hello hello.o edajima.o raiden raiden.o
これでhelloとraidenを作ろうとすると、
pi@raspberrypi:~ $ make hello pi@raspberrypi:~ $ make raiden
となり、面倒です。 そこで、ダミーの依存関係行を追加します。
# Makefile .PHONY: all all: hello raiden hello: hello.o edajima.o gcc -o hello hello.o edajima.o hello.o: hello.c gcc -c hello.c edajima.o: edajima.c gcc -c edajima.c raiden: raiden.o gcc -o raiden raiden.o raiden.o: raiden.c gcc -c raiden.c .PHONY: clean clean: rm -f hello hello.o edajima.o raiden raiden.o
先頭に追加した“all”がミソです。 これでmakeを実行すると、
pi@raspberrypi:~ $ make gcc -c hello.c gcc -c edajima.c gcc -o hello hello.o edajima.o gcc -c raiden.c gcc -o raiden raiden.o
となってめでたく二つのプログラムを一度に作成することができました。
依存関係行の応用その三
C言語ではコンパイルしないけどソースファイルにインクルードされるヘッダーファイルが存在します。 ヘッダーファイルが更新されたときにソースファイルをコンパイルし直すにはどうしたらよいのでしょうか?
この問題を解決するには、同じターゲット名の依存関係行を追加します。 例えば以下のようなファイルを用意します。
char serifu[] = {"聖紆麈 貴様の命 この邪鬼とともにある"};
/* jaki.c */ #include <stdio.h> #include "jaki.h" int main(int argc, char* argv[]) { printf("%s\r\n", serifu); return 0; }
# Makefile .PHONY: all all: jaki jaki: jaki.o gcc -o jaki jaki.o jaki.o: jaki.c gcc -c jaki.c .PHONY: clean clean: rm -rf jaki jaki.o
makeを実行すると
pi@raspberrypi:~ $ make gcc -c jaki.c gcc -o jaki jaki.o
となります。 ここで“jaki.h”を書き換えます。
char serifu[] = {"す すまん 貴様等の期待と信頼を裏切った…………"};
そして、makeを実行すると
pi@raspberrypi:~ $ make make: `all' に対して行うべき事はありません。
といって更新してくれません。 そこで、以下のように“Makefile”を書き換えます。
# Makefile .PHONY: all all: jaki jaki: jaki.o gcc -o jaki jaki.o jaki.o: jaki.c gcc -c jaki.c jaki.o: jaki.h .PHONY: clean clean: rm -rf jaki jaki.o
“jaki.o: jaki.h”という行がポイントです。 そして、makeを実行すると
pi@raspberrypi:~ $ make gcc -c jaki.c gcc -o jaki jaki.o
マクロ
ここから少し難しくなります。 これまではMakefieにファイル名やコマンド名を直接書いていました。 しかし、マクロを使うと直接書かなくてすみ、他への流用などが容易となります。 マクロを定義するには以下のようにします。
マクロ名 = 文字列
マクロを参照するには、
$(マクロ名)
または
${マクロ名}
とします。実際に使ってみるとこんな感じです。
# Makefile objs = hello.o edajima.o hello: $(objs) gcc -o hello $(objs) hello.o: hello.c gcc -c hello.c edajima.o: edajima.c gcc -c edajima.c .PHONY: clean clean: rm -f hello $(objs)
ここでは、オブジェクトファイル名を“objs”というマクロとして定義しています。 “$(objs)”は“hello.o edajima.o”に置換されます。
GNU makeでは、定義済マクロとして以下のものがあります。
マクロ名 | 文字列 | 説明 |
---|---|---|
AR | ar | アーカイブユーティリティ |
AS | as | アセンブラ |
CC | cc | Cコンパイラ |
CXX | g++ | C++コンパイラ |
CO | co | RCS ファイルからリビジョンをチェックアウトする |
CPP | $(CC) -E | Cプリプロセッサ |
FC | f77 | Fortranコンパイラ |
GET | get | 知らね |
LEX | lex | lex |
PC | pc | Pascalコンパイラ |
YACC | yacc | yacc |
YACCR | yacc -r | 知らね |
MAKEINFO | makeinfo | Texinfo -> Info |
TEX | tex | TeX |
TEXI2DVI | texi2dvi | Texinfo -> DVI |
WEAVE | weave | 知らね |
CWEAVE | cweave | 知らね |
TANGLE | tangle | 知らね |
CTANGLE | ctangle | 知らね |
RM | rm -f | ファイルの削除 |
上記のプログラムの引数用のマクロもあります。
マクロ名 | 文字列 | 説明 |
---|---|---|
ARFLAGS | rv | ARの引数 |
ASFLAGS | ASの引数 | |
CFLAGS | CCの引数 | |
CXXFLAGS | CXXの引数 | |
COFLAGS | COの引数 | |
CPPFLAGS | CPPの引数 | |
FFLAGS | FCの引数 | |
GFLAGS | GETの引数 | |
LDFLAGS | リンカldの引数 | |
LFLAGS | LEXの引数 | |
PFLAGS | PCの引数 | |
RFLAGS | 知らね | |
YFLAGS | YACCの引数 |
これらのマクロは再定義可能です。 例えば、こんな感じです。
# Makefile objs = hello.o edajima.o CC = gcc hello: $(objs) $(CC) -o hello $(objs) hello.o: hello.c $(CC) -c hello.c edajima.o: edajima.c $(CC) -c edajima.c .PHONY: clean clean: $(RM) hello $(objs)
ここでは“CC”というマクロを“gcc”という文字列で再定義しています。 また、“RM”というマクロをそのまま使用しています。
内部マクロ
前述のマクロは単純に文字列に置換するだけでしたが、内部マクロはもう少し複雑になります。 例えば、こんな感じの内部マクロがあります。
hello: $(objs) $(CC) -o $@ $(objs)
ここでは“$@”という内部マクロを使用しています。 これはターゲット名を表すものです。
そのため上記の記述は、
hello: $(objs) $(CC) -o hello $(objs)
と解釈されます。 また、以下のものもあります。
hello.o: hello.c $(CC) -c $<
ここでは“$<”という内部マクロを使用しています。 これは依存ファイルの先頭のファイル名を表すものです。 そのため上記の記述は、
hello.o: hello.c $(CC) -c hello.c
と解釈されます。 依存ファイル名のリストを表す“$^”という内部マクロもあります。
内部マクロをまとめるとこんな感じです。
内部マクロ名 | 説明 |
---|---|
$@ | ターゲット名 |
$% | ターゲットメンバ名(ターゲット名が“edajima.a(momo.o)”の場合、$@は“edajima.a”で、$%は“momo.o” |
$< | 依存ファイルの先頭のファイル名 |
$? | 依存ファイルの内、ターゲットより新しいファイルのリスト |
$^ | 依存ファイルのリスト |
$+ | わかんね |
$* | わかんね |
マクロと内部マクロを駆使すると、Makefileはこんな感じになります。
# Makefile program = hello objs = hello.o edajima.o CC = gcc CFLAGS = -g -Wall $(program): $(objs) $(CC) -o $(program) $^ hello.o: hello.c $(CC) $(CFLAGS) -c $< edajima.o: edajima.c $(CC) $(CFLAGS) -c $< .PHONY: clean clean: $(RM) $(program) $(objs)
サフィックスルール
サフィックスルールとは、ファイル名の拡張子(サフィックス)ごとにルールを定義するものです。 例えばこんな感じです。
.SUFFIXES: .o .c .c.o: $(CC) $(CFLAGS) -c $<
“.SUFFIXE”は依存関係行と同じ形ですが、意味が違います。 サフィックスルールを適用する拡張子のリストを書きます。
“.c.o”がサフィックスルールとなっており、拡張子が“.o”のファイルは拡張子を“.c”変えたファイルに依存していることを表します。 変換方法はコマンドで表されています。 例えば、ターゲット名が“hoge.o”ならばmakeはこのサフィックスルールより“hoge.c”に依存していると判断して、コマンドを実行し“hoge.o”を生成します。
サフィックスルールを用いると、こんな感じで書けます。
# プログラム名とオブジェクトファイル名 program = hello objs = hello.o edajima.o # 定義済マクロの再定義 CC = gcc CFLAGS = -g -Wall # サフィックスルール適用対象の拡張子の定義 .SUFFIXES: .c .o # プライマリターゲット $(program): $(objs) $(CC) -o $(program) $^ # サフィックスルール .c.o: $(CC) $(CFLAGS) -c $< # ファイル削除用ターゲット .PHONY: clean clean: $(RM) $(program) $(objs)
ここまでくると、あとは“program”や“objs”を書き換えるだけでいくらでも流用ができます。 ちなみに、ヘッダーファイルの依存関係だけは自分で記述しなければなりません。 例えばこんな感じです。
# Makefile # プログラム名とオブジェクトファイル名 program = jaki objs = jaki.o # 定義済マクロの再定義 CC = gcc CFLAGS = -g -Wall # サフィックスルール適用対象の拡張子の定義 .SUFFIXES: .c .o # プライマリターゲット $(program): $(objs) $(CC) -o $(program) $^ # サフィックスルール .c.o: $(CC) $(CFLAGS) -c $< # ファイル削除用ターゲット .PHONY: clean clean: $(RM) $(program) $(objs) # ヘッダーファイルの依存関係 jaki.o: jaki.h
さらなる応用
分割Makefile
プログラムが複雑になって、ディレクトリごとにソースコードを分けるなどしていくと、一つのMakefileで管理するのは面倒になってきます。 そんな時には、Makefileを分割することができます。 例えば、subdirというサブディレクトリの中に別のMakefileがあるとした場合、カレントディレクトリのMakefileで
subsystem: cd subdir && $(MAKE)
または
subsystem: $(MAKE) -C subdir
とします。
C言語のヘッダーファイルの依存関係の自動解決
C言語でプログラミングしている際に、ソースファイルが増えるとヘッダファイルの依存関係をいちいち記述するのは面倒です。 色々な解決方法があるみたいですが、とりあえずこんなん考えてみました。
# Makefile # プログラム名とオブジェクトファイル名 program = jaki objs = jaki.o # 定義済マクロの再定義 CC = gcc CFLAGS = -g -Wall # サフィックスルール適用対象の拡張子の定義 .SUFFIXES: .c .o # プライマリターゲット .PHONY: all all: depend $(program) # プログラムの生成ルール $(program): $(objs) $(CC) -o $(program) $^ # サフィックスルール .c.o: $(CC) $(CFLAGS) -c $< # ファイル削除用ターゲット .PHONY: clean clean: $(RM) $(program) $(objs) depend.inc # ヘッダーファイルの依存関係 .PHONY: depend depend: $(objs:.o=.c) -@ $(RM) depend.inc -@ for i in $^; do\ cpp -MM $$i | sed "s/\ [_a-zA-Z0-9][_a-zA-Z0-9]*\.c//g" >> depend.inc;\ done -include depend.inc
gccのプリプロセッサであるcppとsedを組み合わせています。 cppは指定したソースファイルの依存関係をmakeの形式で出力してくれるオプションを持っています。 それを使って、全ソースファイルの依存関係を“depend.inc”に出力して、それをインクルードしています。 “make depend”とコマンドを実行すればOKです。
また、“all: depend $(program)”とすることで、makeする際に毎回“depend.inc”を作成するようにしています。
その他
GNU makeには他にも色々な機能があります。詳しくはWebのマニュアルを見てください。また、makeを発展させた、autoconf、automake、libtool、などもあります。これはOS間の差異を吸収するためのツールです。