makefile教學
Makefiles用途
用來決定大型程式需要被重新編譯的部分。
第一個範例
首先安裝make,並且將下面程式放到名稱為Makefile
的檔案裡面。注意Makefile必須要用TAB來縮排而不是用空白鍵。
1 | hello: |
接下來在Makefile
所在的資料夾位置下make指令
1 | $ make |
Makefile語法
Makefile是由許多的規則組合而成,每一條則看起來如下
1 | targets: prerequisites |
- targets是檔案名稱,用空白建作為分隔。通常每個rule只有一個target
- command是產生targets的一系列的步驟。command以Tab作為開頭,而不是空白鍵。
- prerequisites也是檔案名稱,以空白鍵作為分隔,這些是指令開始製作target前必須存在的檔案,因此這些檔案也可以稱為dependencies
Make基礎元素
1 | hello: |
以這個範例來說,
- 有一個名為
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
。接下來執行make,因為我們每有在第一個參數指定目標target,所以第一個target會被執行。第一次執行的時1
2blah:
cc blah.c -o blahblah
檔會被生成,如果再執行一次就會出現make: 'blah' is up to date
的訊息,因為blah
檔已經存在了。但是這有一個問題,如果我們更動blah.c
檔案,make並不會重新編譯!!
要解決這個問題就必須要增加prerequisite。我們增加了1
2blah: blah.c
cc blah.c -o blahblah
的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 | some_file: |
Makefile使用相對路徑
1 | rootdir = $(realpath .) |
https://stackoverflow.com/a/3342259
Variables
變數Variables只能夠是字串,並且用:=
賦值。對make來說單引號跟雙引號並意義,make會把他當成字元來處理。因此賦值的時候不需要加引號。
下面範例使用Variables
1 | files := file1 file2 |
要引用變數可以用${}
或是$()
1 | x := dude |
Targets
all
target
可以用來依次產生所有需要的target,通常會放在第一個target的位置,如此一來只要下make指令就可以生成所有target。
1 | all: one two three |
多個target
當一個rule有多個target的時候,底下的command就會針對每一個target都跑一次
。$@
就是一個有target名稱的automatic variable
,下面範例就可以用$@
看看現在command正在執行的是哪一個target
1 | all: f1.o f2.o |
Automatic Variables and Wildcards
* 萬用字元
在cmake中*
和%
在cmake中都是萬用字元,但是它們代表的意義不一樣。*
最好要包裝在wildcard
萬用字符函式中。否則可能會常陷入下面常見的陷阱。
- 陷阱:
*
不能直接在變量定義中使用 - 陷阱: 當
*
未匹配任何文件時,它會保持不變(除非在萬用字符函數(wildcard
)中運行)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16thing_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)
% 萬用字元
%
非常有用,但由於它可以在各種情況下使用,因此有些令人困惑。
- 在“匹配”模式下使用時,它會在字符串中匹配一個或多個字符。此匹配稱為stem。
- 在“替換”模式下使用時,它會取出匹配的stem,並將其替換為一個字符串。
%
在規則定義和某些特定函數中最常用。
Automatic Variables
這裡有完整的Automatic Variables表,下面只介紹常用的
1 | 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 | CC = gcc # 使用gcc來編譯C語言 |
Static Pattern Rules
他的語法如下
1 | targets...: target-pattern: prereq-patterns ... |
這個語法的意思是,如果有一個target符合target-pattern
(利用% wildcard),且它的所有prerequisite都符合prereq-patterns
,那麼就會執行commands
。
例如我們可以改寫下面makefile
1 | objects = foo.o bar.o all.o |
改寫後如下,可以看到,我們把foo.o
bar.o
all.o
的規則都合併成一個規則$(objects): %.o: %.c
。首先foo.o
符合%.o
,且它的所有prerequisite都符合%.c
,因此會執行%.o: %.c
的規則。
1 | objects = foo.o bar.o all.o |
Static Pattern Rules and Filter
此外,我們可以使用filter函式來過濾掉不需要的檔案,後面會再講到函式這裡只是先展示如何跟函式搭配使用,在下面的範例我們使用了 .raw 和 .result 這兩個擴展名。
1 | obj_files = foo.result bar.o lose.o |
執行的結果如下,首先執行第一條規則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 | touch foo.raw |
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 | all: blah |
他的輸出是
1 | hello |
Commands and execution
指令回顯/禁用
在預設情況下,make會回顯每一個command,如果你不想要回顯,可以在command前面加上@
,如下
1 | all: |
命令執行
每一行命令都會在一個新的shell中執行,因此如果你想要在同一個shell中執行,可以使用分號;
來連接命令,如下
1 | all: |
預設shell
預設情況下,make會使用/bin/sh
來執行命令,如果你想要使用其他的shell,可以使用.SHELL
來指定,如下
1 | SHELL=/bin/bash |
$$符號
在Makefile中,$$
代表一個$
符號,如此一來,我們就可以在Makefile中使用bash
或是sh
的shell variable。在下面這個例子中特別注一一下 Makefile variables 和 Shell variables
1 | make_var = I am a make variable |
用-k
、-i
、-
進行錯誤處理
執行make的時候使用-k
參數可以讓make繼續執行,即使其中一個target失敗了。執行make的時候使用-i
參數可以讓make忽略所有的錯誤。
在command前面加上-
可以讓make忽略該command的錯誤,如下
1 | one: |
打斷或是結束make
ctrl + c
可以打斷或是結束make,他將會刪掉剛生成的target
遞迴使用 make
為了遞迴調用 Makefile,使用特殊的 $(MAKE) 代替 make,因為它將為您傳遞 make 標誌,並且不會受到這些標誌的影響。
當使用 $(MAKE) 來遞迴調用 Makefile 時,它將傳遞先前用於調用 make 的所有標誌和選項,以及在 Makefile 中定義的任何其他變量。這有助於確保在整個項目中使用相同的編譯選項和變量。同時,$(MAKE) 不會受到當前 make 的影響,這可以避免不必要的錯誤和不一致性。
1 | new_contents = "hello:\n\ttouch inside_file" |
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
6all:
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
5shell_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 | new_contents = "hello:\n\techo \$$(cooly)" |
.EXPORT_ALL_VARIABLES
.EXPORT_ALL_VARIABLES
可以直接把所有的make變數都轉換成環境變數1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16.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
make的命令列選項
這裡有make的命令列選項,可以注意--dry-run
, --touch
, --old-file
這幾個。
另外make可以一次接受多個target,例如make clean run test
就會先執行clean,接著run和test。
變數part2
兩種賦值方法
延遲賦值(lazy evaluation)
=
:1
2
3
4
5
6
7VAR = foo
VAR2 = $(VAR)
VAR = bar
all:
# 在此處 VAR2 的值將是 "bar",因為VAR2直到被真正使用展開
echo $(VAR2)?=
這個符號可以為沒被設定過的店數設定值1
2
3
4
5
6
7one = hello
one ?= will not be set #one被設定過了,所以沒作用
two ?= will be set #two還沒被設定做,將設定值
all:
echo $(one)
echo $(two)輸出如下
1
2
3
4echo hello
hello
echo will be set
will be set立即賦值(immediate assignment)
:=
:1
2
3
4
5
6
7VAR := foo
VAR2 := $(VAR)
VAR := bar
all:
# 在此處 VAR2 的值將是 "foo",因為VAR2在:=的時候就展開了
echo $(VAR2)因此
:=
可以append variable,如果是=
就會齣戲無窮迴圈錯誤1
2
3
4
5
6
7one = 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
6one = hello
# 注意這裡用的是 = ,會造成無窮迴圈錯誤
one = ${one} there
all:
echo $(one)
空白
一行字起頭的空白會被make忽略掉,但是尾巴空白的不會,要在起頭加空白可以用$(nullstring)
,更精確地說其實未定義的變數都是empty string
1 | with_spaces = hello # with_spaces has many spaces after "hello" |
append
+=
可以用來append variable
1 | foo := start |
覆寫make命列列參數
override可以用來覆寫make命令列參數,例如下面這個例子,分別用make option_one=hi
和make option_two=hi
去執行,可以發現只有option_one
會被覆寫
1 | # Overrides command line arguments |
針對target設定變數
變數可以只設定給指定的target,例如下面例子,one只定義給all
target
1 | all: one = cool |
Pattern-specific variables
變數也可以指定義給特定的target patterns,例如下面例子,只有符合%.c
pattern的target會被定義one
1 | %.c: one = cool |
Makefile判斷式
if/else
1 | foo = ok |
檢查變數是否為空
1 | nullstring = |
檢查變數是否被定義
1 | bar = |
$(MAKEFLAGS)
下面範例展示如何使用 findstring
和 MAKEFLAGS
測試 make flag。分別用make
指令和make -i
指令執行下面makefile
1 | all: |
Functions
First Functions
函式主要用來處理文字。呼叫函式的方法有$(fn, arguments)
或${fn, arguments}
,而make也內建許多函式。例如subst
替換掉文字。
1 | bar := ${subst not, totally, "I am not superman"} |
而如果你相替換掉空白或是逗號,可以利用變數。
1 | comma := , |
特別注意到不要在逗號和下一個參數之間留空白,因為它會被視為文字。
1 | comma := , |
字串替換
函式$(patsubst pattern,replacement,text)
的功能如下。
1 | foo := a.o b.o l.a c.o |
The foreach function
foreach函式的用法為$(foreach var,list,text)
,foreach會把以空白間區隔文字的list一個一個賦值給var,而text會累加前面的結果,範例如下
1 | foo := who are you |
if function
用法如下
1 | foo := $(if this-is-not-empty,then!,else!) |
The call function
make可以用call來呼叫自定義函式
1 | sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3) |
The shell function
make也可以呼叫shell函式,但是會把輸出的換行符號改成空白鍵
其他功能
Include Makefiles
使用Include
可以讓makefile裡面呼叫其他makefile
vpath 指令
1 | vpath %.h ../headers ../other-directory |
換行
指令太長可以利用\
換行
1 | some_file: |
.phony
在目標中添加”.PHONY”將防止Make將虛擬目標與文件名混淆。在這個例子中,如果創建了名為”clean”的文件,”make clean”仍然會運行。從技術上講,我應該在每個帶有”all”或”clean”的例子中都使用它,但為了保持例子的清晰,我沒有這樣做。此外,”phony”目標通常具有很少用作文件名的名稱,在實踐中許多人都會跳過這一步。
1 | some_file: |
.delete_on_error
如果命令返回非零的退出狀態,make工具將停止運行規則(並將向前傳播到前置要求)。
DELETE_ON_ERROR將在規則以這種方式失敗時刪除該規則的目標。這將對所有目標發生,不僅僅是像PHONY這樣的目標。儘管由於歷史原因,make工具沒有使用這個選項,但始終使用它是一個好主意。
1 | .DELETE_ON_ERROR: |