C語言的本質(38)——makefile之變數

尹成發表於2014-07-24

我們詳細看看Makefile中關於變數的語法規則。先看一個簡單的例子:

foo = $(bar)
bar = Huh?
 
all:
    @echo$(foo)


我們執行make將會打出Huh?。當make讀到foo = $(bar)時,確定foo的值是$(bar),但並不立即展開$(bar),然後讀到bar = Huh?,確定bar的值是Huh?,然後在執行規則all:的命令列表時才需要展開$(foo),得到$(bar),再展開$(bar),得到Huh?。因此,雖然bar的定義寫在foo之後,$(foo)展開還是能夠取到$(bar)的值。

這種特性有好處也有壞處。好處是我們可以把變數的值推遲到後面定義,例如:

main.o: main.c
    $(CC)$(CFLAGS) $(CPPFLAGS) -c $<
 
CC = gcc
CFLAGS = -O -g
CPPFLAGS = -Iinclude


編譯命令可以展開成gcc -O -g -Iinclude -cmain.c。通常把CFLAGS定義成一些編譯選項,例如-O、-g等,而把CPPFLAGS定義成一些預處理選項,例如-D、-I等。用=號定義變數的延遲展開特性也有壞處,就是有可能寫出無窮遞迴的定義,例如CFLAGS = $(CFLAGS) -O,或者:

A = $(B)
B = $(A)


當然,make有能力檢測出這樣的錯誤而不會陷入死迴圈。有時候我們希望make在遇到變數定義時立即展開,可以用:=運算子,例如:

x := foo
y := $(x) bar
 
all:
    @echo"-$(y)-"


當make讀到y :=$(x) bar定義時,立即把$(x)展開,使變數y的取值是foo bar,如果把這兩行顛倒過來:

y := $(x) bar
x := foo

那麼當make讀到y :=$(x) bar時,x還沒有定義,展開為空值,所以y的取值是 bar,注意bar前面有個空格。一個變數的定義從=後面的第一個非空白字元開始(從$(x)的$開始),包括後面的所有字元,直到註釋或換行之前結束。如果要定義一個變數的值是一個空格,可以這樣:

nullstring :=
space := $(nullstring) # end ofthe line


nullstring的值為空,space的值是一個空格,後面寫個註釋是為了增加可讀性,如果不寫註釋就換行,則很難看出$(nullstring)後面有個空格。

還有一個比較有用的賦值運算子是?=,例如foo ?= $(bar)的意思是:如果foo沒有定義過,那麼?=相當於=,定義foo的值是$(bar),但不立即展開;如果先前已經定義了foo,則什麼也不做,不會給foo重新賦值。

+=運算子可以給變數追加值,例如:

objects = main.o
objects += $(foo)
foo = foo.o bar.o


object是用=定義的,+=仍然保持=的特性,objects的值是main.o$(foo)(注意$(foo)前面自動添一個空格),但不立即展開,等到後面需要展開$(objects)時會展開成main.o foo.o bar.o。

再比如:

objects := main.o
objects += $(foo)
foo = foo.o bar.o


object是用:=定義的,+=保持:=的特性,objects的值是main.o$(foo),立即展開得到main.o (這時foo還沒定義),注意main.o後面的空格仍保留。

如果變數還沒有定義過就直接用+=賦值,那麼+=相當於=。

上一節我們用到了特殊變數$@和$<,這兩個變數的特點是不需要給它們賦值,在不同的上下文中它們自動取不同的值。常用的特殊變數有:

•   $@,表示規則中的目標。

•   $<,表示規則中的第一個條件。

•   $?,表示規則中所有比目標新的條件,組成一個列表,以空格分隔。

•   $^,表示規則中的所有條件,組成一個列表,以空格分隔。

例如前面寫過的這條規則:

main: main.o stack.o maze.o
    gccmain.o stack.o maze.o -o main


可以改寫成:

