08. make 作業のレシピ
Photo by Monika Grabkowska on Unsplash
この回で身につける考え方
この回では,make を使って,何段階にも分かれる作業を自動化する方法を学ぶ.
この回で最も大事なのは,Makefile の細かな文法を全部暗記することではなく,
という考え方である.
具体的には,次のことを学ぶ.
makeとMakefileの役割- ターゲット,依存ファイル,レシピという Makefile の最小文法
- 変更があったときに,必要な作業だけをやり直す仕組み
- C プログラムのコンパイル,実行,データ作成,作図を Makefile でつなぐ方法
- Makefile でよく起こるエラーと,それを調べる方法
今回のゴール
最後には,C プログラムを編集してから,端末で1makeと打つだけで,コンパイル,実行,データ作成,グラフ作成まで進められるようにする.
make について
make とは,Makefile と呼ばれるレシピファイルに書かれた内容に沿って,作業を自動的に行うツールである.
作業の対象は,多くの場合,ソースファイル,実行ファイル,データファイル,図のファイル,PDF ファイルなどの「ファイル」である.
make の特徴を,今回必要な範囲に絞って言うと次の通りである.
- レシピの書き方が比較的単純で,使いまわししやすい.
- 「作りたいもの」と「それに直接必要なもの」の関係を書けばよい.
- 作業の順番は
makeが判断してくれる. - やり直す際,必要な作業だけを実行してくれる.
- 途中でエラーが出て止まっても,原因を直して再度
makeすれば,続きから進めやすい.
これまでに学んだシェルスクリプトも,自動的な作業を行うための道具である. しかし,プログラム開発や文書作成のように,
- ソースを少し修正する
- コンパイルする
- 実行する
- 結果を図にする
- 図を確認してまた修正する
という作業を何度も繰り返す場合は,make が非常に便利である.
make のインストール状況
今回扱う make は CLI 用のコマンドであり,基本的には Unix 環境の文字端末エミュレータから実行する.
本授業の推奨環境,すなわち Ubuntu で
1sudo apt install build-essential
を行っている環境ならば,make は使えるはずである.
念の為,端末で
1make --version
としてみよう.バージョン情報が表示されればよい.
もし make: command not found のように言われる場合は,build-essential が入っていない可能性がある.
Makefile の最小文法
Makefile の基本単位は,次の形である.
つまり,基本形は以下である.
1ターゲット: 依存ファイル1 依存ファイル2
2Tab⭾ コマンド1
3Tab⭾ コマンド2
ここで,
- ターゲット: 作りたいもの.多くの場合はファイル名.
- 依存ファイル: そのターゲットを作るために直接必要なもの.
- コマンド: 実際に行う作業.
である.
とくに重要なのは,コマンド行の先頭である. Makefile では,コマンド行の先頭に 本物のタブ文字 が必要である. スペースを何個並べてもタブの代わりにはならない. この点は,今回もっとも引っかかりやすいところなので強く意識しておこう.
Makefile を読むときの合言葉
- 何を作るのか?
- それを作るには何が必要か?
- 必要なものが揃ったら,どのコマンドを実行するのか?
make でサラダを作ってみよう
まずは例えから入ろう.
make が料理人で,Makefile がレシピに相当すると考える.
そして,簡単なサラダを作るレシピを,make の流儀で書くことを考えてみよう.
サラダ作りを,完成品,途中経過品,材料という「モノ」の依存関係として見ると,次のようになる.
最初に,依存関係だけを表にしてみる.
| ターゲット品 | そのターゲットに必要な品 |
|---|---|
| i. サラダ完成品 | 1. 適度な大きさに切られた清潔かつ新鮮な野菜 2. ドレッシング 3. 綺麗な器 |
| ii. 適度な大きさに切られた 清潔かつ新鮮な野菜 |
4. 清潔かつ新鮮な野菜 |
| iii. 清潔かつ新鮮な野菜 | 5. 新鮮な野菜 |
| iv. ドレッシング | 6. 酢 7. サラダ油 8. 醤油 9. 塩 10. コショウ |
次に,この依存関係の左の「ターゲット品」を作るための「作業」を追加で書き入れる.
| ターゲット品 or 作業 | そのターゲットに必要な品 or 作業内容 |
|---|---|
| i. サラダ完成品 | 1. 適度な大きさに切られた清潔かつ新鮮な野菜 2. ドレッシング 3. 綺麗な器 |
| (上のターゲット i. を作るための作業) | 1 を 3 に入れ,2 をふりかける |
| ii. 適度な大きさに切られた 清潔かつ新鮮な野菜 |
4. 清潔かつ新鮮な野菜 |
| (ii. を作る作業) | 4 を適当にカットする |
| iii. 清潔かつ新鮮な野菜 | 5. 新鮮な野菜 |
| (iii. を作る作業) | 5 を洗う |
| iv. ドレッシング | 6. 酢 7. サラダ油 8. 醤油 9. 塩 10. コショウ |
| (iv. を作る作業) | 6-10 を適量,適度に混ぜる |
この表に従うと,作業は以下のように進む.
まず,一番最初のターゲット品である「サラダ完成品」について考える. サラダ完成品がまだ無ければ,それに必要な品,つまり「切られた野菜」「ドレッシング」「綺麗な器」があるかを調べる. 必要な品が揃っていれば,直下の作業でサラダを完成させる. 足りないものがあれば,今度はその足りないものをターゲットとして,さらにその材料を調べる.
このように,必要な品をたどっていき,材料まで到達したら,そこから順に作業を戻していけばサラダが完成する.
これが Makefile を読んで make が行う基本動作である.
サラダレシピを Makefile 風に書く
上のサラダレシピを Makefile 風に書くと,次のようになる.
1サラダ完成品: 適切清潔新鮮野菜␣ドレッシング␣綺麗な器
2Tab⭾ 必要品の最初を最後に入れ,二番目をふりかける
3
4適切清潔新鮮野菜: 清潔新鮮野菜
5Tab⭾ 必要品を適当にカット
6
7清潔新鮮野菜: 新鮮野菜
8Tab⭾ 必要品を洗う
9
10ドレッシング: 酢␣サラダ油␣醤油␣塩␣コショウ
11Tab⭾ 必要品を適量,適度に混ぜる
ここで,Tab⭾ はタブキーを一回押して入力するタブ文字を意味する. また,␣ はスペースを意味する. 画面表示だけではタブとスペースを区別しにくいので,ここでは明示的に書いている.
さて,このレシピが完成したら,あとはシェルで
1make
と打つだけでよい.
材料が揃っているならば,make がこのレシピに沿って作業を行い,サラダが完成するというわけである.
サラダ作成を途中からやり直す?
例えの続きとして,上のレシピでサラダを作った後に,酢が古かったことに気づき,新しい酢でサラダを作り直したくなったとしよう. ただし,切られた野菜など,途中で作ったものはまだ十分に使えるとする. これはコンピュータ上では,中間ファイルが保存されている状態に相当する.

