make
Last Modified: Tue Dec 27 00:06:02 JST 2011

まず白状するが、僕自身は make を使いこなしているとは到底言えない生活を送っている。 ファイルを何十個も集めて作るような、大きなプログラムを作ったことがないのがその理由かもしれない。 どっちにしてもここでは、ごく基本的な(邪悪かもしれない)使い方を説明するにとどめる。 最近手にはいる書籍で、make の使い方を上手に説明しているものを知らないのだが、たぶん実際に使ってみるのが一番よい習得方法だと思う。


どんな Unix ツールもそうだが、make もまた「楽をするため」のプログラムだ。 make は、プログラムのメンテナンスを楽にするためのツールである。 具体的には、依存関係というものを管理するためのプログラムである。

まず単純な例から始めよう。 C 言語のプログラムを作るとき、foo.c というソースファイルをエディタで作成する。 そしてそれを、次のようなコマンドでコンパイルする。

% cc -o program foo.c

蛇足を承知で説明すると、これは「foo.c をコンパイルして program という名前のプログラムとして出力せよ」という意味だ。

プログラムが大きくなり、適度なサイズでファイルを分割するようになると、こんな風になっていくはずだ。

% cc -o program foo.c bar.c hem.c

これは foo.c、bar.c、hem.c という3つのファイルから program という名前のプログラムを作ることを意味する。 さて、プログラムをデバッグしているときは、ファイルの一部を変更し、コンパイルし、テストするという作業の繰り返しになる。 今 bar.c の一部を変更してコンパイルするとしよう。 上記のようなコマンドでは、実際には変更しなかった foo.c や hem.c もコンパイルし直すことになってしまう。 このような場合、ソースファイルからプログラムへ一気にコンパイルしてしまうのではなく、それぞれのソースをいったんオブジェクトファイルへコンパイルし、最後に3つのオブジェクトファイルをプログラムとしてリンクする方法が考えられる。 コマンドで示すとこうなる。

% cc -c -o foo.o foo.c
% cc -c -o bar.o bar.c
% cc -c -o hem.o hem.c
% cc -o program foo.o bar.o hem.o

1〜3行目が、それぞれのファイルをオブジェクトファイルにコンパイルし、最後の4行目がリンクを行っている。 もし bar.c だけを変更したなら、2行目のコマンドを実行した後、4行目でリンクを行うだけでよい。 この方法ならば、ソースファイルの数が増えても、コンパイル時間を増やさないですますことができる。

しかし問題もある。 それは依存関係の管理だ。 ソースファイルを変更したら、オブジェクトファイルへのコンパイルを行わなければならないが、どれをコンパイルし直すべきかということは、変更のたびにいちいちチェックしてやらなければならないのだ。 これはファイルの数が少ないうちはいいが、多くなってくると手に追えなくなる。 かといって、全部を一気にコンパイルし直したら、コンパイル時間を押さえるはずのファイル分割の工夫が無意味になってしまう。

make は、このような問題を解決するのに役立つ。 make は、ファイルのタイムスタンプを調べ、元となるファイルが生成されるファイルより新しいのならば、生成をやり直すという作業を行ってくれる。 もちろん、そのためには「どのファイルが元で、生成物はどれで、どうやって生成作業を行うか」を指示してやらなければならない。 その指示を行うのが Makefile だ。

Makefile はテキストファイルで、以下のような形式になっている。

ターゲット: ソース
	コマンド

これは、ターゲットを生成するためには、ソースのタイムスタンプを調べることを指示している。 そしてもしソースの方がターゲットより新しいのならば、コマンドを実行する。 もしもターゲットの方がソースより新しいのならば、何もせずに黙って終了する。 コマンドの前には、タブを1つ入れる規則になっている。

program の生成のための Makefile はこうなる。

program: foo.o bar.o hem.o
	cc -o program foo.o bar.o hem.o

foo.o: foo.c
	cc -c -o foo.o foo.c

bar.o: bar.c
	cc -c -o bar.o bar.c

hem.o: hem.c
	cc -c -o hem.o hem.c

今、デバッグの最中で bar.c だけを変更し、make を実行したとする。 make の動作はこんな風になる。

  1. 単に make が実行されると、最初に現れるターゲットを生成しようとする。 それはつまり、program だ。
  2. program は foo.o、bar.o、hem.o の各ファイルから作られることが分かる。 そこでそれらの依存関係のチェックが始まる。
  3. foo.o は foo.c から作られる。 そこで foo.o と foo.c のタイムスタンプが比較される。 今は foo.c は変更しておらず、コンパイルが終わっているはずなので foo.o の方が新しい。 コマンドは実行されない。
  4. bar.o は bar.c から作られる。 しかし bar.c は変更したので、bar.o より新しい。 ここでコマンドが実行され、bar.o が更新される。
  5. hem.c は変更していないので、hem.o もそのままにされる。
  6. 最初の program の依存関係のチェックでは、bar.o が program より新しいことが分かった。 そこでリンクのためのコマンドが実行される。

このようにして、ただ make というコマンドを実行するだけで、make はすべての依存関係をチェックし、コンパイルを行ってくれる。

make には、他にも様々な機能を持っている。 たとえば Makefile では複雑な構文規則が使えたり、マクロが使えたりする。 数百に及ぶソースファイルから成る巨大なプログラムも、make ひとつで簡単にメンテナンスすることができるし、 FreeBSD の ports コレクションのような巨大なプロジェクトも、基本的には make によって構築されている。

(続く)


[back to index]