Tuesday, August 2, 2011

Makefile basics

By Rui Chen

--Makefile is really a broad topic, if need more, check the book Autotools.

Reading digest from Makefile tutorial(in Chinese)

每個Makefile中都應該寫一個清空目標文件(.o和執行文件)的規則,這不僅便於重編譯,也很利於保持文件的清潔。這是一個「修養」(呵呵,還記得我的《編程修養》嗎)。一般的風格都是:
clean:
 rm edit $(objects)
更為穩健的做法是:
.PHONY : clean
clean :
 -rm edit $(objects)
前面說過,.PHONY意思表示clean是一個「偽目標」,。而在rm命令前面加了一個小減號的意思就是,也許某些文件出現問題,但不要管,繼續 做後面的事。當然,clean的規則不要放在文件的開頭,不然,這就會變成make的默認目標,相信誰也不願意這樣。不成文的規矩是——「clean從來 都是放在文件的最後」。
上面就是一個makefile的概貌,也是makefile的基礎,下面還有很多makefile的相關細節,準備好了嗎?準備好了就來。

Part 2:
如果我們想定義一系列比較類似的文件,我們很自然地就想起使用通配符。make支援三個通配符:「*」,「?」和「~」。這是和Unix的B-Shell是相同的。
波浪號(「~」)字元在文件名中也有比較特殊的用途。如果是「~/test」,這就表示當前用戶的$HOME目錄下的test目錄。而 「~hchen/test」則表示用戶hchen的宿主目錄下的test目錄。(這些都是Unix下的小知識了,make也支援)而在Windows或是 MS-DOS下,用戶沒有宿主目錄,那麼波浪號所指的目錄則根據環境變數「HOME」而定。
通配符代替了你一系列的文件,如「*.c」表示所以後綴為c的文件。一個需要我們注意的是,如果我們的文件名中有通配符,如:「*」,那麼可以用轉義字元「\」,如「\*」來表示真實的「*」字元,而不是任意長度的字元串。


另給一個變數使用通配符的例子:
a. 列出一確定文件夾中的所有」.c"文件
objects := $(wildcard *.c)
b. 列出(a)中所有文件對應的".o"文件,在(c)中我們可以看到它是由make自動編譯出的。
$(patsubst %.c,%.o,$(wildcard *.c))
c. 由(a)(b)兩步,可寫出編譯並鏈接所有「.c"和」.o"文件
objects := $(patsubst %.c,%.o,$(wildcard *.c))
 foo : $(objects)
        cc -o foo $(objects)  
這種用法由關鍵字「wildcard」,「patsubst"指出,關於Makefile的關鍵字,我們將在後面討論。 


偽目標最早先的一個例子中,我們提到過一個「clean」的目標,這是一個「偽目標」,
clean:
 rm *.o temp
正像我們前面例子中的「clean」一樣,既然我們生成了許多文件編譯文件,我們也應該提供一個清除它們的「目標」以備完整地重編譯而用。 (以「make clean」來使用該目標)
因為,我們並不生成「clean」這個文件。「偽目標」並不是一個文件,只是一個標籤,由於「偽目標」不是文件,所以make無法生成它的 依賴關係和決定它是否要執行。我們只有通過顯式地指明這個「目標」才能讓其生效。當然,「偽目標」的取名不能和文件名重名,不然其就失去了「偽目標」的意 義了。
當然,為了避免和文件重名的這種情況,我們可以使用一個特殊的標記「.PHONY」來顯式地指明一個目標是「偽目標」,向make說明,不管是否有這個文件,這個目標就是「偽目標」。
.PHONY : clean
只要有這個聲明,不管是否有「clean」文件,要運行「clean」這個目標,只有「make clean」這樣。於是整個過程可以這樣寫:
.PHONY : clean
clean :
 rm *.o temp
偽目標一般沒有依賴的文件。但是,我們也可以為偽目標指定所依賴的文件。偽目標同樣可以作為「默認目標」,只要將其放在第一個。一個示例就是,如果 你的Makefile需要一口氣生成若干個可執行文件,但你只想簡單地敲一個make完事,並且,所有的目標文件都寫在一個Makefile中,那麼你可 以使用「偽目標」這個特性:




多目標Makefile的規則中的目標可以不止一個,其支援多目標,有可能我們的多個目標同時依賴於一個文件,並且其生成的命令大體類似。於是我們就能把 其合併起來。當然,多個目標的生成規則的執行命令不是同一個,這可能會可我們帶來麻煩,不過好在我們可以使用一個自動化變數「$@」(關於自動化變數,將 在後面講述),這個變數表示著目前規則中所有的目標的集合,這樣說可能很抽象,還是看一個例子吧。
bigoutput littleoutput : text.g
 generate text.g -$(subst output,,$@) > $@
上述規則等價于:
bigoutput : text.g
 generate text.g -big > bigoutput
littleoutput : text.g
 generate text.g -little > littleoutput
其中,-$(subst output,,$@)中的「$」表示執行一個Makefile的函數,函數名為subst,後面的為參數。關於函數,將在後面講述。這裏的這個函數是替換字元串的意思,「$@」表示目標的集合,就像一個數組,「$@」依次取出目標,並執于命令。


 This page is important to understand default variables.

