一、開始
1.Hello World
新建一個makefile
文件,寫入如下內容,
hello:
echo "Hello World"
clean:
echo "clean all"
執行make
命令,輸出結果為
echo "Hello World"
Hello World
2.makfile 語法
targets:prerequisites
command
...
target
可以是一個 object file(目標文件),也可以是一個執行文件,還可以是一個標簽(label)。prerequisites
生成該 target 所依賴的文件和/或 target。command
該 target 要執行的命令(任意的 shell 命令)。
這是一個文件的依賴關系,也就是說,target 這一個或多個的目標文件依賴於 prerequisites 中的文件,其生成規則定義在 command 中。說白一點就是說:prerequisites 中如果有一個以上的文件比 target 文件要新的話,command 所定義的命令就會被執行。
3.簡單的例子
hello:hello.o
gcc hello.o -o hello # Runs third
hello.o:hello.c
gcc -c hello.c -o hello.o # Runs second
hello.c:
echo "int main() { return 0; }" > hello.c # Runs first
執行make
或make hello
,結果如下
echo "int main() { return 0; }" > hello.c # Runs first
gcc -c hello.c -o hello.o # Runs second
gcc hello.o -o hello # Runs third
可以看到執行的順序與makefile
內容是相反的,當執行make hello
時,由於hello
是由hello.o
生成的,需要找到hello.o
,但是hello.o
是由hello.c
生成的,那么需要先生成hello.c
,以此類推,通過這個例子可以清晰地看到makefile
是通過依賴關系自動推導的。
4.變量
files = file1 file2
some_file:$(files)
echo "look at this variable:" $(files)
file1:
touch file1
file2:
touch file2
clean:
rm -f file1 file2
makefile文件的變量不僅可以用
$()
,也可以用${}
。
二、目標文件
all: one two three
one:
touch one
two:
touch two
three:
touch three
clean:
rm -f one two three
這樣寫的話比較繁瑣,可以使用$@
,當一條規則有多個目標時,將為每個目標運行這些命令,$@
是一個自動變量,包含目標名稱。makefile可以重新寫為
all: one two three
one two three:
echo $@
clean:
rm -r one two three
三、自動推導變量和通配符
1. 通配符 *
thing_wrong := *.o # Don't do this! '*' will not get expanded
thing_right := $(wildcard *.o)
all: one two three four
# Fails, because $(thing_wrong) is the string "*.o"
one: $(thing_wrong)
# Stays as *.o if there are no files that match this pattern :(
two: *.o
# Works as you would expect! In this case, it does nothing.
three: $(thing_right)
# Same as rule three
four: $(wildcard *.o)
*
能夠被用在target
、prerequisites
以及wildcard
函數里。*
直接用在變量中比較危險,不會被認為是通配符,而是被看做字符*
。- 當
*
沒有被匹配到的時候,它就保持原樣,比如像上面two
中的*.o
能夠匹配到后綴為.o
的文件,但是如果沒有匹配到的話,會被看做是普通的字符串*.o
,這樣是比較危險的。
2. 通配符 %
- 當在 "匹配 "模式下使用時,它匹配字符串中的一個或多個字符。這種匹配被稱為stem。
- 當在 "替換 "模式下使用時,它獲取被匹配的stem並將其替換到一個字符串中。
%
最常被用於規則定義和一些特定的功能中。在下文中會被重點說明。
3. 自動推導變量
$@
:代表目標文件(target)$^
:代表所有的依賴文件(prerequisites)$<
:代表第一個依賴文件(prerequisites中最左邊的那個)。$?
:代表示比目標還要新的依賴文件列表。以空格分隔。$%
:僅當目標是函數庫文件中,表示規則中的目標成員名。
hey: one two
# Outputs "hey", since this is the first target
echo $@
# Outputs all prerequisites newer than the target
echo $?
# Outputs all prerequisites
echo $^
touch hey
one:
touch one
two:
touch two
clean:
rm -f hey one two
第一個echo $@
輸出的是hey,即目標文件。echo $?
和echo $^
的輸出看起來沒啥區別,但是調換一下次序,如
hey: one two
echo $^
echo "---------------"
echo $?
echo "---------------"
touch hey
one:
touch one
two:
touch two
clean:
rm -f hey one two
輸出
touch one
touch two
echo one two
one two
echo "---------------"
---------------
echo one two
one two
echo "---------------"
---------------
touch hey
可以明顯地看到echo $?
沒有調用touch one
和touch two
,原因是echo $^
已經調用並生成最新文件,不需要再次調用。
四、高效技能
1. 靜態模式規則
targets ...: target-pattern: prereq-patterns ...
commands
其本質是,給定的目標被目標模式(通過%通配符)匹配。被匹配的內容被稱為stem。然后,該stem被替換到前置條件模式中,以生成目標的前置條件。
objects = foo.o bar.o all.o
all: $(objects)
# These files compile via implicit rules
foo.o: foo.c
bar.o: bar.c
all.o: all.c
all.c:
echo "int main() { return 0; }" > all.c
%.c:
touch $@
clean:
rm -f *.c *.o all
更有效地方式可以這樣寫
objects = foo.o bar.o all.o
all: $(objects)
# These files compile via implicit rules
# Syntax - targets ...: target-pattern: prereq-patterns ...
# In the case of the first target, foo.o, the target-pattern matches foo.o and sets the "stem" to be "foo".
# It then replaces the '%' in prereq-patterns with that stem
$(objects): %.o: %.c
all.c:
echo "int main() { return 0; }" > all.c
%.c:
touch $@
clean:
rm -f *.c *.o all
2. 靜態模式規則和過濾器
obj_files = foo.result bar.o lose.o
src_files = foo.raw bar.c lose.c
all: $(obj_files)
# 只會讓.o的文件執行
$(filter %.o,$(obj_files)): %.o: %.c
echo "target: $@ prereq: $<"
# 只會讓.result的文件執行
$(filter %.result,$(obj_files)): %.result: %.raw
echo "target: $@ prereq: $<"
%.c %.raw:
touch $@
clean:
rm -f $(src_files)
3. 隱式規則
CC = gcc # Flag for implicit rules
CFLAGS = -g # Flag for implicit rules. Turn on debug info
# Implicit rule #1: blah is built via the C linker implicit rule
# Implicit rule #2: blah.o is built via the C compilation implicit rule, because blah.c exists
# Define a pattern rule that compiles every .c file into a .o file
blah:blah.o
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
blah.c:
echo "int main() { return 0; }" > blah.c
clean:
rm -f blah*
4. 雙引號規則
雙冒號規則很少使用,但允許為同一目標定義多個規則。如果這些是單冒號,就會打印出一個警告,而且只有第二組命令會運行。
all: blah
blah::
echo "hello"
blah::
echo "hello again"
五、命令與執行
在命令前添加@
以阻止其被打印出來,也可以在運行make -s
,效果好比在每一行前添加一個@
。
all:
@echo "This make line will not be printed"
echo "But this will"
執行make
輸出
This make line will not be printed
echo "But this will"
But this will
執行make -s
時,輸出
This make line will not be printed
But this will
每條命令都在一個新的shell中運行(或者至少效果是這樣)。
all:
cd ..
# The cd above does not affect this line, because each command is effectively run in a new shell
echo `pwd`
# This cd command affects the next because they are on the same line
cd ..;echo `pwd`
# Same as above
cd ..; \
echo `pwd`
在執行make
時,增加參數-k
,以便在出現錯誤時繼續運行。如果你想一次性看到make
的所有錯誤,這很有幫助。
在命令前加一個-
來抑制錯誤的發生,如果在make
中添加-i
參數,可以使每條命令都出現這種情況。
one:
# This error will be printed but ignored, and make will continue to run
false
touch one
執行make
,會出現出現終止程序錯誤
false
make: *** [one] Error 1
如果在false
前面加上-
,輸出
false
make: [one] Error 1 (ignored)
touch one
注意:如果你在
make
過程中ctrl+c
,會刪除剛剛制作的較新目標文件。
要遞歸調用一個makefile
,請使用特殊的$(MAKE)
而不是make
。
all:
touch two
cd subdir && $(MAKE)
clean:
rm -rf subdir two
在當前文件夾內新建一個名為subdir
的子文件夾,makefile的內容為
hello:
touch one
export
指令獲取一個變量,並使其能夠被調用的make命令訪問。在這個例子中,corey被export
,這樣subdir中的makefile就可以使用它。
new_contents = "hello:\n\\techo \$$(cooly)"
all:
mkdir -p subdir
echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END MAKEFILE CONTENTS---"
cd subdir && $(MAKE)
# Note that variables and exports. They are set/affected globally.
cooly = "The subdirectory can see me!"
export cooly
# This would nullify the line above: unexport cooly
clean:
rm -rf subdir
export
定義的變量也可以在本makefile中使用
one=this will only work locally
export two=we can run subcommands with this
all:
@echo $(one)
@echo $$one # 沒有輸出
@echo $(two)
@echo $$two # 等效於$(two)
輸出
this will only work locally
we can run subcommands with this
we can run subcommands with this
在makefile
中的變量前輸入.EXPORT_ALL_VARIABLES:
,可以等效與在變量前輸入export
。
.EXPORT_ALL_VARIABLES:
new_contents = "hello:\n\techo \$$(cooly)"
cooly = "The subdirectory can see me!"
# This would nullify the line above: unexport cooly
all:
mkdir -p subdir
echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END MAKEFILE CONTENTS---"
cd subdir && $(MAKE)
clean:
rm -rf subdir
六、再談變量
1. 變量符號類別
當使用=
時,該變量使用的時候會全局搜索,不需要提前定義。
當使用:=
是,需要提前定義,否者找不到,用空字符串代替。
# Recursive variable. This will print "later" below
one = one ${later_variable}
# Simply expanded variable. This will not print "later" below
two := two ${later_variable}
later_variable = later
three := three ${later_variable}
all:
echo $(one)
echo $(two)
echo $(three)
輸出
echo one later
one later
echo two
two
echo three later
three later
?=
表示如果先前沒有對該變量賦值,那么會賦值成功,否者沒有效果。
one = hello
one ?= will not be set
two ?= will be set
all:
echo $(one)
echo $(two)
輸出
echo hello
hello
echo will be set
will be set
行末的空格不會被忽略,但行首的空格會被忽略。要使一個變量帶有一個空格,可以使用空字符串變量
with_spaces = hello # with_spaces has many spaces after "hello"
after = $(with_spaces)there
nullstring =
space = $(nullstring) # Make a variable with a single space.
all:
echo "$(after)"
echo "$(space)"start"$(space)"end
輸出
echo "hello there"
hello there
echo " "start" "end
start end
未定義的變量被認為是空字符串
+=
表示追加(append)
foo := start
foo += more
all:
echo $(foo)
輸出
echo start more
start more
make有兩個比較特殊的變量:SHELL”和“ MAKEFLAGS”,這兩個變量除非使用“ unexport”聲明,否則的話在整個 make的執行過程中,它們的值始終自動的傳遞給子make。
2. 命令行重載
用於指示makefile
中定義的變量不能被覆蓋,變量的定義和賦值都需要使用override
關鍵字。
override one = did_override
one = hello
two = world
all:
echo $(one)
echo $(two)
輸出為
echo did_override
did_override
echo world
world
3. 宏定義
用define
...endef
包起來,
define one
echo hello world
endef
all:
@$(one)
輸出
hello world
但是在define
...endef
里的每一行都是單獨執行的,
define one
export str=hello
echo $$str
endef
all:
@$(one)
這樣的話會輸出空字符串,因為他們沒有聯系。makefile允許多目標,當執行多目標的時候,會按照順序全部執行。
all: one = cool
all:
echo $(one)
other:
echo $(one)
make all
有輸出cool
,make other
沒有輸出。
你可以為特定的目標模式分配變量
%.c: one = cool
blah.c:
echo one is defined: $(one)
other:
echo one is nothing: $(one)
只有執行make blah.c
時,才會匹配到第一行。
七、條件
if/else語句
foo = ok
all:
ifeq ($(foo),ok)
echo "foo equal ok"
else
echo "nope"
endif
判斷一個變量是否為空
nullstring =
foo = $(nullstring) # end of line; there is a space here
all:
ifeq ($(strip $(foo)),)
echo "foo is empty after being stripped"
endif
ifeq ($(nullstring),)
echo "nullstring doesn't even have spaces"
endif
判斷變量是否被定義(define),ifdef
只查看是否有定義的東西。
bar =
foo = $(bar)
all:
ifdef foo
echo "foo is defined"
endif
ifdef bar
echo "but bar is not"
endif
輸出為
echo "foo is defined"
foo is defined
八、函數
1. 替換函數
函數的形式有${fn,argument}
和$(fn,argument)
兩種,先看一個字符串替換函數
bar := ${subst not,totally,"I am not superman"}
all:
@echo $(bar)
輸出
I am totally superman
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))
all:
@echo $(bar)
輸出
a,b,c
2. 字符串置換
foo := a.o b.o l.a c.o
one := $(subst %.o,%.c,$(foo))
# This is a shorthand for the above
two := $(foo:%.o=%.c)
# This is the suffix-only shorthand, and is also equivalent to the above.
three := $(foo:.o=.c)
all:
echo $(one)
echo $(two)
echo $(three)
輸出
echo a.c b.c l.a c.c
a.c b.c l.a c.c
echo a.c b.c l.a c.c
a.c b.c l.a c.c
echo a.c b.c l.a c.c
a.c b.c l.a c.c
3. foreach 函數
foo := who are you
# For each "word" in foo, output that same word with an exclamation after
bar := $(foreach wrd,$(foo),$(wrd)!)
all:
# Output is "who! are! you!"
@echo $(bar)
4. if 函數
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)
all:
@echo $(foo) # Output then!
@echo $(bar) # Output else!
5. call 函數
sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)
all:
# Outputs "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:"
@echo $(call sweet_new_fn, go, tigers)
6. shell 函數
all:
@echo $(shell ls -la) # Very ugly because the newlines are gone!
九、 其他特性
1. include
include指令告訴make讀取一個或多個其他makefile文件。它是makefile文件中的一行,看起來像這樣。
include filenames...
2. vpath 指令
使用vpath來指定某些先決條件的存在位置。格式是vpath <pattern> <directories, space/colon separated>
。<pattern>
可以有一個%
,它可以匹配任何零或更多的字符。你也可以用變量VPATH
在全局范圍內這樣做。
vpath %.h ./headers ./other-directory
some_binary: ./headers blah.h
touch some_binary
./headers:
mkdir ./headers
blah.h:
touch ./headers/blah.h
clean:
rm -rf ./headers
rm -f some_binary
3. 多行
可以用\
進行分割多行
some_file:
echo This line is too long, so \
it is broken up into multiple lines
4. .phony
在目標上添加.PHONY可以防止make將虛假的目標與文件名混淆。在這個例子中,如果文件clean被創建,make clean仍然會被運行。
some_file:
touch some_file
touch clean
.PHONY: clean
clean:
rm -f some_file
rm -f clean
5. .delete_on_error
如果一個命令返回非零的退出狀態,make工具將停止運行一個規則(並將傳播回先決條件)。
如果規則以這種方式失敗,DELETE_ON_ERROR將刪除該規則的目標。這將發生在所有的目標上,而不是像PHONY那樣只發生在它之前的那個目標。始終使用這個是個好主意,即使make由於歷史原因沒有這樣做。
.DELETE_ON_ERROR:
all: one two
one:
touch one
false
two:
touch two
false
輸出
touch one
false
make: *** [makefile:21:one] 錯誤 1
make: *** 正在刪除文件“one”