What is Makefile?

甚麼是 Makefile 呢?

舉例而言,像是在這份 Project 中,可以透過 make 指令編譯前端與開啟後端,在下達 make 指令時 make 就會去查找一個叫 Makefile(或者 makefile)的檔案,從其中定義的規則去執行相對應的命令。

Benefits of Makefile?

使用 Makefile 有許多的好處:

  • 管理 files 間的 dependency:
    假設 a.c depends on b.c,此時若 b.c 修改了則 a.c 也應該要重新編譯(或 link)。
  • 建立常用指令的捷徑:
    在同樣的一份 Project 中,會需要透過一些 commands 跟程式互動,若 command 很長的話每次都要重打就會很麻煩,此時就可以透過 makefile 來加速流程。

Grammar

說了這麼多,那具體而言要怎麼寫 Makefile 呢?Makefile 語法基本可以分成以下幾部分:

  • Rule
  • Variable
  • Assignment
  • Special character

Rule

如同上面說的,在下達 make 指令時 make 會到 Makefile 中查找規則,一般 Makefile rule 如下:

1
2
target: target.c
$(CC) target.c
  • target 就是目標檔案。
  • target 後可以看到一個冒號,在冒號後的就是 dependency,當 dependency 改變時,make 會偵測到並重新 compile dependency 再 compile target。
  • $(CC) target.c 就是要執行的命令。
  • 命令前必須為 tab,也就是 \t 字元。

所以當執行 make target 的時候,實際是執行 gcc target.c

$(CC)CC 這個變數展開,變數定義會在下文介紹。

Variable

Makefile 中有許多特殊的 variable,像是上面有用到的 CC,而這些 variable 可以大致分為以下這幾種:

  • CC:指定要使用的 c compiler,e.g. gcc
  • CXX:指定要使用的 c++ compiler,e.g. g++
  • LD:指定要使用的 linker,e.g. ld

Automatic variables

  • $@:target 檔名,如果 makefile rule 如下:
    1
    2
    target: target.c
    $(CC) target.c
    則 $@ 就代表 target
  • $^:所有的 dependency,如果 makefile rule 如下:
    1
    2
    target: target.c dependency.c
    $(CC) target.c
    則 $^ 會 expand 成 target.c, dependency.c
  • $<:第一個 dependency,如果 makefile rule 如下:
    1
    2
    target: target.c dependency.c
    $(CC) target.c
    則 $< 為 target.c
  • $*:使用在 pattern rule,將在 special character 的章節說明。

Common flag variables

  • CFLAG:c files 的 compile flags。
  • CXXFLAGS:c++ files 的 compile flags。
  • LDFLAGS:linker 的 flags。

Target specific variables

可以在 Makefile 中針對特定的 target 定義 variable,如下:

1
2
3
4
hw2a: CFLAGS += -pthread
hw2b: CC = mpicc
hw2b: CXX = mpicxx
hw2b: CFLAGS += -fopenmp

若 Makefile 如下:

1
2
hw2a: hw2a.cc
$(CC) $(CFLAGS) hw2a.cc

則在執行 hw2a 的 rule 時,CFLAGS 就會多上 -pthread 的 flag。因此,可以透過此方法設定對於某些 target file 加上特別的 compile flag。

Assignment

在 Makefile 中可以自定義 variables,而 variable 有很多種 assign 的方法:

  • Simple assignment (=):
    1
    CC = gcc
    要注意的是,= 這種 assignment 屬於 defer assignment,也就是會等到該變數被使用時才會展開右手邊的內容取得最終的值。
  • Immediate assignment (:=):
    1
    DATE := $(shell date)

    $(shell ...) 可以執行 shell command,上面就是透過 shell 取得時間後存在 DATE 變數中
    = 不同的是,:= 這種 asignment 會立即展開右邊取得最終的值,所以在底下的 Makefile 中:

    1
    2
    DATE := $(shell date)
    DATE_DELAY = $(shell date)
    DATEDATE_DELAY 時間將會不同,DATE_DELAY 的時間為該變數被用到時,而 DATE 則為 Makefile parse 到這一行的當下。
  • Appending assignment (+=):
    例如 CFLAGS 本來為 -O3
    1
    CFLAGS += -pthread
    CFLAGS 會變成 -O3 -pthread。通常可以配合上述的 Target specifi variable 使用,對於特定的 target 使用 += 給定特別的 flags。
  • Conditional assignment (?=):
    若變數未定義則指定新的值,否則採用原有的值。與 = 同為 defer assignment。

Special character

在 Makefile 中也有很多的特殊字元,每個都有特別的意義,如下:

  • @:make 一般會 print 出要執行的指令,在 command 前加上 @ 則不會 print 出
    1
    2
    3
    .PHONY: clean
    clean:
    @rm -f $(TARGETS) $(TARGETS:=.o)
  • %:wildcard character,也就是可以 match 各種的 pattern,使用方式如下:
    1
    2
    %.o: %.c
    $(CC) $(CFLAGS) -c $< -o $*.o
    假設下 make target.o,此時會執行 gcc -c target.c -o target.o,可以看到 $* 會自動替換成 % 所 match 到的字元。
  • -:makefile 只要遇到任何錯誤都會中斷執行,加 - 在 command 前面,使得就算 command 出錯,也不影響後續的 commands,如下:
    1
    2
    3
    .PHONY: clean
    clean:
    -rm *.o

其他

PHONY

這是一個很重要的 kerword。在前文中很常看到 PHONY 的身影,這個 keyword 是用來宣告這些 rule 是一個指令,而不是一個檔案,如下:

1
2
3
.PHONY: clean
clean:
@rm -f $(TARGETS) $(TARGETS:=.o)

如果沒有加 .PHONY: clean,此時資料夾中又有 clean 這個檔案,則 makefile 會認為 clean 這個 target up to date,因此 clean 下的指令將永遠不會被執行。而加上 .PHONY: clean 可以解決這樣的問題。

Makefile print

Makefile 內部其實有很多預設的變數,可以透過以下指令查看:

1
2
make -p > make_print
cat make_print

Compiler

有時候會見到以下這樣的 Makefile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CC = gcc
CXX = g++
CFLAGS = -lm -O3
hw2a: CFLAGS += -pthread
hw2b: CC = mpicc
hw2b: CXX = mpicxx
hw2b: CFLAGS += -fopenmp
CXXFLAGS = $(CFLAGS)
TARGETS = hw2a hw2b

.PHONY: all
all: $(TARGETS)

.PHONY: clean
clean:
rm -f $(TARGETS) $(TARGETS:=.o)

此時或許會懷疑又沒有說明 make hw2a 的 rule,make 要怎麼知道使用哪個 compiler 呢?

可以透過前面說的 make -p 查看,其實 make 有很多預設的使用情境,在沒有說明 compiler 的情況下,對於 .c file make 會優先使用 CC 這個 variable 內的 compiler,而對於 .cc or .cpp 則會使用 CXX compiler。

Set variable in CLI

variable 也可以直接在下 make command 時設定,如下:

1
make CC=clang CXX=clang++

References