make の使い方

これも昔書いたもの。

make で何ができるのか?

  • 実行モジュールを作るためのファイルの依存関係を定義。
  • タイムスタンプを比較して必要なものだけコンパイル
  • 一般的なコンパイル規則の提供。
  • コンパイル & インストール作業の抽象化。
make の魂

Unix の世界ではソースファイルが命。

ソースファイルさえあれば実行モジュールは消えても、make しなおせばよい。

オブジェクトファイルや実行ファイルは版管理しない。
版管理されたソースファイルを make すれば、その版の実行ファイルができる。

世界で一番簡単な make の使い方

ソースファイルを用意する。

$ cat hello.c
#include 

int main()
{
    printf("hello world.\n");
    return 0;
}

さっそく make を使ってみる。

$ make hello
cc     hello.c   -o hello
$ ./hello
hello world.
$ make hello
make: `hello' is up to date.

とっても簡単だ。

世界で一番簡単な makefile

普通は makefile という名前のファイルに make への指示を書いておく。

$ cat Makefile
hello:

make してみよう。

$ rm hello

$ make
cc     hello.c   -o hello
$ ./hello
hello world.
$ make
make: `hello' is up to date.

とっても簡単だ。

かなり初歩的な makefile

もう少し普通に makefile を書いてみる。

foo.c と bar.c から foobar を make する。
foo.c は foo.h と bar.h を参照し、bar.c は bar.h を参照する。

$ cat Makefile
foobar: foo.o bar.o
	${CC} ${CFLAGS} ${LDFLAGS} -o foobar foo.o bar.o

foo.o: foo.c foo.h bar.h
	${CC} ${CPPFLAGS} ${CFLAGS} -c foo.c

bar.o: bar.c bar.h
	${CC} ${CPPFLAGS} ${CFLAGS} -c bar.c

":" の前がターゲット名。
":" の後ろが依存するファイル群 (コンポーネント)。

例えば、ターゲット foobarfoo.obar.o に依存する。
foo.obar.o のどちらかが foobar よりも新しければ作りなおす。

ターゲットの依存関係の次の行には、そのターゲットを作るためのアクションを書く。

アクション行は、TAB で始める。
行頭が TAB でないとアクションとは見做さない。
アクション行は複数書ける。

make はカレントディレクトリから makefile または Makefile を読み込む。
そして makefile の最初に出てくるターゲットを make する。
上記の例を明示的に指示すると "make -f Makefile foobar" となる。

変数

makefile にも変数 (マクロ) は使える。

シェルの要領で変数を定義できる。
シェルと違って = の前後に空白があっても OKay.

参照するときは $ を付け、 { } または ( ) で囲む。
同様の記述で環境変数を参照することも可能。

$ cat Makefile
OBJS = foo.o bar.o

foobar: ${OBJS}
	${CC} ${CFLAGS} ${LDFLAGS} -o foobar ${OBJS}

foo.o: foo.c foo.h bar.h
	${CC} ${CPPFLAGS} ${CFLAGS} -c foo.c

bar.o: bar.c bar.h
	${CC} ${CPPFLAGS} ${CFLAGS} -c bar.c

変数名は大文字で書くのが習慣。*1

CC, CFLAGS, LDFLAGS などは make があらかじめ用意している変数。
make -p で確認できる。

サフィックスルール

「ファイルごと」ではなく「拡張子ごと」にアクションを定義できる。

これで .c の数だけアクションを書かずに済む。

$ cat Makefile
OBJS =  foo.o bar.o

foobar:  ${OBJS}
	${CC} ${CFLAGS} ${LDFLAGS} -o $@ ${OBJS}

.c.o:
	${CC} ${CPPFLAGS} ${CFLAGS} -c $<

foo.o: foo.c foo.h bar.h
bar.o: bar.c bar.h

ターゲットにはサフィックスルールで、.c から .o を作ることを指示する。

$@$< はアクションに使える特殊なマクロ。

$@
ターゲット
$<
コンポーネント
$*
コンポーネント (拡張子抜き)
独自のサフィックスルール

まずルールに登場するサフィックスをターゲット .SUFFIXES に定義する。

実際のルールは既存のサフィックスルールと同様に定義する。

$ cat Makefile
.SUFFIXES:
.SUFFIXES: .out .o .c .sqc

    ...

.sqc.c:
	db2 prep $< bindfile

    ...

