Makefile 從入門到實戰完整指南
Makefile 是構建自動化的核心工具,掌握它能極大提升項目構建效率與可維護性。本文系統梳理 Makefile 核心語法、變量、函數、條件判斷、嵌套構建、隱含規則等實戰知識點,附帶大量可運行示例。
一、基本結構與規則
target ... : prerequisites ...
command ... # 必須以 Tab 開頭,非空格
# 或單行寫法
target : prerequisites ; command
command ...
- target:目標文件(可執行文件、.o、.a、.so 等)或偽目標。
- prerequisites:依賴文件/目標。
- command:任意 Shell 命令。
二、核心語法詳解
1. 通配符
* 匹配任意字符
? 匹配單個字符
[abc] 匹配 a/b/c 中任一
[^abc] 匹配非 a/b/c
~ 當前用戶家目錄
2. 路徑搜索 VPATH / vpath
VPATH = src:../include # 所有文件搜索路徑
vpath %.h ./headers # .h 文件搜索路徑
vpath %.c src # .c 文件搜索路徑
vpath # 清除所有 vpath 設置
3. 變量定義與賦值
| 操作符 | 行為 | 示例 |
= | 遞歸展開,可使用後定義的變量 | A = $(B); B = val 有效 |
:= | 即時展開,只能使用前定義變量 | A := $(B); B = val A 為空 |
+= | 追加值 | SRCS += main.c |
?= | 條件賦值(未定義時賦值) | CC ?= gcc |
4. 變量替換與函數
OBJS = $(SRCS:.c=.o) # 後綴替換
OBJS = $(patsubst %.c,%.o,$(SRCS)) # 模式替換(功能更強)
5. 自動變量(規則內部使用)
| 變量 | 含義 |
$@ | 目標文件名 |
$< | 第一個依賴文件 |
$^ | 所有依賴文件(去重) |
$+ | 所有依賴文件(保留重複) |
$? | 比目標新的依賴文件 |
$% | 目標為靜態庫時,庫內成員名 |
$* | 目標文件名(去後綴),GNU make 特有 |
6. 命令前綴
- 無前綴:輸出命令及結果,出錯停止
@:僅輸出結果,不顯示命令
-:忽略錯誤繼續執行
7. 偽目標 .PHONY
.PHONY: clean install test
clean:
-rm -f *.o $(TARGET)
8. 引用其他 Makefile
include config.mk
include *.mk
9. 條件判斷
ifeq ($(DEBUG), 1)
CFLAGS += -g -O0
else
CFLAGS += -O2
endif
ifdef FEATURE_X
CFLAGS += -DFEATURE_X
endif
10. 內建函數精選
| 函數 | 用法示例 | 說明 |
wildcard | $(wildcard *.c) | 獲取匹配文件列表 |
patsubst | $(patsubst %.c,%.o,$(SRCS)) | 模式替換 |
foreach | $(foreach f,$(FILES),$(f).bak) | 遍歷列表生成新列表 |
if | $(if $(VAR),then,else) | 條件表達式 |
call | $(call my_func,arg1,arg2) | 調用自定義參數化宏 |
shell | $(shell pwd) | 執行 shell 命令獲取輸出 |
error | $(error Fatal error!) | 拋出致命錯誤停止構建 |
warning | $(warning Deprecated!) | 輸出警告繼續構建 |
三、實戰模板:C/C++ 項目通用 Makefile
# 編譯器與標志
CC = gcc
CXX = g++
CFLAGS = -Wall -Wextra -std=c11 -I./include
CXXFLAGS = -Wall -Wextra -std=c++17 -I./include
LDFLAGS = -L./lib
LIBS = -lpthread -lm
# 目標
TARGET = myapp
SRCS = $(wildcard src/*.c)
OBJS = $(patsubst src/%.c,build/%.o,$(SRCS))
# 默認目標
all: $(TARGET)
# 鏈接
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
# 編譯規則(自動創建 build 目錄)
build/%.o: src/%.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c -o $@ $<
# 清理
.PHONY: clean
clean:
-rm -rf build $(TARGET)
# 安裝
.PHONY: install
install: $(TARGET)
install -m 755 $(TARGET) /usr/local/bin/
# 依賴自動生成(-MMD 生成 .d 文件)
DEPS = $(OBJS:.o=.d)
-include $(DEPS)
CFLAGS += -MMD -MP
四、常用 make 參數
| 參數 | 作用 |
-j N | 並行構建 N 個任務(如 make -j4) |
-f FILE | 指定 Makefile 文件 |
-B | 強制重新構建所有目標 |
-n | 演練模式,只打印命令不執行 |
-C DIR | 切換目錄後執行 |
--debug | 輸出調試信息 |
五、GNU Make 約定俗成偽目標
all:默認構建目標
clean:刪除構建產物
install:安裝到系統目錄
uninstall:移除安裝文件
dist:打包發布源碼
check/test:運行測試套件
六、避坑指南
- Tab vs 空格:命令行必須以 Tab 開頭,編輯器設置「Makefile 保持 Tab」。
- 變量展開時機:
:= 避免遞歸展開導致的無限循環或性能問題。
- 隱含規則干擾:
make -r 禁用內建規則,避免意外觸發。
- 並行構建衝突:確保目標依賴正確聲明,避免競態條件。
- 路徑分隔符:跨平台用
/,Windows 下 make 也支持。
Makefile 雖然語法古老,但其聲明式依賴圖模型至今無可替代。精通 Makefile,就是掌握了構建系統的「總遙控器」。