在Makefile中重建一類(lèi)目標的標準規則在很多場(chǎng)合需要用到。例如:根據.c源文件創(chuàng )建對應的.o文件,傳統方式是使用GNU 的C編譯器。
“隱含規則”為make提供了重建一類(lèi)目標文件通用方法,不需要在Makefile中明確地給出重建特定目標文件所需要的細節描述。例如:典型地;make對C文件的編譯過(guò)程是由.c源文件編譯生成.o目標文件。當Makefile中出現一個(gè).o文件目標時(shí),make會(huì )使用這個(gè)通用的方式將后綴為.c的文件編譯稱(chēng)為目標的.o文件。
另外,在make執行時(shí)根據需要也可能是用多個(gè)隱含規則。比如:make將從一個(gè).y文件生成對應的.c文件,最后再生成最終的.o文件。就是說(shuō),只要目標文件名中除后綴以外其它部分相同,make都能夠使用若干個(gè)隱含規則來(lái)最終產(chǎn)生這個(gè)目標文件(當然最原始的那個(gè)文件必須存在)。例如;可以在Makefile中這樣來(lái)實(shí)現一個(gè)規則:“foo : foo.h”,只要在當前目錄下存在“foo.c”這個(gè)文件,就可以生成“foo”可執行文件。本文前邊的很多例子中已經(jīng)使用到了隱含規則。
內嵌的“隱含規則”在其所定義的命令行中,會(huì )使用到一些變量(通常也是內嵌變量)。我們可以通過(guò)改變這些變量的值來(lái)控制隱含規則命令的執行情況。例如:內嵌變量“CFLAGS”代表了gcc編譯器編譯源文件的編譯選項,我們就可以在Makefile中重新定義它,來(lái)改變編譯源文件所要使用的參數。
盡管我們不能改變make內嵌的隱含規則,但是我們可以使用模式規則重新定義自己的隱含規則,也可以使用后追規則來(lái)重新定義隱含規則。后綴規則存在某些限制(目前版本make保存它的原因是為了兼容以前版本)。使用模式規則更加清晰明了。
使用make內嵌的隱含規則,在Makefile中就不需要明確給出重建某一個(gè)目標的命令,甚至可以不需要規則。make會(huì )自動(dòng)根據已存在(或者可以被創(chuàng )建)的源文件類(lèi)型來(lái)啟動(dòng)相應的隱含規則。例如:
foo : foo.o bar.o
cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
這里并沒(méi)有給出重建文件“foo.o”的規則,make執行這條規則時(shí),無(wú)論文件“foo.o”存在與否,都會(huì )試圖根據隱含規則來(lái)重建這個(gè)文件(就是試圖重新編譯文件“foo.c”或者其它類(lèi)型的源文件)。
make執行過(guò)程中找到的隱含規則,提供了此目標的基本依賴(lài)關(guān)系,確定了目標的依賴(lài)文件(通常是源文件,不包含對應的頭文件依賴(lài))和重建目標需要使用的命令行。隱含規則所提供的依賴(lài)文件只是一個(gè)最基本的(通常它們之間的對應關(guān)系為:“EXENAME.o”對應“EXENAME.c”、“EXENAME”對應于“EXENAME.o”)。當需要增加這個(gè)目標的依賴(lài)文件時(shí),要在Makefile中使用沒(méi)有命令行的規則給出。
每一個(gè)內嵌的隱含規則中都存在一個(gè)目標模式和依賴(lài)模式,而且同一個(gè)目標模式可以對應多個(gè)依賴(lài)模式。例如:一個(gè).o文件的目標可以由c編譯器編譯對應的.c源文件得到、Pascal編譯器編譯.p的源文件得到,等等。make會(huì )根據不同的源文件來(lái)使用不同的編譯器。對于“foo.c”就是用c編譯,對于“foo.p”就使用Pascal編譯器編譯。
上邊提到,make會(huì )自動(dòng)根據已存在(或者可以被創(chuàng )建)的源文件類(lèi)型來(lái)啟動(dòng)相應的隱含規則。這里的“可被創(chuàng )建”文件是指:這個(gè)文件在Makefile中被作為目標或者依賴(lài)明確的提及,或者可以根據已存在的文件使用其它的隱含規則來(lái)創(chuàng )建它。當一個(gè)隱含規則的目標是另外一個(gè)隱含規則的依賴(lài)時(shí),我們稱(chēng)它們是一個(gè)隱含規則鏈。
通常,make會(huì )對那些沒(méi)有命令行的規則、雙冒號規則尋找一個(gè)隱含規則來(lái)執行。作為一個(gè)規則的依賴(lài)文件,在沒(méi)有一個(gè)規則明確描述它的依賴(lài)關(guān)系的情況下;make會(huì )將其作為一個(gè)目標并為它搜索一個(gè)隱含規則,試圖重建它。
注意:給目標文件指定明確的依賴(lài)文件并不會(huì )影響隱含規則的搜索。我們來(lái)看一個(gè)例子:
foo.o: foo.p
這個(gè)規則指定了“foo”的依賴(lài)文件是“foo.p”。但是如果在工作目錄下存在同名.c源文件“foo.c”。執行make的結果就不是用“pc”編譯“foo.p”來(lái)生成“foo”,而是用“cc”編譯“foo.c”來(lái)生成目標文件。這是因為在隱含規則列表中對.c文件的隱含規則處于.p文件隱含規則之前。
當需要給目標指定明確的重建規則時(shí),規則描述中就不能省略命令行,這個(gè)規則必須提供明確的重建命令來(lái)說(shuō)明目標需要重建所需要的動(dòng)作。為了能夠在存在“foo.c”的情況下編譯“foo.p”。規則可以這樣寫(xiě):
foo.o: foo.p
pc $< -o $@
這一點(diǎn)在多語(yǔ)言實(shí)現的工程編譯中,需要特別注意!否則編譯出來(lái)的可能就不是你想要得程序。
另外:當我們不想讓make為一個(gè)沒(méi)有命令行的規則中的目標搜索隱含規則時(shí),我們需要使用空命令來(lái)實(shí)現。
最后讓我們來(lái)看一個(gè)簡(jiǎn)單的例子,之前在目標指定變量 一節的例子我們就可以簡(jiǎn)化為:
# sample Makefile
CUR_DIR = $(shell pwd)
INCS := $(CUR_DIR)/include
CFLAGS := -Wall –I$(INCS)
EXEF := foo bar
.PHONY : all clean
all : $(EXEF)
foo : CFLAGS+=-O2
bar : CFLAGS+=-g
clean :
$(RM) *.o *.d $(EXES)
例子中沒(méi)有出現任何關(guān)于源文件的描述。所有剩余工作全部交給了make去處理,它會(huì )自動(dòng)尋找到相應規則并執行、最終完成目標文件的重建。
隱含規則為我們提供了一個(gè)編譯整個(gè)工程非常高效的手段,一個(gè)大的工程中毫無(wú)例外的會(huì )用到隱含規則。實(shí)際工作中,靈活運用GNU make所提供的隱含規則功能,可以大大提供效率。
本節羅列出了GUN make常見(jiàn)的一些內嵌隱含規則,除非在Makefile有名確定義、或者使用命令行“-r”或者“-R”參數取消隱含規則,否則這些隱含規則將有效。
需要說(shuō)明的是:即使我們沒(méi)有使用命令行參數“-r”,在make中也并不是所有的這些隱含規則都被定義了。其實(shí),很多的這些看似預定義的隱含規則在make執行時(shí),實(shí)際是用后綴規則來(lái)實(shí)現的;因此,它們依賴(lài)于make中的“后綴列表”(也就是目標.SUFFIXES的后綴列表)。make的默認后綴列表為:“.out”、“.a”、“.ln”、“.o”、“.c”、“.cc”、“.C”、“.p”、“.f”、“.F”、“.r”、“.y”、“.l”、“.s”、“.S”、“.mod”、“.sym”、“.def”、“.h”、“.info”、“.dvi”、“.tex”、“.texinfo”、“.texi”、“txinfo”、“.w”、“.ch”、“.web”、“.sh”、“.elc”、“el”。所有我們下邊將提到的隱含規則,如果其依賴(lài)文件中某一個(gè)滿(mǎn)足列表中列出的后綴,則是后綴規則。如果修改了可識別后綴列表,那么可能會(huì )是許多默認預定義的規則無(wú)效(因為一些后綴可能不會(huì )別識別)。以下是常用的一些隱含規則(對于不常見(jiàn)的隱含規則這里沒(méi)有描述):
1. 編譯C程序
“N.o”自動(dòng)由“N.c” 生成,執行命令為“$(CC) -c $(CPPFLAGS) $(CFLAGS)”。
2. 編譯C++程序
“N.o”自動(dòng)由“N.cc”或者“N.C” 生成,執行命令為“$(CXX) -c $(CPPFLAGS) $(CFLAGS)”。建議使用“.cc”作為C++源文件的后綴,而不是“.C”
3. 編譯Pascal程序
“N.o”自動(dòng)由“N.p”創(chuàng )建,執行命令時(shí)“$(PC) -c $(PFLAGS)”。
4. 編譯Fortran/Ratfor程序
“N.o”自動(dòng)由“N.r”、“N.F”或者“N.f” 生成,根據源文件后綴執行對應的命令:
.f — “$(FC) –c $(FFLAGS)”
.F — “$(FC) –c $(FFLAGS) $(CPPFLAGS)”
.r — “$(FC) –c $(FFLAGS) $(RFLAGS)”
5. 預處理Fortran/Ratfor程序
“N.f”自動(dòng)由“N.r”或者“N.F” 生成。此規則只是轉換Ratfor或有預處理的Fortran程序到一個(gè)標準的Fortran程序。根據源文件后綴執行對應的命令:
.F — “$(FC) –F $(CPPFLAGS) $(FFLAGS)”
.r — “$(FC) –F $(FFLAGS) $(RFLAGS)”
6. 編譯Modula-2程序
“N.sym”自動(dòng)由“N.def” 生成,執行的命令是:“$(M2C) $(M2FLAGS) $(DEFFLAGS)”。“N.o”自動(dòng)由“N.mod”生成,執行的命令是:“$(M2C) $(M2FLAGS) $(MODFLAGS)”。
7. 匯編和需要預處理的匯編程序
“N.s”是不需要預處理的匯編源文件,“N.S”是需要預處理的匯編源文件。匯編器為“as”。
“N.o” 可自動(dòng)由“N.s”生成,執行命令是:“$(AS) $(ASFLAGS)”。
“N.s” 可由“N.S”生成,C預編譯器“cpp”,執行命令是:“$(CPP) $(CPPFLAGS)”。
8. 鏈接單一的object文件
“N”自動(dòng)由“N.o”生成,通過(guò)C編譯器使用鏈接器(GUN ld),執行命令是:“$(CC) $(LDFLAGS) N.o $(LOADLIBES) $(LDLIBS)”。
此規則僅適用:由一個(gè)源文件直接產(chǎn)生可執行文件的情況。當需要有多個(gè)源文件共同來(lái)創(chuàng )建一個(gè)可執行文件時(shí),需要在Makefile中增加隱含規則的依賴(lài)文件。例如:
x : y.o z.o
當“x.c”、“y.c”和“z.c”都存在時(shí),規則執行如下命令:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o
在復雜的場(chǎng)合,目標文件和源文件之間不存在向上邊那樣的名字對應關(guān)系時(shí)(“x”和“x.c”對應,因此隱含規則在進(jìn)行鏈接時(shí),自動(dòng)將“x.c”作為其依賴(lài)文件)。這時(shí),需要在Makefile中明確給出描述目標依賴(lài)關(guān)系的規則。
通常,gcc在編譯源文件時(shí)(根據源文件的后綴名來(lái)啟動(dòng)相應的編譯器),如果沒(méi)有指定“-c”選項,gcc會(huì )在編譯完成之后調用“ld”連接成為可執行文件。
9. Yacc C程序
“N.c”自動(dòng)由“N.y”,執行的命令:“$(YACC) $(YFALGS)”。(“Yacc”是一個(gè)語(yǔ)法分析工具)
10. Lex C程序時(shí)的隱含規則。
“N.c”自動(dòng)由“N.l”,執行的命令是:“$(LEX) $(LFALGS)”。(關(guān)于“Lex”的細節請查看相關(guān)資料)
這里沒(méi)有列出所有的隱含規則,僅列出我個(gè)人在實(shí)際工作中涉及到的。沒(méi)有涉及的很難對英文文檔進(jìn)行深入地說(shuō)明和理解。如果那些沒(méi)有提到的各位有所使用,或者能夠詳細的描述可以添加到這個(gè)文檔中!
在隱含規則中,命令行中的實(shí)際命令是使用一個(gè)變量計算得到,諸如:“COMPILE.c”、“LINK.o”(這個(gè)在前面也看到過(guò))和“PREPROCESS.S”等。這些變量被展開(kāi)之后就是對應的命令(包括了命令行選項),例如:變量“COMPILE.c”的定義為 “cc -c”(如果Makefile中存在“CFLAGS”的定義,它的值會(huì )存在于這個(gè)變量中)。
make會(huì )根據默認的約定,使用“COMPILE.x”來(lái)編譯一個(gè)“.x”的文件。類(lèi)似地使用“LINK.x”來(lái)連接“.x”文件;使用“PREPROCESS.x”對“.x”文件進(jìn)行預處理。
每一個(gè)隱含規則在創(chuàng )建一個(gè)文件時(shí)都使用了變量“OUTPUT_OPTION”。make執行命令時(shí)根據命令行參數來(lái)決定它的值,當命令行中沒(méi)有包含“-o”選項時(shí),它的值為:“-o $@”,否則為空。建議在規則的命令行中明確使用“-o”選項執行輸出文件路徑。這是因為在編譯一個(gè)多目錄的工程時(shí),如果我們的Makefile中使用了“VPATH”指定搜索目錄 時(shí),編譯后的.o文件或者其它文件會(huì )出現在和源文件不同的目錄中。在有些系統的編譯器不接受命令行的“-o”參數,而Makefile中包含“VPAT”的情況時(shí),輸出文件可能會(huì )出現在錯誤的目錄下。解決這個(gè)問(wèn)題的方式就是將“OUTPUT_OPTION”的值賦為“;mv $*.o $@”,其功能是將編譯完成的.o文件改變?yōu)橐巹t中的目標文件。
內嵌隱含規則的命令中,所使用的變量都是預定義的變量。我們將這些變量稱(chēng)為“隱含變量”。這些變量允許對它進(jìn)行修改:在Makefile中、通過(guò)命令行參數或者設置系統環(huán)境變量的方式來(lái)對它進(jìn)行重定義。無(wú)論是用那種方式,只要make在運行時(shí)它的定義有效,make的隱含規則都會(huì )使用這些變量。當然,也可以使用“-R”或“--no–builtin-variables”選項來(lái)取消所有的隱含變量(同時(shí)將取消了所有的隱含規則)。
例如,編譯.c源文件的隱含規則為:“$(CC) -c $(CFLAGS) $(CPPFLAGS)”。默認的編譯命令是“cc”,執行的命令是:“cc –c”。我們可以同上述的任何一種方式將變量“CC”定義為“ncc”,那么編譯.c源文件所執行的命令將是“ncc -c”。同樣我們可以對變量“CFLAGS”進(jìn)行重定義。對這些變量重定義后如果需要整個(gè)工程的各個(gè)子目錄有效,同樣需要使用關(guān)鍵字“export”將他們導出;否則目錄間編譯命令可能出現不一致。編譯.c源文件時(shí),隱含規則使用“$(CC)”來(lái)引用編譯器;“$(CFLAGS)”引用編譯選項。
隱含規則中所使用的變量(隱含變量)分為兩類(lèi):1. 代表一個(gè)程序的名字(例如:“CC”代表了編譯器這個(gè)可執行程序)。2. 代表執行這個(gè)程序使用的參數(例如:變量“CFLAGS”),多個(gè)參數使用空格分開(kāi)。當然也允許在程序的名字中包含參數。但是這種方式建議不要使用。
以下是一些作為程序名的隱含變量定義:
AR
函數庫打包程序,可創(chuàng )建靜態(tài)庫.a文檔。默認是“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 語(yǔ)言轉變?yōu)?/span> C 或 Ratfo 的程序。默認是“lex”。
PC
Pascal語(yǔ)言編譯器。默認是“pc”。
YACC
Yacc文法分析器(針對于C程序)。默認命令是“yacc”。
YACCR
Yacc文法分析器(針對于Ratfor程序)。默認是“yacc -r”。
MAKEINFO
轉換Texinfo源文件(.texi)到Info文件程序。默認是“makeinfo”。
TEX
從TeX源文件創(chuàng )建TeX DVI文件的程序。默認是“tex”。
TEXI2DVI
從Texinfo源文件創(chuàng )建TeX DVI 文件的程序。默認是“texi2dvi”。
WEAVE
轉換Web到TeX的程序。默認是“weave”。
CWEAVE
轉換C Web 到 TeX的程序。默認是“cweave”。
TANGLE
轉換Web到Pascal語(yǔ)言的程序。默認是“tangle”。
CTANGLE
轉換C Web 到 C。默認是“ctangle”。
RM
刪除命令。默認是“rm -f”。
下邊的是代表命令執行參數的變量。如果沒(méi)有給出默認值則默認值為空。
ARFLAGS
執行“AR”命令的命令行參數。默認值是“rv”。
ASFLAGS
執行匯編語(yǔ)器“AS”的命令行參數(明確指定“.s”或“.S”文件時(shí))。
CFLAGS
執行“CC”編譯器的命令行參數(編譯.c源文件的選項)。
CXXFLAGS
執行“g++”編譯器的命令行參數(編譯.cc源文件的選項)。
COFLAGS
執行“co”的命令行參數(在RCS中提取文件的選項)。
CPPFLAGS
執行C預處理器“cc -E”的命令行參數(C 和 Fortran 編譯器會(huì )用到)。
FFLAGS
Fortran語(yǔ)言編譯器“f77”執行的命令行參數(編譯Fortran源文件的選項)。
GFLAGS
SCCS “get”程序參數。
LDFLAGS
鏈接器(如:“ld”)參數。
LFLAGS
Lex文法分析器參數。
PFLAGS
Pascal語(yǔ)言編譯器參數。
RFLAGS
Ratfor 程序的Fortran 編譯器參數。
YFLAGS
Yacc文法分析器參數。
有時(shí),一個(gè)目標文件需要多個(gè)(一系列)隱含規則來(lái)創(chuàng )建。例如:創(chuàng )建文件“N.o”的過(guò)程可能是:首先執行“yacc”由“N.y”生成文件“N.c”,之后由編譯器將“N.c”編譯成為“N.o”。如果一個(gè)目標文件需要一系列隱含規則才能完成它的創(chuàng )建,我們就把這個(gè)系列稱(chēng)為一個(gè)“鏈”。
我們來(lái)看上邊例子的執行過(guò)程。有兩種情況:
1. 如果文件“N.c”存在或者它在Makefile中被提及,那就不需要進(jìn)行其它搜索,make處理的過(guò)程是:首先,make可以確定出“N.o”可由“N.c”創(chuàng )建;之后,make試圖使用隱含規則來(lái)重建“N.c”。它會(huì )尋找“N.y”這個(gè)文件,如果“N.y”存在,則執行隱含規則來(lái)重建“N.c”這個(gè)文件。之后再由“N.c”重建“N.o”;當不存在“N.y”文件時(shí),直接編譯“N.c”生成“N.o”。
2. 文件“N.c”不存在也沒(méi)有在Makefile中提及的情況,只要存在“N.y”這個(gè)文件,那么make也會(huì )經(jīng)過(guò)這兩個(gè)步驟來(lái)重建“N.o”(N.y → N.c → N.o)。這種情況下,文件“N.c”作為一個(gè)中間過(guò)程文件。Make在執行規則時(shí),如果需要一個(gè)中間文件才能完成目標的重建,那么這個(gè)文件將會(huì )自動(dòng)地加入到依賴(lài)關(guān)系鏈中(和Makefile中明確提及的目標作相同處理),并使用合適的隱含規則對它進(jìn)行重建。
make的中間過(guò)程文件和那些明確指定的文件在規則中的地位完全相同。但make在處理時(shí)兩者之間存在一些差異:
第一:中間文件不存在時(shí),make處理兩者的方式不同。對于一個(gè)普通文件來(lái)說(shuō),因為Makefile中有明確的提及,此文件可能是作為一個(gè)目標的依賴(lài),make在執行它所在的規則前會(huì )試圖重建它。但是對于中間文件,因為沒(méi)有明確提及,make不會(huì )去試圖重建它。除非這個(gè)中間文件所依賴(lài)的文件(上例第二種情況中的文件“N.y”;N.c是中間過(guò)程文件)被更新。
第二:如果make在執行時(shí)需要用到一個(gè)中間過(guò)程文件,那么默認的動(dòng)作將是:這個(gè)過(guò)程文件在make執行結束后會(huì )被刪除(make會(huì )在刪除中間過(guò)程文件時(shí)打印出執行的命令以顯示那些文件被刪除了)。因此一個(gè)中間過(guò)程文件在make執行結束后就不再存在了。
在Makefile中明確提及的所有文件都不被作為中間過(guò)程文件來(lái)處理,這是缺省地。不過(guò)我們可以在Makefile中使用特殊目標“.INTERMEDIATE”來(lái)指除將那些文件作為中間過(guò)程文件來(lái)處理(這些文件作為目標“.INTERMEDIATE”的依賴(lài)文件羅列),即使它們在Makefile中被明確提及,這些作為特殊目標“.INTERMEDIATE”依賴(lài)的文件在make執行結束之后會(huì )被自動(dòng)刪除。
另一方面,如果我們希望保留某些中間過(guò)程文件(它沒(méi)有在Makefile中被提及),不希望make結束時(shí)自動(dòng)刪除它們??梢栽?/span>Makefile中使用特使目標“.SECONDARY”來(lái)指出這些文件(這些文件將被作為“secondary”文件;需要保留的文件作為特殊目標“.SECONDARY”的依賴(lài)文件羅列)。注意:“secondary”文件也同時(shí)被作為中間過(guò)程文件來(lái)對待。
需要保留中間過(guò)程文件還存在另外一種實(shí)現方式。例如需要保留所有.o的中間過(guò)程文件,我們可以將.o文件的模式(%.o)作為特殊目標“.PRECIOUS”的依賴(lài)。
一個(gè)“鏈”可以包含兩個(gè)以上隱含規則的調用過(guò)程。同一個(gè)隱含規則在一個(gè)“鏈”中只能出現一次。否則就會(huì )出現像“foo”依賴(lài)“foo.o.o”甚至“foo.o.o.o.o…”這樣不合邏輯的情況發(fā)生。因為,如果允許在一個(gè)“鏈”中多次調用同一隱含規則(N : N.o; $(LINK.o) $(LDFLAGS) N.o $(LOADLIBES) $(LDLIBS) ),將會(huì )導致make進(jìn)入到無(wú)限的循環(huán)中去。
隱含規則鏈中的某些隱含規則,在一些情況會(huì )被優(yōu)化處理。例如:從文件“foo.c”創(chuàng )建可執行文件“foo”,這一過(guò)程可以是:使用隱含規則將“foo.c”編譯生成“foo.o”文件,之后再使用另一個(gè)隱含規則來(lái)完成對“foo.o”的鏈接,最后生成執行文件“foo”。這個(gè)過(guò)程中對源文件的編譯和對.o文件的鏈接分別使用了兩個(gè)獨立的規則(它們組成一個(gè)隱含規則鏈)。但是實(shí)際情況是,對源文件的編譯和對.o文件的鏈接是在一個(gè)規則中完成的,規則使用命令“cc foo.c foo”。make的隱含規則表中,所有可用的優(yōu)化規則處于首選地位。
模式規則類(lèi)似于普通規則。只是在模式規則中,目標名中需要包含有模式字符“%”(一個(gè)),包含有模式字符“%”的目標被用來(lái)匹配一個(gè)文件名,“%”可以匹配任何非空字符串。規則的依賴(lài)文件中同樣可以使用“%”,依賴(lài)文件中模式字符“%”的取值情況由目標中的“%”來(lái)決定。例如:對于模式規則“%.o : %.c”,它表示的含義是:所有的.o文件依賴(lài)于對應的.c文件。我們可以使用模式規則來(lái)定義隱含規則。
要注意的是:模式字符“%”的匹配和替換發(fā)生在規則中所有變量和函數引用展開(kāi)之后,變量和函數的展開(kāi)一般發(fā)生在make讀取Makefile時(shí)(變量和函數的展開(kāi)可參考 第五 章 使用變量 和 第七章 make的函數 ),而模式規則中的“%”的匹配和替換則發(fā)生在make執行時(shí)。
在模式規則中,目標文件是一個(gè)帶有模式字符“%”的文件,使用模式來(lái)匹配目標文件。文件名中的模式字符“%”可以匹配任何非空字符串,除模式字符以外的部分要求一致。例如:“%.c”匹配所有以“.c”結尾的文件(匹配的文件名長(cháng)度最少為3個(gè)字母),“s%.c”匹配所有第一個(gè)字母為“s”,而且必須以“.c”結尾的文件,文件名長(cháng)度最小為5個(gè)字符(模式字符“%”至少匹配一個(gè)字符)。在目標文件名中“%”匹配的部分稱(chēng)為“莖”(前面已經(jīng)提到過(guò),參考 靜態(tài)模式 一節)。使用模式規則時(shí),目標文件匹配之后得到“莖”,依賴(lài)根據“莖”產(chǎn)生對應的依賴(lài)文件,這個(gè)依賴(lài)文件必須是存在的或者可被創(chuàng )建的。
因此,一個(gè)模式規則的格式為:
%.o : %.c ; COMMAND...
這個(gè)模式規則指定了如何由文件“N.c”來(lái)創(chuàng )建文件“N.o”,文件“N.c”應該是已存在的或者可被創(chuàng )建的。
模式規則中依賴(lài)文件也可以不包含模式字符“%”。當依賴(lài)文件名中不包含模式字符“%”時(shí),其含義是所有符合目標模式的目標文件都依賴(lài)于一個(gè)指定的文件(例如:%.o : debug.h,表示所有的.o文件都依賴(lài)于頭文件“debug.h”)。這樣的模式規則在很多場(chǎng)合是非常有用的。
同樣一個(gè)模式規則可以存在多個(gè)目標。多目標的模式規則和普通多目標規則有些不同,普通多目標規則的處理是將每一個(gè)目標作為一個(gè)獨立的規則來(lái)處理,所以多個(gè)目標就就對應多個(gè)獨立的規則(這些規則各自有自己的命令行,各個(gè)規則的命令行可能相同)。但對于多目標模式規則來(lái)說(shuō),所有規則的目標共同擁有依賴(lài)文件和規則的命令行,當文件符合多個(gè)目標模式中的任何一個(gè)時(shí),規則定義的命令就有可能將會(huì )執行;因為多個(gè)目標共同擁有規則的命令行,因此一次命令執行之后,規則不會(huì )再去檢查是否需要重建符合其它模式的目標??匆粋€(gè)例子:
#sample Makefile
Objects = foo.o bar.o
CFLAGS := -Wall
%x : CFLAGS += -g
%.o : CFLAGS += -O2
%.o %.x : %.c
$(CC) $(CFLAGS) $< -o $@
當在命令行中執行“make foo.o foo.x”時(shí),會(huì )看到只有一個(gè)文件“foo.o”被創(chuàng )建了,同時(shí)make會(huì )提示“foo.x”文件是最新的(其實(shí)“foo.x”并沒(méi)有被創(chuàng )建)。此過(guò)程表明了多目標的模式規則在make處理時(shí)是被作為一個(gè)整體來(lái)處理的。這是多目標模式規則和多目標的普通規則的區別之處。大家不妨將上邊的例子改為普通多目標規則試試看將會(huì )得到什么樣的結果。
最后需要說(shuō)明的是:
1. 模式規則在Makefile中的順序需要注意,當一個(gè)目標文件同時(shí)符合多個(gè)目標模式時(shí),make將會(huì )把第一個(gè)目標匹配的模式規則作為重建它的規則。
2. Makefile中明確指定的模式規則會(huì )覆蓋隱含模式規則。就是說(shuō)如果在Makefile中出現了一個(gè)對目標文件合適可用的模式規則,那么make就不會(huì )再為這個(gè)目標文件尋找其它隱含規則,而直接使用在Makefile中出現的這個(gè)規則。在使用時(shí),明確規則永遠優(yōu)先于隱含規則。
3. 另外,依賴(lài)文件存在或者被提及的規則,優(yōu)先于那些需要使用隱含規則來(lái)創(chuàng )建其依賴(lài)文件的規則。
本小節來(lái)看一些使用模式規則的例子,這些模式規則在GNU make中已經(jīng)被預定義。首先看編譯.c文件到.o文件的隱含模式規則:
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
此規則描述了一個(gè).o文件如何由對應的.c文件創(chuàng )建。規則的命令行中使用了自動(dòng)化變量“$<”和“$@”,其中自動(dòng)化變量“$<”代表規則的依賴(lài),“$@”代表規則的目標。此規則在執行時(shí),命令行中的自動(dòng)化變量將根據實(shí)際的目標和依賴(lài)文件取對應值。
make中第二個(gè)內嵌模式規則是:
% :: RCS/%,v
$(CO) $(COFLAGS) $<
這個(gè)規則的含義是:任何一個(gè)文件“X”都可以由目錄“RCS”下的相應文件“x.v”來(lái)生成。規則的目標為“%”,它匹配任何文件名,因此只要存在相對應的依賴(lài)文件(N.v),目標(N)都可被創(chuàng )建。雙冒號表示該規則是最終規則,意味著(zhù)規則的依賴(lài)文件不是中間過(guò)程文件。
另外,一個(gè)具有多目標的隱含規則是:
%.tab.c %.tab.h: %.y
bison -d $<
它是一個(gè)多目標模式規則,關(guān)于多目標的特征可參考 模式規則介紹 一小節最后一個(gè)例子。
模式規則中,規則的目標和依賴(lài)文件名代表了一類(lèi)文件名;規則的命令是對所有這一類(lèi)文件重建過(guò)程的描述,顯然,在命令中不能出現具體的文件名,否則模式規則失去意義。那么在模式規則的命令行中該如何表示文件,將是本小節的討論的重點(diǎn)。
假如你需要書(shū)寫(xiě)一個(gè)將.c文件編譯到.o文件的模式規則,那么你該如何為gcc書(shū)寫(xiě)正確的源文件名?當然了,不能使用任何具體的文件名,因為在每一次執行模式規則時(shí)源文件名都是不一樣的。為了解決這個(gè)問(wèn)題,就需要使用“自動(dòng)環(huán)變量”,自動(dòng)化變量的取值是根據具體所執行的規則來(lái)決定的,取決于所執行規則的目標和依賴(lài)文件名。
下面對所有的自動(dòng)化變量進(jìn)行說(shuō)明:
$@
表示規則的目標文件名。如果目標是一個(gè)文檔文件(Linux中,一般稱(chēng).a文件為文檔文件,也稱(chēng)為靜態(tài)庫文件),那么它代表這個(gè)文檔的文件名。在多目標模式規則中,它代表的是哪個(gè)觸發(fā)規則被執行的目標文件名。
$%
當規則的目標文件是一個(gè)靜態(tài)庫文件時(shí),代表靜態(tài)庫的一個(gè)成員名。例如,規則的目標是“foo.a(bar.o)”,那么,“$%”的值就為“bar.o”,“$@”的值為“foo.a”。如果目標不是靜態(tài)庫文件,其值為空。
$<
規則的第一個(gè)依賴(lài)文件名。如果是一個(gè)目標文件使用隱含規則來(lái)重建,則它代表由隱含規則加入的第一個(gè)依賴(lài)文件。
$?
所有比目標文件更新的依賴(lài)文件列表,空格分割。如果目標是靜態(tài)庫文件名,代表的是庫成員(.o文件)。
$^
規則的所有依賴(lài)文件列表,使用空格分隔。如果目標是靜態(tài)庫文件,它所代表的只能是所有庫成員(.o文件)名。一個(gè)文件可重復的出現在目標的依賴(lài)中,變量“$^”只記錄它的一次引用情況。就是說(shuō)變量“$^”會(huì )去掉重復的依賴(lài)文件。
$+
類(lèi)似“$^”,但是它保留了依賴(lài)文件中重復出現的文件。主要用在程序鏈接時(shí)庫的交叉引用場(chǎng)合。
$*
在模式規則和靜態(tài)模式規則中,代表“莖”。“莖”是目標模式中“%”所代表的部分(當文件名中存在目錄時(shí),“莖”也包含目錄(斜杠之前)部分)。例如:文件“dir/a.foo.b”,當目標的模式為“a.%.b”時(shí),“$*”的值為“dir/a.foo”。“莖”對于構造相關(guān)文件名非常有用。
自動(dòng)化變量“$*”需要兩點(diǎn)說(shuō)明:
Ø 對于一個(gè)明確指定的規則來(lái)說(shuō)不存在“莖”,這種情況下“$*”的含義發(fā)生改變。此時(shí),如果目標文件名帶有一個(gè)可識別的后綴,那么“$*”表示文件中除后綴以外的部分。例如:“foo.c”則“$*”的值為:“foo”,因為.c是一個(gè)可識別的文件后綴名。GUN make對明確規則的這種奇怪的處理行為是為了和其它版本的make兼容。通常,在除靜態(tài)規則和模式規則以外,明確指定目標文件的規則中應該避免使用這個(gè)變量。
Ø 當明確指定文件名的規則中目標文件名包含不可識別的后綴時(shí),此變量為空。
自動(dòng)化變量“$?”在顯式規則中也是非常有用的,使用它規則可以指定只對更新以后的依賴(lài)文件進(jìn)行操作。例如,靜態(tài)庫文件“libN.a”,它由一些.o文件組成。這個(gè)規則實(shí)現了只將更新后的.o文件加入到庫中:
lib: foo.o bar.o lose.o win.o
ar r lib $?
以上羅列的自動(dòng)量變量中。其中有四個(gè)在規則中代表文件名($@、$<、$%、$*)。而其它三個(gè)的在規則中代表一個(gè)文件名列表。GUN make中,還可以通過(guò)這七個(gè)自動(dòng)化變量來(lái)獲取一個(gè)完整文件名中的目錄部分和具體文件名部分。在這些變量中加入“D”或者“F”字符就形成了一系列變種的自動(dòng)環(huán)變量。這些變量會(huì )出現在以前版本的make中,在當前版本的make中,可以使用“dir”或者“notdir”函數來(lái)實(shí)現同樣的功能。
$(@D)
表示目標文件的目錄部分(不包括斜杠)。如果“$@”是“dir/foo.o”,那么“$(@D)”的值為“dir”。如果“$@”不存在斜杠,其值就是“.”(當前目錄)。注意它和函數“dir”的區別!
$(@F)
目標文件的完整文件名中除目錄以外的部分(實(shí)際文件名)。如果“$@”為“dir/foo.o”,那么“$(@F)”只就是“foo.o”。“$(@F)”等價(jià)于函數“$(notdir $@)”。
$(*D)
$(*F)
分別代表目標“莖”中的目錄部分和文件名部分。
$(%D)
$(%F)
當以如“archive(member)”形式靜態(tài)庫為目標時(shí),分別表示庫文件成員“member”名中的目錄部分和文件名部分。它僅對這種形式的規則目標有效。
$(<D)
$(<F)
分別表示規則中第一個(gè)依賴(lài)文件的目錄部分和文件名部分。
$(^D)
$(^F)
分別表示所有依賴(lài)文件的目錄部分和文件部分(不存在同一文件)。
$(+D)
$(+F)
分別表示所有依賴(lài)文件的目錄部分和文件部分(可存在重復文件)。
$(?D)
$(?F)
分別表示被更新的依賴(lài)文件的目錄部分和文件名部分。
在討論自動(dòng)化變量時(shí),為了和普通變量(如:“CFLAGS”)區別,我們直接使用了“$<”的形式。這種形式僅僅是為了和普通變量進(jìn)行區別,沒(méi)有別的目的。其實(shí)對于自動(dòng)環(huán)變量和普通變量一樣,代表規則第一個(gè)依賴(lài)文件名的變量名實(shí)際上是“<”,我們完全可以使用“$(<)”來(lái)替代“$<”。但是在引用自動(dòng)化變量時(shí)通常的做法是“$<”,因為自動(dòng)化變量本身是一個(gè)特殊字符。
GUN make同時(shí)支持“Sysv”特性,允許在規則的依賴(lài)列表中使用特殊的變量引用(一般的自動(dòng)化變量只能在規則的命令行中被引用)“$$@”、“$$(@D)”和“$$(@F)”(注意:要使用“$$”),它們分別代表了“目標的完整文件名”、“目標文件名中的目錄部分”和“目標的實(shí)際文件名部分”。這三個(gè)特殊的變量只能用在明確指定目標文件名的規則中或者是靜態(tài)模式規則中,不用于隱含規則中。另外Sysv make和GNU make對規則依賴(lài)的處理也不盡相同。Sysv make對規則的依賴(lài)進(jìn)行兩次替換展開(kāi),而GUN make對依賴(lài)列表的處理只有一次,對其中的變量和函數引用直接進(jìn)行展開(kāi)。
自動(dòng)化變量的這個(gè)古怪的特性完全是為了兼容Sysv 版本的makefile文件。在使用GNU make時(shí)可以不考慮這個(gè),也可以在Makefile中使用偽目標“.POSIX”來(lái)禁止這一特性。
通常,模式規則中目標模式由前綴、后綴、模式字符“%”組成,這三個(gè)部分允許兩個(gè)同時(shí)為空。實(shí)際文件名應該是以模式指定的前綴開(kāi)始、后綴結束的任何文件名。文件名中除前綴和后綴以外的所有部分稱(chēng)之為“莖”(模式字符“%”可以代表若干字符。因此:模式“%.o”所匹配的文件“test.c”中“test”就是“莖”)。模式規則中依賴(lài)文件名的確定過(guò)程是:首先根據規則定義的目標模式匹配實(shí)際的目標文件,確定“莖”,之后使用 “莖”替代規則依賴(lài)文件名中的模式字符“%”,生成依賴(lài)文件名。這樣就產(chǎn)生了一個(gè)明確指定了目標和依賴(lài)文件的規則。例如模式規則:“%.o : %.c”,當“test.o”需要重建時(shí)將形成規則“test.o : test.c”。
當目標模式中包含斜杠(目錄部分)。在進(jìn)行目標文件匹配時(shí),文件名中包含的目錄字符串在匹配之前被移除,只進(jìn)行基本文件名的匹配;匹配成功后,再將目錄部分加入到匹配之后的字符串之前形成“莖”。來(lái)看一個(gè)例子:例如目標模式為“e%t”,文件“src/eat”匹配這個(gè)模式,那么“莖”就是“src/a”;模式規則中依賴(lài)文件的產(chǎn)生:首先使用“莖”中的非目錄部分(“a”)替代依賴(lài)文件中的模式字符“%”,之后再將目錄部分(“src/”)加入到形成的依賴(lài)文件名之前構成依賴(lài)文件的全路徑名。這里如果模式規則的依賴(lài)模式為“c%r”,則那么目標“src/eat”對應的依賴(lài)文件就為“src/car”。
當模式規則的目標只是一個(gè)模式字符“%”(它可以匹配任何文件名)時(shí),我們稱(chēng)這個(gè)規則為萬(wàn)用規則。萬(wàn)用規則在書(shū)寫(xiě)Makefile時(shí)非常有用,但它會(huì )影響make的執行效率,因為make在執行時(shí)將會(huì )使用萬(wàn)用規則來(lái)試圖重建其它規則的目標和依賴(lài)文件。
假如在一個(gè)存在萬(wàn)用規則的Makefile中提及了文件“foo.c”。為了創(chuàng )建這個(gè)目標,make會(huì )試圖使用以下規則來(lái)創(chuàng )建這個(gè)目標:1.對一個(gè).o文件“foo.c.o”進(jìn)行鏈接并產(chǎn)生文件“foo.c”;2.使用c編譯和連接程器由文件“foo.c.c”來(lái)創(chuàng )建這個(gè)文件;3. 編譯并鏈接Pascal程序“foo.c.p”來(lái)創(chuàng )建;等等??傊?/span>make會(huì )試圖使用所有可能的隱含規則來(lái)完成對這個(gè)文件的創(chuàng )建。
當然,我們很清楚以上這樣的過(guò)程是沒(méi)有必要的,我們知道“foo.c”是一個(gè).c原文件,而不是一個(gè)可執行程序。make在執行時(shí)都會(huì )試圖根據可能的隱含規則來(lái)創(chuàng )建這個(gè)文件,但由于其依賴(lài)的文件(“foo.c.o”、“foo.c.c”等)不存在,最終這些可能的隱含規則都會(huì )被否定。但是如果在Makefile中存在一個(gè)萬(wàn)用規則,那么make執行時(shí)所要考慮的情況就比較復雜,也很多(它會(huì )試圖功過(guò)隱含規則來(lái)創(chuàng )建那些依賴(lài)文件,雖然最終這些文件不可能被創(chuàng )建,也無(wú)從創(chuàng )建),從而導致make的執行效率會(huì )很低。
為了避免萬(wàn)用規則帶來(lái)的效率問(wèn)題,我們可以對萬(wàn)用規則的使用加以限制。通常有兩種方式,需要在定義一個(gè)萬(wàn)用規則時(shí)對其進(jìn)行限制。
1. 將萬(wàn)用規則設置為最終規則,定義時(shí)使用雙冒號規則。作為最終規則,此規則只有在它的依賴(lài)文件存在時(shí)才能被應用,即使它的依賴(lài)可以由隱含規則創(chuàng )建也不行。就是說(shuō),最終規則中沒(méi)有進(jìn)一步的“鏈”。
例如,從RCS和SCCS文件中提取源文件的內嵌隱含規則就是一個(gè)最終規則。因此如果文件“foo.c,v”不存在,make就不會(huì )試圖從一個(gè)中間文件“foo.c,v.o”或“RCS/SCCS/s.foo.c,v”來(lái)創(chuàng )建它。 RCS 和 SCCS 文件一般都是最終的源文件,它不能由其它任何文件重建;make可以記錄它的時(shí)間戳,但不會(huì )尋找重建它們的方式。
如果萬(wàn)用規則沒(méi)有定義為最終規則,那么它就是一個(gè)非最終規則。非最終的萬(wàn)用規則不會(huì )被用來(lái)創(chuàng )建那些符合某一個(gè)明確模式規則的目標和依賴(lài)文件。就是說(shuō)如果在Makefile中存在匹配此文件的模式規則(非萬(wàn)用規則),那么對于這個(gè)文件來(lái)說(shuō)其重建規則只會(huì )是它所匹配的這個(gè)模式,而不是這個(gè)非最終的萬(wàn)用規則。例如,文件“foo.c”,如果在Makefile中同時(shí)存在一個(gè)萬(wàn)用規則和模式規則 “%.c : %.y”(該規則運行Yacc)。無(wú)論該規則是否會(huì )被使用(如果存在文件“foo.y”,那么規則將被執行)。那么make試圖重建“foo.c”的規則都是“%.c : %.y”,而不是萬(wàn)用規則。這樣做是為了避免make執行時(shí)試圖使用非最終的萬(wàn)用規則來(lái)重建文件“foo.c”的情況發(fā)生。
2. 定義一個(gè)特殊的內嵌啞模式規則給出如何重建某一類(lèi)文件,避免使用非最終萬(wàn)用規則。啞模式規則沒(méi)有依賴(lài),也沒(méi)有命令行,在make的其它場(chǎng)合被忽略。例如,內嵌的啞模式規則:“%.p :”為Pascal源程序如“foo.p”指定了重建規則(規則不存在依賴(lài)文件、也不存在任何命令),這樣就避免了make試圖“foo.p”而去尋找“foo.p.o”或“foo.p.c”的過(guò)程。
我們可以為所有的make可識別的后綴創(chuàng )建一個(gè)形如“%.p :”的啞模式規則。
一個(gè)隱含規則,我們可以對它進(jìn)行重建。重建一個(gè)隱含規則時(shí),需要使用相同的目標和依賴(lài)模式,命令可以不同(重新指定規則的命令)。這樣就可以替代有相同目標和依賴(lài)的那個(gè)make內嵌規則,替代之后隱含規則可能被使用的順序由它在Makefile中的位置決定。例如通常Makefile中可能會(huì )包含這樣一個(gè)規則:
%.o : %.c
$(CC) $(CFLAGS) –D__DEBUG__ $< -o $@
它替代了編譯.c文件的內嵌隱含規則。
也可以取消一個(gè)內嵌的隱含規則。同樣需要定義一個(gè)和隱含規則具有相同目標和依賴(lài)的規則,但這個(gè)規則沒(méi)有命令行。例如下邊的這個(gè)規則取消了編譯.s文件的內嵌規則。
%.o : %.s
有時(shí)make會(huì )需要一個(gè)缺省的規則,在執行過(guò)程中無(wú)法為一個(gè)文件找到合適的重建規則(在Makefile中沒(méi)有給出重建它的明確規則,同時(shí)也沒(méi)有合適可用的隱含規則)。那么make就使用這個(gè)規則來(lái)重建它。就是說(shuō),當所需要的重建的目標文件沒(méi)有可用的命令時(shí)、就執行這個(gè)缺省規則命令。
這樣一個(gè)規則,我們可以使用最終萬(wàn)用規則。例如:調試Makefile時(shí)(可能一些源文件還沒(méi)有完成),我們關(guān)心的是Makefile中所有的規則是否可正確執行,而源文件的具體內容卻不需要關(guān)心?;谶@一點(diǎn)我們就可以使用空文件(和源文件同名的文件),在Makefile中定義這樣一個(gè)規則:
%::
touch $@
執行make過(guò)程中,對所有不存在的.c文件將會(huì )使用“touch”命令創(chuàng )建這樣一個(gè)空的源文件。
實(shí)現一個(gè)缺省規則的方式也可以不使用萬(wàn)用規則,而使用偽目標“.DEFAULT”。上邊的例子也可以這樣實(shí)現:
.DEFAULT :
touch $@
需要注意:沒(méi)有指定命令行的偽目標“.DEFAULT”,含義是取消前邊所有使用“.DEFAULT”指定的缺省執行命令。
同樣,也可以讓這個(gè)缺省的規則不執行任何命令(給它定義個(gè)一個(gè)空命令)。
另外缺省規則也可用來(lái)實(shí)現在一個(gè)Makefile中重載另一個(gè)makefile文件。
后綴規則是一種古老定義隱含規則的方式,在新版本的make中使用模式規則作為對它的替代,模式規則相比后綴規則更加清晰明了。在現在版本中保留它的原因是為了能夠兼容舊的makefile文件。后綴規則有兩種類(lèi)型:“雙后綴”和“單后綴”。
雙后綴規則定義一對后綴:目標文件的后綴和依賴(lài)目標的后綴。它匹配所有后綴為目標后綴的文件。對于一個(gè)匹配的目標文件,它的依賴(lài)文件這樣形成:將匹配的目標文件名中的后綴替換為依賴(lài)文件的后綴得到。如:一個(gè)描述目標和依賴(lài)后綴的“.o”和“.c”的規則就等價(jià)于模式規則“%o : %c”。
單后綴規則只定義一個(gè)后綴:此后綴是源文件名的后綴。它可以匹配任何文件,其依賴(lài)文件這樣形成:將后綴直接追加到目標文件名之后得到。例如:?jiǎn)魏缶Y“.c”就等價(jià)于模式規則“% : %.c”。
判斷一個(gè)后綴規則是單后綴還是雙后綴的過(guò)程:判斷后綴規則的目標,如果其中只存在一個(gè)可被make識別的后綴,則規則是一個(gè)“單后綴”規則;當規則目標中存在兩個(gè)可被make識別的后綴時(shí),這個(gè)規則就是一個(gè)“雙后綴”規則。
例如:“.c”和“.o”都是make可識別的后綴。因此當定義了一個(gè)目標是“.c.o”的規則時(shí)。make會(huì )將它作為一個(gè)雙后綴規則來(lái)處理,它的含義是所有“.o”文件的依賴(lài)文件是對應的“.c”文件。下邊是使用后追規則定義的編譯.c源文件的規則:
.c.o:
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
注意:一個(gè)后綴規則中不存在任何依賴(lài)文件。否則,此規則將被作為一個(gè)普通規則對待。因此規則:
.c.o: foo.h
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
就不是一個(gè)后綴規則。它是一個(gè)目標文件為“.c.o”、依賴(lài)文件是“foo.h”的普通規則。它也不等價(jià)于規則:
%.o: %.c foo.h
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
需要注意的是:沒(méi)有命令行的后綴規則是沒(méi)有任何意義的。它和沒(méi)有命令行的模式規則不同,它也不能取消之前使用后追規則定義的規則。它所實(shí)現的僅僅是將這個(gè)后綴規則作為目標加入到make的數據庫中。
可識別的后綴指的是特殊目標“.SUFFIXES”所有依賴(lài)的名字。通過(guò)給特殊目標“SUFFIXES”添加依賴(lài)來(lái)增加一個(gè)可被識別的后綴。像下邊這樣:
.SUFFIXES: .hack .win
它所實(shí)現的功能是把后綴“.hack”和“.win”加入到可識別后綴列表的末尾。
如果需要重設默認的可識別后綴,因該這樣來(lái)實(shí)現:
.SUFFIXES: #刪除所有已定義的可識別后綴
.SUFFIXES: .c .o .h #重新定義
首先使用沒(méi)有依賴(lài)的特殊目標“.SUFFIXES”來(lái)刪除所有已定義的可識別后綴;之后再重新定義。
注意:make的“-r”或“-no-builtin-rules”可以清空所有已定義的可識別后綴。
在make讀取所有的makefile文件之前,變量“SUFFIXE”被定義為默認的可識別后綴列表。雖然存在這樣一個(gè)變量,但是請不要通過(guò)修改這個(gè)變量值的方式來(lái)改變可識別的后綴列表,應該使用特殊目標“.SUFFIXES”來(lái)實(shí)現。
對于目標“T”,make為它搜索隱含規則的算法如下。此算法適合于:1. 任何沒(méi)有命令行的雙冒號規則;2. 任何沒(méi)有命令行的普通規則;3. 那些不是任何規則的目標、但它是另外某些規則的依賴(lài)文件;4. 在遞歸搜索過(guò)程中,隱含規則鏈中前一個(gè)規則的依賴(lài)文件。
在搜索過(guò)程中沒(méi)有提到后綴規則,因為所有的后綴規則在make讀取Makefile時(shí),都被轉換為對應的模式規則。
對于形式為“ARCHIVE(MEMBER)”的目標,下邊的算法會(huì )執行兩次,第一次的目標是整個(gè)目標名“T”(“ARCHIVE(MEMBER)”),如果搜索失敗,進(jìn)行第二次搜索,第二次以“member”作為目標來(lái)搜索。
搜索過(guò)程如下:
1. 將目標“T”的目錄部分分離,分離后目錄部分稱(chēng)為“D”,其它部分稱(chēng)“N”。例如:“T”為“src/foo.o”時(shí),D就是“src/”,“N”就為“foo.o”。
2. 列出所有和“T”或者“N”匹配的模式規則。如果模式規則的目標中包含斜杠,則認為和“T”相匹配,否則認為此模式規則和“N”相匹配。
3. 只要這個(gè)模式規則列表中包含一個(gè)非萬(wàn)用規則的規則,那么將列表中所有的非最終萬(wàn)用規則刪除。
4. 刪除這個(gè)模式規則列表中的所有沒(méi)有命令行的規則。
5. 對于這個(gè)模式規則列表中的所有規則:
a) 計算出模式規則的“莖”S,S應該是“T”或“N”中匹配“%”的非空的部分。
b) 計算依賴(lài)文件。把依賴(lài)中的“%”用“S”替換。如果目標模式中不包含斜杠,則把“D”加在替換之后的每一個(gè)依賴(lài)文件開(kāi)頭,構成完整的依賴(lài)文件名。
c) 測試規則的所有依賴(lài)文件是否存在或是應該存在(一個(gè)文件,如果在Makefile中它作為一個(gè)明確規則的目標,或者依賴(lài)文件被提及,我們就說(shuō)這個(gè)文件是一個(gè)“應該存在”的文件)。如果所有的依賴(lài)文件都存在、應該存在或是這個(gè)規則沒(méi)有依賴(lài)。退出查找,使用該規則。
6. 截止到第5步,合適的規則還是沒(méi)有被找到,進(jìn)一步搜索。對于這個(gè)模式規則列表中的每一規則:
a) 如果規則是一個(gè)終止規則,則忽略它,繼續下一條模式規則。
b) 計算依賴(lài)文件(同第5步)。
c) 測試此規則的依賴(lài)文件是否存在或是應該存在。
d) 對于此規則中不存在的依賴(lài)文件,遞歸的調用這個(gè)算法查找它是否可由隱含規則來(lái)創(chuàng )建。
e) 如果所有的依賴(lài)文件存在、應該存在、或是它可以由一個(gè)隱含規則來(lái)創(chuàng )建。退出查找,使用該規則。
7. 如果沒(méi)有隱含規則可以創(chuàng )建這個(gè)規則的依賴(lài)文件,則執行特殊目標“.DEFAULT”所指定的命令(可以創(chuàng )建這個(gè)文件,或者給出一個(gè)錯誤提示)。如果在Makefile中沒(méi)有定義特殊目標“DEFAULT”,就沒(méi)有可用的命令來(lái)完成“T”的創(chuàng )建。make退出。
一旦為一類(lèi)目標查找到合適的模式規則。除匹配“T”或者“N”的模式以外,對其它模式規則中的目標模式進(jìn)行配置,使用“莖”S替換其中的模式字符“%”,將得到的文件名保存直到執行命令更新這個(gè)目標文件(“T”)。在命令執行以后,把每一個(gè)儲存的文件名放入數據庫,并且標志為已更新,其時(shí)間戳和文件“T”相同。
在執行更新文件“T”的命令時(shí),使用自動(dòng)化變量表示規則中的依賴(lài)文件和目標文件。
聯(lián)系客服