【嵌入式Linux基础】Makefile详解 - 教程
Makefile详解
时隔多日,我真是AI偷懒,虽然学了很多了,但是记录也得加把劲了。
一、什么是 Makefile?
1.1 核心作用
Makefile 是一个自动化构建脚本,用于定义项目的编译规则、依赖关系和执行流程。它的核心价值在于:
- 自动化编译:一键完成「源码 → 目标文件 → 可执行文件」的全流程
- 避免复杂命令行并减少编译时间
- 增量构建:只重新编译修改过的文件,大幅提升开发效率
- 统一规范:为项目提供标准化的构建方式,便于团队协作
1.2 适用场景
C/C++ 等编译型语言项目(最经典场景)
- 多文件、多模块的复杂项目
- 需要自定义构建流程的场景(如文档生成、部署脚本)
- 跨平台项目的构建适配
1.3 依赖工具
Makefile 依赖 make 命令执行,主流操作系统默认预装:Linux/macOS:自带 GNU MakeWindows:需安装 MinGW 或 WSL
1.4 make 命令执行流程
make命令会在当前目录下按顺序寻找GNUmakefile、makefile、 Makefile文件- 把
Makefile中第一个目标文件作为最终目标 - 按“堆栈”顺序,依序找到每个目标文件,判断新旧关系,必要时生成新的目标文件,直到最终生成最终目标。
1.5 make选项
| 选项 | 作用说明 | 示例 |
|---|---|---|
| -h / --help | 显示 make 的帮助信息(包含所有选项说明) | make -h 或 make --help |
| -v / --version | 显示 make 的版本信息 | make -v 或 make --version |
| -f / --file= | 指定要使用的 Makefile 文件(默认查找 Makefile 或 makefile) | make -f MyMakefile (使用 MyMakefile) |
| -n / --just-print | 模拟执行(“干跑”),只显示要执行的命令,不实际运行 | make -n (查看构建步骤,不真正编译) |
| -B / --always-make | 强制重新构建所有目标(忽略文件时间戳,即使目标已 “最新”) | make -B (完全重新编译所有内容) |
| -j / --jobs= | 指定并行执行的任务数(加速编译,n 为并行数量) | make -j4 (4 个任务并行编译) |
| -k / --keep-going | 即使某个目标构建失败,继续执行其他可独立构建的目标 | make -k (一个模块出错,不中断其他) |
| -s / --silent | 静默模式,不显示执行的命令(只输出命令的结果) | make -s (仅看构建结果,不看命令) |
| -t / --touch | 不执行命令,仅更新目标文件的时间戳(让 make 认为目标已 “最新”) | make -t (标记目标为已更新) |
| -d | 显示调试信息(详细输出 make 的解析过程、依赖检查、执行逻辑等) | make -d (调试复杂 Makefile 时用) |
| -C
| 切换到指定目录
| make -C src (进入 src 目录执行 make) |
二、Makefile 基础语法
2.1 基本结构
- 一个 Makefile 的核心由「规则(Rule)」组成,规则的基本格式:
目标(Target): 依赖(Prerequisites)
命令(Commands)
目标:要生成的文件(如可执行文件、目标文件)或执行的动作(如 clean)
依赖:生成目标所需的文件或其他目标
命令:实现目标的具体操作
例如:
main: main.c
gcc main.c -o main
2.2 核心概念
1)默认目标
Make 会默认执行第一个规则的目标(无需指定目标名称)。如需指定默认目标,可在开头添加:
.DEFAULT_GOAL := main
如此一来,最终目标便会被强制指定为对应的目标,而不是第一个目标:
.DEFAULT_GOAL :=main
clean:
rm -f main
mian: main.c
gcc main.c -o main
2)伪目标(Phony Target)
用于执行动作(而非生成文件)的目标,需用 .PHONY 声明,避免与同名文件冲突:
.PHONY: clean
clean:
rm -f main
如此一来,程序并不会直接执行clean ,而是当使用make clean的时候才会执行对应的动作。
如clean般的伪目标,一般习惯放在末尾,不能放在开头
2.3 变量
用于简化规则、统一配置,分为自定义变量和自动变量。
1)自定义变量
CC := gcc # 编译器
CFLAGS := -Wall -g # 编译选项(-Wall 显示警告,-g 生成调试信息)
TARGET := app # 目标文件名
SRC := main.c utils.c # 源码文件
# 使用变量($(变量名))
$(TARGET): $(SRC)
$(CC) $(CFLAGS) $(SRC) -o $(TARGET)
- 变量赋值:常见的有如下三种
=延迟赋值,当使用=赋值时,变量为使用时赋值:=立即赋值,当使用:=赋值时,变量为直接赋值?=条件赋值,当使用?=赋值时,变量为空赋值- 举例说明
# 1. = 延迟赋值(使用时才计算,受后续修改影响)
x = $(y)
y = 100
x2 = $(y)
y = 200 # 后续修改y
# 2. := 立即赋值(定义时计算,不受后续修改影响)
a := $(b)
b = 300
a2 := $(b)
b = 400 # 后续修改b
# 3. ?= 条件赋值(仅变量未定义时生效)
c ?= 500 # c未定义,赋值生效
c ?= 600 # c已定义,赋值无效
d = 700 # d已提前定义
d ?= 800 # d已定义,赋值无效
# 打印变量值
all:
@echo "= 延迟赋值:"
@echo "x = $(x) "
@echo "x2 = $(x2)"
@echo ""
@echo ":= 立即赋值:"
@echo "a = $(a) "
@echo "a2 = $(a2)"
@echo ""
@echo "?= 条件赋值:"
@echo "c = $(c) "
@echo "d = $(d)"
结果:
- 需要注意的是,当我们在使用
make 变量名=值时,从命令行传入的值会直接覆盖定义好的值,但是可以通过添加override指示符,就可以使用在Makefile中设置的参数了 - 绝对不能出现,循环嵌套!!!
2)自动化变量
- 自动变量用于简化命令,无需重复写目标 / 依赖名称
- 常见的自动化变量有
$@ 、$^ 、 $< 、$?等。$@:当前规则的目标$<:当前规则的第一个依赖文件$^:当前规则的所有依赖$?:当前规则中比目标文件更新的所有依赖文件
- 举例:
CC := gcc #编译器
CFLAGS := -Wall -g #编译选项
TARGET := main #目标文件
SRC := main.c utils.c
# 通过变量和自动化变量,Makefile文档可重复利用
$(TARGET): $(SRC)
$(CC) $(CFLAGS) $^ -o $@-o app
.PHONY: clean
clean:
rm -rf $(TARGET) *.o
3)预定义变量
- AR:归档维护程序,默认为ar
- AS:汇编程序,默认为as
- CC:C编译程序,默认为cc
- CPP:C预处理程序, 默认为cpp
- RM:文件删除程序,默认为:rm –f
- ARFLAGS:传给归档维护程序的参数,默认rv
- ASFLAGS:传给汇编程序的参数,无默认值
- CFLAGS:传给C编译器的参数,无默认值
- CPPFLAGS:传给C预处理器的参数,无默认值
- LDFLAGS:传给链接器的参数,无默认值
三、Makefile 进阶用法
3.1 条件判断
- 语法
# 基本结构(else 可选)
conditional-directive
text-if-true # 条件为真时执行的内容
else
text-if-false # 条件为假时执行的内容(可选)
endif
- 主要有四种条件判断
ifeq、ifneq、ifdef、ifndef
# 定义测试变量
VAR1 = hello
VAR2 = world
VAR3 = hello # 与 VAR1 相等
# VAR4 未定义(用于 ifdef/ifndef 测试)
# 1. ifeq:判断两个参数是否相等(支持字符串/变量)
ifeq ($(VAR1), hello)
MSG1 = "VAR1 等于 'hello'"
else
MSG1 = "VAR1 不等于 'hello'"
endif
# 2. ifneq:判断两个参数是否不相等
ifneq ($(VAR1), $(VAR2))
MSG2 = "VAR1 不等于 VAR2"
else
MSG2 = "VAR1 等于 VAR2"
endif
# 3. ifdef:判断变量是否已定义(只要被赋值过,包括空值)
ifdef VAR3
MSG3 = "VAR3 已定义"
else
MSG3 = "VAR3 未定义"
endif
# 4. ifndef:判断变量是否未定义
ifndef VAR4
MSG4 = "VAR4 未定义"
else
MSG4 = "VAR4 已定义"
endif
# 打印结果
all:
@echo "ifeq 测试: $(MSG1)"
@echo "ifneq 测试: $(MSG2)"
@echo "ifdef 测试: $(MSG3)"
@echo "ifndef 测试: $(MSG4)"
结果:
3.2 函数
Makefile 内置常用函数,用于文件查找、字符串处理等,支持的函数语法如下:
$(function arg1,arg2,…)
常用函数:
foreach函数:
$(foreach ,,)
功能::把list中的单词逐一取出,送入var指定的变量中,然后再执行test表达式。每执行一次test返回一个字串,所有字串用空格连接起来就是函数的返回值。
names:=a b c
$(foreach n, $(names), $(n).c)
返回:a.c b.c c.c
if函数:
$(if ,,)
功能:如条件为真(condition非空),则返回then部分,否则返回else部分(或空)
tmp=
res=$(if tmp,tmp not exist,tmp exist)
all:
@echo $(m)
# 结果为 tmp not exist
- wildcard函数:
$(wildcard )
功能:匹配当前目录下所有符合模式的文件(支持*等通配符),返回文件名列表(空格分隔)。
# 匹配当前目录下所有.c文件
src_files := $(wildcard *.c)
# 若当前目录有a.c、b.c、main.c,则返回:a.c b.c main.c
- patsubst函数:
$(patsubst ,,)
功能:对
# 将.c文件替换为.o文件
obj_files := $(patsubst %.c,%.o,a.c b.c main.c)
# 返回:a.o b.o main.o
- notdir函数:
$(notdir )
功能:从文件路径列表中移除目录部分,仅保留文件名。
# 提取路径中的文件名
files := $(notdir src/a.c include/b.h lib/c.so)
# 返回:a.c b.h c.so
- shell函数:
$(shell )
功能:执行指定的 shell 命令,返回命令的输出结果(自动去除末尾换行符)。
# 获取当前目录路径
cur_dir := $(shell pwd)
# 若当前目录为/home/user/project,则返回:/home/user/project
# 获取当前时间(格式:年-月-日)
today := $(shell date +%Y-%m-%d)
# 若当天是2025-11-10,则返回:2025-11-10
常用的函数如上,当然还有很多函数自行解锁吧。
3.3 Makefile嵌套执行
Makefile嵌套执行指的是在一个主Makefile中调用其他子目录中的Makefile(通常用于大型项目的模块化管理,将不同模块的编译规则拆分到各自目录的Makefile中)。

