GNU make　Makefileについて【初心者向け】

# はじめに

Makefileについてわかりやすい解説が少なかったのでまとめて見ました。
初心者向けに書いたものになります。
主に[GNU make](https://www.gnu.org/software/make/manual/make.html#Rules)の記述を訳しています。不自然な点、間違いがあったらご指摘いただければ幸いです。

Makeを理解するには、コンパイルへの理解が重要です。
コンパイルについては別記事でまとめています。よければどうぞ。
[gccの概要 【初心者向け】 - Qiita](https://qiita.com/keitean/items/28a519ec1caa8c15abcf)

# 基本

主にMakefileはmakeにプログラムのコンパイルおよびリンク方法を指示します。（応用することで他の使い方もできます）
Makefileに依存関係(プログラムの前提条件となるファイルとの親子関係)を記述しておくと、各ファイルの更新を取得して必要なものだけをコンパイルしてくれるようになります。

Makefileに含まれる情報は主に3つです。

- 変数の定義(変数、自動変数、暗黙の変数、特殊変数)
- ルール
- その他(他のMakefileをoverrideするときの情報やデバッグ情報など)

# ルールと呼ばれる基本形

依存関係を記す部分です。

```Makefile
target(ターゲット) :[prerequisites(前提条件)]
  recipe(レシピ)
```

前提条件はあってもなくてもよいです。前提条件はレシピが成立する前提条件となります。依存関係は、ターゲットが子、前提条件の親になります。

|名前|説明|
|---|---|
|target|プログラムによって生成されるファイルの名前|
|prerequisites|ターゲットを作成するための入力として使用されるファイル<br>複数ファイルも可<br>省略可|
|recipe|makeが実行するアクション<br>複数行可(行を分割する場合は\を使う)<br>行頭にタブ文字必須|

**例**

```Makefile
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`で明示的に宣言することで、その名前のファイルが存在するかどうか、またはその最終変更時刻に関係なく、無条件にレシピを実行します。

```Makefile
.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　事前に定義された変数一覧 - Qiita](https://qiita.com/keitean/items/7b5fb6b562d8dc92dc20)

## 変数の定義

Makefile内での変数の定義の仕方には2種類あります。

|記号|詳細|
|---|---|
|=|再帰的に展開される変数|
|:=|単純に展開される変数|

```Makefile
foo = $(bar)
bar = $(ugh)
ugh = Huh?

all:
  echo $(foo) # => Hub?
```

## 変数の呼び出し

`$(name)`、または`${name}`

**例**

```Makefile
objects = program.o foo.o utils.o # => program.o foo.o utils.o
program : $(objects)
        cc -o program $(objects) 

$(objects) : defs.h
```

## ルール内で機能する自動変数

**自動変数**とはターゲットと前提条件を呼び出すときに使用できる記法です。省略形として使えるだけではなく、条件に合わせて、必要なものだけ参照する機能を持つものもあります。

参考サイトの解説より引用し、自分なりに書き換えています。

参考: [Makefile の特殊変数・自動変数の一覧](https://tex2e.github.io/blog/makefile/automatic-variables)

### 早見表

自動変数|機能
--- | ---
$@|ターゲット名
$<|依存関係の最初の名前
$^|ターゲットのすべての依存関係の名前
$?|ターゲットよりタイムスタンプが新しい依存関係の名前
$+|ターゲットの全ての依存関係の名前
$*|ターゲットのパターンマッチに一致した部分

### $@

現在のターゲット名。

| 変数   | 説明 |
| ---   | --- |
| $@    | ルールのターゲットの名前。$(@) と書いても同じ意味を持つ。 |
| $(@D) | ルールのターゲットのディレクトリ名。 |
| $(@F) | ルールのターゲットのファイル名。 |

```Makefile
aaa/bbb/foo:
	echo $@     # => aaa/bbb/foo
	echo $(@D)  # => aaa/bbb
	echo $(@F)  # => foo
```

### $<

前提条件の一番最初の名前。

| 変数 | 説明 |
| ---| --- |
| $<    | 前提条件の一番最初の名前。$(<) と書いても同じ意味を持つ。 |
| $(<D) | 前提条件の一番最初のディレクトリ名。 |
| $(<F) | 前提条件の一番最初のファイル名。 |

```Makefile
output/foo: input/bar input/baz
	echo $<     # => input/bar
	echo $(<D)  # => input
	echo $(<F)  # => bar
```

### $^

ターゲットの全ての前提条件の名前。

| 変数   | 説明 |
|:--    |:--   |
| $^    | ターゲットの前提条件の名前。$(^) と書いても同じ意味を持つ。 |
| $(^D) | ターゲットの前提条件のディレクトリ名。 |
| $(^F) | ターゲットの前提条件のファイル名。 |

```Makefile
output/foo: input/bar input/baz
	echo $^     # => input/bar input/baz
	echo $(^D)  # => input input
	echo $(^F)  # => bar baz
```

### $?

ターゲットよりタイムスタンプが新しい前提条件の名前。

| 変数 | 説明 |
|:--  |:--   |
| $? | ターゲットより新しい全ての前提条件の名前。$(?) と書いても同じ意味を持つ。 |
| $(?D) | ターゲットより新しい全ての前提条件のディレクトリ名。 |
| $(?F) | ターゲットより新しい全ての前提条件のファイル名。 |

```Makefile
output/foo: input/bar input/baz
	echo $?     # => input/bar
	echo $(?D)  # => input
	echo $(?F)  # => bar
```

### $+

ターゲットの全ての前提条件の名前 (重複があっても省略しない)。 一般的には $^ の方がよく使われます。

| 変数 | 説明 |
|:--  |:--  |
| $+ | 重複を含むターゲットの前提条件の名前。$(+) と書いても同じ意味を持つ。 |
| $(+D) | 重複を含むターゲットの前提条件のディレクトリ名。 |
| $(+F) | 重複を含むターゲットの前提条件のファイル名。 |

```Makefile
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) | ターゲットのパターンマッチに一致した部分のファイル名。 |

```Makefile
%.o: %.c
	echo $*     # => lib/foo
	echo $(*D)  # => lib
	echo $(*F)  # => foo
```

## テキスト変換のための関数

関数は、makefileにおいて変数を定義する際、テキスト操作をするために使われます。ここでは主な関数を紹介します。

関数は以下の構文で呼び出されます。

```Makefile
$(function arguments)
${function arguments}
```

参考: [GNU make](https://www.gnu.org/software/make/manual/make.html#Text-Functions)

### shell関数

シェルを実行します。

```Makefile
# こんなのとか
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…)

```Makefile
FILES := foo bar
all:
	echo $(addprefix src/,$(FILES))  # => src/foo src/bar
```

### dir関数

ファイル名に対する関数。ディレクトリだけを抽出します。

$(dir names…)

namesのファイル名からディレクトリ部を抜き出します。
ファイル名のディレクトリ部は最後のスラッシュより前のすべてです。
スラッシュを含まない場合は、ディレクトリ部は文字列`./`となります。

```Makefile
FILES := src/hoge.c src/hoge.h index.html
all:
	echo $(dir $(FILES))  # => src/ src/ ./
```

### 置換参照

置換参照は、変数の値を指定した変更で置き換えます。
これは `$(var:置換前 = 置換後)` (または `$ {var：置換前 = 置換後}`)の形式で、変数varの値を取得し、単語の末尾にあるすべてのaをその値にbを入力し、結果の文字列を置き換えます。
`%`は単語内の任意の数の文字に一致するワイルドカードとして機能します。
※置換参照は、patsubst関数の省略形です。

```Makefile
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(対象))

```Makefile
foo := a.o b.o c.o
bar := $(patsubst %.o,%.c,$(foo))

all:
    echo $(bar) # => a.c b.c c.c
```

関数について、こちらのサイトでも見やすくまとめられています。
参考: [Makefile の関数一覧](https://tex2e.github.io/blog/makefile/functions)

## Makefileのデバッグ方法

GNU Makeの-nオプションは、Makefileのコマンドを実行せずに出力します。

```terminal

# ===Makefileの定義===
# CC = gcc
# OBJ = hoge.c
# CFLAGS = -c $(OBJ)
# ===================

$ make -n
gcc -c hoge.c -o hoge
```

### warning関数

make実行時に内容を出力してくれます。

```Makefile
$(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でビルド出来ます。

```sh
# 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の最後に記述します。

```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)
```

## 環境変数の読み込み

環境変数を外部ファイルから読み込ませる事もできます。

```makefile
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](https://www.gnu.org/software/make/manual/make.html#Rules)
公式ドキュメント

[Debugging Makefiles | Dr Dobb's](https://www.drdobbs.com/tools/debugging-makefiles/197003338?pgno=2)
Makefileのデバッグについて

[oracle : make ユーティリティ](https://docs.oracle.com/cd/E19620-01/805-5827/6j5gfranb/index.html)
oracleのサイト

[晴耕雨読 Makefile](https://tex2e.github.io/blog/#makefile)
実行例と共にMakefileの情報がきれいにまとめられているサイト。記事の書き方としても大いに参考にさせていただきました。

暗黙のルールについて詳しく書かれています。
[Make覚書 - Qiita](https://qiita.com/Shigets/items/27170827707e5136ee89)