はじめに
Makefileについてわかりやすい解説が少なかったのでまとめて見ました。
初心者向けに書いたものになります。
主にGNU makeの記述を訳しています。不自然な点、間違いがあったらご指摘いただければ幸いです。
Makeを理解するには、コンパイルへの理解が重要です。
コンパイルについては別記事でまとめています。よければどうぞ。
gccの概要 【初心者向け】 - Qiita
基本
主にMakefileはmakeにプログラムのコンパイルおよびリンク方法を指示します。(応用することで他の使い方もできます)
Makefileに依存関係(プログラムの前提条件となるファイルとの親子関係)を記述しておくと、各ファイルの更新を取得して必要なものだけをコンパイルしてくれるようになります。
Makefileに含まれる情報は主に3つです。
- 変数の定義(変数、自動変数、暗黙の変数、特殊変数)
- ルール
- その他(他のMakefileをoverrideするときの情報やデバッグ情報など)
ルールと呼ばれる基本形
依存関係を記す部分です。
target(ターゲット) :[prerequisites(前提条件)]
recipe(レシピ)
前提条件はあってもなくてもよいです。前提条件はレシピが成立する前提条件となります。依存関係は、ターゲットが子、前提条件の親になります。
名前 | 説明 |
---|---|
target | プログラムによって生成されるファイルの名前 |
prerequisites | ターゲットを作成するための入力として使用されるファイル 複数ファイルも可 省略可 |
recipe | makeが実行するアクション 複数行可(行を分割する場合は\を使う) 行頭にタブ文字必須 |
例
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c$$
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
editという実行ファイルの依存関係、その他のオブジェクトコードのファイル同士の依存関係とcleanアクションが記述されています。
cleanは生成されるtargetとなるファイルの名前ではありません。前提条件を持たないためファイルを参照せず、コマンドのみを実行します。これは擬似ターゲットと呼ばれます。
擬似のターゲットについては、コマンドのaliasだと理解していて、問題ないと思います。
擬似のターゲットのための.PHONY
前提条件を持たない擬似ターゲットと同じ名前のファイルがディレクトリ内に存在すると、レシピが実行されません。.PHONY
で明示的に宣言することで、その名前のファイルが存在するかどうか、またはその最終変更時刻に関係なく、無条件にレシピを実行します。
.PHONY: clean
# 複数の指定もできます。
.PHONY: cleanall cleanobj cleandiff
変数
Makefileの変数には2つあります。暗黙の変数と新しく定義される変数です。
暗黙の変数は暗黙のルールで使用される特定の定義済み変数のことを言います。
暗黙のルールとは
多様されるルールはわざわざ記述しなくても、予め暗黙のルールが定められています。
Cソースファイルのコンパイルに使用されるレシピは、実際には「$(CC)-c $(CFLAGS)$(CPPFLAGS)」というコードが実行されています。
CC
, CFLAGS
, CPPFLAGS
は予め変数として用意されており、この内容を上書きすることで、コンパイルのレシピを書き換えることができます。
暗黙の変数一覧
主なもの。
変数名 | 説明 | default |
---|---|---|
AR | アーカイブのメインテナンスプログラム | ar |
CC | Cプログラムのコンパイルプログラム | cc |
CFLAGS | Cコンパイラに与えるフラグ | なし |
RM | ファイルを削除するコマンド | rm -f |
暗黙の変数の一覧は以下にまとめました。
変数の定義
Makefile内での変数の定義の仕方には2種類あります。
記号 | 詳細 |
---|---|
= | 再帰的に展開される変数 |
:= | 単純に展開される変数 |
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
echo $(foo) # => Hub?
変数の呼び出し
$(name)
、または${name}
例
objects = program.o foo.o utils.o # => program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
ルール内で機能する自動変数
自動変数とはターゲットと前提条件を呼び出すときに使用できる記法です。省略形として使えるだけではなく、条件に合わせて、必要なものだけ参照する機能を持つものもあります。
参考サイトの解説より引用し、自分なりに書き換えています。
早見表
自動変数 | 機能 |
---|---|
$@ | ターゲット名 |
$< | 依存関係の最初の名前 |
$^ | ターゲットのすべての依存関係の名前 |
$? | ターゲットよりタイムスタンプが新しい依存関係の名前 |
$+ | ターゲットの全ての依存関係の名前 |
$* | ターゲットのパターンマッチに一致した部分 |
$@
現在のターゲット名。
変数 | 説明 |
---|---|
$@ | ルールのターゲットの名前。$(@) と書いても同じ意味を持つ。 |
$(@D) | ルールのターゲットのディレクトリ名。 |
$(@F) | ルールのターゲットのファイル名。 |
aaa/bbb/foo:
echo $@ # => aaa/bbb/foo
echo $(@D) # => aaa/bbb
echo $(@F) # => foo
$<
前提条件の一番最初の名前。
変数 | 説明 |
---|---|
$< | 前提条件の一番最初の名前。$(<) と書いても同じ意味を持つ。 |
$(<D) | 前提条件の一番最初のディレクトリ名。 |
$(<F) | 前提条件の一番最初のファイル名。 |
output/foo: input/bar input/baz
echo $< # => input/bar
echo $(<D) # => input
echo $(<F) # => bar
$^
ターゲットの全ての前提条件の名前。
変数 | 説明 |
---|---|
$^ | ターゲットの前提条件の名前。$(^) と書いても同じ意味を持つ。 |
$(^D) | ターゲットの前提条件のディレクトリ名。 |
$(^F) | ターゲットの前提条件のファイル名。 |
output/foo: input/bar input/baz
echo $^ # => input/bar input/baz
echo $(^D) # => input input
echo $(^F) # => bar baz
$?
ターゲットよりタイムスタンプが新しい前提条件の名前。
変数 | 説明 |
---|---|
$? | ターゲットより新しい全ての前提条件の名前。$(?) と書いても同じ意味を持つ。 |
$(?D) | ターゲットより新しい全ての前提条件のディレクトリ名。 |
$(?F) | ターゲットより新しい全ての前提条件のファイル名。 |
output/foo: input/bar input/baz
echo $? # => input/bar
echo $(?D) # => input
echo $(?F) # => bar
$+
ターゲットの全ての前提条件の名前 (重複があっても省略しない)。 一般的には $^ の方がよく使われます。
変数 | 説明 |
---|---|
$+ | 重複を含むターゲットの前提条件の名前。$(+) と書いても同じ意味を持つ。 |
$(+D) | 重複を含むターゲットの前提条件のディレクトリ名。 |
$(+F) | 重複を含むターゲットの前提条件のファイル名。 |
output/foo: input/baz input/baz input/baz
echo $+ # => input/baz input/baz input/baz
echo $(+D) # => input input input
echo $(+F) # => baz baz baz
$*
ターゲットのパターンマッチに一致した部分。 関連するファイルを作成するときなどに役立つ。
変数 | 説明 |
---|---|
$* | ターゲットのパターンマッチに一致した部分。$(*) と書いても同じ意味を持つ。 |
$(*D) | ターゲットのパターンマッチに一致した部分のディレクトリ名。 |
$(*F) | ターゲットのパターンマッチに一致した部分のファイル名。 |
%.o: %.c
echo $* # => lib/foo
echo $(*D) # => lib
echo $(*F) # => foo
テキスト変換のための関数
関数は、makefileにおいて変数を定義する際、テキスト操作をするために使われます。ここでは主な関数を紹介します。
関数は以下の構文で呼び出されます。
$(function arguments)
${function arguments}
参考: GNU make
shell関数
シェルを実行します。
# こんなのとか
files := $(shell echo *.c)
INCLUDE := $(shell find $(INCDIRS) -type d)
SRCDIR = ./srcs
SRCS := $(shell find $(SRCDIR) -name *.c)
all:
echo $(files) # => hoge.c foo.c
echo $(INCLUDE) # => include
echo $(SRCDIR) # => ./srcs
echo $(SRCS) # => hoge.c foo.c
# こんなのとか
RESULT = $(shell seq 1 10)
all:
echo $(RESULT) # => 1 2 3 4 5 6 7 8 9 10
addprefix関数
先頭に文字列を追加します。-l
オプションとか使うときに便利そうです。
$(addprefix prefix,names…)
FILES := foo bar
all:
echo $(addprefix src/,$(FILES)) # => src/foo src/bar
dir関数
ファイル名に対する関数。ディレクトリだけを抽出します。
$(dir names…)
namesのファイル名からディレクトリ部を抜き出します。
ファイル名のディレクトリ部は最後のスラッシュより前のすべてです。
スラッシュを含まない場合は、ディレクトリ部は文字列./
となります。
FILES := src/hoge.c src/hoge.h index.html
all:
echo $(dir $(FILES)) # => src/ src/ ./
置換参照
置換参照は、変数の値を指定した変更で置き換えます。
これは $(var:置換前 = 置換後)
(または $ {var:置換前 = 置換後}
)の形式で、変数varの値を取得し、単語の末尾にあるすべてのaをその値にbを入力し、結果の文字列を置き換えます。
%
は単語内の任意の数の文字に一致するワイルドカードとして機能します。
※置換参照は、patsubst関数の省略形です。
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
all:
echo $(bar) # => a.c b.c c.c
patsubst関数を使用した場合
$(patsubst pattern(置換前), replacement(置換後), text(対象))
foo := a.o b.o c.o
bar := $(patsubst %.o,%.c,$(foo))
all:
echo $(bar) # => a.c b.c c.c
関数について、こちらのサイトでも見やすくまとめられています。
参考: Makefile の関数一覧
Makefileのデバッグ方法
GNU Makeの-nオプションは、Makefileのコマンドを実行せずに出力します。
# ===Makefileの定義===
# CC = gcc
# OBJ = hoge.c
# CFLAGS = -c $(OBJ)
# ===================
$ make -n
gcc -c hoge.c -o hoge
warning関数
make実行時に内容を出力してくれます。
$(warning MAKE = $(MAKE)) # => Makefile:18: MAKE = /Library/Developer/CommandLineTools/usr/bin/make
$(warning CC = $(CC)) # => Makefile:19: CC = gcc
$(warning CFLAGS = $(CFLAGS)) # => Makefile:20: CFLAGS = -Wall -Wextra -Werror
マルチスレッドでビルド
Makefileが書けたら、ビルドを走らせましょう。
make
単体でも実行できますが、-j
オプションで複数スレッドを指定することで、より早くビルドを走ることができます。
私の環境では、12sかかるビルドが3分の1の4sでビルド出来ます。
# time make
make 7.18s user 4.26s system 90% cpu 12.642 total
# スレッド4でビルド make -j4
make -j4 8.37s user 4.94s system 334% cpu 3.977 total
ヘッダーファイルの更新も取得する
コンパイルフラグで-MMD -MP
を指定して
depends = $(objs:.o=.d)
で.d
ファイルを保持して、
-include
をMakefileの最後に記述します。
NAME =prog
CXX = clang++
CXXFLAGS = -MMD -MP
srcs = main.cpp
objs = $(srcs:.cpp=.o)
depends = $(objs:.o=.d)
.PHONY: all
all: $(NAME)
$(NAME): $(objs)
$(CXX) $(CXXFLAGS) -o $(NAME) $(objs)
-include $(depends)
環境変数の読み込み
環境変数を外部ファイルから読み込ませる事もできます。
include .env
.PHONY: log
log:
@sudo cat $(MYSQL_LOG) | pt-query-digest --limit 5 > /tmp/pt-query-digest.txt
-@curl -X POST -F txt=@/tmp/pt-query-digest.txt $(WEBHOOK_URL) -s -o /dev/null
参考
GNU make
公式ドキュメント
Debugging Makefiles | Dr Dobb's
Makefileのデバッグについて
oracle : make ユーティリティ
oracleのサイト
晴耕雨読 Makefile
実行例と共にMakefileの情報がきれいにまとめられているサイト。記事の書き方としても大いに参考にさせていただきました。
暗黙のルールについて詳しく書かれています。
Make覚書 - Qiita