make覚書
最近makeを使うことになったので、make関する覚書を作成しています。
環境
私の作業場所で扱う計算機が下記の様に異なりますが、
特に区別せずに作業しています、ご了承ください。
- Debian8 bash GNU Make 4.0
- macOS-X bash GNU Make 3.81
make
makeはMakefileにルールを記述しておき、煩雑なコンパイル作業等を自動化するコマンドです。
一連の作業の中で変更の無いファイルに対してコンパイルを飛ばしたりしてくれるので便利です。
基本的なルール
Makefileはターゲット(target)、必須項目(prereq)、コマンド(comands)から成り、
必須項目のファイルがターゲットよりも新しい場合にコマンドが実行されます。
ちなみにインデントはTABでないと駄目です。
target: prereq1 prereq2
commands
↓Makefileの簡単な例
foo: foo.c
gcc foo.c -o foo
上記のMakefileとfoo.cが保存されているディレクトリで
makeと打てばコンパイルされます。
make targetで対象とするターゲットを指定できます。
指定しない場合は一番上のターゲットが対象になります。
処理の出力
make -nでmakeがどのような処理を行うのかを出力できます。
思った通りに動いてなさそうで困ったときに便利です。
Makefile無くても動く
組み込まれているパターンルールにより条件が整っていれば、
ターゲットを指定することによってMakefileが無くても動かすことができます。
$ make foo
擬似ターゲット
ターゲットに実在しない名前を設定して、何かしらの意味をもたせたものです。
ターゲットが存在しないので、必須項目の新旧問わずに実行されます。
| target | memo |
|---|---|
| all | 全ての作業 |
| install | インストール |
| clean | バイナリ削除 |
| deisclean | 元の配布物以外の生成物を削除 |
| TAGS | エディタが使用するtagsを作成する |
| info | GNU infoを作成する |
| check | テスト実行 |
自動変数
Makefile内で使用する自動変数です。
特定のタイミングで展開されます。
| var | memo |
|---|---|
| $@ | ターゲットファイル名 |
| $% | ターゲットがアーカイブメンバだったときのターゲットメンバ名 |
| $< | 最初の依存するファイルの名前 |
| $? | ターゲットより新しいすべての依存するファイル名 |
| $^ | すべての依存するファイルの名前 |
| $+ | Makefileと同じ順番の依存するファイルの名前 |
| $* | サフィックスを除いたターゲットの名前 |
パターンルール
Makefileに記述しなくても暗黙的に動作する組み込みルールが存在し、
make --print-data-baseで確認することができます。
パターンルール内の%はワイルドカードの様な働きをします。
↓.mdから.htmlへのパターンルール(例)
%.html: %.md
pandoc $< -c $(CSS) -o $@
静的パターンルール
ターゲットの横にパターンルールを記述すると
そのターゲット生成にのみに適用するルールが書けます。
$(TARGET): %.o: %.c
基本的な変数
| symbol | memo |
|---|---|
| CURDIR | カレントディレクトリ |
| MAKEFILE_LIST | makeが読み込んだファイルのリスト |
| .VARIABLES | 変数のリスト |
暗黙のルールで使われている変数
make --print-data-baseするとよくわかります。
| symbol | memo | default |
|---|---|---|
| CXX | C++コンパイルコマンド | g++ |
| CXXFLAGS | C++コンパイルオプション | |
| CPPFLAGS | プリプロセッサ用オプション | |
| TARGET_ARCH | ||
| LDFLAGS | リンカ用オプション | |
| COMPILE.cc | C++コンパイル実行 | $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c |
| COMPILE.C | C++コンパイル実行 | $(COMILE.cc) |
| COMPILE.cpp | C++コンパイル実行 | $(COMILE.cc) |
| LINK.cc | C++リンク実行 | $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) |
| LINK.C | C++リンク実行 | $(LINK.cc) |
| LINK.cpp | C++リンク実行 | $(LINK.cc) |
| LINK.o | オブジェクのリンク | $(CC) $(LDFLAGS) $(TARGET_ARCH) |
| OUTPUT_OPTION | -o $@ |
特殊ターゲット
必須項目側に特殊な意味を持たせるターゲット。
| target | memo |
|---|---|
| .PHONY | 疑似ターゲット指定 |
| .INTERMEDIATE | 中間ファイル指定(make終了後自動削除) |
| .SECONDARY | 中間ファイル指定(自動削除しない) |
| .PRECIOUS | make中断時に勝手に削除しない |
| .DELETE_ON_ERROR | make中断時に削除する |
※通常makeはシグナルで中断された場合にのみターゲットを削除するらしい。
自動的な依存関係の生成
gcc -M main.cでそのファイルの依存しているヘッダの一覧がでます。
makeで使用可能な記述をしてくれるので扱いやすいです。
>$ gcc -I include -M main.c
main.o: main.c /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/features.h /usr/include/i386-linux-gnu/sys/cdefs.h \
/usr/include/i386-linux-gnu/bits/wordsize.h \
/usr/include/i386-linux-gnu/gnu/stubs.h \
/usr/include/i386-linux-gnu/gnu/stubs-32.h \
/usr/lib/gcc/i586-linux-gnu/4.9/include/stddef.h \
/usr/include/i386-linux-gnu/bits/types.h \
/usr/include/i386-linux-gnu/bits/typesizes.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/i586-linux-gnu/4.9/include/stdarg.h \
/usr/include/i386-linux-gnu/bits/stdio_lim.h \
/usr/include/i386-linux-gnu/bits/sys_errlist.h include/main.h
後はこれをうまくMakefileに組み込めればOK。
動きとしては↓の様なイメージですが、make dependを忘れる可能性が高いです。
depend: hoge.c piyo.c fuga.c
$(CC) -M $(CPPFLAGS) $^ > $@
include depend
そこでdependに指定しているファイルを.dとして,.dをターゲットに持った処理をincludeしようと考えます。
まず、%.d: %.cのルールを追記して.cが変更されたときに.dを生成するようにします。
SOURCES=hoge.c piyo.c fuga.c
include $(subst .c,.d,$(SOURCES))
%.d: %.c
$(CC) -M $(INCLUDES) $(CPPFLAGS) $< > $@.tmp
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.tmp > $@
rm -f $@.tmp
※substは置換。
マニュアルでは↓になっているらしいです。
(なぜPIDを使ってユニークなファイル名にしなくてはいけないのかは良くわかりませんでした)
%.d: %.c
$(CC) -M $(INCLUDES) $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
ライブラリを-l形式でターゲットと必須項目に使えない
Makefileでライブラリをターゲットとして指定する場合に、
そのファイルを別のターゲットの必須項目に-l 形式(-lflとか)で指定できないらしいです。
二重コロン
あるターゲットに対して必須項目によって実行する手順が異なるときに使うらしいです。
file-list:: generate-list-script
chmod +x $<
generate-list-script $(files) > file-list
file-list:: $(files)
generate-list-script $(files) > file-list
代入演算子
Makefileで使われる代入演算子。
動作や展開されるタイミングが異なります。
| ope | memo |
|---|---|
| := | 右辺を直ぐに展開 |
| = | 変数が使われる時に展開 |
| ?= | 変数が値を持っていない場合にのみ代入 |
| += | 変数に追加 |
マクロ
改行を含めた変数のように扱うと重宝します。
変数と同じように展開されます。
define create-depend
@echo Creating $@...
$(CC) -M $(INCLUDES) $(CPPFLAGS) $< > $@.tmp
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.tmp > $@
rm -f $@.tmp
endef
%.d: %.c
$(create-depend)
引数
マクロへの引数は、
$(call macro-name, param1, ...)で与え、
マクロ側では$nで受け取ることができます。
※変数はスペースを含んで渡してしまうので注意が必要です。
define func1
@echo Creating $1
endef
%.d: %.c
$(call func1, hoge)
組み込み関数
組み込み関数の代表的なものを以下にあげます。
| func | memo |
|---|---|
| $(filter pattern, text) | textをスペースで区切られたリストとしてpatternとマッチしたものを返す |
| $(filter-out pattern, text) | filterの逆 |
| $(findstring string, text) | stringとマッチした時にstringを返す |
| $(subst search-string,replace-string,text) | 置換 |
| $(patsubst search-pattern,replace-pattern,text) | 置換(パターンなので%が使える) |
| $(words text) | text内の単語数を返す |
| $(word n, text) | text内のn番目の単語を返します |
| $(firstword text) | text内の最初の単語を返します |
| $(wordlist start_num, end_num, text) | start_numからend_numまでの単語列を返す |
| $(sort list) | 重複を取り除き並び替える |
| $(shell commad) | コマンドを実行して標準出力を返す(標準エラー出力は返さない) |
| $(wildcard pattern) | パターンを展開して返す |
| $(dir list) | listのファイル名を取り除きディレクトリ部分だけにして返す |
| $(notdir name) | nameのファイル名だけを返す |
| $(suffix name) | nameのサフィックスのリストを返す |
| $(basename name) | ファイルからサフィックスすを取り除いたものを返す |
| $(addsuffix suffix,name) | nameの各単語に対してsuffixを付加する |
| $(addprefix prefix,name) | nameの各単語に対してprefixを付加する |
| $(join prefix-list,suffix-list) | prefixとsuffixをひとつづつ結合してリストを返す |
※patsubstは$(variable:search=replace)でも実現可能。
関数の実行制御
if
if文として下記の記述ができます。
$(if condition,then-part,else-part)
else-partなどにエラーを出力させる命令を書きたい場合は、
下記の記述でメッセージと行番号を出力させることができます。
$(if condition,then-part,$(error text))
又は
$(if condition,then-part,$(warning text))
※errorはmakeを停止させますが、warnignは停止させません。
foreach
$(foreach variable,list,body)
list内の単語を一つづつvariableに代入してbodyを実行します。
展開される順番
おおきく2段階に分かれています。
この結果、マクロの宣言位置などの順序は実行に影響を及ぼさずに済んでいるようです。
-
変数とルールをmake内部のDBに格納し、依存関係グラフを作成
- includeの読み込み
- 代入演算子左辺の展開
- :=右辺の展開
- +=右辺の展開(再帰的でない単純な変数の場合)
- マクロ名の展開
- ターゲットと必須項目の展開
-
依存関係グラフからターゲットを特定し各コマンドを実行
- 各右辺展開
- マクロ本体展開
- コマンドの展開
変数
ターゲット固有の変数
特定のターゲットに対して変数の内容を変更したい場合は下記のように対応できます。
hoge.o: CPPFLAGS += add-some-rules
hoge.o: hoge.h
コマンドラインでの変数定義
$ make CFLAGS=-gのように実行時に変数を定義てきます。
Makefile内の内容よりも優先されますが、Makefile内でoverride命令を使うことによってMakefile内の内容を優先させることもできます。
override CFLAGS=-g
環境変数
makeの開始時にmake変数として定義されます。
優先順位は低いのでコマンドラインやMakefile内の記述で上書きできます。
make実行時に--environment-overrideを使うと環境変数を優先にできます。
また、makeから別のmakeを起動した際、環境変を引き継ぎます。なので渡したい変数がある場合はexportを使います。
(この時、変数名に-が入っていても問題なく渡せます。)
export var = hoge
条件分岐
Makefile内でif条件の形式で下記の分岐が利用できます。
- ifdef variable-name
- ifndef variable-name
- ifeq test
- ifeq test
※variable-nameは$表記しないで使います。
※testは"a" "b"か(a, b)の形で使います。
※カンマ直後のスペース以外は無視されないので( a, a )のような書き方には注意が必要で、stripなどを使えば安心できます。
※コマンド部分で条件分岐を記載する際、条件行のインデントにTABを使わないように注意が必要です。