Makefile Memo
Makefile Memo
一、Makefile的核心作用
-
单文件工程用GCC命令编译便捷,多文件/大型工程中GCC力不从心,需借助make工具实现自动化编译
-
make是解释makefile指令的命令工具,多数IDE内置(如VC++的nmake、QtCreator的qmake)
-
makefile定义工程编译规则(文件编译顺序、重编译条件等),支持执行系统命令,类似Shell脚本
-
优势:一旦写好,执行
make命令即可完成全工程自动编译,大幅提升开发效率 -
命名与位置:支持
makefile和Makefile两种命名;make命令在哪个目录执行,就加载该目录下的makefile;一个项目可在不同目录放置多个makefile
二、规则(Makefile框架核心)
1. 基本格式
1 | # 每条规则的语法格式: |
2. 三部分组成
-
目标(target):与命令对应,可生成同名文件;支持多目标;不生成文件仅执行动作的目标称为“伪目标”
-
依赖(depend):规则执行的必需条件,可使用目标文件(如*.o);依赖可为空;可引用其他规则的目标,形成规则嵌套;支持多个依赖
-
命令(command):规则的执行动作(如编译、生成库、进入目录等),多为Shell命令;支持多个命令,每个命令前必须有Tab缩进且独占一行
3. 示例
1 | # 例1:单目标、多依赖、单命令(源文件a.c/b.c/c.c生成可执行程序app) |
三、工作原理
1. 规则的执行逻辑
-
make命令优先查找Makefile中第一条规则,分析并执行动作
-
若规则依赖不存在,该规则命令无法执行,需新增规则将缺失依赖作为目标,生成依赖后再执行原规则
-
执行非第一条规则:需在make后指定目标,如
make b.o(仅执行生成b.o的规则)
2. 文件的时间戳(重编译判断依据)
-
正常情况:目标时间戳 > 所有依赖时间戳 → 不执行规则命令
-
依赖更新:目标时间戳 < 部分依赖时间戳 → 重新执行规则命令生成目标
-
目标不存在:必执行规则命令生成目标
示例:修改a.c后执行make,仅重新生成a.o和app,其他目标(b.o、c.o)不重新编译
3. 自动推导(make的默认规则)
-
编译.c文件时,无需手动写.c→.o的规则,make会自动匹配默认规则:用
cc -c编译.c文件生成对应的.o文件 -
只需指定.o目标,make会自动寻找对应的.c依赖并执行默认编译命令
1 | # 项目目录含add.c/div.c/main.c/mult.c/sub.c/head.h/makefile |
四、变量(提升规则灵活性)
分为自定义变量、预定义变量、自动变量三类
1. 自定义变量
-
定义:
变量名=变量值(无类型,必须赋值) -
引用:
$(变量名)
1 | # 示例:用自定义变量简化规则 |
2. 预定义变量(可直接使用,无需定义)
| 变量名 | 含义 | 默认值 |
|---|---|---|
| AR | 生成静态库的程序名称 | ar |
| CC | C语言编译器名称 | cc |
| CXX | C++语言编译器名称 | g++ |
| RM | 删除文件程序名称 | rm -f |
| CFLAGS | C语言编译器选项 | 无 |
| CXXFLAGS | C++语言编译器选项 | 无 |
1 | # 示例:结合自定义变量和预定义变量 |
3. 自动变量(仅在规则命令中使用,代表目标/依赖)
| 变量 | 含义 |
|---|---|
| $* | 目标文件名(不含扩展名) |
| $+ | 所有依赖文件(按顺序,含重复) |
| $< | 第一个依赖文件名称 |
| $? | 所有比目标时间戳晚的依赖文件 |
| $@ | 目标文件名(含扩展名) |
| $^ | 所有不重复的依赖文件 |
1 | # 示例:用自动变量简化命令 |
4. 赋值语法
| 语法 | 含义 | 示例 | 最终值 |
|---|---|---|---|
= |
延迟赋值(使用变量时才展开) | A = $(B)``B = hello |
A = hello |
:= |
立即赋值(定义时就展开) | A := $(B)``B = hello |
A = (定义 A 时 B 还未赋值) |
+= |
追加赋值(基于现有值加新内容) | A = a``A += b c |
A = a b c |
?= |
仅变量未定义时赋值(避免覆盖) | A ?= hello``A ?= world |
A = hello |
五、模式匹配(精简冗余规则)
1. 作用
多个.c→.o的规则语法重复(命令均为gcc *.c -c),可通过模式匹配整理为一个规则模板
2. 模板格式
1 |
|
3. 效果
替代所有单个.c→.o的规则,如add.o:add.c、div.o:div.c等,大幅精简makefile
六、常用函数(均有返回值,格式:$(函数名 参数1,参数2,…))
1. wildcard(获取指定目录下指定类型文件)
-
原型:
$(wildcard PATTERN...) -
参数:PATTERN为目录+文件类型(如*.c、./sub/*.c),多目录用空格分隔
-
返回值:空格分隔的符合条件的文件名列表
1 | # 示例:搜索3个目录下的.c文件 |
2. patsubst(按模式替换文件名后缀)
-
原型:
$(patsubst <pattern>,<replacement>,<text>) -
参数:pattern(待替换后缀模式,如%.c)、replacement(新后缀模式,如%.o)、text(原始文件列表)
-
返回值:替换后的文件名列表
1 | # 示例:将src中的.cpp后缀替换为.o |
七、Makefile编写进化过程(从简单到标准)
项目目录:add.c/div.c/main.c/mult.c/sub.c/head.h
版本1:简单但低效(修改一个文件全量重编译)
1 | calc:add.c div.c main.c mult.c sub.c |
版本2:分模块编译(提升效率,规则冗余)
1 | calc:add.o div.o main.o mult.o sub.o |
版本3:变量+模式匹配(精简规则)
1 | obj=add.o div.o main.o mult.o sub.o |
版本4:函数自动获取文件(解放双手)
1 | src=$(wildcard *.c) # 自动搜索当前目录所有.c文件 |
版本5:添加清理规则(存在伪目标问题)
1 | src=$(wildcard *.c) |
问题:若目录存在clean文件,make clean会提示“clean is up to date”,无法执行删除
版本6:最终版(声明伪目标,优化清理命令)
1 | src=$(wildcard *.c) |
八、练习题(多目录项目Makefile)
项目目录结构
1 | . |
对应的Makefile
1 | target = app # 最终目标名 |
九、多目录工程Makefile框架(范式)
本框架是复杂多目录项目的标准化编译方案,整合「顶层Makefile」和「顶层Makefile.build」核心功能,只需在各级子目录通过obj-y指定编译文件/子目录,即可实现全工程一键编译/清理,支持交叉编译和精细化编译控制。
1. 完整框架代码
1 | # 一、工具链与全局变量定义(顶层Makefile核心) |
2. 核心构成与功能拆解
2.1 工具链与全局变量定义(顶层Makefile)
-
工具链前缀
CROSS_COMPILE:支持交叉编译(如ARM交叉编译可设为arm-linux-gcc-),未设置则使用本地工具链 -
工具定义:
AS、LD、CC等编译链接工具,通过CROSS_COMPILE拼接,确保工具链统一 -
全局编译/链接选项:
CFLAGS指定警告、优化、头文件目录;LDFLAGS指定链接规则(如链接动态库) -
导出变量:通过
export使子目录的Makefile能继承使用这些变量,保证全工程编译规则一致
2.2 核心目标规则(顶层Makefile)
-
总目标
all:触发递归编译(start_recursive_build)和最终目标生成,执行make时默认执行 -
递归编译触发:
start_recursive_build调用Makefile.build,递归处理所有子目录的编译 -
最终目标生成:链接
built-in.o(全工程所有目标文件的打包文件)生成可执行程序 -
清理目标:
clean清理.o和可执行程序;distclean额外清理依赖文件(.d),适合工程重构
2.3 递归编译与打包规则(Makefile.build)
核心作用:标准化各级目录的编译流程,自动处理子目录编译和目标文件打包,无需手动编写每个目录的编译规则。
-
子目录处理:自动筛选
obj-y中的子目录,递归进入子目录执行Makefile.build,生成子目录的built-in.o -
当前目录目标文件处理:筛选
obj-y中的.o文件,自动生成依赖文件(.xxx.o.d),确保修改头文件时触发对应.o重编译 -
目标文件打包:通过
LD -r(部分链接)将当前目录的.o和所有子目录的built-in.o合并为当前目录的built-in.o,最终顶层目录的built-in.o包含全工程目标文件 -
精细化编译控制:支持
EXTRA_CFLAGS(当前目录额外编译选项)、CFLAGS_xxx.o(单个文件专属编译选项)
3. 核心工作流程
-
执行
make:默认执行顶层Makefile的all目标 -
触发递归编译:调用
Makefile.build,从顶层目录开始,递归进入所有obj-y指定的子目录 -
子目录编译:每个子目录生成自身的
built-in.o,并向上级目录传递 -
顶层打包与链接:顶层目录合并所有子目录的
built-in.o和自身.o,生成顶层built-in.o,最终链接为可执行程序
4. 关键配置说明
-
obj-y+= 文件名/子目录/:核心配置项(y=yes,编译),指定当前目录要编译的文件(如main.o)或要递归编译的子目录(如a/);多目录框架中约定用obj-y标记“需要参与编译的内容” -
obj-n+= 文件名:辅助配置项(n=no,不编译),指定当前目录需排除、不参与编译的文件;需配合filter-out函数过滤,框架不会自动识别,仅为行业约定命名 -
obj-m+= 文件名:拓展配置项(m=module,模块),常见于Linux内核/驱动开发,指定将文件编译为可加载内核模块(生成.ko文件,而非链接到主程序) -
CROSS_COMPILE:交叉编译时设置,如ARM架构设为arm-none-linux-gnueabi- -
CFLAGS += -I 目录:添加头文件搜索目录,确保编译器能找到#include的头文件 -
LDFLAGS:添加链接选项,如链接动态库可设为-L ./lib -lm(-L指定库目录,-lm链接数学库)