この場合に必要な作業は,
- 新しい酢を使ってドレッシングを作り直す.
- すでにある野菜に,新しいドレッシングをかけてサラダを作り直す.
という 2つだけでよい. 野菜を洗う作業や切る作業は,もう一度やる必要がない.
では,この「2つの作業だけでよい」という事実を料理人,すなわち make にどう指示すればよいだろうか?
答えは,具体的には指示しなくてよい である.
make は,ファイルの依存関係と更新時刻を調べ,必要な作業だけを自動的に判断してくれる.
つまり,酢を取り替えた後でもう一度
1make
と打つだけで,必要な部分だけを作り直してくれるのである.
これが make の大きな利点である.
実習の準備
ここからは,実際にプログラムを作って make を使ってみる.
まず,実習用ディレクトリを作ってそこに移動しておこう.
1cd ~
2mkdir -p work/make08
3cd work/make08
4pwd
以降の作業は,この make08 ディレクトリで行うものとする.
過去のファイルと混ざると分かりにくいので,別のディレクトリで作業することを勧める.
改行コードは Unix に合わせよう
Makefile や C のソースファイルは,Unix の改行コードで保存するのが無難である. Windows 側のエディタで作る場合は,LF / Unix 形式で保存する設定にしておこう.
改行コードの合わせ方は,例えば以下の通りである.
| ツール | How to |
|---|---|
| Emacs | ファイルを編集しているときに C-x Return f とすると,その文書の漢字コード・改行コードを尋ねられるので,unix と入力すればよい. |
| Notepad++ | 「設定 > 環境設定 > 新規文書」でフォーマットを Unix にする.これから新しく作るファイルは Unix の改行コードになるはずである. |
| VS Code | 画面右下の CRLF または LF の表示をクリックし,LF を選ぶ. |
単純な例: コンパイルして実行する
最初はごく簡単に,C プログラムをコンパイルし,それを実行してデータファイルを作る,という二段階の作業をさせてみよう.

