make 是 Unix 世界最經典的建置自動化工具,配合 Makefile 實現「只重建變更過的目標」,是 C/C++、核心模組、嵌入式專案的標準建置系統。本文涵蓋語法核心、依賴圖邏輯、變數/函數、模式規則、遞歸建置、自動化工具鏈與最佳實踐。
# 註解
變數 = 值 # 遞歸展開 (延遲)
變數 := 值 # 立即展開 (推薦)
變數 ?= 值 # 僅當未定義時賦值
變數 += 值 # 追加
目標: 依賴1 依賴2 ...
配方行1 (必須以 Tab 開頭)
配方行2
...
# 內建變數
CC = gcc
CFLAGS = -Wall -O2
LDFLAGS = -lm
RM = rm -f
# Makefile
CC = gcc
CFLAGS = -Wall -O2 -I.
TARGET = myapp
SRCS = main.c utils.c
OBJS = $(SRCS:.c=.o)
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
$(RM) $(OBJS) $(TARGET)
.PHONY: all clean
| 變數 | 含義 | 範例 |
|---|---|---|
$@ | 目標完整名稱 | myapp |
$< | 第一個依賴 | main.c |
$^ | 所有依賴 (去重) | main.o utils.o |
$? | 比目標新的依賴 | 修改過的 .c 檔 |
$% | 歸檔成員名 (靜態庫用) |
# 模式規則 (隱含規則)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 靜態模式 (限定特定目標集合)
$(OBJS): %.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 搜尋所有 .c 檔
SRCS := $(wildcard *.c)
# 取檔名不含副檔名
BASES := $(basename $(SRCS))
# 字串替換
OBJS := $(SRCS:.c=.o)
# 條件判斷
ifeq ($(DEBUG),1)
CFLAGS += -g -DDEBUG
endif
# Shell 命令結果
GIT_VER := $(shell git describe --tags --always)
SUBDIRS = lib app test
all: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
clean:
for d in $(SUBDIRS); do $(MAKE) -C $$d clean; done
現代建議:避免遞歸 Make (Recursive Make Considered Harmful),改用非遞歸 Make:單一頂層 Makefile,以目錄前綴區分目標 (如
lib/foo.o),利用VPATH或out-of-tree build。
CFLAGS += -MMD -MP
DEPS := $(OBJS:.o=.d)
-include $(DEPS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
$(RM) $(OBJS) $(DEPS) $(TARGET)
-MMD 編譯時產生 .d 檔記錄標頭檔相依,-MP 為缺失標頭產生空目標避免錯誤,-include 靜默包含。
BUILD_DIR := build
OBJS := $(addprefix $(BUILD_DIR)/, $(SRCS:.c=.o))
$(BUILD_DIR)/%.o: %.c
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
project/
├── Makefile # 頂層總覽,包含 config.mk、core.mk
├── config.mk # 工具鏈、旗標、安裝前綴 (可被使用者覆蓋)
├── core.mk # 核心規則、函數、自動相依邏輯
├── src/
│ ├── module1/
│ │ ├── Makefile # 僅定義 MODULE_SRCS、MODULE_DEPS
│ │ └── *.c/*.h
│ └── module2/
└── tests/
└── Makefile
config.mk 範例:
PREFIX ?= /usr/local
CC ?= gcc
CFLAGS ?= -Wall -Wextra -O2
CFLAGS += -I$(CURDIR)/include
LDFLAGS ?=
DESTDIR ?=
| 工具 | 定位 | 學習曲線 | 適用場景 |
|---|---|---|---|
| 手寫 Makefile | 完全控制 | 中 | 中小專案、核心模組、嵌入式、教學 |
| Autotools (autoconf/automake) | GNU 標準、極致可移植 | 高 | 開源庫、需支援古老 Unix、系統級軟體 |
| CMake | 跨平台、IDE 整合、現代標準 | 中 | C++ 大型專案、Qt、LLVM、ROS、交叉編譯 |
| Meson | 快速、Python 腳本、強類型 | 低 | 新專案、GNOME、Flatpak、追求建置速度 |
選型建議:新專案首選 Meson 或 CMake;維護舊專案/核心模組繼承 Makefile;需極致可移植 (含非 GNU 系統) 用 Autotools。
| 現象 | 原因 | 修正 |
|---|---|---|
| 「missing separator」 | 配方行未用 Tab (用空白) | 編輯器設定「Tab 插入 Tab 字元」 |
| 目標重複定義 | 同一目標出現多條規則 | 合併規則或使用雙冒號 :: |
| 循環相依 | A 依賴 B,B 依賴 A | 重構模組拆分 |
並行建置失敗 (-j) | 缺少相依宣告 | 補全依賴、使用 .NOTPARALLEL (臨時) |
| 變數未展開 | 使用 = 導致遞歸展開順序錯誤 | 改用 := 立即展開 |
make # 建置預設目標 (第一個目標,通常是 all)
make clean # 執行 clean 目標
make -j8 # 8 核心並行建置
make -n # 乾跑 (只印指令不執行)
make -p # 印出完整內建規則與變數資料庫
make VAR=value # 命令列覆蓋變數
make -C subdir # 切換目錄執行
make --trace # 追蹤規則觸發 (GNU Make 4.0+)
核心心法:「Makefile 是宣告式依賴圖,而非腳本。寫規則描述『什麼依賴什麼』,讓 make 決定『何時怎麼建』。善用自動變數、模式規則、自動相依,讓 Makefile 精簡、健壯、可擴展。」