Makeを理解する

August 1, 2026 · 訪問者

makeは、私が触れてきたほぼすべての組み込みおよびシステム系プロジェクトに登場する——Yoctoのレシピ、カーネルビルド、U-Boot、小さなファームウェアリポジトリなど。それはまた、人々が何年も模倣によって使い続けているツールの一つでもある。前のプロジェクトからMakefileをコピーするだけで、その裏にあるモデルをきちんと把握しないままに。ここでは、そのモデルを直接示す。

Makeとは何か

Makeはビルド自動化ツールである。ファイル(Makefile)を読み込み、そこには一連のターゲット、各ターゲットが依存する前提条件、そして前提条件からそのターゲットを生成するために必要なレシピ(シェルコマンド)が記述されている。この記述をもとに、makeはどのターゲットが古くなっているかを判断し、それらを最新にするために必要なレシピだけを実行する——それ以上は何もしない。

main: main.o utils.o
	gcc -o main main.o utils.o

main.o: main.c
	gcc -c main.c

makeを実行すると、main.cmain.oより新しい場合(あるいはmain.oがまだ存在しない場合)にのみmain.oをビルドし、main.oまたはutils.oが変更された場合にのみmainをリンクする。不要な再ビルドは一切発生しない。

誰のためのものか

Makeは1976年4月、Bell LabsのStuart Feldmanによって書かれた。彼自身の話によれば、きっかけは同僚——yaccの作者であるSteve Johnson——が、実際には正しいプログラムのデバッグに丸一日の朝を費やしてしまったことだった。バイナリがソースの変更後に再リンクされていなかっただけだったのだ。Feldmanはこれにより2003年のACM Software System Awardを受賞した。あの午後から生まれたこのツール——依存関係を一度記録し、何を作り直す必要があるかを機械に判断させる——は、ソフトウェアツールにおいて最も広く模倣されたアイデアの一つとなった。今日ほぼ誰もが使っているバージョンであるGNU Makeは、GNUプロジェクトによるフリーな再実装である (1) (5) (6)。

実際のところ、これは非自明な成果物——コンパイル済みのバイナリ、ドキュメント、生成されたファイル群——をより小さな部品から組み立てるすべての人のためのものであり、変更のたびにゼロからすべてを再ビルドするのが無駄あるいは遅すぎる場合に役立つ。これはC/C++プロジェクト、カーネルやブートローダーのビルド、LaTeX文書、そしてコードのコンパイルとはまったく関係のない多くの場当たり的な自動化をカバーする。

いつ使うべきか

Makeは、次の3つが同時に成り立つときに真価を発揮する。

相互依存のない少数のファイルであれば、シェルスクリプトの方がシンプルで誠実なことが多い。Makeが元を取り始めるのは、「これは本当に再ビルドが必要だったのか?」が、手作業ではなく自動的に問うべき価値のある問いになったときである。

ツールチェーンのどこに位置するか

Makeは通常、コンパイラの一段上、そして人間が実際に実行するものの一段下に位置する。典型的なCプロジェクトでは、ソースファイル →(コンパイラ、Makeによって駆動される)→ オブジェクトファイル →(リンカ、Makeによって駆動される)→ 最終的なバイナリ、という流れになる。Make自体は何もコンパイルもリンクもしない——それを行うツールを、各レシピの中でシェルコマンドとして呼び出すだけである。

またMakeは、より高レベルなビルド設定ツールの下層に位置する傾向もある。例えばCMake、Autotools、YoctoのBitBakeなどは、Makeを置き換えるというよりも、実際の実行バックエンドとしてMakefile(あるいはそれに相当するもの)を生成し駆動している。だから、Makefileを直接見せないプロジェクトであっても、パイプラインのどこかでMakeを実際に動かしていることが多い。

なぜそのように動作するのか

中核となる設計上の選択——ファイルの更新時刻を比較して何が古くなっているかを判断する——こそが、Makeを大規模なツリーでも高速にしている理由である。タイムスタンプのチェックは安価なので、一行の変更後に巨大なプロジェクトを再ビルドする際にも、ほとんどをスキップしつつ正しさを保てる。ただし、与えられた依存グラフが正確である限りにおいてである。

