开发工具(三)
內(nèi)建規(guī)則
到目前為止,我們已經(jīng)在makefile文件中確切的指定了如何執(zhí)行過(guò)程的每一步。事實(shí)上,makefile有大量的內(nèi)建規(guī)則從而可以很大程度的簡(jiǎn)化makefile文件,特別是當(dāng)我們有大量源文件的時(shí)候。下面我們創(chuàng)建foo.c,這是一個(gè)傳統(tǒng)的Hello World程序。
#include <stdlib.h>
#include <stdio.h>
int main()
{
??? printf(“Hello World/n”);
??? exit(EXIT_SUCCESS);
}
不指定makefile文件,我們嘗試使用make來(lái)編譯。
$ make foo
cc???? foo.c -o foo
$
正如我們所看到的,make知道如何調(diào)用編譯器,盡管在這種情況下,他選擇cc而不是gcc(在Linux下這可以正常工作,因?yàn)橥ǔc鏈接到gcc)。有時(shí),這些內(nèi)建規(guī)則是推斷規(guī)則(inference rules)。默認(rèn)的規(guī)則使用宏,所以通過(guò)為這些宏指定一個(gè)新值,我們可以改變默認(rèn)的行為。
$ rm foo
$ make CC=gcc CFLAGS=”-Wall -g” foo
gcc -Wall -g??? foo.c?? -o foo
$
我們可以使用-p選項(xiàng)使得make打印出其內(nèi)建規(guī)則。內(nèi)建規(guī)則太多而不能在這里全部列出,但是下面是GNU版本的make的make -p的簡(jiǎn)短輸出,演示了其中的部分規(guī)則:
OUTPUT_OPTION = -o $@
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
%.o: %.c
# commands to execute (built-in):
??????? $(COMPILE.c) $(OUTPUT_OPTION) $<
我們現(xiàn)在可以通過(guò)指定構(gòu)建目標(biāo)文件的規(guī)則使用這些內(nèi)建規(guī)則來(lái)簡(jiǎn)化我們的makefile文件,所以makefile文件的相關(guān)部分簡(jiǎn)化為:
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h
后綴與模式規(guī)則
我們所看到的內(nèi)建規(guī)則使用后綴進(jìn)行工作(與Windows和MS-DOS的文件名擴(kuò)展相類(lèi)似),所以當(dāng)指定一個(gè)帶有擴(kuò)展名的文件時(shí),make知道應(yīng)使用哪條規(guī)則來(lái)創(chuàng)建帶有不同擴(kuò)展名的文件。在這里最通常的規(guī)則就是由以.c為結(jié)尾的文件創(chuàng)建以.o為結(jié)尾的文件。這個(gè)規(guī)則就是使用編譯器編譯文件,但是并不鏈接源文件。
有時(shí)我們需要能夠創(chuàng)建新規(guī)則。程序開(kāi)發(fā)作者過(guò)去在一些源文件上需要使用不同的編譯器進(jìn)行編譯:兩個(gè)在MS-DOS下,以及Linux下的gcc。要滿足MS-DOS編譯器的要求,C++源文件而不是C源文件,需要以.cpp為后綴進(jìn)行命名。不幸的是,現(xiàn)在Linux下使用的make版本并沒(méi)有編譯.cpp文件的內(nèi)建規(guī)則。(他確實(shí)具有一個(gè)在Unix下更為常見(jiàn)的.cc的規(guī)則)
所以或者是為每一個(gè)單獨(dú)的文件指定一個(gè)規(guī)則,或者是我們需要教給make一個(gè)新的規(guī)則來(lái)由以.cpp為擴(kuò)展名的文件創(chuàng)建目標(biāo)文件。假如我們?cè)谶@個(gè)工程中有大量的源文件,指定一個(gè)新規(guī)則節(jié)省了大量的輸入工作,并且使得在工程中添加一個(gè)新源文件更為容易。
要添加一個(gè)新的后綴規(guī)則,我們首先在makefile文件中添加一行,告訴make新的后綴;然后我們就可以使用這個(gè)新的后綴來(lái)編寫(xiě)一條規(guī)則。make使用下面的語(yǔ)法樣式來(lái)定義一條通用的規(guī)則來(lái)由具有舊后綴的文件創(chuàng)建具有新后綴的文件:
.<old_suffix>.<new_suffix>:
下面是我們的makefile文件中一條新的通用規(guī)則的代碼片段,用于將.cpp文件轉(zhuǎn)換為.o文件:
.SUFFIXES:????? .cpp
.cpp.o:
?? $(CC) -xc++ $(CFLAGS) -I$(INCLUDE) -c $<
特殊依賴(lài).cpp.o:告訴make接下來(lái)的規(guī)則用于將以.cpp為后綴的文件轉(zhuǎn)換為以.o為后綴的文件。當(dāng)我們編寫(xiě)這個(gè)依賴(lài)時(shí),我們使用特殊的宏名,因?yàn)槲覀儾⒉恢牢覀儗⒁D(zhuǎn)換的實(shí)際文件名。要理解這條規(guī)則,我們只需要簡(jiǎn)單的回憶起$<會(huì)擴(kuò)展為起始文件名(帶有舊后綴)即可。注意,我們只是告訴make如何由.cpp文件得到.o文件;make已經(jīng)知道如何由一個(gè)目標(biāo)文件獲得二進(jìn)制可執(zhí)行文件。
當(dāng)我們調(diào)用make時(shí),他使用我們的新規(guī)則由bar.cpp獲得bar.o,然后使用其內(nèi)建規(guī)則由.o獲得一個(gè)可執(zhí)行文件。-xc++標(biāo)記用于告訴gcc這是一個(gè)C++源文件。
在近些時(shí)候,make知道如何處理帶有.cpp擴(kuò)展名的C++源文件,但是當(dāng)將一種文件類(lèi)型轉(zhuǎn)換為另一種文件類(lèi)型時(shí),這個(gè)技術(shù)是十分有用的。
更為舊的make版本包含一個(gè)對(duì)應(yīng)的語(yǔ)法用來(lái)達(dá)到同樣的效果,而且更好。例如,匹配規(guī)則使用通配符語(yǔ)法來(lái)匹配文件,而不是僅依賴(lài)于文件擴(kuò)展名。
對(duì)于上面例子中與.cpp規(guī)則等同的模式規(guī)則如下:
%.cpp: %o
?? $(CC) -xc++ $(CFLAGS) -I$(INCLUDE) -c $<
使用make管理庫(kù)
當(dāng)我們正處理一個(gè)大型工程時(shí),使用庫(kù)來(lái)管理多個(gè)編譯產(chǎn)品通常是比較方便的。庫(kù)是文件,通常以.a為擴(kuò)展名,包含一個(gè)目標(biāo)文件的集合。make命令有一個(gè)處理庫(kù)的特殊語(yǔ)法,從而使得他們更易于管理。
這個(gè)語(yǔ)法就是lib (file.o),這就意味著目標(biāo)文件file.o存儲(chǔ)在庫(kù)lib.a中。make具有一個(gè)內(nèi)建的規(guī)則用于管理庫(kù),通常如下面的樣子:
.c.a:
?? $(CC) -c $(CFLAGS) $<
?? $(AR) $(ARFLAGS) $@ $*.o
宏$(AR)與$(ARFLAGS)通常分別默認(rèn)為命令ar與選項(xiàng)rv。這個(gè)簡(jiǎn)短的語(yǔ)法告訴make由一個(gè).c文件得到.a庫(kù),他必須執(zhí)行兩條規(guī)則:
第一條規(guī)則是他必須編譯源文件并且生成一個(gè)目標(biāo)文件
第二條規(guī)則是使用ar命令來(lái)修改庫(kù),添加新的目標(biāo)文件
所以,如果我們有一個(gè)庫(kù)fud,包含文件bas.o,在第一條規(guī)則中$<被替換為bas.c。在第二條規(guī)則中,$@被替換為庫(kù)fud.a,而$*被替換為bas。
試驗(yàn)--管理庫(kù)
實(shí)際上,管理庫(kù)的規(guī)則的使用是相當(dāng)簡(jiǎn)單的。下面我們修改我們的程序,從而文件2.o與3.o保存在一個(gè)名為mylib.a的庫(kù)中。我們的makefile文件需要一些小的修改,所以Makefile5如下所示:
all: myapp
# Which compiler
CC = gcc
# Where to install
INSTDIR = /usr/local/bin
# Where are include files kept
INCLUDE = .
# Options for development
CFLAGS = -g -Wall -ansi
# Options for release
# CFLAGS = -O -Wall -ansi
# Local Libraries
MYLIB = mylib.a
myapp: main.o $(MYLIB)
?? $(CC) -o myapp main.o $(MYLIB)
$(MYLIB): $(MYLIB)(2.o) $(MYLIB)(3.o)
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h
clean:
?? -rm main.o 2.o 3.o $(MYLIB)
install: myapp
?? @if [ -d $(INSTDIR) ]; /
??? then /
????? cp myapp $(INSTDIR);/
????? chmod a+x $(INSTDIR)/myapp;/
????? chmod og-w $(INSTDIR)/myapp;/
????? echo “Installed in $(INSTDIR)”;/
?? else /
????? echo “Sorry, $(INSTDIR) does not exist”;/
?? fi
在這里需要注意我們是如何使用默認(rèn)規(guī)則來(lái)完成大多數(shù)工作的。現(xiàn)在讓我們來(lái)測(cè)試我們的新版本makefile文件。
$ rm -f myapp *.o mylib.a
$ make -f Makefile5
gcc -g -Wall -ansi?? -c -o main.o main.c
gcc -g -Wall -ansi?? -c -o 2.o 2.c
ar rv mylib.a 2.o
a - 2.o
gcc -g -Wall -ansi?? -c -o 3.o 3.c
ar rv mylib.a 3.o
a - 3.o
gcc -o myapp main.o mylib.a
$ touch c.h
$ make -f Makefile5
gcc -g -Wall -ansi?? -c -o 3.o 3.c
ar rv mylib.a 3.o
r - 3.o
gcc -o myapp main.o mylib.a
$
工作原理
我們首先刪除所有的目標(biāo)文件以及庫(kù),并且允許make構(gòu)建myapp,他通過(guò)編譯并且在使用庫(kù)鏈接main.o之前創(chuàng)建庫(kù),從而創(chuàng)建myapp。然后我們測(cè)試3.o的測(cè)試規(guī)則,他會(huì)通知make,如果c.h發(fā)生變動(dòng),那么3.c必須進(jìn)行重新編譯。他會(huì)正確的完成這些工作,在重新鏈接之前會(huì)編譯3.c并且更新庫(kù),從而創(chuàng)建一個(gè)新的可執(zhí)行文件myapp。
高級(jí)主題:Makefile與子目標(biāo)
如果我們編寫(xiě)一個(gè)大工程,有時(shí)將組成庫(kù)的文件由主文件分離并且存儲(chǔ)在一個(gè)子目錄中是十分方便的。使用make可以兩種方法來(lái)完成這個(gè)任務(wù)。
首先,我們?cè)诖俗幽夸浛梢杂械诙€(gè)makefile文件來(lái)編譯文件,將其存儲(chǔ)在一個(gè)庫(kù)中,然后將庫(kù)拷貝到上一層主目錄。在高層目錄中的主makefile文件然后有一條規(guī)則用于構(gòu)建這個(gè)庫(kù),其調(diào)用第二個(gè)makefile文件的語(yǔ)法如下:
mylib.a:
?? (cd mylibdirectory;$(MAKE))
這就是說(shuō)我們必須總是嘗試構(gòu)建mylib.a。當(dāng)make調(diào)用這條規(guī)則用于構(gòu)建庫(kù)時(shí),他會(huì)進(jìn)入子目錄mylibdirectory,然后調(diào)用一個(gè)新的make命令來(lái)管理庫(kù)。因?yàn)檫@會(huì)調(diào)用一個(gè)新的shell,使用makefile的程序并不會(huì)執(zhí)行cd命令。然而,所調(diào)用的用于執(zhí)行規(guī)則構(gòu)建庫(kù)的shell是在一個(gè)不同的目錄中。括號(hào)可以保證他們都會(huì)在一個(gè)單獨(dú)的shell中進(jìn)行處理。
第二個(gè)方法是在一個(gè)單獨(dú)的makefile文件中使用一些額外的宏。這些額外的宏是通過(guò)在我們已經(jīng)討論過(guò)的這些宏的基礎(chǔ)上添加D(對(duì)目錄而言)或是F(就文件而言)來(lái)生成的。然后我們可以用下面的規(guī)則來(lái)覆蓋內(nèi)建的.c.o前綴規(guī)則:
.c.o:
???? $(CC) $(CFLAGS) -c $(@D)/$(<F) -o $(@D)/$(@F)
來(lái)在子目錄中編譯文件并且將目標(biāo)文件留下子目錄中。然后我們可以用如下的依賴(lài)與規(guī)則來(lái)更新當(dāng)前目錄中的庫(kù):
mylib.a:?? mydir/2.o mydir/3.o
???? ar -rv mylib.a $?
我們需要決定在我們自己的工程中我們更喜歡哪種方法。許多工程只是簡(jiǎn)單的避免具有子目錄,但是這樣會(huì)導(dǎo)致在源碼目錄中有大量的文件。正如我們?cè)谇懊娴母庞[中所看到的,我們?cè)谧幽夸浿惺褂胢ake只是簡(jiǎn)單的增加了復(fù)雜性。
GNU make與gcc
如果我們正使用GNU make與GNU gcc編譯器,還有兩個(gè)有趣的選項(xiàng):
第一個(gè)就是make的-jN("jobs")選項(xiàng)。這會(huì)使用make同時(shí)執(zhí)行N條命令。此時(shí)make可以同時(shí)調(diào)用多條規(guī)則,獨(dú)立的編譯工程的不同部分。依據(jù)于我們的系統(tǒng)配置,這對(duì)于我們重新編譯的時(shí)候是一個(gè)巨大的改進(jìn)。如果我們有多個(gè)源文件,嘗試這個(gè)選項(xiàng)是很有價(jià)值的。通常而言,小的數(shù)字,例如-j3,是一個(gè)好的起點(diǎn)。如果我們與其他用戶共享我們的機(jī)器,那么要小心使用這個(gè)選項(xiàng)。其他用戶也許不會(huì)喜歡每次編譯時(shí)我們啟動(dòng)大量的進(jìn)程數(shù)。
另一個(gè)有用的選項(xiàng)就是gcc的-MM選項(xiàng)。這會(huì)產(chǎn)生一個(gè)適合于make的依賴(lài)列表。在一個(gè)具有大量源碼文件的工程中,每一個(gè)文件都會(huì)包含不同的頭文件組合,要正確的獲得依賴(lài)關(guān)系是非常困難的,但是卻是十分重要的。如果我們使用每一個(gè)源文件依賴(lài)于每一個(gè)頭文件,有時(shí)我們就會(huì)編譯不必須的文件。另一方面,如果我們忽略一些依賴(lài),問(wèn)題就會(huì)更為嚴(yán)重,因?yàn)槲覀儠?huì)沒(méi)有編譯那些需要重新編譯的文件。
試驗(yàn)--gcc -MM
下面我們使用gcc的-MM選項(xiàng)來(lái)為我們的例子工程生成一個(gè)依賴(lài)列表:
$ gcc -MM main.c 2.c 3.c
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h
$
工作原理
gcc編譯只是簡(jiǎn)單的以適于插入一個(gè)makefile文件中的形式輸出所需要依賴(lài)行。我們所需要做的就是將輸出保存到一個(gè)臨時(shí)文件中,然后將其插入makefile文件中,從而得到一個(gè)完美的依賴(lài)規(guī)則集。如果我們有一個(gè)gcc的輸出拷貝,我們的依賴(lài)就沒(méi)有出錯(cuò)的理由。
如果我們對(duì)于makefile文件十分自信,我們可以嘗試使用makedepend工具,這些執(zhí)行與-MM選項(xiàng)類(lèi)似的功能,但是會(huì)將依賴(lài)實(shí)際添加到指定的makefile文件的尾部。
在我們離開(kāi)makefile話題之前,也許很值得指出我們并不是只能限制自己使用makefile來(lái)編譯代碼或是創(chuàng)建庫(kù)。我們可以使用他們來(lái)自動(dòng)化任何任務(wù),例如,有一個(gè)序列命令可以使得我們由一些輸入文件得到一個(gè)輸出文件。通常"非編譯器"用戶也許適用于調(diào)用awk或是sed來(lái)處理一些文件,或是生成手冊(cè)頁(yè)。我們可以自動(dòng)化任何文件處理,只要是make由文件的日期與時(shí)間信息的修改可以處理的。
轉(zhuǎn)載于:https://www.cnblogs.com/dyllove98/archive/2009/03/14/2461963.html
總結(jié)
- 上一篇: 【转】Scott_ASP.NET MVC
- 下一篇: 多线程搜索磁盘上的文件