Makefiles用途
用來決定大型程式需要被重新編譯的部分。
第一個範例
首先安裝make,並且將下面程式放到名稱為Makefile
的檔案裡面。注意Makefile必須要用TAB來縮排而不是用空白鍵。
hello:
echo "Hello, World"
接下來在Makefile
所在的資料夾位置下make指令
1
2
3
$ make
echo "Hello, World"
Hello, World
Makefile語法
Makefile是由許多的規則組合而成,每一條則看起來如下
1
2
3
4
targets: prerequisites
command
command
command
- targets是檔案名稱,用空白建作為分隔。通常每個rule只有一個target
- command是產生targets的一系列的步驟。command以Tab作為開頭,而不是空白鍵。
- prerequisites也是檔案名稱,以空白鍵作為分隔,這些是指令開始製作target前必須存在的檔案,因此這些檔案也可以稱為dependencies
Make基礎元素
1
2
3
hello:
echo "Hello, World"
echo "This line will always print, because the file hello does not exist."
以這個範例來說,
- 有一個名為
hello
的target - 這個target有兩個command
- 這個target沒有prerequisites 接下來我們執行
make hello
,由於hello
檔不存在,所以下面的指令會被執行。如果hello
檔存在,make就不會做任何事。
特別注意在這裡hello
同時代表target
以及檔案
,因為通常來說下面的command執行的目的就是要生成target。
下面舉一個編譯c語言的例子,首先我們製作一個blah.c
檔。1 2
// blah.c int main() { return 0; }
接下來製作另一個
Makefile
。1 2
blah: cc blah.c -o blah
接下來執行make,因為我們每有在第一個參數指定目標target,所以第一個target會被執行。第一次執行的時
blah
檔會被生成,如果再執行一次就會出現make: 'blah' is up to date
的訊息,因為blah
檔已經存在了。但是這有一個問題,如果我們更動blah.c
檔案,make並不會重新編譯!!
要解決這個問題就必須要增加prerequisite。1 2
blah: blah.c cc blah.c -o blah
我們增加了
blah
的prerequisite,這時候make就除了檢查blah有沒有存在以外,還會另外去檢查blah.c
是不是比blah
還新。這裡我們可以看到make是利用系統的時間戳來判定blah.c有沒有被修改過,因此如果修改blah.c之後又把blah.c時間戳改回修改前的時間,那make就會以為blah.c沒有被修改過。
Make clean
clean常用來清除make產生的檔案,但是他並不是make的關鍵字。可以用make clean
清除make產生的檔案。我們可以在makefile中編寫clean要清除的檔案。注意clean這個target除非你用make clean
指令,不然他不會被執行。
1
2
3
4
5
some_file:
touch some_file
clean:
rm -f some_file
Makefile使用相對路徑
1
rootdir = $(realpath .)
https://stackoverflow.com/a/3342259
Variables
變數Variables只能夠是字串,並且用:=
賦值。對make來說單引號跟雙引號並意義,make會把他當成字元來處理。因此賦值的時候不需要加引號。 下面範例使用Variables
1
2
3
4
5
6
7
8
9
10
11
12
files := file1 file2
some_file: $(files)
echo "Look at this variable: " $(files)
touch some_file
file1:
touch file1
file2:
touch file2
clean:
rm -f file1 file2 some_file
要引用變數可以用${}
或是$()
1
2
3
4
5
6
7
8
x := dude
all:
echo $(x)
echo ${x}
# Bad practice, but works
echo $x
Targets
all
target
可以用來依次產生所有需要的target,通常會放在第一個target的位置,如此一來只要下make指令就可以生成所有target。
1
2
3
4
5
6
7
8
9
10
11
all: one two three
one:
touch one
two:
touch two
three:
touch three
clean:
rm -f one two three
多個target
當一個rule有多個target的時候,底下的command就會針對每一個target都跑一次
。
$@
就是一個有target名稱的automatic variable
,下面範例就可以用$@
看看現在command正在執行的是哪一個target
1
2
3
4
5
6
7
8
9
all: f1.o f2.o
f1.o f2.o:
echo $@
# Equivalent to:
# f1.o:
# echo f1.o
# f2.o:
# echo f2.o
Automatic Variables and Wildcards
* 萬用字元
在cmake中*
和%
在cmake中都是萬用字元,但是它們代表的意義不一樣。*
最好要包裝在wildcard
萬用字符函式中。否則可能會常陷入下面常見的陷阱。
- 陷阱:
*
不能直接在變量定義中使用 - 陷阱: 當
*
未匹配任何文件時,它會保持不變(除非在萬用字符函數(wildcard
)中運行)。 ```makefile 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
## % 萬用字元
`%`非常有用,但由於它可以在各種情況下使用,因此有些令人困惑。
1. 在“匹配”模式下使用時,它會在字符串中匹配一個或多個字符。此匹配稱為stem。
2. 在“替換”模式下使用時,它會取出匹配的stem,並將其替換為一個字符串。
3. `%`在規則定義和某些特定函數中最常用。
## Automatic Variables
這裡有完整的[Automatic Variables表](https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html),下面只介紹常用的
```makefile
hey: one two
# Outputs "hey", since this is the target name
# 印出目前的target
echo $@
# Outputs all prerequisites newer than the target
# 印出所有比target新的prerequisite
echo $?
# Outputs all prerequisites
# 印出所有的prerequisite
echo $^
touch hey
one:
touch one
two:
touch two
clean:
rm -f hey one two
Rules
Make的隱藏規則
CC
CXX
CFLAGS
CXXFLAGS
LDFLAGS
LDLIBS
這些變數是make的隱藏規則。
CC
: 編譯C語言的編譯器; 預設是 ccCXX
: 編譯C++語言的編譯器; 預設是 g++CFLAGS
: 給C語言編譯器的額外參數CXXFLAGS
: 給C++語言編譯器的額外參數CPPFLAGS
: 給C/C++編譯器的額外參數LDFLAGS
: 給連結器的額外參數
下面範例使用隱藏規則
1
2
3
4
5
6
7
8
9
10
11
12
CC = gcc # 使用gcc來編譯C語言
CFLAGS = -g # 給gcc的額外參數,開啟debug模式
# 隱藏規則 #1: blah會由C連接器產生(即使我們的command沒有呼叫C連接器)
# 隱藏規則 #2: blah.o 會由c編譯器產生,因為blah.c存在(即使我們的command沒有呼叫c編譯器)
blah: blah.o
blah.c:
echo "int main() { return 0; }" > blah.c
clean:
rm -f blah*
Static Pattern Rules
他的語法如下
1
2
targets...: target-pattern: prereq-patterns ...
commands
這個語法的意思是,如果有一個target符合target-pattern
(利用% wildcard),且它的所有prerequisite都符合prereq-patterns
,那麼就會執行commands
。 例如我們可以改寫下面makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
改寫後如下,可以看到,我們把foo.o
bar.o
all.o
的規則都合併成一個規則$(objects): %.o: %.c
。首先foo.o
符合%.o
,且它的所有prerequisite都符合%.c
,因此會執行%.o: %.c
的規則。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
Static Pattern Rules and Filter
此外,我們可以使用filter函式來過濾掉不需要的檔案,後面會再講到函式這裡只是先展示如何跟函式搭配使用,在下面的範例我們使用了 .raw 和 .result 這兩個擴展名。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
obj_files = foo.result bar.o lose.o
src_files = foo.raw bar.c lose.c
all: $(obj_files)
# Note: PHONY is important here. Without it, implicit rules will try to build the executable "all", since the prereqs are ".o" files.
.PHONY: all
# Ex 1: .o files depend on .c files. Though we don't actually make the .o file.
$(filter %.o,$(obj_files)): %.o: %.c
@echo "target: $@ prereq: $<"
# Ex 2: .result files depend on .raw files. Though we don't actually make the .result file.
$(filter %.result,$(obj_files)): %.result: %.raw
@echo "target: $@ prereq: $<"
%.c %.raw:
@echo "touch $@ prereq: $<"
touch $@
clean:
rm -f $(src_files)
執行的結果如下,首先執行第一條規則all: $(obj_files)
產生第一個target foo.result
,並由$(filter %.result,$(obj_files)): %.result: %.raw
產生foo.result
。foo.result
的prerequisitfoo.raw
由%.c %.raw:
產生。 可以看到,我們把foo.result
bar.o
lose.o
的規則都合併成一個規則$(filter %.result,$(obj_files)): %.result: %.raw
。首先foo.result
符合%.result
,且它的所有prerequisite都符合%.raw
,因此會執行%.result: %.raw
的規則。
1
2
3
4
5
6
touch foo.raw
target: foo.result prereq: foo.raw
touch bar.c
target: bar.o prereq: bar.c
touch lose.c
target: lose.o prereq: lose.c
Pattern Rules
我們可以將Pattern Rules視為兩種用法。
- 自定義的implicit rules
1 2 3
# 自訂一個 pattern rule 將每一個.c檔編譯成.o檔 %.o : %.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
- 簡化版的static pattern rules
1 2 3 4
# 定義一個沒有 prerequisites 的 pattern rule # 他將會在需要的時候產出一個空白的.c檔 %.c: touch $@
在這裡
%
代表任意非空白的字串。
Double-Colon Rules
Double-Colon Rules 很少被用到,他允許對同一個target定義多個規則,且這些規則可以是不同的commands。如以下範例
1
2
3
4
5
6
7
all: blah
blah::
@echo "hello"
blah::
@echo "hello again"
他的輸出是
1
2
hello
hello again
Commands and execution
指令回顯/禁用
在預設情況下,make會回顯每一個command,如果你不想要回顯,可以在command前面加上@
,如下
1
2
3
all:
@echo "This make line will not be printed"
echo "But this will"
命令執行
每一行命令都會在一個新的shell中執行,因此如果你想要在同一個shell中執行,可以使用分號;
來連接命令,如下
1
2
3
4
5
6
7
8
9
10
11
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`
預設shell
預設情況下,make會使用/bin/sh
來執行命令,如果你想要使用其他的shell,可以使用.SHELL
來指定,如下
1
2
3
4
SHELL=/bin/bash
cool:
echo "Hello from bash"
$$符號
在Makefile中,$$
代表一個$
符號,如此一來,我們就可以在Makefile中使用bash
或是sh
的shell variable。在下面這個例子中特別注一一下 Makefile variables 和 Shell variables
1
2
3
4
5
6
7
make_var = I am a make variable
all:
# Same as running "sh_var='I am a shell variable'; echo $sh_var" in the shell
sh_var='I am a shell variable'; echo $$sh_var
# Same as running "echo I am a make variable" in the shell
echo $(make_var)
用-k
、-i
、-
進行錯誤處理
執行make的時候使用-k
參數可以讓make繼續執行,即使其中一個target失敗了。執行make的時候使用-i
參數可以讓make忽略所有的錯誤。
在command前面加上-
可以讓make忽略該command的錯誤,如下
1
2
3
4
one:
# This error will be printed but ignored, and make will continue to run
-false
touch one
打斷或是結束make
ctrl + c
可以打斷或是結束make,他將會刪掉剛生成的target
遞迴使用 make
為了遞迴調用 Makefile,使用特殊的 $(MAKE) 代替 make,因為它將為您傳遞 make 標誌,並且不會受到這些標誌的影響。
當使用 $(MAKE) 來遞迴調用 Makefile 時,它將傳遞先前用於調用 make 的所有標誌和選項,以及在 Makefile 中定義的任何其他變量。這有助於確保在整個項目中使用相同的編譯選項和變量。同時,$(MAKE) 不會受到當前 make 的影響,這可以避免不必要的錯誤和不一致性。
1
2
3
4
5
6
7
8
new_contents = "hello:\n\ttouch inside_file"
all:
mkdir -p subdir
printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
cd subdir && $(MAKE)
clean:
rm -rf subdir
Export, environments, and recursive make
當make執行的時候,他會先把所有環境變數轉換成make的變數,例如下面範例假如我們先在shell設定環境變數shell_env_var
。
- 設定環境並且執行make
1
export shell_env_var='I am an environment variable'; make
- 執行下面的makefile
1 2 3 4 5 6
all: # Print out the Shell variable echo $$shell_env_var # Print out the Make variable echo $(shell_env_var)
- make的
export
指令可以把make的變數直接轉換成環境變數1 2 3 4 5
shell_env_var=Shell env var, created inside of Make export shell_env_var all: echo $(shell_env_var) echo $$shell_env_var
如此一來當我們在make command呼叫make的時候,就可以利用export
指令將變數傳遞給子make程式。下面範例中cooly
變數會被傳遞到子資料夾內所執行的make的makefile裡面。這裡可以注意到,cooly
變數在all
target之前被定義,但是他還是可以被all
target使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new_contents = "hello:\n\techo \$$(cooly)"
all:
mkdir -p subdir
printf $(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_ALL_VARIABLES
.EXPORT_ALL_VARIABLES
可以直接把所有的make變數都轉換成環境變數 ```shell .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 printf $(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
## make的命令列選項
[這裡有make的命令列選項](https://www.gnu.org/software/make/manual/make.html#Options-Summary),可以注意`--dry-run`, `--touch`, `--old-file`這幾個。
另外make可以一次接受多個target,例如`make clean run test`就會先執行clean,接著run和test。
# 變數part2
## 兩種賦值方法
* 延遲賦值(lazy evaluation)`=`:
```bash
VAR = foo
VAR2 = $(VAR)
VAR = bar
all:
# 在此處 VAR2 的值將是 "bar",因為VAR2直到被真正使用展開
echo $(VAR2)
?=
這個符號可以為沒被設定過的店數設定值
1
2
3
4
5
6
7
one = hello
one ?= will not be set #one被設定過了,所以沒作用
two ?= will be set #two還沒被設定做,將設定值
all:
echo $(one)
echo $(two)
輸出如下
1
2
3
4
echo hello
hello
echo will be set
will be set
- 立即賦值(immediate assignment)
:=
: ```bash VAR := foo VAR2 := $(VAR) VAR := bar
all: # 在此處 VAR2 的值將是 “foo”,因為VAR2在:=的時候就展開了 echo $(VAR2)
1
2
3
4
5
6
7
8
9
因此`:=`可以append variable,如果是`=`就會齣戲無窮迴圈錯誤
```makefile
one = hello
# 這段程式可以運行
# one gets defined as a simply expanded variable (:=) and thus can handle appending
one := ${one} there
all:
echo $(one)
下面這段程式會出現無窮迴圈錯誤。
1
2
3
4
5
6
one = hello
# 注意這裡用的是 = ,會造成無窮迴圈錯誤
one = ${one} there
all:
echo $(one)
空白
一行字起頭的空白會被make忽略掉,但是尾巴空白的不會,要在起頭加空白可以用$(nullstring)
,更精確地說其實未定義的變數都是empty string
1
2
3
4
5
6
7
8
9
with_spaces = hello # with_spaces has many spaces after "hello"
after = $(with_spaces)there
start_space = $(nullstring) hello
all:
echo "$(after)"
echo "$(start_space)"
echo $(nowhere) #這也會輸出empty string
append
+=
可以用來append variable
1
2
3
4
5
foo := start
foo += more
all:
echo $(foo)
覆寫make命列列參數
override可以用來覆寫make命令列參數,例如下面這個例子,分別用make option_one=hi
和make option_two=hi
去執行,可以發現只有option_one
會被覆寫
1
2
3
4
5
6
7
# Overrides command line arguments
override option_one = did_override
# Does not override command line arguments
option_two = not_override
all:
echo $(option_one)
echo $(option_two)
針對target設定變數
變數可以只設定給指定的target,例如下面例子,one只定義給all
target
1
2
3
4
5
6
7
all: one = cool
all:
echo one is defined: $(one)
other:
echo one is nothing: $(one)
Pattern-specific variables
變數也可以指定義給特定的target patterns,例如下面例子,只有符合%.c
pattern的target會被定義one
1
2
3
4
5
6
7
%.c: one = cool
blah.c:
echo one is defined: $(one)
other:
echo one is nothing: $(one)
Makefile判斷式
if/else
1
2
3
4
5
6
7
8
foo = ok
all:
ifeq ($(foo), ok)
echo "foo equals ok"
else
echo "nope"
endif
檢查變數是否為空
1
2
3
4
5
6
7
8
9
10
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
檢查變數是否被定義
1
2
3
4
5
6
7
8
9
10
bar =
foo = $(bar)
all:
ifdef foo
echo "foo is defined"
endif
ifndef bar
echo "but bar is not"
endif
$(MAKEFLAGS)
下面範例展示如何使用 findstring
和 MAKEFLAGS
測試 make flag。分別用make
指令和make -i
指令執行下面makefile
1
2
3
4
5
all:
# Search for the "-i" flag. MAKEFLAGS is just a list of single characters, one per flag. So look for "i" in this case.
ifneq (,$(findstring i, $(MAKEFLAGS)))
echo "i was passed to MAKEFLAGS"
endif
Functions
First Functions
函式主要用來處理文字。呼叫函式的方法有$(fn, arguments)
或${fn, arguments}
,而make也內建許多函式。例如subst
替換掉文字。
1
2
3
bar := ${subst not, totally, "I am not superman"}
all:
@echo $(bar)
而如果你相替換掉空白或是逗號,可以利用變數。
1
2
3
4
5
6
7
8
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))
all:
@echo $(bar)
特別注意到不要在逗號和下一個參數之間留空白,因為它會被視為文字。
1
2
3
4
5
6
7
8
9
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space), $(comma) , $(foo))
all:
# Output is ", a , b , c". Notice the spaces introduced
@echo $(bar)
字串替換
函式$(patsubst pattern,replacement,text)
的功能如下。
1
2
3
4
5
6
7
8
9
10
11
foo := a.o b.o l.a c.o
one := $(patsubst %.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)
The foreach function
foreach函式的用法為$(foreach var,list,text)
,foreach會把以空白間區隔文字的list一個一個賦值給var,而text會累加前面的結果,範例如下
1
2
3
4
5
6
7
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)
if function
用法如下
1
2
3
4
5
6
7
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)
all:
@echo $(foo)
@echo $(bar)
The call function
make可以用call來呼叫自定義函式
1
2
3
4
5
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)
The shell function
make也可以呼叫shell函式,但是會把輸出的換行符號改成空白鍵
其他功能
Include Makefiles
使用Include
可以讓makefile裡面呼叫其他makefile
vpath 指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vpath %.h ../headers ../other-directory
# Note: vpath allows blah.h to be found even though blah.h is never in the current directory
some_binary: ../headers blah.h
touch some_binary
../headers:
mkdir ../headers
# We call the target blah.h instead of ../headers/blah.h, because that's the prereq that some_binary is looking for
# Typically, blah.h would already exist and you wouldn't need this.
blah.h:
touch ../headers/blah.h
clean:
rm -rf ../headers
rm -f some_binary
換行
指令太長可以利用\
換行
1
2
3
some_file:
echo This line is too long, so \
it is broken up into multiple lines
.phony
在目標中添加”.PHONY”將防止Make將虛擬目標與文件名混淆。在這個例子中,如果創建了名為”clean”的文件,”make clean”仍然會運行。從技術上講,我應該在每個帶有”all”或”clean”的例子中都使用它,但為了保持例子的清晰,我沒有這樣做。此外,”phony”目標通常具有很少用作文件名的名稱,在實踐中許多人都會跳過這一步。
1
2
3
4
5
6
7
8
some_file:
touch some_file
touch clean
.PHONY: clean
clean:
rm -f some_file
rm -f clean
.delete_on_error
如果命令返回非零的退出狀態,make工具將停止運行規則(並將向前傳播到前置要求)。 DELETE_ON_ERROR將在規則以這種方式失敗時刪除該規則的目標。這將對所有目標發生,不僅僅是像PHONY這樣的目標。儘管由於歷史原因,make工具沒有使用這個選項,但始終使用它是一個好主意。
1
2
3
4
5
6
7
8
9
10
.DELETE_ON_ERROR:
all: one two
one:
touch one
false
two:
touch two
false