Linux Makefile 完整教程
目录
什么是 Make 和 Makefile
Make 是一个自动化构建工具,Makefile 是它的配置文件。
- 核心思想:只重新编译发生变化的源文件,提高构建效率
- 应用场景:C/C++ 项目编译、大型工程构建、自动化部署等
基本工作流程
Makefile → make 命令 → 检查依赖关系 → 只编译有变化的文件
基本语法
1 基本结构
# 目标: 依赖
# <Tab> 命令(注意:必须用 Tab,不能用空格!)
target: dependency1 dependency2
<command1>
<command2>
2 第一个 Makefile 示例
# 编译 main.c 生成 main 可执行文件
main: main.o utils.o
gcc -o main main.o utils.o
main.o: main.c
gcc -c main.c
utils.o: utils.c
gcc -c utils.c
# 清理生成的文件
clean:
rm -f .o main
运行:
make # 构建 make clean # 清理
3 伪目标
.PHONY: clean all install
clean:
rm -f .o main
all: main
.PHONY声明伪目标,避免与同名文件冲突。
变量
1 变量定义
# 简单赋值(立即求值) CC = gcc CFLAGS = -Wall -g # 递归赋值(延迟求值) CC ?= gcc # 如果未定义则赋值 CC := gcc # 简单赋值,立即展开 CC += -std=c11 # 追加赋值 # 条件赋值 override CC = clang # 覆盖命令行传入的值
2 常用内置变量
CC # C 编译器 CXX # C++ 编译器 CFLAGS # C 编译选项 CXXFLAGS # C++ 编译选项 LDFLAGS # 链接选项 LDLIBS # 链接库 AR # 归档工具 RM # 删除命令
3 使用变量
CC = gcc
CFLAGS = -Wall -O2
TARGET = myapp
SRCS = main.c utils.c helper.c
OBJS = $(SRCS:.c=.o)
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
= 目标名,
$^= 所有依赖,$<= 第一个依赖
模式规则
1 通配符规则
# %.o: %.c 表示所有 .o 文件依赖于同名的 .c 文件
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
2 多模式规则
# 同时匹配 .c 和 .cpp
%.o: %.c %.cpp
$(CC) $(CFLAGS) -c $< -o $@
3 静态模式规则
OBJS = main.o utils.o helper.o
$(OBJS): %.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
自动变量
| 变量 | 含义 |
|---|---|
| 目标文件名 | |
$< |
第一个依赖文件名 |
$^ |
所有依赖文件名(去重) |
| 所有依赖文件名(不去重) | |
| 目标中 匹配的部分 | |
| 比目标更新的依赖文件列表 | |
| 所有先决条件(包括 order-only) |
示例
main.o: main.c main.h
gcc -c $< -o $@ # 等价于 gcc -c main.c -o main.o
# 使用 $? 只编译更新的依赖
main: main.o utils.o
gcc -o $@ $? # 只链接更新的依赖
函数
1 常用函数
# wildcard: 获取匹配文件 SRCS = $(wildcard .c) # patsubst: 模式替换 OBJS = $(patsubst %.c,%.o,$(SRCS)) # notdir: 去除路径 DIRS = $(notdir $(SRCS)) # addprefix: 添加前缀 PATHS = $(addprefix src/,$(SRCS)) # realpath: 获取绝对路径 REAL_PATHS = $(realpath $(DIRS)) # foreach: 循环 # $(foreach var,list,text) OBJS = $(foreach src,$(SRCS),$(src:.c=.o)) # if: 条件判断 # $(if condition,true-part[,false-part]) RESULT = $(if $(CC),using gcc,using clang) # warning/error: 输出警告/错误 $(warning This is a warning) $(error This is an error) # shell: 执行 shell 命令 DATE = $(shell date +%Y%m%d)
2 函数示例
# 获取所有 C 源文件
SRCS = $(wildcard src/.c)
# 转换为对象文件
OBJS = $(patsubst src/%.c,build/%.o,$(SRCS))
# 创建目录
build/%.o: src/%.c | build
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
build:
mkdir -p build
clean:
rm -rf build
条件判断
1 ifeq / ifneq
ifeq ($(CC),gcc)
CFLAGS += -O3
else ifeq ($(CC),clang)
CFLAGS += -O3 -Qunused-arguments
else
CFLAGS += -O2
endif
2 ifdef / ifndef
ifdef DEBUG CFLAGS += -g -DDEBUG else CFLAGS += -O2 endif ifndef VERBOSE QUIET := @ endif
3 结合变量使用
ifeq ($(OS),Linux)
LDFLAGS += -lpthread
else ifeq ($(OS),Darwin)
LDFLAGS += -lpthread
endif
实战示例
1 简单 C 项目
# ==================== 配置区 ====================
CC = gcc
CFLAGS = -Wall -Wextra -std=c11 -g
LDFLAGS =
LDLIBS = -lm
TARGET = myapp
SRCDIR = src
OBJDIR = build
BINDIR = bin
SRCS = $(wildcard $(SRCDIR)/.c)
OBJS = $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS))
# ==================== 规则区 ====================
.PHONY: all clean install uninstall
all: $(TARGET)
$(TARGET): $(OBJS) | $(BINDIR)
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR):
mkdir -p $(OBJDIR)
$(BINDIR):
mkdir -p $(BINDIR)
clean:
rm -rf $(OBJDIR) $(BINDIR) $(TARGET)
install: $(TARGET)
cp $(TARGET) $(BINDIR)/
uninstall:
rm -f $(BINDIR)/$(TARGET)
2 C++ 项目
# ==================== 配置区 ====================
CXX = g++
CXXFLAGS = -Wall -Wextra -std=c++17 -O2
LDFLAGS =
LDLIBS = -lpthread -lstdc++fs
TARGET = mycppapp
SRCDIR = src
OBJDIR = build
# 自动收集所有 .cpp 文件
SRCS = $(wildcard $(SRCDIR)/.cpp)
OBJS = $(patsubst $(SRCDIR)/%.cpp,$(OBJDIR)/%.o,$(SRCS))
# 自动收集头文件依赖
DEPS = $(OBJS:.o=.d)
# ==================== 规则区 ====================
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS)
$(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS)
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
@mkdir -p $(@D)
$(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@
# 包含自动生成的依赖文件
-include $(DEPS)
clean:
rm -rf $(OBJDIR) $(TARGET)
-MMD -MP自动生成.d依赖文件,当修改头文件时会自动重新编译相关源文件。
3 多目标项目
# 构建多个可执行文件
TARGETS = app1 app2 app3
.PHONY: all clean
all: $(TARGETS)
app1: app1.o lib.o
$(CC) -o $@ $^
app2: app2.o lib.o
$(CC) -o $@ $^
app3: app3.o lib.o
$(CC) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGETS) .o
4 带测试的项目
CC = gcc
CFLAGS = -Wall -g
LDFLAGS =
LDLIBS = -lcheck -lm -lpthread
TARGET = mylib
TEST_TARGET = test_mylib
.PHONY: all clean test
all: $(TARGET) $(TEST_TARGET)
# 编译库
$(TARGET): main.o utils.o
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
# 编译测试
$(TEST_TARGET): test_main.o main.o utils.o
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
test: $(TEST_TARGET)
./$(TEST_TARGET)
clean:
rm -f $(TARGET) $(TEST_TARGET) .o
最佳实践
1 通用模板
# 1. 始终使用 .PHONY 声明伪目标 .PHONY: all clean install uninstall test # 2. 使用变量而非硬编码 CC ?= gcc CFLAGS ?= -Wall -Wextra CXX ?= g++ CXXFLAGS ?= -Wall -Wextra # 3. 使用自动变量 # 4. 使用模式规则减少重复 # 5. 提供 clean 目标 # 6. 使用 -include 包含依赖文件 # 7. 使用 @ 前缀抑制命令回显(可选) # 8. 使用 - 前缀忽略错误(可选) # 示例:安静模式 quiet_cmd = @echo " CC $<" quiet_cmd = @$(CC) $(CFLAGS) -c $< -o $@
2 常见陷阱
# ❌ 错误:使用空格而不是 Tab
target: dep
command # 空格!会报错
# ✅ 正确:使用 Tab
target: dep
command # Tab
# ❌ 错误:变量名大小写敏感
CC = gcc
$(cc) ... # cc 未定义!
# ✅ 正确
$(CC) ...
# ❌ 错误:忘记 .PHONY
clean:
rm -f .o
# 如果当前目录有名为 clean 的文件,make 不会执行
# ✅ 正确
.PHONY: clean
clean:
rm -f .o
3 调试技巧
# 显示执行的命令 make -n # 预览命令(dry run) make -d # 详细调试信息 make VERBOSE=1 # 自定义 verbose 模式 # 在 Makefile 中调试 $(info CC is $(CC)) $(warning CFLAGS is $(CFLAGS)) $(error Stop here)
快速参考卡片
基本结构: target: dependencies
<tab> command
变量: VAR = value
$(VAR) 或 ${VAR}
自动变量: $@ 目标 $< 第一个依赖 $^ 所有依赖
函数: $(wildcard .c)
$(patsubst %.c,%.o,$(SRCS))
条件: ifeq / ifneq / ifdef / ifndef
伪目标: .PHONY: clean all
| 概念 | 要点 |
|---|---|
| 核心 | 依赖关系 + 增量编译 |
| 语法 | 目标: 依赖 → Tab 命令 |
| 变量 | 用 $(VAR) 引用,善用自动变量 |
| 模式规则 | %.o: %.c 减少重复 |
| 函数 | wildcard, patsubst, foreach 等 |
| 最佳实践 | 使用 .PHONY、变量抽象、自动依赖 |
Makefile 是 Linux 开发中不可或缺的工具,掌握它能极大提升构建效率,建议从简单项目开始,逐步掌握高级特性。
首发原创文章,作者:世雄 - 原生数据库架构专家,如若转载,请注明出处:https://idctop.com/article/483362.html