実習
以下の手順に従い,make を動かしてみよう.
ただし,ただタイプするだけではなく,
- 何を作ろうとしているのか
- それを作るには何が必要なのか
- どのコマンドが実行されるのか
を確認しながら進めよう.
1. C プログラムを作る
まず,f-sin.c という名前のファイルを作り,次の中身を書き込んでおく.
1#include <stdio.h>
2#include <math.h>
3int main () {
4 double x, dx;
5 dx = 1.0/1000;
6 x = 0.0;
7 for ( x = 0.0; x <= 0.5+dx; x += dx) {
8 printf("%6.3f %6.3f\n", x, sin(100*x)+sin(110*x));
9 }
10 return 0;
11}
内容は,$x \in [0,0.5]$ の範囲で $0.001$ おきに $\sin(100x)+\sin(110x)$ を計算して出力するプログラムである.
2. Makefile を作る
同じディレクトリに Makefile という名前のファイルを作り,次の中身を書き込んでおく.
1f-sin.dat: f-sin.exe
2Tab⭾ \rm -f f-sin.dat
3Tab⭾ ./f-sin.exe > f-sin.dat
4
5f-sin.exe: f-sin.c
6Tab⭾ gcc f-sin.c -lm -o f-sin.exe
上で書いているように,2, 3, 6行目の Tab⭾ は「タブ」キーを押して入力するタブ文字を意味している.
実際の Makefile には Tab⭾ という文字列を書くのではなく,タブ文字を入力すること.
この Makefile は,次の意味である.
f-sin.datを作るにはf-sin.exeが必要である.f-sin.exeが用意できたら,それを実行してf-sin.datを作る.f-sin.exeを作るにはf-sin.cが必要である.f-sin.cが用意できたら,gccでコンパイルしてf-sin.exeを作る.
\rmの先頭のバックスラッシュは,rmに alias を設定している場合でも,素のrmコマンドを使うためのものである.
3. make してみる
Makefile と f-sin.c があることを確認してから,次を実行する.
1make
問題がなければ,f-sin.c がコンパイルされて f-sin.exe が作られ,さらにそれが実行されて f-sin.dat が作られるはずである.
確認してみよう.
1ls -l
2less f-sin.dat
4. もう一度 make してみる
もう一度,何も変更せずに
1make
としてみよう.
このとき,何が表示されるだろうか.
すでに必要なファイルが新しい状態で揃っていれば,make は「やるべき作業がない」と判断するはずである.
5. データファイルだけ消して make してみる
今度は,結果のデータファイルだけを消してみよう.
1rm f-sin.dat
2make
この時,どの作業が行われるかを注意深く見ておこう.
f-sin.exe が残っていれば,C プログラムのコンパイルはやり直さず,実行だけで f-sin.dat を作り直すはずである.
6. C プログラムを修正して make してみる
次に,f-sin.c の for の行を次のように修正してみる.
1for ( x = 0.0; x <= 1.0+dx; x += dx) {
つまり,0.5 だったところを 1.0 に修正する. その後で,再び
1make
としてみよう. 今度は,ソースファイルが変更されたので,コンパイルからやり直されるはずである.
結果を確認してみよう.
1less f-sin.dat
このように,どのファイルを変更しても,基本的には make と打てば必要な作業だけが実行される.
ちょっと複雑な例: グラフ PDF まで作る
次に,今の例を少しだけ拡張しよう. C プログラムをコンパイルして,実行して,データファイルを作り,さらにそのデータファイルからグラフ PDF を作る,という三段階の作業をさせる.

実習
先ほどの続きとして,以下のようにやってみよう.
1. gnuplot を準備する
この例では,gnuplot というグラフ描画ソフトウェアを使う.
まだインストールされていない場合,本授業の推奨環境の Ubuntu では次のようにすればよい.
1sudo apt install gnuplot
2. gnuplot 用のファイルを作る
先と同じディレクトリに sin2pdf.gpl という名前のファイルを作り,次の中身を書き込んでおく.
1set output "f-sin.pdf"
2set term pdf
3plot "f-sin.dat" with lines
最後の行のあとにも改行を入れておくのが無難である.
3. Makefile を修正する
Makefile の中身を以下のように修正する.
先頭に f-sin.pdf のルールが加わる.
1f-sin.pdf: f-sin.dat
2Tab⭾ gnuplot "sin2pdf.gpl"
3
4f-sin.dat: f-sin.exe
5Tab⭾ \rm -f f-sin.dat
6Tab⭾ ./f-sin.exe > f-sin.dat
7
8f-sin.exe: f-sin.c
9Tab⭾ gcc f-sin.c -lm -o f-sin.exe
この Makefile では,最初のターゲットが f-sin.pdf である.
そのため,単に
1make
と打つと,make は f-sin.pdf を作ろうとする.
そのために f-sin.dat が必要で,さらにそのために f-sin.exe が必要なので,結局,必要な作業を順にたどって実行する.
4. make して PDF を見る
1make
問題がなければ,f-sin.pdf という画像ファイルができているので,見てみよう. Windows ならば,エクスプローラーでそのファイルをダブルクリックすればよいはずである. 他の OS でも似たような方法で開けるだろう.
すると,下記のようになっているはずである.
このように,作業が何段階かになっていても make は問題なく使える.
もうちょっとだけ続くんじゃ...
今度は,上の作業をさらに拡張する. 次のような作業を行うことにしよう.
- 2つのプログラムをコンパイルする.
- 2つの実行ファイルを実行して,2つのデータファイルを作る.
- 片方のデータファイルを処理して,もう1つデータファイルを作る.
- それら 3つのデータを同時にプロットしたグラフ PDF を 1つ作る.
依存関係を図にすると,次のようになる.

理屈がわかっていれば,やることは単純である. Makefile では,局所的なルールを並べていけばよい.
実習
先の続きとして,以下のようにやってみよう.
1. f-cos.c を作る
f-cos.c という名前のファイルを作り,次の中身を書き込んでおく.
1#include <stdio.h>
2#include <math.h>
3int main () {
4 double x, dx;
5 dx = 1.0/1000;
6 x = 0.0;
7 for ( x = 0.0; x <= 1.0+dx; x += dx) {
8 printf("%6.3f %6.3f\n", x, 1.0+cos(10*x));
9 }
10 return 0;
11}
何をするプログラムか,読んで確認しておこう.
2. Makefile を修正する
Makefile の中身を以下のように修正する. 結構変わるので,注意深く作業しよう.
1all.pdf: f-sin.dat f-cos.dat f-cosm.dat
2Tab⭾ gnuplot "all2pdf.gpl"
3
4f-sin.dat: f-sin.exe
5Tab⭾ \rm -f f-sin.dat
6Tab⭾ ./f-sin.exe > f-sin.dat
7
8f-cos.dat: f-cos.exe
9Tab⭾ \rm -f f-cos.dat
10Tab⭾ ./f-cos.exe > f-cos.dat
11
12f-cosm.dat: f-cos.dat
13Tab⭾ \rm -f f-cosm.dat
14Tab⭾ cat f-cos.dat | awk '{print $$1, -$$2}' > f-cosm.dat
15
16f-sin.exe: f-sin.c
17Tab⭾ gcc f-sin.c -lm -o f-sin.exe
18
19f-cos.exe: f-cos.c
20Tab⭾ gcc f-cos.c -lm -o f-cos.exe
Makefile 内の awk と
$
awkでは第1列,第2列を$1,$2と書く. しかし,Makefile の中で単に$1や$2と書くと,makeの変数と解釈されてしまう. そのため,Makefile の中では$$1,$$2と書いている.
3. all2pdf.gpl を作る
先と同じディレクトリに all2pdf.gpl という名前のファイルを作り,次の中身を書き込んでおく.
1set output "all.pdf"
2set term pdf
3plot "f-sin.dat" w l, "f-cos.dat" w l, "f-cosm.dat" w l
やはり,最後の行のあとに改行を入れておくのが無難である.
4. make してみる
1make
問題がなければ,all.pdf という画像ファイルができているので,上と同じ方法で見てみよう. 下記のような図のはずである.
5. 修正して,再び make してみる
プログラムのいずれかを適当に修正してみよう.
ただし,グラフがおかしくならないような修正にしておくこと.
例えば,f-cos.c の 1.0+cos(10*x) の係数や定数を少し変えてみる,などが考えられる.
その後,再び
1make
としてみよう. どこから作業がやり直されるかを確認し,最後に all.pdf を見てみよう.
よくあるエラーと確認方法
Makefile は便利だが,慣れるまでは同じところでよく引っかかる. まずは次の点を疑うとよい.
1. missing separator と言われる
例えば,次のようなエラーが出ることがある.
1Makefile:2: *** missing separator. Stop.
これは,多くの場合,コマンド行の先頭がタブ文字ではなくスペースになっていることが原因である. Makefile の中の見えない文字を確認するには,次のようにするとよい.
1cat -A Makefile
タブ文字は ^I のように見える.
コマンド行の先頭が ^I になっていれば,そこはタブ文字である.
2. No rule to make target と言われる
例えば,次のようなエラーが出ることがある.
1make: *** No rule to make target 'f-sin.c', needed by 'f-sin.exe'. Stop.
これは,必要なファイルが無いか,ファイル名の綴りが Makefile と実際のファイルで食い違っている場合によく起こる. まず,
1ls
でファイル名を確認しよう.
大文字・小文字の違いや,ハイフン - とアンダースコア _ の違いにも注意すること.
3. make しても何も起こらない
すでにターゲットが最新ならば,make は何もしない.
これはエラーではない.
何が起こるかを実行前に確認したい場合は,
1make -n
を使うとよい. これは「実際には実行せず,実行する予定のコマンドだけを表示する」機能である.
4. とにかく最初からやり直したい
授業中の実習では,途中でよく分からなくなったら,作られたファイルを消してからやり直す方が早い場合もある. 今回の例なら,例えば
1rm -f f-sin.exe f-cos.exe f-sin.dat f-cos.dat f-cosm.dat f-sin.pdf all.pdf
2make
のようにする. ただし,C のソースファイルや Makefile まで消さないように注意 すること.
発展・補足編
基本編の内容に対する、発展・補足的な内容を以下に述べる.
学習初期においては無視しても良い内容として、授業時も解説を省略する予定である.
もう少しだけ詳しく: 便利なターゲット
Makefile では,実際のファイル名ではない「作業名」のようなターゲットを書くことも多い.
典型例が clean である.
1clean:
2Tab⭾ rm -f f-sin.exe f-cos.exe f-sin.dat f-cos.dat f-cosm.dat f-sin.pdf all.pdf
こう書いておくと,
1make clean
で作業途中にできたファイルをまとめて消せる.
ただし,もし clean という名前のファイルが存在すると意味が変わってしまうので,実際には次のように書くことが多い.
1.PHONY: clean
2clean:
3Tab⭾ rm -f f-sin.exe f-cos.exe f-sin.dat f-cos.dat f-cosm.dat f-sin.pdf all.pdf
.PHONY は「これは実際のファイル名ではなく,作業名としてのターゲットである」という意味である.
もう少しだけ詳しく: TeX 文書作成に make を使う
TeX を使っている人は,TeX のコンパイル作業にも make を使える.
例えば,paper.tex から paper.pdf を作るだけなら,概念的には次のような Makefile が考えられる.
1paper.pdf: paper.tex
2Tab⭾ platex paper.tex
3Tab⭾ dvipdfmx paper.dvi
ただし,実際の TeX 文書では,参考文献,索引,図の作成などが絡むこともあり,もう少し複雑になる. その場合でも,
- 最終的に作りたいものは何か
- そのために直接必要なファイルは何か
- 必要なものが揃ったら,どのコマンドを実行するのか
という考え方は同じである.
もう少しだけ詳しく: make の文法を使うと楽できるが...
これまでの Makefile を見ると,似たようなことが何回も書かれていて,面倒な上に間違えやすい.
もちろん,make の文法は,このような重複を減らせるように作られている.
例えば,先の Makefile とほぼ同じ動作,ただし f-cosm.dat は無し,をするようなシンプルな Makefile は次のように書ける.
1.SUFFIXES: .dat .exe
2DATA = f-sin.dat f-cos.dat
3
4.c.exe:
5Tab⭾ gcc $< -lm -o $@
6
7.exe.dat:
8Tab⭾ rm -f $@
9Tab⭾ ./$< > $@
10
11all.pdf: $(DATA)
12Tab⭾ gnuplot "all2pdf.gpl"
13
14$(DATA):
上の Makefile 中には繰り返しの記述が少ないことに注意しよう.
ただし,$< や $@ などの自動変数,サフィックスルール,変数 $(DATA) などが出てくるので,最初からここまで覚える必要はない.
まずは,今回の授業で扱った素朴な書き方を理解しよう.
追加実習
余裕があるならば make の文法を調べ,上の Makefile を理解してみよう.
参考資料
- GNU Make
GNU make の公式ページである.情報の起点としてはここがよい.ただし,授業中にすべて読む必要はない. - GNU Make 第3版
O'Reilly の GNU Make に関する書籍の web ページ.書籍の内容が PDF として公式に公開されている. - GNU Make ver.3.79 マニュアルの日本語訳
新堂安孝氏による GNU Make 日本語マニュアルである. - GNU Make ver.3.77 マニュアルの日本語訳
いのまた みつひろ氏による GNU Make 日本語マニュアルである.
レポート No.8
注意
近年はセキュリティ上の懸念から,実行形式のプログラムなどをメールに添付すると,そのメール自体の受信を受信側サーバが拒否することがある.
そういうことを避けるため,レポートをファイルで提出するときは,そのような懸念のあるファイル形式を避けるようにしよう.
要するに,レポートは PDF ファイルにして送るのが良い と思っておけばよい.
以下の課題について,自らの将来のスキルアップに繋がるように調査と考察を行い,
学籍番号-氏名-08.pdf
というファイルとしてレポートを作成し,
webフォーム
から教官宛に提出しよう.
なお,レポートを $\TeX$ 等で作成したものを印刷した紙媒体として教官に直接手渡す形で提出してもよいが,物質によるレポート提出は常に破損や紛失の可能性があるので,あまりお勧めはしない.
課題
- Makefile にさらに追加して,できたグラフファイルを圧縮する機能をつけてみよ.
- Makefile を変更して,
makeを実行すると,以前作ったグラフファイルをどこかへバックアップしてから新しいグラフを作るようにしてみよ. - TeX が使える者は,今回の授業例を修正して,TeX の編集作業に
makeを使えないか考えてみよ.