LoginSignup
30
34

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

Last updated at Posted at 2020-07-29

はじめに

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 事前に定義された変数一覧 - Qiita

変数の定義

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

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

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

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

参考: Makefile の特殊変数・自動変数の一覧

早見表

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

$@

現在のターゲット名。

変数 説明
$@ ルールのターゲットの名前。$(@) と書いても同じ意味を持つ。
$(@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

30
34
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
34