🐧 Linux 総合学習プラットフォーム
C・ビルドツール ・ 中級

Makefileの基本

ソースが増えると毎回長い gcc コマンドを打つのは大変です。Makefile にビルド手順を書いておけば、make と打つだけで必要な部分だけを再ビルドできます。基本の書式は「ターゲット: 依存ファイル」の行に続けて、TABで字下げした実行コマンドを書きます。make は依存ファイルの更新時刻を見て、変更があったターゲットだけを作り直すため無駄が省けます。コマンド行の字下げは必ずスペースではなくTAB文字である必要があり、ここを間違えると Missing separator エラーになります。

ソースファイルが1つのうちは gcc prog.c -o prog で十分だ。だが、プログラムが育ってファイルが何枚にもなると話が変わる。

複数の .c をそれぞれコンパイルしてオブジェクトファイル(.o)を作り、それらをまとめてリンクし……という長いコマンドを、変更のたびに手で打ち直すのは現実的ではない。打ち間違いも起きる。

どのファイルを作り直すべきかを毎回考えるのも面倒で、考え違いをすれば古い .o が混じったまま動かしてしまう事故も起きる。

🔗
たとえMakefile は料理のレシピ、make はそれを見て動く調理担当。手順を一度書けば、あとは make の一言で再現できる。

この面倒と事故を一掃する仕組みが Makefile だ。ビルド手順を1つの設定ファイルにあらかじめ書いておき、make と打つだけで自動実行させる。

make コマンドはこの Makefile を読んでビルドを進める。Makefile にやり方を一度書いておけば、以後は make の3文字でビルドが完了する。

💡
ポイントmake は変更があった部分だけを賢く作り直す。扱うソースが大きくなるほど、この効き目が大きくなる。

しかも、変更があった部分だけを賢く作り直してくれるため、扱うソースの規模が大きくなるほど効果が大きくなる。その「やり方」の書き方を見ていこう。

🧱 ルールは3つの要素でできている

Makefile は、いくつもの「ルール」を並べて書く。身構える必要はない。1つのルールは3つの要素からできているだけだ。

作りたい成果物であるターゲット、それを作るのに必要な入力である依存ファイル、そして実際に実行するコマンドである。

hello:hello.c← ターゲット : 依存ファイル[TAB]gcc hello.c -o hello↑ 実行するコマンド(行頭は必ずTAB)

書式は、1行目に「ターゲット: 依存ファイル」と書き、次の行から実行コマンドを字下げして書く。

たとえば次のような形だ。hello: hello.c と書いた行に続けて、字下げして gcc hello.c -o hello と書く。

これは「hello を作るには hello.c が必要で、作り方は gcc hello.c -o hello だ」という意味のルールである。

このターゲットと依存ファイルの結び付きが、これから見る make の判断のすべての土台になる。

⏱ make が全部を作り直さずに済む理由

make の核心は、依存ファイルの更新時刻(タイムスタンプ)を見て、必要なものだけを作り直す点にある。

ここで、さきほどのターゲットと依存ファイルの結び付き、すなわち「作るもの」と「そのために必要なもの」の関係が効いてくる。

💡
ポイントmake はターゲットと依存ファイルの更新時刻を比べ、依存ファイルが新しいときだけ作り直す。新しくなければ何もしない。

make は、ターゲットと依存ファイルの更新時刻を比べ、依存ファイルのほうが新しい(=ソースが変更された)ときだけコマンドを実行する。逆に、ターゲットのほうが新しければ「もう最新だ」と判断して何もしない。

file1.c← 編集したmain.cfile2.cfile1.o作り直すmain.o(再利用)file2.o(再利用)リンク実行ファイル

だから、たくさんのファイルがあっても、編集した1つに関係する部分だけが再コンパイルされ、触っていないファイルは再利用される。

たとえば main.o file1.o file2.o の3つをリンクして実行ファイルを作る構成を考える。それぞれの .o は対応する .c を依存ファイルに持つルールで作られている。

