Makefile

Photo by Lanju Fotografie on Unsplash
Photo by Lanju Fotografie on Unsplash
Makefile 是 Linux 中最常用的編譯工具了。Stuart Feldman 在 1967 的 Bell Labs 裡創造了它。雖然它可能比你我的年紀都還大,但是它現在還是依然地活躍。本文章將介紹 Makefile 的一些基本語法。

Makefile 是 Linux 中最常用的編譯工具了。Stuart Feldman 在 1967 的 Bell Labs 裡創造了它。雖然它可能比你我的年紀都還大,但是它現在還是依然地活躍。本文章將介紹 Makefile 的一些基本語法。

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。

OperatorsDescirptions
=Recursively expanded variable assignment
:=Simply expanded variable assignment
?=Conditional variable assignment
+=附加更加的字串
Variable Assignments

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 VariablesDescriptions
$@引起執行 rule 的 recipe 的 target。
$<第一個 prerequisite.
$?所有比 target 還要新的 prerequisites,且用空白分隔。假如 target 不存在,則會是所有的 prerequisites。
$^所有的 prerequisites,且用空白分隔。重複的會被移除。
$+如同 $^,但重複的會被包含。
$*Pattern rule 匹配到的 stem。
Automatic Variables

以下範例顯示如何使用 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

FunctionsDescriptions
$(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
Functions

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 成 textvar 是每次要被轉換的 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

TargetsDescirptions
.PHONY防止 make 認為 phone target 是檔案名稱。
.PHONY : all clean
Special Built-in Target Names

結語

本文章只有介紹一部分的 Makefile 語法。它還有很多東西沒有被介紹到,如 Implicit Rules。雖然現在已經有不少更先進的工具可以使用,但是還是有很多的專案使用 Makefile。而且,對於小專案來說,Makefile 反而是很好的選擇,因為 make 在很多 Linux 平台上都有被安裝。

參考

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

You May Also Like
Photo by Timothée Geenens on Unsplash
Read More

x86 Memory Map

在 x86 PC 起動後,它會處在 real mode。此時,我們可以存取 1 MB 以下的 memory。然而,BIOS 也會使用一些 memory。因此,我們必須要知道 BIOS 佔據哪些範圍,才能夠避開它們
Read More
Photo by NEOM on Unsplash
Read More

ELF

ELF file 是 Linux 使用的檔案格式。不管是要學習 linkers 或是反組譯,都需要了解 ELF file。本文章將介紹 ELF 檔案格式。
Read More