權(quán)威資料:http://www.gnu.org/software/make/manual/make.html,到這個頁面的尾部有index,可以快速查找一些關(guān)鍵詞來定位你想要找的東西,例如automatic variable
一個不錯的中文總結(jié):http://www.cnblogs.com/liangxiaxu/archive/2012/07/31/2617384.html
自我學(xué)到的一些知識點:
1。makefile中的每一個target都是一個文件,make的最終目標都是生成這個文件,target可以帶路徑,例如dir1/dir2/test;
2。%.o: %.c中的%可以匹配路徑,例如test1/test2/*.o都可以從test1/test2/*.c來推導(dǎo);
3。關(guān)于PHONY目標,能成為PHONY的target應(yīng)該是不能對應(yīng)具體文件的,例如clean,例如我們經(jīng)常用all來收集一批需要產(chǎn)生的真正的target,偽目標的特性就是:當(dāng)make ABC時,不管當(dāng)前目錄是否有ABC文件,都會去分析其依賴項并執(zhí)行recipes;
4。如果一個target沒有任何依賴,那么這個target永遠都是最新的,因此只要這個target文件存在,那么永遠不會去執(zhí)行它的recipes,如果我們把這個target聲明為偽目標,那么不論這個target文件是否存在,執(zhí)行其recipes;
5。一個target依賴多項時,可以分開寫,例如:
A: B1
A:
A:;
A: B2
echo $@, $^
最終A依賴于B1和B2
6。可以一次性寫多個target的依賴,例如:A B C: D
7。A: PARAM := test.cpp這種寫法表明PARAM這個變量的作用域僅僅是生成A時有用
8。A: B C | D E,這里D E被稱為order-only依賴,就是說對于D E,和B C一樣,該更新就更新,而且還是先處理依賴項D,再處理依賴項E,只不過到最后即便D或者E更新了,也不會因此而更新A,除非B或C更新了,千萬不要誤以為只要D存在就關(guān)心D的死活了(即是否要更新);
9。make test -n會打印出make過程所有需要執(zhí)行的命令,但并不會執(zhí)行;
10。make test -d會打印出make過程所有的分析過程,例如怎么解決的依賴,自動推導(dǎo)規(guī)則,為什么有些target需要更新,有些不需要;
11。make test -p會打印出make過程中所有的變量和規(guī)則的推導(dǎo)結(jié)果,即所有變量都展開了,而且會告訴你所執(zhí)行的命令都來自于哪個makefile的哪一行;
12。make --debug[=FLAGS]
Print debugging information in addition to normal processing. If the FLAGS are omitted, then the behavior is the same as if -d was specified. FLAGS may be a for all debugging output (same as using -d),
b for basic debugging, v for more verbose basic debugging, i for showing implicit rules, j for details on invocation of commands, and m for debugging while remaking makefiles.
13。make V=1的含義是:定義了一個變量,名字是V,值是1,可以在Makefile中通過$(V)來得到V的值;
14。make -j 8的含義是:用8個進程同時build,即速度加快8倍;
15。$+和$^差不多,區(qū)別是$^去重了,$+都保留下來了;
16。想查看一個target的所有依賴項,有時候一個target的依賴項會分散在多個文件中,例如對于target all,可以增加如下代碼即可:
all:
#$^
這里當(dāng)然也可以用@echo $^,但上面這種簡單寫法也可以,反正recipe就是寫shell命令
recipe可以由換行,也可以有make的一些條件控制語句,例如:
all:
@echo "hello"
@echo "world"
ifeq "$(abc)" "hello"
@echo "equal"
else
@echo "not equal"
endif
總結(jié):尤其是make -d/-p,基本可以搞定所有你弄不清楚的make問題,仔細分析就好了。
$(if ifeq "foo" "bar", @echo match is broken, @echo match works)
$(if ifneq "foo" "bar", @echo match works, @echo match is broken)
$(if ...) conditional function evaluates to true when the first argument passed to it is non-empty. In you case the condition is literal text: ifeq "foo" "bar", which is, obviously, non-empty.
ifeq/ifneq conditionals are in fact directives, not functions. They can't be used inside variable definition and in functions.
Back to your example, to test string for equality inside the condition use functions like filter, filter-out and findstring:
$(if $(filter-out foo,bar),@echo not equal,@echo equal)
====================================================================
下面是之前學(xué)習(xí)make時寫的一些東西,現(xiàn)在看來好像很垃圾:
[make的規(guī)則]
在定義好依賴關(guān)系后,后續(xù)的那一行定義了如何生成目標文件的操作系統(tǒng)命令,一定要以一個Tab鍵作為開頭。記住,make并不管命令是怎么工作的,他只管執(zhí)行所定義的命令。
make會比較targets文件和prerequisites文件的修改日期(mtime,而不是ctime或atime),如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的話,那么,make就會執(zhí)行后續(xù)定義的命令。
也可以像下面這樣把命令放在后面,通過一個分號來搞定
targets : prerequisites ; command
command
比如你的第一條命令是cd命令,你希望第二條命令得在cd之后的基礎(chǔ)上運行,那么你就不能把這兩條命令寫在兩行上,而應(yīng)該把這兩條命令寫在一行上,用分號分隔。也就是說:一行內(nèi)的命令是在一個子shell環(huán)境中執(zhí)行的。
make會一按順序一條一條的執(zhí)行命令,每條命令的開頭必須以[Tab]鍵開頭,除非,命令是緊跟在依賴規(guī)則后面的分號后的。在命令行之間中的空格或是空行會被忽略,但是如果該空格或空行是以Tab鍵開頭的,那么make會認為其是一個空命令。
當(dāng)我們用“@”字符在命令行前,那么,這個命令將不被make顯示出來,最具代表性的例子是,我們用這個功能來像屏幕顯示一些信息。如:
@echo 正在編譯XXX模塊......
當(dāng)make執(zhí)行時,會輸出“正在編譯XXX模塊......”字串,但不會輸出命令,如果沒有“@”,那么,make將輸出:
echo 正在編譯XXX模塊......
正在編譯XXX模塊......
如果不給make命令指定target,那么make會將makefile文件中出現(xiàn)的第一個target作為此次執(zhí)行make的目標,如果該target不是一個,那么取第一個.其實make一次只對一個target負責(zé),除非你在執(zhí)行make時指定多個target,如make target1 target2
[變量定義]
=定義的時候不會被展開,在具體引用時才會被展開
:=定義的時候就會被展開,建議使用該種變量定義
[自動推導(dǎo)]
main.o : defs.h
make會自動為你添加main.cpp這個依賴文件
[引用其他Makefile]
include foo.make *.mk $(bar)
[make的工作方式]
GNU的make工作時的執(zhí)行步驟入下:(想來其它的make也是類似)
1. 讀入所有的Makefile。
2. 讀入被include的其它Makefile。
3. 初始化文件中的變量。
4. 推導(dǎo)隱晦規(guī)則,并分析所有規(guī)則。
5. 為所有的目標文件創(chuàng)建依賴關(guān)系鏈。
6. 根據(jù)依賴關(guān)系,決定哪些目標要重新生成。
7. 執(zhí)行生成命令。
1-5步為第一個階段,6-7為第二個階段。第一個階段中,如果定義的變量被使用了,那么,make會把其展開在使用的位置。但make并不會完全馬上展開,make使用的是拖延戰(zhàn)術(shù),如果變量出現(xiàn)在依賴關(guān)系的規(guī)則中,那么僅當(dāng)這條依賴被決定要使用了,變量才會在其內(nèi)部展開。
當(dāng)然,這個工作方式你不一定要清楚,但是知道這個方式你也會對make更為熟悉。有了這個基礎(chǔ),后續(xù)部分也就容易看懂了。
[make命令的參數(shù)]
make -i 忽略出錯命令,等同于在makefile中對應(yīng)命令前加上‘-’符號
make -k 如果某規(guī)則中的命令出錯了,那么就終目該規(guī)則的執(zhí)行,但繼續(xù)執(zhí)行其它規(guī)則
make -n 那么其只是顯示命令,但不會執(zhí)行命令
make -s 或“--slient”則是全面禁止命令的顯示
make -w 在“嵌套執(zhí)行”中比較有用的參數(shù),“-w”或是“--print-directory”會在make的過程中輸出一些信息,讓你看到目前的工作目錄。
[文件搜尋]
Makefile文件中的特殊變量“VPATH”就是完成這個功能的,如果沒有指明這個變量,make只會在當(dāng)前的目錄中去找尋依賴文件和目標文件。如果定義了這個變量,那么,make就會在當(dāng)當(dāng)前目錄找不到的情況下,到所指定的目錄中去找尋文件了。
VPATH = src:../headers
上面的的定義指定兩個目錄,“src”和“../headers”,make會按照這個順序進行搜索。目錄由“冒號”分隔。(當(dāng)然,當(dāng)前目錄永遠是最高優(yōu)先搜索的地方)
另一個設(shè)置文件搜索路徑的方法是使用make的“vpath”關(guān)鍵字(注意,它是全小寫的),這不是變量,這是一個make的關(guān)鍵字,這和上面提到的那個 VPATH變量很類似,但是它更為靈活。它可以指定不同的文件在不同的搜索目錄中。這是一個很靈活的功能。它的使用方法有三種:
1. vpath < pattern> < directories>
為符合模式< pattern>的文件指定搜索目錄< directories>。
2. vpath < pattern>
清除符合模式< pattern>的文件的搜索目錄。
3. vpath
清除所有已被設(shè)置好了的文件搜索目錄。
vapth使用方法中的< pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”結(jié)尾的文件。< pattern>指定了要搜索的文件集,而< directories>則指定了的文件集的搜索的目錄。例如:
vpath %.h ../headers
該語句表示,要求make在“../headers”目錄下搜索所有以“.h”結(jié)尾的文件。(如果某文件在當(dāng)前目錄沒有找到的話)
[偽目標]
“偽目標”并不是一個文件,只是一個標簽,由于“偽目標”不是文件,所以make無法生成它的依賴關(guān)系和決定它是否要執(zhí)行。我們只有通過顯示地指明這個“目標”才能讓其生效。
沒有依賴項的target自動成為偽目標,放在.PHONY的依賴項中的target自動成為偽目標.
澄清一個錯誤觀點: 放在.PHONY中的依賴項并不是make后就自動執(zhí)行,而僅僅只是聲明該target是個偽目標而已,make命令依然還是執(zhí)行makefile中的第一個target
偽目標的更新時間永遠都是最新的,因此只要執(zhí)行到了偽目標,那么必然會被執(zhí)行,時間戳在它面前無效.
因此對于通過make命令就期望強制執(zhí)行很多target的話,可以在makefile開頭這么寫:
all : prog1 prog2 prog3
.PHONY : all
[靜態(tài)規(guī)則]
語法:
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...
看一個例子:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
使用filter:
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
[makefile中的函數(shù)]
形式為:$(函數(shù)名 參數(shù))
自定義函數(shù):
define 函數(shù)名
函數(shù)體
endef
[gcc技巧]
如果你使用GNU的C/C++編譯器,你得用“-MM”參數(shù),不然,“-M”參數(shù)會把一些標準庫的頭文件也包含進來。
[嵌套執(zhí)行 & 變量或參數(shù)向下傳遞]
嵌套執(zhí)行make
在一些大的工程中,我們會把我們不同模塊或是不同功能的源文件放在不同的目錄中,我們可以在每個目錄中都書寫一個該目錄的Makefile,這有利于讓我們的Makefile變得更加地簡潔,而不至于把所有的東西全部寫在一個Makefile中,這樣會很難維護我們的Makefile,這個技術(shù)對于我們模塊編譯和分段編譯有著非常大的好處。
例如,我們有一個子目錄叫subdir,這個目錄下有個Makefile文件,來指明了這個目錄下文件的編譯規(guī)則。那么我們總控的Makefile可以這樣書寫:
subsystem:
cd subdir && $(MAKE)
其等價于:
subsystem:
$(MAKE) -C subdir
定義$(MAKE)宏變量的意思是,也許我們的make需要一些參數(shù),所以定義成一個變量比較利于維護。這兩個例子的意思都是先進入“subdir”目錄,然后執(zhí)行make命令。
我們把這個Makefile叫做“總控Makefile”,總控Makefile的變量可以傳遞到下級的Makefile中(如果你顯示的聲明),但是不會覆蓋下層的Makefile中所定義的變量,除非指定了“-e”參數(shù)。
如果你要傳遞變量到下級Makefile中,那么你可以使用這樣的聲明:
export <variable ...>
如果你不想讓某些變量傳遞到下級Makefile中,那么你可以這樣聲明:
unexport <variable ...>
如:
export variable := value #定義的同時也export了
export variable += value
如果你要傳遞所有的變量,那么,只要一個export就行了。后面什么也不用跟,表示傳遞所有的變量。
需要注意的是,有兩個變量,一個是SHELL,一個是MAKEFLAGS,這兩個變量不管你是否export,其總是要傳遞到下層Makefile 中,特別是MAKEFILES變量,其中包含了make的參數(shù)信息,如果我們執(zhí)行“總控Makefile”時有make參數(shù)或是在上層Makefile中定義了這個變量,那么MAKEFILES變量將會是這些參數(shù),并會傳遞到下層Makefile中,這是一個系統(tǒng)級的環(huán)境變量。
但是make命令中的有幾個參數(shù)并不往下傳遞,它們是“-C”,“-f”,“-h”“-o”和“-W”(有關(guān)Makefile參數(shù)的細節(jié)將在后面說明),如果你不想往下層傳遞參數(shù),那么,你可以這樣來:
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=
如果你定義了環(huán)境變量MAKEFLAGS,那么你得確信其中的選項是大家都會用到的,如果其中有“-t”,“-n”,和“-q”參數(shù),那么將會有讓你意想不到的結(jié)果,或許會讓你異常地恐慌。
還有一個在“嵌套執(zhí)行”中比較有用的參數(shù),“-w”或是“--print-directory”會在make的過程中輸出一些信息,讓你看到目前的工作目錄。比如,如果我們的下級make目錄是“/home/hchen/gnu/make”,如果我們使用“make -w”來執(zhí)行,那么當(dāng)進入該目錄時,我們會看到:
make: Entering directory `/home/hchen/gnu/make'.
而在完成下層make后離開目錄時,我們會看到:
make: Leaving directory `/home/hchen/gnu/make'
當(dāng)你使用“-C”參數(shù)來指定make下層Makefile時,“-w”會被自動打開的。如果參數(shù)中有“-s”(“--slient”)或是“--no-print-directory”,那么,“-w”總是失效的。
[自動生成依賴關(guān)系]
自動生成依賴性
在Makefile中,我們的依賴關(guān)系可能會需要包含一系列的頭文件,比如,如果我們的main.c中有一句“#include "defs.h"”,那么我們的依賴關(guān)系應(yīng)該是:
main.o : main.c defs.h
但是,如果是一個比較大型的工程,你必需清楚哪些C文件包含了哪些頭文件,并且,你在加入或刪除頭文件時,也需要小心地修改Makefile,這是一個很沒有維護性的工作。為了避免這種繁重而又容易出錯的事情,我們可以使用C/C++編譯的一個功能。大多數(shù)的C/C++編譯器都支持一個“-M”的選項,即自動找尋源文件中包含的頭文件,并生成一個依賴關(guān)系。例如,如果我們執(zhí)行下面的命令:
cc -M main.c
其輸出是:
main.o : main.c defs.h
于是由編譯器自動生成的依賴關(guān)系,這樣一來,你就不必再手動書寫若干文件的依賴關(guān)系,而由編譯器自動生成了。需要提醒一句的是,如果你使用GNU的C/C++編譯器,你得用“-MM”參數(shù),不然,“-M”參數(shù)會把一些標準庫的頭文件也包含進來。
gcc -M main.c的輸出是:
main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
/usr/include/bits/sched.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/include/bits/wchar.h /usr/include/gconv.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
/usr/include/bits/stdio_lim.h
gcc -MM main.c的輸出則是:
main.o: main.c defs.h
那么,編譯器的這個功能如何與我們的Makefile聯(lián)系在一起呢。因為這樣一來,我們的Makefile也要根據(jù)這些源文件重新生成,讓 Makefile自已依賴于源文件?這個功能并不現(xiàn)實,不過我們可以有其它手段來迂回地實現(xiàn)這一功能。GNU組織建議把編譯器為每一個源文件的自動生成的依賴關(guān)系放到一個文件中,為每一個“name.c”的文件都生成一個“name.d”的Makefile文件,[.d]文件中就存放對應(yīng)[.c]文件的依賴關(guān)系。
于是,我們可以寫出[.c]文件和[.d]文件的依賴關(guān)系,并讓make自動更新或自成[.d]文件,并把其包含在我們的主Makefile中,這樣,我們就可以自動化地生成每個文件的依賴關(guān)系了。
這里,我們給出了一個模式規(guī)則來產(chǎn)生[.d]文件:
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
這個規(guī)則的意思是,所有的[.d]文件依賴于[.c]文件,“rm -f $@”的意思是刪除所有的目標,也就是[.d]文件,第二行的意思是,為每個依賴文件“$<”,也就是[.c]文件生成依賴文件,“$@”表示模式 “%.d”文件,如果有一個C文件是name.c,那么“%”就是“name”,“$$$$”意為一個隨機編號,第二行生成的文件有可能是 “name.d.12345”,第三行使用sed命令做了一個替換,關(guān)于sed命令的用法請參看相關(guān)的使用文檔。第四行就是刪除臨時文件。
總而言之,這個模式要做的事就是在編譯器生成的依賴關(guān)系中加入[.d]文件的依賴,即把依賴關(guān)系:
main.o : main.c defs.h
轉(zhuǎn)成:
main.o main.d : main.c defs.h
于是,我們的[.d]文件也會自動更新了,并會自動生成了,當(dāng)然,你還可以在這個[.d]文件中加入的不只是依賴關(guān)系,包括生成的命令也可一并加入,讓每個[.d]文件都包含一個完賴的規(guī)則。一旦我們完成這個工作,接下來,我們就要把這些自動生成的規(guī)則放進我們的主Makefile中。我們可以使用Makefile的“include”命令,來引入別的Makefile文件(前面講過),例如:
sources = foo.c bar.c
include $(sources:.c=.d)
上述語句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一個替換,把變量$(sources)所有[.c]的字串都替換成[.d],關(guān)于這個“替換”的內(nèi)容,在后面我會有更為詳細的講述。當(dāng)然,你得注意次序,因為include是按次來載入文件,最先載入的[.d]文件中的目標會成為默認目標