Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=2897
隱含規則
————
在我們使用Makefile時,有一些我們會經常使用,而且使用頻率非常高的東西,比如,我們編譯C/C++的源程序為中間目標文件(Unix下是[.o]文件,Windows下是[.obj]文件)。本章講述的就是一些在Makefile中的“隱含的”,早先約定了的,不需要我們再寫出來的規則。
“隱含規則”也就是一種慣例,make會按照這種“慣例”心照不喧地來運行,那怕我們的Makefile中沒有書寫這樣的規則。例如,把[.c]文件編譯成[.o]文件這一規則,你根本就不用寫出來,make會自動推導出這種規則,并生成我們需要的[.o]文件。
“隱含規則”會使用一些我們系統變量,我們可以改變這些系統變量的值來定制隱含規則的運行時的參數。如系統變量“CFLAGS”可以控制編譯時的編譯器參數。
我們還可以通過“模式規則”的方式寫下自己的隱含規則。用“后綴規則”來定義隱含規則會有許多的限制。使用“模式規則”會更回得智能和清楚,但“后綴規則”可以用來保證我們Makefile的兼容性。
我們了解了“隱含規則”,可以讓其為我們更好的服務,也會讓我們知道一些“約定俗成”了的東西,而不至于使得我們在運行Makefile時出現一些我們覺得莫名其妙的東西。當然,任何事物都是矛盾的,水能載舟,亦可覆舟,所以,有時候“隱含規則”也會給我們造成不小的麻煩。只有了解了它,我們才能更好地使用它。
一、使用隱含規則
如果要使用隱含規則生成你需要的目標,你所需要做的就是不要寫出這個目標的規則。那么,make會試圖去自動推導產生這個目標的規則和命令,如果make可以自動推導生成這個目標的規則和命令,那么這個行為就是隱含規則的自動推導。當然,隱含規則是make事先約定好的一些東西。例如,我們有下面的一個Makefile:
??? foo : foo.o bar.o
??????????? cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
我們可以注意到,這個Makefile中并沒有寫下如何生成foo.o和bar.o這兩目標的規則和命令。因為make的“隱含規則”功能會自動為我們自動去推導這兩個目標的依賴目標和生成命令。
make會在自己的“隱含規則”庫中尋找可以用的規則,如果找到,那么就會使用。如果找不到,那么就會報錯。在上面的那個例子中,make調用的隱含規則是,把[.o]的目標的依賴文件置成[.c],并使用C的編譯命令“cc –c $(CFLAGS) [.c]”來生成[.o]的目標。也就是說,我們完全沒有必要寫下下面的兩條規則:
??? foo.o : foo.c
??????????? cc –c foo.c $(CFLAGS)
??? bar.o : bar.c
??????? cc –c bar.c $(CFLAGS)
因為,這已經是“約定”好了的事了,make和我們約定好了用C編譯器“cc”生成[.o]文件的規則,這就是隱含規則。
當然,如果我們為[.o]文件書寫了自己的規則,那么make就不會自動推導并調用隱含規則,它會按照我們寫好的規則忠實地執行。
還有,在make的“隱含規則庫”中,每一條隱含規則都在庫中有其順序,越靠前的則是越被經常使用的,所以,這會導致我們有些時候即使我們顯示地指定了目標依賴,make也不會管。如下面這條規則(沒有命令):
??? foo.o : foo.p
依賴文件“foo.p”(Pascal程序的源文件)有可能變得沒有意義。如果目錄下存在了“foo.c”文件,那么我們的隱含規則一樣會生效,并會通過“foo.c”調用C的編譯器生成foo.o文件。因為,在隱含規則中,Pascal的規則出現在C的規則之后,所以,make找到可以生成foo.o的C的規則就不再尋找下一條規則了。如果你確實不希望任何隱含規則推導,那么,你就不要只寫出“依賴規則”,而不寫命令。
二、隱含規則一覽
這里我們將講述所有預先設置(也就是make內建)的隱含規則,如果我們不明確地寫下規則,那么,make就會在這些規則中尋找所需要規則和命令。當然,我們也可以使用make的參數“-r”或“--no-builtin-rules”選項來取消所有的預設置的隱含規則。
當然,即使是我們指定了“-r”參數,某些隱含規則還是會生效,因為有許多的隱含規則都是使用了“后綴規則”來定義的,所以,只要隱含規則中有“后綴列表”(也就一系統定義在目標.SUFFIXES的依賴目標),那么隱含規則就會生效。默認的后綴列表是:.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。具體的細節,我們會在后面講述。
還是先來看一看常用的隱含規則吧。
1、編譯C程序的隱含規則。
“<n>.o”的目標的依賴目標會自動推導為“<n>.c”,并且其生成命令是“$(CC) –c $(CPPFLAGS) $(CFLAGS)”
2、編譯C++程序的隱含規則。
“<n>.o”的目標的依賴目標會自動推導為“<n>.cc”或是“<n>.C”,并且其生成命令是“$(CXX) –c $(CPPFLAGS) $(CFLAGS)”。(建議使用“.cc”作為C++源文件的后綴,而不是“.C”)
3、編譯Pascal程序的隱含規則。
“<n>.o”的目標的依賴目標會自動推導為“<n>.p”,并且其生成命令是“$(PC) –c? $(PFLAGS)”。
4、編譯Fortran/Ratfor程序的隱含規則。
“<n>.o”的目標的依賴目標會自動推導為“<n>.r”或“<n>.F”或“<n>.f”,并且其生成命令是:
??? “.f”? “$(FC) –c? $(FFLAGS)”
??? “.F”? “$(FC) –c? $(FFLAGS) $(CPPFLAGS)”
??? “.f”? “$(FC) –c? $(FFLAGS) $(RFLAGS)”
5、預處理Fortran/Ratfor程序的隱含規則。
“<n>.f”的目標的依賴目標會自動推導為“<n>.r”或“<n>.F”。這個規則只是轉換Ratfor或有預處理的Fortran程序到一個標準的Fortran程序。其使用的命令是:
??? “.F”? “$(FC) –F $(CPPFLAGS) $(FFLAGS)”
??? “.r”? “$(FC) –F $(FFLAGS) $(RFLAGS)”
6、編譯Modula-2程序的隱含規則。
“<n>.sym”的目標的依賴目標會自動推導為“<n>.def”,并且其生成命令是:“$(M2C) $(M2FLAGS) $(DEFFLAGS)”。“<n.o>” 的目標的依賴目標會自動推導為“<n>.mod”,并且其生成命令是:“$(M2C) $(M2FLAGS) $(MODFLAGS)”。
7、匯編和匯編預處理的隱含規則。
“<n>.o” 的目標的依賴目標會自動推導為“<n>.s”,默認使用編譯品“as”,并且其生成命令是:“$(AS) $(ASFLAGS)”。“<n>.s” 的目標的依賴目標會自動推導為“<n>.S”,默認使用C預編譯器“cpp”,并且其生成命令是:“$(AS) $(ASFLAGS)”。
8、鏈接Object文件的隱含規則。
“<n>”目標依賴于“<n>.o”,通過運行C的編譯器來運行鏈接程序生成(一般是“ld”),其生成命令是:“$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)”。這個規則對于只有一個源文件的工程有效,同時也對多個Object文件(由不同的源文件生成)的也有效。例如如下規則:
??? x : y.o z.o
并且“x.c”、“y.c”和“z.c”都存在時,隱含規則將執行如下命令:
??? 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
如果沒有一個源文件(如上例中的x.c)和你的目標名字(如上例中的x)相關聯,那么,你最好寫出自己的生成規則,不然,隱含規則會報錯的。
9、Yacc C程序時的隱含規則。
“<n>.c”的依賴文件被自動推導為“n.y”(Yacc生成的文件),其生成命令是:“$(YACC) $(YFALGS)”。(“Yacc”是一個語法分析器,關于其細節請查看相關資料)
10、Lex C程序時的隱含規則。
“<n>.c”的依賴文件被自動推導為“n.l”(Lex生成的文件),其生成命令是:“$(LEX) $(LFALGS)”。(關于“Lex”的細節請查看相關資料)
11、Lex Ratfor程序時的隱含規則。
“<n>.r”的依賴文件被自動推導為“n.l”(Lex生成的文件),其生成命令是:“$(LEX) $(LFALGS)”。
12、從C程序、Yacc文件或Lex文件創建Lint庫的隱含規則。
“<n>.ln” (lint生成的文件)的依賴文件被自動推導為“n.c”,其生成命令是:“$(LINT) $(LINTFALGS) $(CPPFLAGS) -i”。對于“<n>.y”和“<n>.l”也是同樣的規則。
三、隱含規則使用的變量
在隱含規則中的命令中,基本上都是使用了一些預先設置的變量。你可以在你的makefile中改變這些變量的值,或是在make的命令行中傳入這些值,或是在你的環境變量中設置這些值,無論怎么樣,只要設置了這些特定的變量,那么其就會對隱含規則起作用。當然,你也可以利用make的“-R”或“--no–builtin-variables”參數來取消你所定義的變量對隱含規則的作用。
例如,第一條隱含規則——編譯C程序的隱含規則的命令是“$(CC) –c $(CFLAGS) $(CPPFLAGS)”。Make默認的編譯命令是“cc”,如果你把變量“$(CC)”重定義成“gcc”,把變量“$(CFLAGS)”重定義成“-g”,那么,隱含規則中的命令全部會以“gcc –c -g $(CPPFLAGS)”的樣子來執行了。
我們可以把隱含規則中使用的變量分成兩種:一種是命令相關的,如“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文法分析器參數。
四、隱含規則鏈
有些時候,一個目標可能被一系列的隱含規則所作用。例如,一個[.o]的文件生成,可能會是先被Yacc的[.y]文件先成[.c],然后再被C的編譯器生成。我們把這一系列的隱含規則叫做“隱含規則鏈”。
在上面的例子中,如果文件[.c]存在,那么就直接調用C的編譯器的隱含規則,如果沒有[.c]文件,但有一個[.y]文件,那么Yacc的隱含規則會被調用,生成[.c]文件,然后,再調用C編譯的隱含規則最終由[.c]生成[.o]文件,達到目標。
我們把這種[.c]的文件(或是目標),叫做中間目標。不管怎么樣,make會努力自動推導生成目標的一切方法,不管中間目標有多少,其都會執著地把所有的隱含規則和你書寫的規則全部合起來分析,努力達到目標,所以,有些時候,可能會讓你覺得奇怪,怎么我的目標會這樣生成?怎么我的makefile發瘋了?
在默認情況下,對于中間目標,它和一般的目標有兩個地方所不同:第一個不同是除非中間的目標不存在,才會引發中間規則。第二個不同的是,只要目標成功產生,那么,產生最終目標過程中,所產生的中間目標文件會被以“rm -f”刪除。
通常,一個被makefile指定成目標或是依賴目標的文件不能被當作中介。然而,你可以明顯地說明一個文件或是目標是中介目標,你可以使用偽目標“.INTERMEDIATE”來強制聲明。(如:.INTERMEDIATE : mid )
你也可以阻止make自動刪除中間目標,要做到這一點,你可以使用偽目標“.SECONDARY”來強制聲明(如:.SECONDARY : sec)。你還可以把你的目標,以模式的方式來指定(如:%.o)成偽目標“.PRECIOUS”的依賴目標,以保存被隱含規則所生成的中間文件。
在“隱含規則鏈”中,禁止同一個目標出現兩次或兩次以上,這樣一來,就可防止在make自動推導時出現無限遞歸的情況。
Make會優化一些特殊的隱含規則,而不生成中間文件。如,從文件“foo.c”生成目標程序“foo”,按道理,make會編譯生成中間文件“foo.o”,然后鏈接成“foo”,但在實際情況下,這一動作可以被一條“cc”的命令完成(cc –o foo foo.c),于是優化過的規則就不會生成中間文件。