main: main.o stack.o maze.o
    gcc$^ -o $@


這樣即使以後又往條件裡新增了新的目標檔案,編譯命令也不需要修改,減少了出錯的可能。

$?變數也很有用,有時候希望只對更新過的條件進行操作,例如有一個庫檔案libsome.a依賴於幾個目標檔案:

libsome.a: foo.o bar.o lose.owin.o
    arr libsome.a $?
    ranliblibsome.a


這樣,只有更新過的目標檔案才需要重新打包到libsome.a中,沒更新過的目標檔案原本已經在libsome.a中了,不必重新打包。

在上一節我們看到make的隱含規則資料庫中用到了很多變數,有些變數沒有定義(例如CFLAGS),有些變數定義了預設值(例如CC),我們寫Makefile時可以重新定義這些變數的值,也可以在預設值的基礎上追加。以下列舉一些常用的變數,請讀者體會其中的規律。

AR

靜態庫打包命令的名字,預設值是ar。

ARFLAGS

靜態庫打包命令的選項,預設值是rv。

AS

彙編器的名字,預設值是as。

ASFLAGS

彙編器的選項,沒有定義。

CC

C編譯器的名字,預設值是cc。

CFLAGS

C編譯器的選項,沒有定義。

CXX

C++編譯器的名字,預設值是g++。

CXXFLAGS

C++編譯器的選項,沒有定義。

CPP

C前處理器的名字,預設值是$(CC) -E。

CPPFLAGS

C前處理器的選項,沒有定義。

LD

連結器的名字,預設值是ld。

LDFLAGS

連結器的選項,沒有定義。

TARGET_ARCH

和目標平臺相關的命令列選項,沒有定義。

OUTPUT_OPTION

輸出的命令列選項,預設值是-o $@。

LINK.o

把.o檔案連結在一起的命令列,預設值是$(CC) $(LDFLAGS) $(TARGET_ARCH)。

LINK.c

把.c檔案連結在一起的命令列,預設值是$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)。

LINK.cc

把.cc檔案(C++原始檔)連結在一起的命令列,預設值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)。

COMPILE.c

編譯.c檔案的命令列,預設值是$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c。

COMPILE.cc

編譯.cc檔案的命令列,預設值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c。

RM

刪除命令的名字,預設值是rm -f。

 

常用的make命令列選項:

-n選項只列印要執行的命令,而不會真的執行命令,這個選項有助於我們檢查Makefile寫得是否正確,由於Makefile不是順序執行的,用這個選項可以先看看命令的執行順序,確認無誤了再真正執行命令。

 

-C選項可以切換到另一個目錄執行那個目錄下的Makefile,比如先退到上一級目錄再執行我們的Makefile(假設我們的原始碼都放在testmake目錄下):

 

$ cd ..
$ make -C testmake
make: Entering directory`/home/djkings/testmake'
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
make: Leaving directory`/home/djkings/testmake'


一些規模較大的專案會把不同的模組或子系統的原始碼放在不同的子目錄中,然後在每個子目錄下都寫一個該目錄的Makefile,然後在一個總的Makefile中用make -C命令執行每個子目錄下的Makefile。例如Linux核心原始碼根目錄下有Makefile,子目錄fs、net等也有各自的Makefile,二級子目錄fs/ramfs、net/ipv4等也有各自的Makefile。

 

在make命令列也可以用=或:=定義變數,如果這次編譯我想加除錯選項-g,但我不想每次編譯都加-g選項,可以在命令列定義CFLAGS變數,而不必修改Makefile編譯完了再改回來:

 

$ make CFLAGS=-g
cc -g   -c -o main.o main.c
cc -g   -c -o stack.o stack.c
cc -g   -c -o maze.o maze.c
gcc main.o stack.o maze.o -o main


如果在Makefile中也定義了CFLAGS變數,則命令列的值覆蓋Makefile中的值。

 

 

 

 

 

 

相關文章