2. 隱含規則和模式規則

上一節的Makefile寫得中規中矩,比較繁瑣,是為了講清楚基本概念,其實Makefile有很多靈活的寫法,可以寫得更簡潔,同時減少出錯的可能。本節我們來看看這樣一個例子還有哪些改進的餘地。

一個目標依賴的所有條件不一定非得寫在一條規則中,也可以拆開寫,例如:

main.o: main.h stack.h maze.h

main.o: main.c
	gcc -c main.c

就相當於:

main.o: main.c main.h stack.h maze.h
	gcc -c main.c

如果一個目標拆開寫多條規則,其中只有一條規則允許有命令列表,其它規則應該沒有命令列表,否則make會報警告並且採用最後一條規則的命令列表。

這樣我們的例子可以改寫成:

main: main.o stack.o maze.o
	gcc main.o stack.o maze.o -o main

main.o: main.h stack.h maze.h
stack.o: stack.h main.h
maze.o: maze.h main.h

main.o: main.c
	gcc -c main.c

stack.o: stack.c
	gcc -c stack.c

maze.o: maze.c
	gcc -c maze.c

clean:
	-rm main *.o

.PHONY: clean

這不是比原來更繁瑣了嗎?現在可以把提出來的三條規則刪去,寫成:

main: main.o stack.o maze.o
	gcc main.o stack.o maze.o -o main

main.o: main.h stack.h maze.h
stack.o: stack.h main.h
maze.o: maze.h main.h

clean:
	-rm main *.o

.PHONY: clean

這就比原來簡單多了。可是現在main.ostack.omaze.o這三個目標連編譯命令都沒有了,怎麼編譯的呢?試試看:

$ make
cc    -c -o main.o main.c
cc    -c -o stack.o stack.c
cc    -c -o maze.o maze.c
gcc main.o stack.o maze.o -o main

現在解釋一下前三條編譯命令是怎麼來。如果一個目標在Makefile中的所有規則都沒有命令列表,make會嘗試在內建的隱含規則(Implicit Rule)資料庫中查找適用的規則。make的隱含規則資料庫可以用make -p命令打印,打印出來的格式也是Makefile的格式,包括很多變數和規則,其中和我們這個例子有關的隱含規則有:

# default
OUTPUT_OPTION = -o $@

# default
CC = cc

# default
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c

%.o: %.c
#  commands to execute (built-in):
        $(COMPILE.c) $(OUTPUT_OPTION) $<

#號在Makefile中表示單行註釋,就像C語言的//註釋一樣。CC是一個Makefile變數,用CC = cc定義和賦值,用$(CC)取它的值,其值應該是cc。Makefile變數像C的宏定義一樣,代表一串字元,在取值的地方展開。cc是一個符號連結,通常指向gcc,在有些UNIX系統上可能指向另外一種C編譯器。

$ which cc
/usr/bin/cc
$ ls -l /usr/bin/cc
lrwxrwxrwx 1 root root 20 2008-07-04 05:59 /usr/bin/cc -> /etc/alternatives/cc
$ ls -l /etc/alternatives/cc
lrwxrwxrwx 1 root root 12 2008-11-01 09:10 /etc/alternatives/cc -> /usr/bin/gcc

CFLAGS這個變數沒有定義,$(CFLAGS)展開是空,CPPFLAGSTARGET_ARCH也是如此。這樣$(COMPILE.c)展開應該是cc␣空␣空␣空␣-c,去掉所有的“”得到cc␣␣␣␣-c,注意中間留下4個空格,所以%.o: %.c規則的命令$(COMPILE.c)␣$(OUTPUT_OPTION)␣$<展開之後是cc␣␣␣␣-c␣-o␣$@␣$<,和上面的編譯命令已經很接近了。

$@$<是兩個特殊的變數,$@的取值為規則中的目標,$<的取值為規則中的第一個條件。%.o: %.c是一種特殊的規則,稱為模式規則(Pattern Rule)。現在回顧一下整個過程,在我們的Makefile中以main.o為目標的規則都沒有命令列表,所以make會查找隱含規則,發現隱含規則中有這樣一條模式規則適用,main.o符合%.o的模式,現在%就代表main(稱為main.o這個名字的Stem),再替換到%.c中就是main.c。所以這條模式規則相當於:

main.o: main.c
	cc    -c -o main.o main.c

隨後,在處理stack.o目標時又用到這條模式規則,這時又相當於:

stack.o: stack.c
	cc    -c -o stack.o stack.c

maze.o也同樣處理。這三條規則可以由make的隱含規則推導出來,所以不必寫在Makefile中。

先前我們寫Makefile都是以目標為中心,一個目標依賴于若干條件,現在換個角度,以條件為中心,Makefile還可以這麼寫:

main: main.o stack.o maze.o
	gcc main.o stack.o maze.o -o main

main.o stack.o maze.o: main.h
main.o maze.o: maze.h
main.o stack.o: stack.h

clean:
	-rm main *.o

.PHONY: clean

我們知道,寫規則的目的是讓make建立依賴關係圖,不管怎麼寫,只要把所有的依賴關係都描述清楚了就行。對於多目標的規則,make會拆成幾條單目標的規則來處理,例如

target1 target2: prerequisite1 prerequisite2
	command $< -o $@

這樣一條規則相當於:

target1: prerequisite1 prerequisite2
	command prerequisite1 -o target1

target2: prerequisite1 prerequisite2
	command prerequisite1 -o target2

注意兩條規則的命令列表是一樣的,但$@的取值不同。