初めに .SUFFIXES をクリアしてから定義する。
クリアしないと既存の .SUFFIXES への追加になる。 *2

既存の .SUFFIXESmake -p で確認できる。

もう少し洗練された makefile

上記の初歩的な (foo.c と bar.c を使う) makefile を洗練させてみる。

$ cat Makefile
OBJS = foo.o bar.o

foobar: ${OBJS}
	${CC} ${CFLAGS} ${LDFLAGS} -o $@ ${OBJS}

foo.o: foo.c foo.h bar.h
bar.o: bar.c bar.h

現在 使用していなくても CFLAGS, LDFLAGS などのマクロを参照するのは重要。
例えばデバックオプションを指定して make するとき、いちいち makefile を変更する必要がなくなる。

$ make 'CFLAGS=-g'
cc -g   -c -o foo.o foo.c
cc -g   -c -o bar.o bar.c
cc -g  -o foobar foo.o bar.o

ダミーターゲット

ターゲットそのものを作るわけではなく、一連の処理へのエントリを作る。

install

普通 /bin や /usr/local/bin/ などのディレクトリに、いきなりターゲットを作ったりしない。

ターゲットをソースファイルと同じディレクトリに作った後、(テストした後) make install でターゲットを指定のディレクトリにコピーする。

$ cat Makefile
    ...
install: 
	${INSTALL_PROGRAM} ${TARGET} ${DESTDIR}/${TARGET}
    ...
clean

全ターゲットを削除するために make clean を使う。

$ cat Makefile
    ...
clean: 
	rm -f ${TARGET} *.o
    ...
all

ひとつの makefile で複数のターゲットを作成する場合、ターゲット all を使う。

t1, t2, t3 を作る場合、以下のような makefile を用意する。

$ cat Makefile
all: t1 t2 t3

t1: 
	...
t2: 
	...
t3: 
	...

make とだけ打つと all をターゲットに make されて、 t1, t2, t3 が make される。
普通 make all とタイプすることはない。

all は作られないので、毎回 all を make しようとするが、 t1, t2, t3 は必要なときだけ make される。

階層構造

コンパイルすべきソースファイルが複数のディレクトリ存在するとき、ひとつの makefile で別のディレクトリのソースファイルをコンパイルしてはいけない。

以下のディレクトリ構造なら、makefile はそれぞれのディレクトリごとにみっつ作る。

libsrc にある makefile はこれまで通り。
自分のディレクトリにあるターゲットだけ責任を持って make する。

上位層にある makefile は、libsrc を make するよう指示する。*3

$ cat Makefile
all: make_lib make_src

make_lib:
	(cd lib; make)

make_src:
	(cd src; make)

同様に installclean も下位の make を呼び出せるようにする。

上位層の makefile で設定した変数は、下位層の make には引き継がれない。
渡したい変数は明示する。

$ cat Makefile
all: make_lib make_src

make_lib:
	(cd lib; make "CFLAGS=${CFLAGS}")

make_src:
	(cd src; make "CFLAGS=${CFLAGS}")

アクションはシェルスクリプト

make はルールに基づいて再生成が必要だと判断すると、アクションの各行を変数 SHELL で指定したシェルで処理する。

アクション行の変数を評価してから、それを表示しシェルを起動する。

SHELL には普通 /bin/sh が設定される。

実行した結果 ($?) が 0 でなければ、make は処理を中断する。

コマンドの前に "-" を書くと、エラーが起きても処理を中断しない。

コマンドの前に "@" を書くと、アクション行を評価した結果を表示せずに実行する。

$ cat Makefile
target:
	echo "成功すれば次のアクションへ"
	-echo "失敗しても次のアクションへ"
	@echo "実行するコマンドを表示しない"

行ごとにシェルを起動するので、行をまたいでシェル変数を渡すことはできない。
行末が "\" なら、次のアクションを連結して、ひとつのアクションと見做す。

シェル変数を make に評価させないためには "$""$" で escape して "$$" と表記する。

$ cat Makefile
TARGETS =  foo bar

install: ${TARGETS}
	for t in ${TARGETS}; do \
	  ${INSTALL_PROGRAM} $$t ${DESTDIR}/$$t; \
	done

*1:マクロだし

*2:この例ではクリアせずに追加するだけでも意味は同じだった。

*3:サブシェルにする意味あるのかなぁ?