- 例如:
1)目录结构:
project/
├── Makefile # 主 Makefile(嵌套执行入口)
├── src/ # 源码目录
│ ├── main.c
│ └── Makefile # src 目录的子 Makefile
└── test/ # 测试目录
├── test.c
└── Makefile # test 目录的子 Makefile
2)子目录 Makefile 实现:src/Makefile
# 目标:生成可执行文件 app
app: main.c
gcc main.c -o app
@echo "src 目录编译完成:生成 app"
# 清理 src 目录生成的文件
clean:
rm -f app
@echo "src 目录清理完成"
test/Makefile
# 目标:生成测试可执行文件 test_app
test_app: test.c
gcc test.c -o test_app
@echo "test 目录编译完成:生成 test_app"
# 清理 test 目录生成的文件
clean:
rm -f test_app
@echo "test 目录清理完成"
3)主Makefile实现
# 定义子目录列表
SUBDIRS := src test
# 默认目标:编译所有子目录
all: $(SUBDIRS)
# 嵌套执行子目录的 make(% 匹配子目录名)
$(SUBDIRS):
@echo "\n===== 开始编译 $@ 目录 ====="
$(MAKE) -C $@ # -C $@ 切换到子目录 $@,执行该目录的 Makefile
@echo "===== $@ 目录编译结束 =====\n"
# 清理所有子目录的生成文件
clean:
@echo "\n===== 开始清理所有目录 ====="
$(foreach dir, $(SUBDIRS), $(MAKE) -C $(dir) clean;) # 循环调用子目录的 clean 目标
@echo "===== 所有目录清理结束 =====\n"
.PHONY: all clean $(SUBDIRS) # 声明伪目标,避免与同名目录冲突
执行 make 或者 make all
总结
makefile博大精深,还有很多内容如隐含规则等并没有在本文中介绍,日后精进了,再写其进阶。
浙公网安备 33010602011771号