この最後の条件こそが、人々が遭遇する「Makeが壊れている」という不満のほぼすべての源であり、じっくり考える価値がある。Makeは、それに伝えられたグラフと同程度にしか正しくない。ある.cファイルが#includeしているヘッダーが、対応する.oファイルの前提条件として列挙されていない場合、そのヘッダーを編集しても再ビルドはトリガーされない——Makeが混乱しているからではなく、その依存関係の存在を誰もMakeに伝えたことがないからだ。だからこそ実際のMakefileは、手書きのヘッダーリストではなく、コンパイラが生成する依存関係のステップ(GCC/Clangでの-MMD -MP)とほぼ常に組み合わされる——コンパイラはすでに真のインクルードグラフを知っているので、それこそが唯一信頼できる情報源だからだ (4)。

実際にどう動作するのか

ターゲット、前提条件、レシピ。 基本単位は次のとおりである。

target: prerequisite1 prerequisite2
	recipe line 1
	recipe line 2

レシピの行はスペースではなくタブでインデントしなければならない——今日でも人々をつまずかせる、有名な歴史的な欠陥である。

自動変数は繰り返しを減らす。

%.o: %.c
	gcc -c $< -o $@

$@はターゲット、$<は最初の前提条件を表す——このパターンルール一つだけで、対応する.cファイルから各.oをビルドでき、ファイルごとに一行書く必要はない。

.PHONYターゲットは、cleanalltestのような、実際のファイルに対応しない名前をマークするためのものだ。これにより、たまたま同名のファイルが存在してもMakeが混乱せず、タイムスタンプをチェックする代わりに常にレシピを実行するようになる (2)。

.PHONY: clean
clean:
	rm -f *.o main

変数は、あちこちでフラグを繰り返す手間を省き、プロジェクト間での再利用も容易にする。

CC = gcc
CFLAGS = -Wall -O2

main.o: main.c
	$(CC) $(CFLAGS) -c main.c

自動依存関係生成は、上で説明したギャップを埋める。

CFLAGS = -Wall -O2 -MMD -MP
-include $(wildcard *.d)

%.o: %.c
	$(CC) $(CFLAGS) -c $<

-MMDはコンパイラに、オブジェクトごとに.dファイルを出力させ、そのコンパイルで実際に取り込まれたすべてのヘッダーを列挙させる。-includeはそれらのファイルをMakefileに取り込み直すことで、ヘッダーが変化しても依存グラフが正確な状態を保つ——誰も手作業でそれを維持する必要はない (3)。

結びに

Makeの癖のように見えるものの大半——タイムスタンプの比較、タブへのこだわり、依存関係が欠けていても大きな音を立てず静かに失敗するその挙動——は、1976年に下された一つの設計判断に由来する。モデルをシンプルに保ち、チェックを安価にし、Makefileが現実を正確に記述していると信頼すること。この視点に立てば、「Makeが変な動きをしている」ように見える瞬間のほとんどは、実は「自分が与えたグラフが不完全だった」だけだと分かる——そしてそれは、はるかに直しやすい問題である。

関連記事

参考文献

  1. Feldman, S. I. (1979). Make — A Program for Maintaining Computer Programs. Software: Practice and Experience, 9(4), 255–265. — Makeを紹介したFeldman自身の論文で、その原点となった動機も記されている。
  2. GNU Make Manual — GNU Makeの構文とセマンティクスに関する完全かつ権威あるリファレンス。
  3. GNU Make Manual: Automatic Prerequisites-MMD/-includeパターンについて、メンテナ自身の言葉による解説。
  4. GCC: Preprocessor Options (-M family)-MMD-MPが内部で何を行っているか。
  5. Make (software) — Wikipedia — Makeの各実装(BSD Make、GNU Make、NMAKEなど)の一般的な歴史と系譜。
  6. Stuart Feldman — Wikipedia — Bell LabsでのFeldmanの仕事と、2003年のACM Software System Awardに関する背景情報。

コメント