引き続き「UNIXプログラミングの道具箱」(make)

自分でMakefileを書いたこともあるけど、サンプルのコピペで全く内容を理解できていなかった。

make

Makefileには、次の構文で記述する。

ターゲット [ ... ]:[依存コンポーネント [ ... ] ]
	コマンド

ターゲットは生成したいファイルで、依存コンポーネントはそのために使用するファイル。
ターゲットと依存コンポーネントはコロン(:)区切りで1行に記述する。
それぞれ複数のものを指定したい時は、スペース区切りで並べる。
コマンドの前はタブが必要らしい。

makeは以下の場合にコマンドを実行するそうな

  1. ターゲットが存在しない
  2. ターゲットが存在しても、依存オブジェクトの方がタイムスタンプが新しい場合
とりあえずmakeしてみる

Hello Worldを出すsample.c用のMakefileだと次のようになる

sample: sample.c
	cc -o sample sample.c

でも、1ファイルから1オブジェクトを作る場合は、
Makefileなしでも"make sample"ってやるとコンパイルできてしまう。

[kobakoba0723@fedora13-intel64 temp]$ ls
sample.c
[kobakoba0723@fedora13-intel64 temp]$ make sample
cc     sample.c   -o sample
[kobakoba0723@fedora13-intel64 temp]$ ./sample 
Hello World

依存オブジェクトが複数でてくるような状態だとさすがにコンパイルできないみたい

[kobakoba0723@fedora13-intel64 temp]$ ls
Makefile  sample.h  sample1.c  sample2.c
[kobakoba0723@fedora13-intel64 temp]$ cat Makefile 
sample1: sample1.o sample2.o
	cc -o sample1 sample1.o sample2.o

sample1.o: sample1.c sample.h
	cc -c sample1.c

sample2.o: sample2.c sample.h
	cc -c sample2.c
[kobakoba0723@fedora13-intel64 temp]$ rm Makefile 
[kobakoba0723@fedora13-intel64 temp]$ make sample1
cc     sample1.c   -o sample1
/tmp/ccYmfd6M.o: In function `main':
sample1.c:(.text+0x29): undefined reference to `sample2'
collect2: ld はステータス 1 で終了しました
make: *** [sample1] エラー 1

単一のファイルの場合は、どんな作用が働いてコンパイルできてるんだろう。。。

仮想ターゲット

ファイルとして生成しないターゲットを指定するのに使う。
make clean とかでファイルを消すのに使ってたのはこれみたい。

そう考えてみると、makeってコンパイルするためだけのコマンドじゃないのか。
あるターゲットに対して、特定のコマンドを実行させるルールを書くものなんだから。

ほぼ共通のコンポーネントの中で、構成によって特定のファイルだけ使い分けたいなんてこともできるのか。

[kobakoba0723@fedora13-intel64 temp]$ ls
Makefile  sample1.c  sample2.c  sample3_1.c  sample3_2.c  sample4.c
[kobakoba0723@fedora13-intel64 temp]$ cat Makefile 
compA: sample1.c sample2.c sample3_1.c sample4.c
	mkdir compA
	cp sample1.c sample2.c sample3_1.c sample4.c compA

compB: sample1.c sample2.c sample3_2.c sample4.c
	mkdir compB
	cp sample1.c sample2.c sample3_2.c sample4.c compB
[kobakoba0723@fedora13-intel64 temp]$ make compA
mkdir compA
cp sample1.c sample2.c sample3_1.c sample4.c compA
[kobakoba0723@fedora13-intel64 temp]$ make compB
mkdir compB
cp sample1.c sample2.c sample3_2.c sample4.c compB
[kobakoba0723@fedora13-intel64 temp]$ diff compA compB
compAだけに発見: sample3_1.c
compBだけに発見: sample3_2.c
マクロ

以下のように定義しておくと、make 実行時に定義した内容に自動的に置き換えられる。

(定義時)
マクロ名=マクロ内容
(定義追加時)
マクロ名:=$(マクロ名) 追加内容

特殊マクロとして次のようなものが使える。

マクロ 利用可能場所 意味
$@ コマンド行 ターゲット名
$? コマンド行 ターゲットよりタイムスタンプの新しい依存コンポーネント
$ 拡張子ルール内 ソースコンポーネント
$* 拡張子ルール内 コンポーネントのファイル名から拡張子を除いたもの

あぁ、こういう意味だったんだ。なんの呪文かと思ってたよ。

拡張子ルールと拡張子リスト

ある拡張子から別の拡張子のファイルを生成するための規則
.c から .o を生成するためのルールは

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

でも、このルールは大概の処理系に組み込まれているから書かなくても大丈夫だけど、可搬性を持たせるために書いておいても良い

で、この拡張子ルールに使える拡張子を指定するのが拡張子リスト
拡張子リストは、.SUFFIESという特別なターゲットを使って設定する。
ターゲットの依存コンポーネントに何も書かなければ、既に定義されている拡張子リストをクリアし、
スペース区切りで拡張子を指定すると、指定した拡張子が拡張しリストに追加される。
.c.o と .cpp.o を拡張子ルールとして定義できるようにするには、

.SUFFIXES:
.SUFFIXES: .o .c .cpp .h

拡張子リストは左ほど優先度が高いから同じ名前の .c ファイルと .cpp ファイルがあると、.c から .o が生成される

マクロと拡張子ルール/リストを使うと、さっきの2つのソースから sample1 を生成するMakefile

CC=cc
CFLAGS=
TARGET=sample1
OBJS=sample1.o sample2.o

.SUFFIXES:
.SUFFIXES: .o .c

all: $(TARGET)

sample1.o: sample1.c sample.h
sample2.o: sample2.c sample.h

.c.o:
	$(CC) $(CFLAGS) -c $<        # よくよく考えたら、sample.h は必要ないのかな

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)

.c と .h から .o を作るはずなのに、コマンドに .h が出てこなくても問題ないのはなんでだ。
.h だけ touch して、make すると全てコンパイルが走るし、なんでだろう。

include

複数ディレクトリから構成され、それぞれのにMakefileがある時に、共通設定(マクロや拡張子ルール)を外だしする時に有効

include ファイル名(相対パス)