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:
# fooConditional Variable Assignment
Conditional variable assignment 是當 variable 還未被宣告時,才會被執行。它使用的符號是 ?= 。
foo = foo
foo ?= $(bar)
bar = bar $(ugh)
ugh := Huh?
all:
echo $(foo)
# Output:
# foofoo ?= 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:
# fooPattern 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
recipeStatic 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
recipesAutomatic 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 fooif
如果 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 平台上都有被安裝。








