你就想像你在寫 BashPython 這種腳本語言,只不過 make 更針對編譯這個情境設計。

既然是腳本語言,那肯定以下幾個是不能少的:

  • 指令
  • 變數
  • 函式
  • 條件判斷

四大要件

指令

就是你在命令列(Command Line)會打的那些命令。但請注意,指令前面必須要是以 <Tab> 縮排,不能用空白鍵!

變數

在 Makefile 習慣變數名稱用大寫,要取變數值必須用 $(VAR)${VAR}

變數賦值

  • := (立即賦值 / Simple Assignment):定義的當下就會把值並確定下來,最常用且最推薦。
  • = (延遲賦值 / Recursive Assignment):用到該變數時,才會去尋找並展開它當下的值。這可能導致無窮迴圈或非預期結果。
  • ?= (條件賦值 / Conditional Assignment):如果這個變數前面還沒有被定義過,才會賦予它這個值;如果已經有值了,這行就會被忽略。
  • += (附加賦值 / Append):將後面的字串接在原本變數內容的後面(會自動用一個空白字元隔開)。

自動化變數

  • $@ 工作目標檔名
  • $* 工作目標的主檔名
  • $< 第一個必要條件的檔名
  • $^ 所有必要條件的檔名,並以空格隔開這些檔名
  • $? 所有比工作目標還要新的必要條件檔名

其他符號修飾

  • % 代表所有字串,像是 Regex 的 *
  • @ 不要顯示當前執行的命令
  • - 就算當前命令出錯,也不要中斷後續的執行

函式

在 Makefile 中叫做目標(Target),和函式一樣可以被呼叫和依賴:

all:
    # Compile Commands
clean:
    # Clean Commands

我們可以像呼叫函式那樣呼叫任意區塊,那當然也可能會有參數(依賴條件):

target: <obj1> <obj2>
    # Commands

Makefile 如果在呼叫 make 不接任何參數,就會執行第一個區塊。而 make 會先確認參數裡的每個區塊都準備完畢,才會執行該區塊:

FILE = main.l
NAME = $(basename $(FILE))

run: $(NAME)
    ./$(NAME)

$(NAME): lex.yy.c
    cc -o $(NAME) -O lex.yy.c -ll

lex.yy.c: $(FILE)
    lex $(FILE)

這就是 run $(NAME) lex.yy.c $(FILE),也就是只要有 FILE,其實就可以執行 run 了。因此要運行上述腳本,只須 make FILE=test.l 即可!

真實的內建函式

除了把 Target 當作函式,Makefile 也有提供真正的字串與檔案處理函式,語法通常是 $(function_name arguments)

  • $(wildcard *.c):抓取當前目錄下所有 .c 結尾的檔案。
  • $(patsubst %.c,%.o,$(SRCS)):字串替換,把 $(SRCS) 裡所有 .c 結尾的字串換成 .o
  • $(shell ls -la):執行外部的 Shell 指令並捕捉其輸出結果。

條件判斷

Makefile 也支援條件判斷(ifeq, ifneq, ifdef, ifndef),最常用來判斷不同的作業系統或編譯環境:

OS := $(shell uname)

ifeq ($(OS), Darwin)
    CC = clang
    CFLAGS += -D MAC_OS
else ifeq ($(OS), Linux)
    CC = gcc
    CFLAGS += -D LINUX_OS
else
    CC = cc
endif

build:
    $(CC) $(CFLAGS) main.c -o main