makefile的規則
組成
- target
- prerequisites
- command
target這一個或多個的目標文件依賴于prerequisites中的文件,其生成規則定義在command中。說白一點就是說,prerequisites中如果有一個以上的文件比target文件要新的話,command所定義的命令就會被執行。這就是Makefile的規則。也就是Makefile中最核心的內容
文件名
“GNUmakefile”或“Makefile”或“makefile”的文件(也可以通過make -f
或者make --file
來指定文件)
makefile工作流程
讀入所有的Makefile。
讀入被include的其它Makefile。
初始化文件中的變量。
推導隱晦規則,并分析所有規則。
為所有的目標文件創建依賴關系鏈。
根據依賴關系,決定哪些目標要重新生成。
執行生成命令。
編譯多個c文件過程例子
在默認的方式下,也就是我們只輸入make命令。那么,
- make會在當前目錄下找名字叫“GNUmakefile”或“Makefile”或“makefile”的文件(也可以通過
make -f
或者make --file
來指定文件)。 - 如果找到,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到“edit”這個文件,并把這個文件作為最終的目標文件。
- 如果edit文件不存在,或是edit所依賴的后面的 .o 文件的文件修改時間要比edit這個文件新,那么,他就會執行后面所定義的命令來生成edit這個文件。
- 如果edit所依賴的.o文件也存在,那么make會在當前文件中找目標為.o文件的依賴性,如果找到則再根據那一個規則生成.o文件。(這有點像一個堆棧的過程)
- 當然,你的C文件和H文件是存在的啦,于是make會生成 .o 文件,然后再用 .o 文件聲明make的終極任務,也就是執行文件edit了。
這就是整個make的依賴性,make會一層又一層地去找文件的依賴關系,直到最終編譯出第一個目標文件。在找尋的過程中,如果出現錯誤,比如最后被依賴的文件找不到,那么make就會直接退出,并報錯,而對于所定義的命令的錯誤,或是編譯不成功,make根本不理。make只管文件的依賴性,即,如果在我找了依賴關系之后,冒號后面的文件還是不在,那么對不起,我就不工作啦。
通過上述分析,我們知道,像clean這種,沒有被第一個目標文件直接或間接關聯,那么它后面所定義的命令將不會被自動執行,不過,我們可以顯示要make執行。即命令——“make clean”,以此來清除所有的目標文件,以便重編譯。
于是在我們編程中,如果這個工程已被編譯過了,當我們修改了其中一個源文件,比如file.c,那么根據我們的依賴性,我們的目標file.o會被重編譯(也就是在這個依性關系后面所定義的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改時間要比edit要新,所以edit也會被重新鏈接了(詳見edit目標文件后定義的命令)。
而如果我們改變了“command.h”,那么,kdb.o、command.o和files.o都會被重編譯,并且,edit會被重鏈接。
語法
基本格式
target:prerequisites
command
或者
target:prerequisites;command
! command 前面是tab鍵,不是空格,可以用
\
來分行,target、prerequisite、command都可以多個用空格隔開,后面的command要用前一條的結果需要使用分號隔開,比如
cd /home;pwd
和
cd home
pwd
是不同的,command可以多行
注釋
只支持行注釋,用#
號
默認target
只輸入make,默認執行第一個target,也可以指定
default: modules
變量
VAR=......
$(VAR)
- 大小寫敏感
- 要使用
$
時,用$$
,不是\$
- 除了用
=
,還可以用:=
,這種方法,前面的變量不能使用后面的變量,只能使用前面已定義好了的變量
x := foo
y := $(x) bar
x := later
等價于
y := foo bar
x := later
如果使用定義在后面的變量,則沒有值
y := $(x) bar
x := foo
y=bar,x=foo
-
?=
:沒有定義這個變量就定義一個 -
$(var.a=b)
:替換 變量中的值
foo := a.o b.o c.o
bar := $(foo:.o=.c)
- 變量的值當成變量
x = y
y = z
a := $($(x))
- 變量組合
first_second = Hello
a = first
b = second
all = $($a_$b)
- 追加變量值
objects = main.o foo.o bar.o utils.o
objects += another.o
objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o
- 目標變量(Target-specific Variable)
只在這個目標內生效(類似C局部變量)
<target ...> : <variable-assignment>
<target ...> : overide <variable-assignment>
- 模式變量(Pattern-specific Variable)
make的“模式”一般是至少含有一個“%”的,所以,我們可以以如下方式給所有以[.o]結尾的目標定義目標變量:
%.o : CFLAGS = -O
同樣,模式變量的語法和“目標變量”一樣:
<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>
override同樣是針對于系統環境傳入的變量,或是make命令行指定的變量。
-
自動化變量
自動化變量,就是這種變量會把模式中所定義的一系列的文件自動地挨個取出,直至所有的符合模式的文件都取完了。這種自動化變量只應出現在規則的命令中。
override指示符
如果有變量是通常make的命令行參數設置的,那么Makefile中對這個變量的賦值會被忽略。如果你想在Makefile中設置這類參數的值,那么,你可以使用“override”指示符。其語法是:
override <variable> = <value>
override <variable> := <value>
當然,還可以追加:
override <variable> += <more text>
多行變量:
override define foo
bar
endef
.PHONY(偽目標)
target相同名的文件在makefile目錄下,執行target而不是
.PHONY: modules
modules:
include
include<filename>
- filename可以是當前操作系統Shell的文件模式(可以保含路徑和通配符)
- 在include前面可以有一些空字符,但是絕不能是[Tab]鍵開始。include和可以用一個或多個空格隔開。
忽略警告繼續執行
clean:
-rm not_exist.file exist.file
-include<filename>
環境變量MAKEFILES
所有makefile都會include它,影響到所有makefile,不建議使用
通配符
-
~
:當前用戶 -
*
: -
?
:
轉義符、單行拆分成多行
clean:
VAR=find ./ -name "a*"
echo $(VAR)
rm a &&\
touch b
vpath
Makefile文件中的特殊變量“VPATH”就是完成這個功能的,如果沒有指明這個變量,make只會在當前的目錄中去找尋依賴文件和目標文件。如果定義了這個變量,那么,make就會在當當前目錄找不到的情況下,到所指定的目錄中去找尋文件了。
VPATH = src:../headers
上面的的定義指定兩個目錄,“src”和“../headers”,make會按照這個順序進行搜索。目錄由“冒號”分隔。(當然,當前目錄永遠是最高優先搜索的地方)
另一個設置文件搜索路徑的方法是使用make的“vpath”關鍵字(注意,它是全小寫的),這不是變量,這是一個make的關鍵字,這和上面提到的那個VPATH變量很類似,但是它更為靈活。它可以指定不同的文件在不同的搜索目錄中。這是一個很靈活的功能。它的使用方法有三種:
vpath < pattern> < directories> 為符合模式< pattern>的文件指定搜索目錄<directories>。
vpath < pattern> 清除符合模式< pattern>的文件的搜索目錄。
vpath 清除所有已被設置好了的文件搜索目錄。
vapth使用方法中的< pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”結尾的文件。< pattern>指定了要搜索的文件集,而< directories>則指定了的文件集的搜索的目錄。例如:
vpath %.h ../headers
可以連續地使用vpath語句,以指定不同搜索策略
vpath %.c foo
vpath % blish
vpath %.c bar
多target
$@
:目前規則中所有的目標集合
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
其中,-@)中的“
@”表示目標的集合,就像一個數組,“$@”依次取出目標,并執于命令。
靜態模式
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
自動生成依賴性
cc -M main.c
其輸出是:
main.o : main.c defs.h
注意gcc
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
系統命令
可以使用系統的命令,默認使用/bin/sh
命令顯示(回顯)
在命令前家@
可以不顯示執行的命令
@echo 'hello'
不會輸出echo `hello`,只輸出hello
使用make -n(或者--just-print)
,只顯示過程,真正執行命令,用來調試makefile
嵌套makefile
subsystem:
cd subdir && $(MAKE)
傳遞變量:export <variable>
傳遞所有變量:export
始終會默認傳遞的變量:MAKEFLAGS
,SHELL
記錄嵌套層數的變量:MAKELEVEL
定義命令包(多行變量)
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
foo.c : foo.y
$(run-yacc)
命令包“run-yacc”中的“
@”就是“foo.c”
條件判斷
ifeq(,) #ifeq ' ' ' ' 或者ifeq " " " "或ifeq ' ' " "
...
else
...
endif
ifneq ( , )
...
endif
ifdef <...>
...
endif
函數
$開頭,參數之間用,
隔開
$(<function> <arguments> )
或是
${<function> <arguments>}
- 字符串操作函數
$(subst <from>,<to>,<text> )
名稱:字符串替換函數——subst。
功能:把字串<text>中的<from>字符串替換成<to>。
返回:函數返回被替換過后的字符串。
$(patsubst <pattern>,<replacement>,<text> )
名稱:模式字符串替換函數——patsubst。
功能:查找<text>中的單詞(單詞以“空格”、“Tab”或“回車”“換行”分隔)是否符合模式<pattern>,如果匹配的話,則以<replacement>替換。這里,<pattern>可以包括通配符“%”,表示任意長度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的這個“%”將是<pattern>中的那個“%”所代表的字串。(可以用“\”來轉義,以“\%”來表示真實含義的“%”字符)返回:函數返回被替換過后的字符串。
$(strip <string> )
名稱:去空格函數——strip。
功能:去掉<string>字串中開頭和結尾的空字符。
返回:返回被去掉空格的字符串值。
$(findstring <find>,<in> )
名稱:查找字符串函數——findstring。
功能:在字串<in>中查找<find>字串。
返回:如果找到,那么返回<find>,否則返回空字符串。
$(filter <pattern...>,<text> )
名稱:過濾函數——filter。
功能:以<pattern>模式過濾<text>字符串中的單詞,保留符合模式<pattern>的單詞。可
以有多個模式。
返回:返回符合模式<pattern>的字串。
示例:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
$(filter-out <pattern...>,<text> )
名稱:反過濾函數——filter-out。
功能:以<pattern>模式過濾<text>字符串中的單詞,去除符合模式<pattern>的單詞。可
以有多個模式。
返回:返回不符合模式<pattern>的字串。
$(sort <list> )
名稱:排序函數——sort。
功能:給字符串<list>中的單詞排序(升序)。
返回:返回排序后的字符串。
示例:$(sort foo bar lose)返回“bar foo lose” 。
備注:sort函數會去掉<list>中相同的單詞。
$(word <n>,<text> )
名稱:取單詞函數——word。
功能:取字符串<text>中第<n>個單詞。(從一開始)
返回:返回字符串<text>中第<n>個單詞。如果<n>比<text>中的單詞數要大,那么返回空
字符串。
$(wordlist <s>,<e>,<text> )
名稱:取單詞串函數——wordlist。
功能:從字符串<text>中取從<s>開始到<e>的單詞串。<s>和<e>是一個數字。
返回:返回字符串<text>中從<s>到<e>的單詞字串。如果<s>比<text>中的單詞數要大,那
么返回空字符串。如果<e>大于<text>的單詞數,那么返回從<s>開始,到<text>結束的單
詞串。
示例: $(wordlist 2, 3, foo bar baz)返回值是“bar baz”。
$(words <text> )
名稱:單詞個數統計函數——words。
功能:統計<text>中字符串中的單詞個數。
返回:返回<text>中的單詞數。
示例:$(words, foo bar baz)返回值是“3”。
備注:如果我們要取<text>中最后的一個單詞,我們可以這樣:$(word $(words <text>
),<text> )。
$(firstword <text> )
名稱:首單詞函數——firstword。
功能:取字符串<text>中的第一個單詞。
返回:返回字符串<text>的第一個單詞。
示例:$(firstword foo bar)返回值是“foo”。
備注:這個函數可以用word函數來實現:$(word 1,<text> )。
- 文件名操作函數
$(dir <names...> )
名稱:取目錄函數——dir。
功能:從文件名序列<names>中取出目錄部分。目錄部分是指最后一個反斜杠(“/”)之
前的部分。如果沒有反斜杠,那么返回“./”。
返回:返回文件名序列<names>的目錄部分。
示例: $(dir src/foo.c hacks)返回值是“src/ ./”。
$(notdir <names...> )
名稱:取文件函數——notdir。
功能:從文件名序列<names>中取出非目錄部分。非目錄部分是指最后一個反斜杠(“/”
)之后的部分。
返回:返回文件名序列<names>的非目錄部分。
示例: $(notdir src/foo.c hacks)返回值是“foo.c hacks”。
$(suffix <names...> )
名稱:取后綴函數——suffix。
功能:從文件名序列<names>中取出各個文件名的后綴。
返回:返回文件名序列<names>的后綴序列,如果文件沒有后綴,則返回空字串。
示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。
$(basename <names...> )
名稱:取前綴函數——basename。
功能:從文件名序列<names>中取出各個文件名的前綴部分。
返回:返回文件名序列<names>的前綴序列,如果文件沒有前綴,則返回空字串。
示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar h
acks”。
$(addsuffix <suffix>,<names...> )
名稱:加后綴函數——addsuffix。
功能:把后綴<suffix>加到<names>中的每個單詞后面。
返回:返回加過后綴的文件名序列。
示例:$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。
$(addprefix <prefix>,<names...> )
名稱:加前綴函數——addprefix。
功能:把前綴<prefix>加到<names>中的每個單詞后面。
返回:返回加過前綴的文件名序列。
示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。
$(join <list1>,<list2> )
名稱:連接函數——join。
功能:把<list2>中的單詞對應地加到<list1>的單詞后面。如果<list1>的單詞個數要比<
list2>的多,那么,<list1>中的多出來的單詞將保持原樣。如果<list2>的單詞個數要比
<list1>多,那么,<list2>多出來的單詞將被復制到<list2>中。
返回:返回連接過后的字符串。
示例:$(join aaa bbb , 111 222 333)返回值是“aaa111 bbb222 333”。
foreach函數
$(foreach <var>,<list>,<text> )
names := a b c d
files := $(foreach n,$(names),$(n).o)
上面的例子中,$(name)中的單詞會被挨個取出,并存到變量“n”中,“$(n).o”每次根據“$(n)”計算出一個值,這些值以空格分隔,最后作為foreach函數的返回,所以,$(f
iles)的值是“a.o b.o c.o d.o”。
注意,foreach中的<var>參數是一個臨時的局部變量,foreach函數執行完后,參數<var>的變量將不在作用,其作用域只在foreach函數當中。
- if函數
$(if <condition>,<then-part> )
或是
$(if <condition>,<then-part>,<else-part> )
- call函數
call函數是唯一一個可以用來創建新的參數化的函數。你可以寫一個非常復雜的表達式,這個表達式中,你可以定義許多參數,然后你可以用call函數來向這個表達式傳遞參數。其語法是:
$(call <expression>,<parm1>,<parm2>,<parm3>...)
reverse = $(1) $(2)
foo = $(call reverse,a,b)
(foo值:a b)
reverse = $(2) $(1)
foo = $(call reverse,a,b)
此時的foo的值就是“b a”。
- origin函數
origin函數不像其它的函數,他并不操作變量的值,他只是告訴你你的這個變量是哪里來的?其語法是:
$(origin <variable> )
取值:
“undefined”
如果<variable>從來沒有定義過,origin函數返回這個值“undefined”。
“default”
如果<variable>是一個默認的定義,比如“CC”這個變量,這種變量我們將在后面講述。
“environment”
如果<variable>是一個環境變量,并且當Makefile被執行時,“-e”參數沒有被打開。
“file”
如果<variable>這個變量被定義在Makefile中。
“command line”
如果<variable>這個變量是被命令行定義的。
“override”
如果<variable>是被override指示符重新定義的。
“automatic”
如果<variable>是一個命令運行中的自動化變量。關于自動化變量將在后面講述。
- shell 函數
shell 函數也不像其它的函數。顧名思義,它的參數應該就是操作系統Shell的命令。它和反引號“`”是相同的功能。這就是說,shell函數把執行操作系統命令后的輸出作為函數
返回。于是,我們可以用操作系統命令以及字符串處理命令awk,sed等等命令來生成一個變量,如:
contents := $(shell cat foo)
files := $(shell echo *.c)
! 注意,這個函數會新生成一個Shell程序來執行命令,所以你要注意其運行性能,如果你的Makefile中有一些比較復雜的規則,并大量使用了這個函數,那么對于你的系統性能是有害的。特別是Makefile的隱晦的規則可能會讓你的shell函數執行的次數比你想像的多得多。
- 控制make的函數
$(error <text ...> )
$(warning <text ...> )
make退出碼
0 —— 表示成功執行。
1 —— 如果make運行時出現任何錯誤,其返回1。
2 —— 如果你使用了make的“-q”選項,并且make使得一些目標不需要更新,那么返回2。
make指定目標
GNU這種開源軟件的發布時,其 makefile都包含了編譯、安裝、打包等功能。我們可以參照這種規則來書寫我們的makefile中的目標。
**“all” ** 這個偽目標是所有目標的目標,其功能一般是編譯所有的目標。
**“clean” **這個偽目標功能是刪除所有被make創建的文件。
**“install” **這個偽目標功能是安裝已編譯好的程序,其實就是把目標執行文件拷貝到指定的目標中去。
**“print” **這個偽目標的功能是例出改變過的源文件。
**“tar” ** 這個偽目標功能是把源程序打包備份。也就是一個tar文件。
**“dist” ** 這個偽目標功能是創建一個壓縮文件,一般是把tar文件壓成Z文件。或是gz文件。
**“TAGS” ** 這個偽目標功能是更新所有的目標,以備完整地重編譯使用。
**“check”和“test” **這兩個偽目標一般用來測試makefile的流程。
make檢查規則 make調試
- make時加參數
有時候,我們不想讓我們的makefile中的規則執行起來,我們只想檢查一下我們的命令,或是執行的序列。于是我們可以使用make命令的下述參數:
“-n”
“--just-print”
“--dry-run”
“--recon”
不執行參數,這些參數只是打印命令,不管目標是否更新,把規則和連帶規則下的命令打印出來,但不執行,這些參數對于我們調試makefile很有用處。
“-t”
“--touch”
這個參數的意思就是把目標文件的時間更新,但不更改目標文件。也就是說,make假裝編譯目標,但不是真正的編譯目標,只是把目標變成已編譯過的狀態。
“-q”
“--question”
這個參數的行為是找目標的意思,也就是說,如果目標存在,那么其什么也不會輸出,當然也不會執行編譯,如果目標不存在,其會打印出一條出錯信息。
“-W <file>”
“--what-if=<file>”
“--assume-new=<file>”
“--new-file=<file>”
這個參數需要指定一個文件。一般是是源文件(或依賴文件),Make會根據規則推導來運行依賴于這個文件的命令,一般來說,可以和“-n”參數一同使用,來查看這個依賴文件
所發生的規則命令。
另外一個很有意思的用法是結合“-p”和“-v”來輸出makefile被執行時的信息(這個將在后面講述)。
- 在makefile中添加調試信息
使用
$(`信息等級` `...調試信息..`)
如:
$(info "hello")
$(warning "hello")
$(error "hello")
或者使用
@echo ......
這種的局限就是只能在目標后面使用
make參數
參見make --help
或者man make
隱含規則
報錯立即停止
makefile執行錯誤,結果還會繼續執行,此處是由于是上層makefile調用下層子makefile,子makefile執行出錯,停止返回到上層后,上層沒有判斷返回值,導致還是會繼續執行。
解決辦法是,對于子makefile調用,判斷返回值,
比如將:
make $@;
改為:
make $@ || exit "$$?";
這樣make執行錯誤返回值為非0,然后就可以執行后面的exit而退出了。
==================================