Makefile 是 Linux 中最常用的編譯工具了。Stuart Feldman 在 1967 的 Bell Labs 裡創造了它。雖然它可能比你我的年紀都還大,但是它現在還是依然地活躍。本文章將介紹 Makefile 的一些基本語法。
Table of Contents
Rules
Makefile 其實是由很多個 rules 所組成的。一個 rule 可以依賴於其他的 rules。因此,我們可以撰寫出複雜的 rules 來編譯我們的專案。一個 rule 的語法如下:
targets ... : prerequisites ... recipes ...
- targets:一個或多個檔案名稱,用空格分割。這些檔案常常是 executable 或 object files。
- prerequisites:一個或多個檔案名稱,用空格分割。這些檔案常常是用來產生 targets 的輸入檔案,如 source files。也就是說,targets 依賴於 prerequisites。
- recipes:一個或多個動作。這些動作常常會產生 target files。
當 prerequisites 有更動時,就會執行 recipes 來產生 targets。它是根據 targets 和 prerequisites 的檔案修改時間,如果 prerequisites 的時間在 targets 之後,則表示 prerequisites 有更動過。
例如,在以下的 Makefile 中,targets 是 hello 執行檔,prerequisites 是 hello.c 原始碼檔案,而 recipes 是用 gcc 將 hello.c 編譯成 hello。
hello : hello.c gcc -c hello.c -o hello
我們在命令列中執行 make hello
,它就會執行 Makefile 中 target 為 hello 的 rule。當它發現 hello.c 有更動,也就是當 hello.c 的檔案修改時間在 hello 之後的話,它就會執行 gcc 來重新產生 hello。
Variables
Makefile 允許我們宣告 variables。Variable 的值是一個字串,而你可以在 targets、prerequisites、和 recipes 中使用 variables。在使用 variable 時,我們必須要用 $()
或 ${}
將 variable 包起來,如 $(foo)
或 ${foo}
。
Makefile 提供幾種方式將值設給 variables。
Operators | Descirptions |
---|---|
= | Recursively expanded variable assignment |
:= | Simply expanded variable assignment |
?= | Conditional variable assignment |
+= | 附加更加的字串 |
Recursively Expanded Variable Assignment
假設 variable A 包含 variable B 的參考,當我們在取得 variable A 的值時,variable B 的參考會被展開,即使 variable B 是在 variable A 之後被宣告。這就是 recursively expanded variable assignment,而使用的符號是 =
。
foo = foo $(bar) bar = bar $(ugh) ugh = Huh? all: echo $(foo) # Output: # foo bar Huh?
Simply Expanded Variable Assignment
假設 variable A 包含 variable B 的參考,而且 variable B 是在 variable A 之後被宣告。當宣告 variable A 時,variable B 的參考會被展開。但是此時,variable B 尚未被宣告,所以它被展開後是空字串。之後,即使 variable B 被宣告了,variable A 的值也不會再變動了。所以,variable A 的值,是在宣告時就固定了,這就是和 recursively expanded variable assignment 不同的地方。Simply expanded variable assignment 使用的符號是 :=
。
foo := foo $(bar) bar := bar $(ugh) ugh := Huh? all: echo $(foo) # Output: # foo
Conditional Variable Assignment
Conditional variable assignment 是當 variable 還未被宣告時,才會被執行。它使用的符號是 ?=
。
foo = foo foo ?= $(bar) bar = bar $(ugh) ugh := Huh? all: echo $(foo) # Output: # foo
foo ?= foo $(bar) bar = bar $(ugh) ugh := Huh? all: echo $(foo) # Output: # foo bar Huh?
附加更多的字串
若 variable A 已被宣告,而我們想要再附加更多的字串時,可以用符號 +=
。當對 variable A 使用 +=
附加字串時,那它會是 recursively expanded variable assignment 還是 simply expanded variable assignment 呢?這不是 +=
決定的,而是要看當 variable A 被宣告時,是用 =
還是 :=
。
foo = foo foo += $(bar) bar = bar $(ugh) ugh := Huh? all: echo $(foo) # Output: # foo bar Huh?
foo := foo foo += $(bar) bar = bar $(ugh) ugh := Huh? all: echo $(foo) # Output: # foo
Pattern Rules
Pattern Rules
Pattern rules 和一般的 rules 很像,除了它包含 %
字元。%
稱為 wildcard 字元,它可以匹配任何非空的字串。例如,%.c
匹配任何以 .c
結尾的檔案名稱。而且,%
所匹配到的子字串稱為 stem。例如,我們用 %.c
匹配到 hello.c 時,則 hello 是一個 stem。
我們可以在 targets 和 prerequisites 中使用 %。當同時在 targets 和 prerequisites 中使用 % 時,他們會匹配到相同的 stem。如下面範例中,它會匹配到 hello.o 和 hello.c。
%.o : %.c recipe
Static Pattern Rules
我們已經學到一般的 rules 和 pattern rules。一般的 rules 很嚴格,因為它要求指定完整的檔案名稱。相對來說,pattern rules 非常地靈活,它可以用 wildcard 字元來匹配所有可能的檔案名稱。而,static pattern rules 則介於這兩者之間。
Static pattern rules 的語法如下。其中 targets 和一般的 rules 一樣,必須要指定完整的檔案名稱。然後,使用 target-pattern 和 prerequisite-patterns 來匹配任何在 targets 中的檔案名稱。
targets ... : target-pattern : prerequisite-patterns ... recipes ...
如下面的範例,它只會匹配到 foo.o、foo.c、bar.o、和 bar.c。它不會匹配到 hello.o 和 hello.c。
objects = foo.o bar.o $(objects) : %.o : %.c recipes
Automatic Variables
當我們使用 pattern rules 時,我們要如何在 recipes 中使用匹配到的檔案名稱呢?這時候,我們就要使用 Makefile 的 automatic variables。Automatic variables 是數個 Makefile 預先定義好的 variables。我們則可以透過他們來取得匹配到的 targets 或 prerequisites。這邊列出幾個常用的 automatic variables,其他的請參照 Automatic Variables。
Automatic Variables | Descriptions |
---|---|
$@ | 引起執行 rule 的 recipe 的 target。 |
$< | 第一個 prerequisite. |
$? | 所有比 target 還要新的 prerequisites,且用空白分隔。假如 target 不存在,則會是所有的 prerequisites。 |
$^ | 所有的 prerequisites,且用空白分隔。重複的會被移除。 |
$+ | 如同 $^,但重複的會被包含。 |
$* | Pattern rule 匹配到的 stem。 |
以下範例顯示如何使用 automatic variables 來撰寫 pattern rules 的 recipes。
EXEC = hello OBJS = hello.o lib.o all : $(EXEC) $(EXEC) : $(OBJS) $(CC) -o $@ $^ %.o : %.c $(CC) -c $< -o $@ $(CFLAGS)
Recursive Use of make
假設目前的資料夾下有一個 Makefile,而且在子資料夾下也有一個 Makefile,我們希望在目前的資料夾下執行 make
後,它會遞迴地在子資料下也執行 make
。有兩種方式可以遞迴地執行 make
。一種是切換到子資料夾下,並執行 $(MAKE)
。
subsystem: cd subdir && $(MAKE)
另一種是使用 $(MAKE) -C
。
subsystem: $(MAKE) -C subdir
如果我們想要將某個 variable 傳給 sub-make 的話,我們可以用 export
。
# Top-level Makefile foo = foo bar = $(foo) bar export bar all : cd sub && $(MAKE)
# Makefile under sub/ directory all : echo $(bar)
Functions
Makefile 提供不少 built-in 函式。我們只列出一些常用的函式,其他函式請參考 Functions for Transforming Text。
Functions | Descriptions |
---|---|
$(wildcard pattern …) | 搜尋所有匹配 pattern 的檔案。 |
$(subst from,to,text) | 用 to 取代 text 裡的 from 。 |
$(patsubst pattern,replacement,text) $(var:pattern=replacement) $(var:suffix=replacement) | 搜尋 text 裡匹配 pattern 的字,且用 replacement 來取代它們。 |
$(filter pattern …,text) | 回傳在 text 裡匹配 pattern 的字。 |
$(if condition,then,else) | 假如 condition 不是空的,則回傳 then ;反之,回傳 else 。 |
$(foreach var,list,text) | 轉換每一個在 list 的字成 text 。 |
$(shell command …) | 執行 command 。 |
wildcard
wildcard
函式搜尋目前目錄下所有符合 pattern
的檔案名稱,並用空格分割。
# wildcard syntax $(wildcard pattern ...) # Expand all file names matching *.c, sorted. SRCS = $(wildcard *.c) # Expand all file names matching *.c, sorted, # followed by all file names matching *.h, sorted. SRCS = $(wildcard *.c *.h)
subst
subst
函式將 text
裡的 from
字串取代成 to
。
# subst syntax $(subst from,to,text) # Replace EE with ee txt1 = feet on the street txt2 = $(subst ee,EE,feet on the street) # txt2 = fEEt on the strEEt
patsubst
patsubst
函式搜尋 text
裡符合 pattern
的 words,並將它們取代成 replacement
。
它有另外兩種簡單的寫法。第一種方式是對一個特定的 variable 搜尋符合 pattern
的 words,並將它們取代成 replacement
。這種方式讓我們可以省略 patsubst
名稱。第二種方式是針對 suffix
的替換。這不但省略了 patsubst
名稱,還省略了 %
字元。
# patsubst syntax $(patsubst pattern,replacement,text) $(var:pattern=replacement) $(var:suffix=replacement) # Replace .c by .o for all source file names SRCS = foo.c bar.c OBJS1 = $(patsubst %.c,%.o,$(SRCS)) # OBJS1 = foo.o bar.o # Use the first shorthand OBJS2 = $(SRCS:%.c=%.o) # OBJS2 = foo.o bar.o # Use the second shorthand OBJS3 = $(SRCS:.c=.o) # OBJS3 = foo.o bar.o
filter
filter
函式回傳 list
中符合 pattern
的 words。
# filter syntax $(filter pattern ...,text) # Filter out header files SRCS = foo.c foo.h bar.c bar.h foo : $(SRCS) $(CC) $(filter %.c,$(SRCS)) -o foo
if
如果 condition
是非空字串,則 if
回傳 then
。反之,若 condition
是空字串,則回傳 else
。
# if syntax $(if condition,then,else) con1 := not-empty echo $(if $(con1),then part,else part) # Output: then part con2 := echo $(if $(con2),then part,else part) # Output: else part
foreach
foreach
函式會轉換每一個在 list
裡的 word 成 text
。var
是每次要被轉換的 word。
# foreach syntax $(foreach var,list,text) # foo = foo1 foo2 bar = $(foreach w,$(foo),$(w)_bar) # bar = foo1_bar foo2_bar # Find all file names in the list of dirs. dirs := a b c d files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
shell
shell
函式允許我們執行指令,並且它會將回傳值裡的換行符號替換成空白。
# shell syntax $(shell command ...) contents := $(shell cat hello.c)
Conditionals
Makefile 還提供 conditional-directives 讓我們可以條件式地選擇是否執行一部分的 Makefile,其語法如下。
conditional-directive-1 text-if-1-is-true else conditional-directive-2 text-if-2-is-true else text-if-1-and-2-are-false endif
Makefile 提供的 conditional-directives 有四個。
# If text arg1 and arg2 are equal ifeq (arg1, arg2) # If text arg1 and arg2 are equal ifneq (arg1, arg2) # If variable is defined ifdef variable-name # If variable is not defined ifndef variable-name
ifeq ($(CC),gcc) libs = -lgnu else libs = endif ifdef libs echo $(libs) endif foo: $(objects) $(CC) -o foo $(objects) $(libs)
Special Built-in Target Names
Makefile 預先定義好一些 targets。更多的詳情,請參照 Special Built-in Target Name。
Targets | Descirptions |
---|---|
.PHONY | 防止 make 認為 phone target 是檔案名稱。 .PHONY : all clean |
結語
本文章只有介紹一部分的 Makefile 語法。它還有很多東西沒有被介紹到,如 Implicit Rules。雖然現在已經有不少更先進的工具可以使用,但是還是有很多的專案使用 Makefile。而且,對於小專案來說,Makefile 反而是很好的選擇,因為 make
在很多 Linux 平台上都有被安裝。