ここで file1.c だけを直したなら、make はタイムスタンプを比べて file1.o だけを作り直し、main.o と file2.o はそのまま使い、最後にリンクし直すだけで済ませる。

ソースが何十枚もある大きなプログラムほど、この「変わったところだけ」の効き目は大きく、ビルド時間の無駄を劇的に減らす。

🧹 ファイルを作らないターゲット

ターゲットは必ずしも実ファイルである必要はない。実ファイルを作らない便宜的なターゲットもよく使われる。

make clean ……clean: の行に rm -f *.o hello を書いておくと、.o や実行ファイルをまとめて削除できる。

代表が clean で、生成物を消すためのルールだ。clean: と書いた行に続けて、字下げして rm -f *.o hello のように書いておくと、make clean と打つだけで .o や実行ファイルをまとめて削除できる。

同様に、複数の成果物をまとめてビルドする入り口として all というターゲットを用意し、Makefile の先頭付近に置く慣習もある。

コツmake を引数なしで実行すると、ファイル先頭の最初のターゲットが作られる。だから all を先頭に置くと make 一発で全部ビルドできる。

先頭に置くのには理由がある。make を引数なしで実行すると、ファイルの先頭にある最初のターゲットが既定で作られるため、all を先頭に置いて「make 一発で全部ビルド」を実現するのだ。

clean のように実ファイルと結び付かないターゲットは、慣れてくると .PHONY という指定で明示するのが望ましいとされる。

⚠ 字下げを間違えると動かない

Makefile で初学者がほぼ必ず一度ははまるのが、コマンド行の字下げだ。

つまずきコマンド行の行頭はスペースではなく必ずTAB文字。間違えると make は missing separator エラーで止まる。

ルールのコマンド行の先頭は、スペースではなく必ずTAB文字でなければならない。見た目は同じ「字下げ」でも、make はこの1文字を厳密に区別する。

スペースで字下げしてしまうと、make は Makefile:行番号: *** missing separator. Stop. という、いわゆる Missing separator エラーを出して止まる。

やっかいなのは原因が目に見えにくいことだ。エディタの設定によってはTABが自動でスペースに変換されることがあり、これが見えにくいトラブルを生む。

コツエラーが出たら、まず字下げがTABかスペースかを疑う。エディタがTABを自動でスペースに変える設定だと、この罠にはまりやすい。

「コマンドの行頭はTAB」。これはルールというより鉄則として、最初に強く意識しておいてほしい。エラーが出たら、まず字下げがTABかスペースかを疑うのが定石だ。

🛠 Makefileは汎用の自動化ツール

Makefile は、Cプログラムのビルドにとどまらず、決まった手順を名前で呼び出す汎用の自動化ツールとして広く使われる。

テストを走らせる test、配置を行う install、ドキュメントを生成する docs といったターゲットを用意し、make test のように呼び分ける運用は一般的だ。

組込みLinuxの開発でも、カーネルや各種ソフトのビルドはほぼ make を起点に進む。

実際の現場では、毎回同じファイル名を書くのを避けるため変数(CC や CFLAGS など)を使ったり、パターンルールで .c から .o への変換をまとめて書いたりと、より省力化した書き方に発展していく。

だが出発点はあくまで「ターゲット・依存ファイル・コマンド」という素朴なルールの集まりだ。

まずは小さな Makefile を1つ手で書き、make と make clean が動く感覚をつかむことが、自動ビルドへの確かな入り口になる。

この項目に出てくる用語

Makefileめいくふぁいる
make コマンドが読む、ビルド手順を記述した設定ファイル。
ターゲット/依存関係たーげっと/いぞんかんけい
Makefileで「作るもの」と「そのために必要なもの」を結ぶ関係。
オブジェクトファイルおぶじぇくとふぁいる
ソースを機械語へ変換した、リンク前の中間ファイル(.o)。

関連コマンド

makegcc

▶ 学習アプリでこの続きを学ぶ・演習する