我們可以把隱含規則中使用的變數分成兩種:一種是命令相關的,如「CC」;一種是參數相的關,如「CFLAGS」。下面是所有隱含規則中會用到的變數:
1、關於命令的變數。
AR
函數庫打包程式。默認命令是「ar」。
AS
彙編語言編譯程式。默認命令是「as」。
CC
C語言編譯程式。默認命令是「cc」。
CXX
C++語言編譯程式。默認命令是「g++」。
CO
從 RCS文件中擴展文件程式。默認命令是「co」。
CPP
C程式的預處理器(輸出是標準輸出設備)。默認命令是「$(CC) –E」。
FC
Fortran 和 Ratfor 的編譯器和預處理程式。默認命令是「f77」。
GET
從SCCS文件中擴展文件的程式。默認命令是「get」。
LEX
Lex方法分析器程式(針對於C或Ratfor)。默認命令是「lex」。
PC
Pascal語言編譯程式。默認命令是「pc」。
YACC
Yacc文法分析器(針對於C程式)。默認命令是「yacc」。
YACCR
Yacc文法分析器(針對於Ratfor程式)。默認命令是「yacc –r」。
MAKEINFO
轉換Texinfo源文件(.texi)到Info文件程式。默認命令是「makeinfo」。
TEX
從TeX源文件創建TeX DVI文件的程式。默認命令是「tex」。
TEXI2DVI
從Texinfo源文件創建軍TeX DVI 文件的程式。默認命令是「texi2dvi」。
WEAVE
轉換Web到TeX的程式。默認命令是「weave」。
CWEAVE
轉換C Web 到 TeX的程式。默認命令是「cweave」。
TANGLE
轉換Web到Pascal語言的程式。默認命令是「tangle」。
CTANGLE
轉換C Web 到 C。默認命令是「ctangle」。
RM
刪除文件命令。默認命令是「rm –f」。
2、關於命令參數的變數
下面的這些變數都是相關上面的命令的參數。如果沒有指明其默認值,那麼其默認值都是空。
ARFLAGS
函數庫打包程式AR命令的參數。默認值是「rv」。
ASFLAGS
彙編語言編譯器參數。(當明顯地調用「.s」或「.S」文件時)。
CFLAGS
C語言編譯器參數。
CXXFLAGS
C++語言編譯器參數。
COFLAGS
RCS命令參數。
CPPFLAGS
C預處理器參數。( C 和 Fortran 編譯器也會用到)。
FFLAGS
Fortran語言編譯器參數。
GFLAGS
SCCS 「get」程式參數。
LDFLAGS
鏈接器參數。(如:「ld」)
LFLAGS
Lex文法分析器參數。
PFLAGS
Pascal語言編譯器參數。
RFLAGS
Ratfor 程式的Fortran 編譯器參數。
YFLAGS
Yacc文法分析器參數。
==========
3、自動化變數
在上述的模式規則中,目標和依賴文件都是一系例的文件,那麼我們如何書寫一個命令來完成從不同的依賴文件生成相應的目標?因為在每一次的對模式規則的解析時,都會是不同的目標和依賴文件。
自動化變數就是完成這個功能的。在前面,我們已經對自動化變數有所提涉,相信你看到這裏已對它有一個感性認識了。所謂自動化變數,就是這種變數會把模式中所定義的一系列的文件自動地挨個取出,直至所有的符合模式的文件都取完了。這種自動化變數只應出現在規則的命令中。
下面是所有的自動化變數及其說明:
$@
表示規則中的目標文件集。在模式規則中,如果有多個目標,那麼,"$@"就是匹配于目標中模式定義的集合。
$%
僅當目標是函數庫文件中,表示規則中的目標成員名。例如,如果一個目標是"foo.a(bar.o)",那麼,"$%"就是 "bar.o","$@"就是"foo.a"。如果目標不是函數庫文件(Unix下是[.a],Windows下是[.lib]),那麼,其值為空。
$< 
依賴目標中的第一個目標名字。如果依賴目標是以模式(即"%")定義的,那麼"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。
$?
所有比目標新的依賴目標的集合。以空格分隔。
$^
所有的依賴目標的集合。以空格分隔。如果在依賴目標中有多個重複的,那個這個變數會去除重複的依賴目標,只保留一份。
$+
這個變數很像"$^",也是所有依賴目標的集合。只是它不去除重複的依賴目標。
$*
這個變數表示目標模式中"%"及其之前的部分。如果目標是"dir/a.foo.b",並且目標的模式是"a.%.b",那麼,"$*"的值就是"dir /a.foo"。這個變數對於構造有關聯的文件名是比較有較。如果目標中沒有模式的定義,那麼"$*"也就不能被推導出,但是,如果目標文件的後綴是 make所識別的,那麼"$*"就是除了後綴的那一部分。例如:如果目標是"foo.c",因為".c"是make所能識別的後綴名,所以," $*"的值就是"foo"。這個特性是GNU make的,很有可能不兼容於其它版本的make,所以,你應該盡量避免使用"$*",除非是在隱含規則或是靜態模式中。如果目標中的後綴是make所不 能識別的,那麼"$*"就是空值。
當你希望只對更新過的依賴文件進行操作時,"$?"在顯式規則中很有用,例如,假設有一個函數庫文件叫"lib",其由其它幾個object文件更新。那麼把object文件打包的比較有效率的Makefile規則是:
lib : foo.o bar.o lose.o win.o
           ar r lib $?
在上述所列出來的自動量變數中。四個變數($@、$<、$%、$*)在擴展時只會有一個文件,而另三個的值是一個文件列表。這七個自動化變數 還可以取得文件的目錄名或是在當前目錄下的符合模式的文件名,只需要搭配上"D"或"F"字樣。這是GNU make中老版本的特性,在新版本中,我們使用函數"dir"或"notdir"就可以做到了。"D"的含義就是Directory,就是目錄,"F"的 含義就是File,就是文件。

No comments:

Post a Comment