外观
00.深入浅出Makefile-精炼
约 7806 字大约 26 分钟
滤波算法个人随笔记录
2022-06-20
1.2 makefile是一个描述文件
- 定义一系列的规则来指定源文件编译的先后顺序
- 拥有特定的语法规则,支持函数定义和函数调用
- 能够直接集成操作系统中的各种命令
1.5 make程序的使用示例
make -f mf.txt hello
功能说明:以hello关键字作为目标查找mf.txt文件(-f 后),并执行hello处的命令。
1.6 make程序的简写实例
make hello
功能说明:以hello关键字作为目标查找makefile或Makefile文件,并执行hello处的命令。
make
功能说明:查找makefile或Makefile文件中最顶层目标。并执行最顶层目标的命令。
2.5 依赖规则
- 当目标对应的文件不存在,执行对应命令
- 当依赖在时间上比目标更新(代表程序改动),执行对应命令
- 当依赖关系连续发生时,对比依赖链上的每一个目标,确保都存在
2.6 小技巧
makefile 中可以在命令前加上@符,作用为命令无回显。
3.1 默认情况下
- make认为目标对应着一个文件
- make 比较目标文件和依赖文件的新旧关系,决定是否执行命令
- make 以文件处理作为第一优先级
3.4 makefile 中的伪目标
- 通过.PHONY关键字声明一个伪目标
- 伪目标不对应任何实际的文件
- 不管伪目标的依赖是否更新,命令总是执行
3.8 技巧:绕开.PHONY 关键字定义伪目标(GNU的make才有.PHONY,若不是GNU怎么办? )
原理︰如果一个规则没有命令或者依赖,并且它的目标不是一个存在的文件名;在执行此规则时,目标总会被认为是最新的。
4.1 makefile 中的变量
- makefile 中的变量只代表文本数据(字符串)
- makefile 中的变量名规则
- 变量名可以包含字符,数字,下划线
- 不能包含“:”,“#”,“=”," "
- 变量名大小写敏感
4.5 简单赋值(∶=)
- 程序设计语言中的通用的赋值方式
- 只针对当前语句的变量有效

4.6 递归赋值( = )
- 赋值操作可能影响多个其它变量
- 所有与目标变量相关的其它变量都将受到影响

4.7 条件赋值( ?= )
- 如果变量未定义,使用赋值符号中的值定义变量
- 如果变量已经定义,赋值无效

4.8 追加赋值(+=)
- 原变量值之后加上一个新值
- 原变量值与新值之间由空格隔开

5.2 自动变量的意义
- $@
- 当前规则中触发命令被执行的目标
- $^
- 当前规则中的所有依赖
- $<
- 当前规则中的第一个依赖
5.3 自动变量的使用示例

注意:
1、“$”对于makefile有特殊含义
- 输出时需要加上一个“$”进行转义
2、“$@”对于 Bash Shell有特殊含义
- 输出时需要加上“\”进行转义
5.5 一些特殊变量的含义
$(MAKE)
- 当前make解释器的文件名
$(MAKECMDGOALS )
- 命令行中指定的目标名(make的命令行参数)
$(MAKEFILE_LIST)
- make所需要处理的makefile文件列表
- 当前makefile 的文件名总是位于列表的最后
- 文件名之间以空格进行分隔
$(MAKE_VERSION)
- 当前make解释器的版本
$(CURDIR)
- 当make 解释器的工作目录
$(.VARIABLES)
- 所有已经定义的变量名列表(预定义变量和自定义变量)
.PHONY : all out
TDelphi := Delphi Tang
D.T.Software := D.T.
all out :
@echo "$(MAKE)"
@echo "$(MAKECMDGOALS)"
@echo "$(MAKEFILE_LIST)"
@echo "$(MAKE_VERSION)"
@echo "$(CURDIR)"
@echo "$(.VARIABLES)"
6.1 变量值的替换
使用指定字符(串)替换变量值中的后缀字符(串)
语法格式︰
$(var:a=b)或${var:a=b}- 替换表达式中(两个括号里面)不能有任何的空格
- make 中支持使用
${}对变量进行取值
src := a.cc b.cc c.cc
obj := $(src:cc=o)
test :
@echo "obj =>$(obj)"
6.2 变量的模式替换
使用%保留变量值中的指定字符,替换其它字符
语法格式︰
$(var:a%b=x%y)或${var:a%b=x%y}- 替换表达式中(两个括号里面)不能有任何的空格
- make 中支持使用
${}对变量进行取值
src := a1b.c a2b.c a3b.c
obj := $(src:a%b.c=x%y)
test :
@echo "obj => $(obj)"
6.3 规则中的模式替换
targets : target-pattern : prereq-pattern
command1
command2
….
意义:通过target-pattern从targets中匹配子目标;再通过prereq-pattern从子目标生成依赖;进而构成完整的规则。
6.4 规则中的模式替换示例
$(OBJS) : %.o : %.c
① $(OBJS) : %.o 模式匹配,把目标OBJS中符合模式的目标名取出来
② : %.c 将目标名换成.c
③ $(OBJS) : 是对列表进行处理;与10.4相对应,是面向当前目录

6.6 变量值的嵌套引用
- 一个变量名之中可以包含对其它变量的引用
- 套嵌引用的本质是使用一个变量表示另外一个变量

6.7 命令行变量
- 运行make 时,在命令行定义变量
- 命令行变量默认覆盖makefile 中定义的变量

6.8 override 关键字
- 用于指示makefile 中定义的变量不能被覆盖
- 变量的定义和赋值都需要使用override 关键字

6.9 define关键字
- 用于在makefile 中定义多行变量
- 多行变量的定义从变量名开始到endef结束
- 可使用override关键字防止变量被覆盖
- define定义的变量等价于使用=定义的变量

7.3 变量在不同makefile之间的传递方式
- 直接在外部定义环境变量进行传递(在当前makefile执行时,都将改变)
- 使用export定义变量进行传递(定义临时环境变量,不会修改系统的环境变量)(在当前makefile执行时,都将改变)
- 定义make命令行变量进行传递(推荐)(在当前makefile执行时,都将改变)
Makefile1
JAVA_HOME := java home #修改环境变量
export var := Joker #export临时环境变量传递
new := Cryin
test :
@echo "JAVA_HOME => $(JAVA_HOME)"
@echo "make another file ..."
@$(MAKE) -f makefile.2
@(MAKE)−fmakefile.2new:=(new) #命令行传递
Makefile2
test:
@echo "JAVA_HOME => $(JAVA_HOME)"
@echo "var => $(var)"
@echo "new => $(new)"

7.5 目标变量(局部变量)
作用域只在指定目标及连带规则中
- target : name <assignment> value
- target : override name <assignment> value
var := Cryin
test : var := test-var
test :
@echo "test : "
@echo "var =>$(var)"
打印test-var
7.6 模式变量
模式变量是目标变量的扩展
作用域只在符合模式的目标及连带规则中
- pattern : name <assignment> value
- pattern : override name <assignment> value
new := Cryin
%e : override new := test-new #%e 作用于结尾是e的目标
rule :
@echo "rule: "
@echo "new =>$(new)"
7.8 makefile 中的三种变量
- 全局变量︰makefile外部定义的环境变量(当前操作系统的环境变量;在不同makefile之间传递变量值,一般不这么做;使用export关键字,修饰变量进行值传递,或者使用命令行进行值传递)
- 文件变量︰makefile 中定义的变量(作用域:当前文件当中)
- 局部变量︰指定目标的变量(作用域:指定目标的规则当中,及依赖目标所对应的规则当中)
8.2 条件判断语句的语法说明
- 注意事项
条件判断语句只能用于控制make 实际执行的语句;但是,不能控制规则中命令的执行过程。
- 常用形式
- ifxxx(arg1,arg2)
- 其它合法形式
- ifxxx "arg1" "arg2"
- ifxxx 'arg1' 'arg2'
- ifxxx "arg1" 'arg2'
- Ifxxx 'arg1' "arg2"

- 条件判断关键字

8.4 一些工程经验
- 条件判断语句之前可以有空格,但不能有Tab 字符('\t'),命令前用Tab键。
- 在条件语句中不要使用自动变量($@, ,< )
- 一条完整的条件语句必须位于同一个makefile 中
- 条件判断类似C语言中的宏,预处理阶段有效,执行阶段无效
- make 在加载makefile时
- 首先计算表达式的值(赋值方式不同,计算方式不同)
- 根据判断语句的表达式决定执行的内容
8.7 小结
- 条件判断根据条件的值来决定make 的执行
- 条件判断可以比较两个不同变量或者变量和常量值
- 条件判断在预处理阶段有效,执行阶段无效
- 条件判断不能控制规则中命令的执行过程
9.1 makefile 中支持函数的概念
- make 解释器提供了一系列的函数供makefile 调用
- 在makefile 中支持自定义函数实现,并调用执行
- 通过define关键字实现自定义函数
9.3 深入理解自定义函数
- 自定义函数是一个多行变量,无法直接调用
- 自定义函数是一种过程调用,没有任何的返回值
- 自定义函数用于定义命令集合,并应用于规则中
.PHONY : test
define func1
@echo "My name is $(0)"
endef
define func2
@echo "My name is $(0)"
@echo "Param 1 => $(1)"
@echo "Param 2 => $(2)"
endef
var := $(call func1) #call****作用下将实参的值替换到对应的位置
new := $(func1)
test :
@echo "new => $(new)"
@echo "var => $(var)" #call的作用只是替换入参变量,并没有执行
$(call func1) #① 将实参的值替换到对应的位置 ② @echo My name is func1
$(call func2, Sumjess, Exp.Joker)

9.5 make解释器中的预定义函数
- make的函数提供了处理文件名,变量和命令的函数
- 可以在需要的地方调用函数来处理指定的参数
- 函数在调用的地方被替换为处理结果
9.7 问题:为什么自定义函数和预定义函数的调用形式完全不同?
9.8 本质剖析
- makefile 中不支持真正意义上的自定义函数
- 自定义函数的本质是多行变量
- 预定义的call函数在调用时将参数传递给多行变量
- 自定义函数是call函数的实参,并在call中被执行
10.2 工具原料
- $(wildcard _pattern)
- 获取当前工作目录中满足_pattern的文件或目录列表
- $(addprefix _prefix,_names)
- 给名字列表_names中的每一个名字增加前缀_prefix
10.3 关键技巧
- 自动获取当前目录下的源文件列表(函数调用)
- SRCS := $(wildcard *.c)
- 根据源文件列表生成目标文件列表(变量的值替换)
- OBJS := $( SRCS: .c=.o)
- 对每一个目标文件列表加上路径前缀(函数调用)
- OBJS := $( addprefix path/ , $(OBJS ) )
10.4 规则中的模式替换(目录结构)
当前工作目录下逐个对工作目录中的文件进行,与6.4相对应.

-g 生成调试信息。GNU 调试器可利用该信息。
反编译:objdump -S hello-makefile.out
生成调试信息
11.7 预备工作(原材料)
- Linux命令sed
- 编译器依赖生成选项gcc-MM ( gcc-M )
11.8 Linux中的sed命令
- sed是一个流编辑器,用于流文本的修改(增/删/查/改)
- sed可用于流文本的中的字符串替换
- sed的字符串替换方式为 : sed 's: src:des:g'

11.9 sed的正则表达式支持
- 在sed 中可以用正在表达式匹配替换目标
- 并且可以使用匹配的目标生成替换结果


sed 's,(.).o[ :],objs/\1.o : ,g'
① 分隔符:既可以是,也可以是:
② (.).o[ :]
- (.).o[ :]
- 想匹配.o文件的路径
- \文件夹名字\文件夹名字.o
- 在这个路径之后,如果有空格有冒号也算匹配成功
- 无论匹配目标后面有多少个空格或者:都转换成空格:空格(sed 's,(.).o[ :],objs/\1.o : ,g')

- objs/\1.o :
- 将把前面的匹配结果加上objs/前缀
11.10 gcc 关键编译选项
- 生成依赖关系
- 获取目标的完整依赖关系
- gcc -M test.c
- 获取目标的部分依赖关系
- gcc -MM test.c
- gcc -MM -E main.c 其中的-E告诉编译器只要做初步解析即可,无需做进一步的工作,会高效一些
- 获取目标的完整依赖关系
11.11 小技巧:拆分目标的依赖
- 将目标的完整依赖拆分为多个部分依赖

12.2 make 对include关键字的处理方式
- 在当前目录搜索或指定目录搜索目标文件
- 搜索成功:将文件内容搬入当前makefile 中
- 搜索失败:产生警告
- 以文件名作为目标查找并执行对应规则
- 当文件名对应的规则不存在时,最终产生错误
12.5 makefile 中命令的执行机制
- 规则中的每个命令默认是在一个新的进程中执行(Shell)
- 可以通过接续符(😉****将多个命令组合成一个命令(就算出错也继续执行下一个)
- 组合的命令依次在同一个进程中被执行
- set -e 指定发生错误后立即退出执行
$(filter %.c, $^) 只取依赖中所有的.c文件
13.3 为什么一些.dep 依赖文件会被重复创建多次?

13.4 问题本质分析
- deps文件夹的时间属性会因为依赖文件创建而发生改变
- make 发现deps文件夹比对应的目标更新
- 触发相应规则的重新解析和命令的执行
13.5 解决方案的优化:使用ifeq动态决定.dep目标的依赖
ifeq ("$(wildcard $(DIR_DEPS))", "")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif
@echo "Creating $@ ..."
@set -e; \
$(CC) -MM -E $(filter %.c, $^) | sed 's,(.).o[ :],objs/\1.o : ,g' > $@
13.7 include暗黑操作一
使用减号(-)不但关闭了include 发出的警告,同时关闭了错误;当错误发生时make将忽略这些错误!
13.9 include暗黑操作三
① 当include文件后,make检查这个文件名,有没有对应的规则
② 如果有,则进步一检查依赖的时间戳是否比文件更新
③ 如果确实更新,就会执行规则下的命令
13.11 关于include的总结
总结 1
- 当目标文件不存在
- 以文件名查找规则,并执行
- 当目标文件不存在,且查找到的规则中创建了目标文件
- 将创建成功的目标文件包含进当前makefile
总结 2
- 当目标文件存在
- 将目标文件包含进当前****makefile
- 以目标文件名查找是否有相应规则
- YES : 比较规则的依赖关系,决定是否执行规则的命令
- NO : NULL(无操作)
总结 3
- 当目标文件存在,且目标名对应的规则被执行
- 规则中的命令更新了目标文件
- make重新包含目标文件,替换之前包含的内容
- 目标文件未被更新
- NULL(无操作)
- 规则中的命令更新了目标文件
14.5 小结
- makefile 中可以将目标的依赖拆分写到不同的地方
- include关键字能够触发相应规则的执行
- 如果规则的执行导致依赖更新,可能导致再次解释执行相应规则
- 依赖文件也需要依赖于源文件得到正确的编译决策
- 自动生成文件间的依赖关系能够提高makefile的移植性
15.3 makefile 中出现同名目标时
- 依赖︰
- 所有的依赖将合并在一起,成为目标的最终依赖
- 命令∶
- 当多处出现同一目标的命令时,make发出警告
- 所有之前定义的命令被最后定义的命令取代
15.4 注意事项
当使用include关键字包含其它文件时,需要确保被包含文件中的同名目标只有依赖,没有命令;否则,同名目标的命令将被覆盖!
15.6 什么是隐式规则( built-in rules ) ?
- make提供了一些常用的,例行的规则实现
- 当相应目标的规则未提供时,make尝试使用隐式规则
16.2 隐式规则的副作用
- 编译行为难以控制
- 大量使用隐式规则可能产生意想不到的编译行为
- 编译效率低下
- make从隐式规则和自定义规则中选择最终使用的规则
16.3 隐式规则链
当依赖的目标不存在时,make 会极力组合各种隐式规则对目标进行创建,进而产生意料之外的编译行为!

16.4 问题:make提供了多少隐式规则?如何查看隐式规则?
查看隐式规则
- 查看所有:make -p
- 查看具体规则:make -p | grep "XXX”

16.5 隐式规则的禁用
- 局部禁用
- 在makefile 中自定义规则
- 在makefile 中定义模式(如:%.o : %.p )
- 全局禁用
- make -r
16.5 后缀规则简介
- 后缀规则是旧式的“模式规则”
- 可以通过后缀描述的方式自定义规则

- 双后缀规则
- 定义一对文件后缀(依赖文件后缀和目标文件后缀)
- 如: .cpp.o <--> %.o :%.cpp
- 定义一对文件后缀(依赖文件后缀和目标文件后缀)
- 单后缀规则
- 定义单个文件后缀(源文件后缀)
- 如:.c <--> %∶%.c
- 定义单个文件后缀(源文件后缀)
16.5 关于后缀规则的注意事项
- 后缀规则中不允许有依赖
- 后缀规则必须有命令,否则无意义
- 后缀规则将逐步被模式规则取代
16.7 小结
- 隐式规则可能造成意想不到的编译行为
- 在实际工程项目中尽量不使用隐式规则
- 后缀规则是一种旧式的模式规则
- 后缀规则正逐步被模式规则取代
17.4 特殊的预定义变量VPATH(全大写)
vPATH变量的值用于指示make 如何查找文件
不同文件夹可作为VPATH的值同时出现
文件夹的名字之间需要使用分隔符进行区分

17.5 make 对于VPATH值的处理方式
- 当前文件夹找不到需要的文件时,VPATH会被使用
- make会在VPATH指定的文件夹中依次搜索文件
- 当多个文件夹存在同名文件时,选择第一次搜索到的文件
17.6 注意事项
- VPATH只能决定make的搜索路径,无法决定命令的搜索路径
- 对于特定的编译命令( gcc ) ,需要独立指定编译搜索路径

17.8 VPATH存在的问题
inc文件夹中意外出现源文件( C / Cpp文件),那么可能产生编译错误!
17.9 替换方案:vpath关键字(全小写)
为不同类型的文件指定不同的搜索路径
语法︰
- 在Directory中搜索符合Pattern的规则的文件

17.10 取消搜索规则
取消已经设置的某个搜索规则
- .vpath Pattern
例: vpath %.h inc # 在inc中搜索.h文件
vpath %.h # 不再到inc中搜索.h文件
取消所有已经设置的规则
- vpath
17.12 小结
- VPATH变量用于指示make 如何查找文件
- make 会在 VPATH 指定的文件夹中依次搜索文件
- vpath关键字可以为不同类型的文件指定不同的搜索路径
- vpath 比 VPATH 更灵活易用,可动态设置/取消搜索路径
18.3 编程实验:VPATH和vpath同时出现
18.4 实验结论
make首先在当前文件夹搜索需要的文件
如果失败:
- make优先在vpath 指定的文件夹中搜索目标文件
- 当vpath 搜索失败时,转而搜索VPATH 指定的文件夹

18.7 编程实验:vpath 指定多个文件夹
18.8 实验结论
make首先在当前文件夹搜索需要的文件
如果失败:
- make 以自上而下的顺序搜索vpath指定的文件夹
- 当找到目标文件,搜索结束

18.11 编程实验:搜索路径对目标的影响
将生成的app.out移到src文件夹,再make,会发生什么呢?
18.12 实验结论
- 当app.out完全不存在∶
- make在当前文件下创建app.out
- 当src文件夹中存在app.out :
- 所有目标和依赖的新旧关系不变,make 不会重新创建app.out
- 当依赖文件被更新,make在当前文件夹下创建app.out
18.13 问题
当依赖改变时,如何使得src下的app.out被更新?
18.14 解决方案
- 使用GPATH特殊变量指定目标文件夹
- GPATH:= src
- 当app.out完全不存在
- make默认在当前文件夹创建app.out
- 当app.out存在于src ,且依赖文件被更新
- make在 src 中创建app.out
- 当app.out完全不存在
18.15 工程项目中的几点建议
- 尽量使用vpath为不同文件指定搜索路径
- 不要在源码文件夹中生成目标文件
- 为编译得到的结果创建独立的文件夹
- 避免VPATH和GPATH特殊变量的使用
19.3 工具原料
- $(wildcard $(DIR)/_pattern)
- 获取$(DIR)文件夹中满足_pattern的文件
- $(notdir _names)
- 去除_names中每一个文件名的路径前缀
- $(patsubst _pattern,replacement,_text)
- 将_text 中符合_pattern的部分替换为replacement
19.4 关键技巧
自动获取源文件列表(函数调用)
SRCS := $(wildcard src/* .c)
根据源文件列表生成目标文件列表(变量的值替换)
OBJS := $(SRCS: .c=.o)
替换每一个目标文件的路径前缀(函数调用)
OBJS := (patsubstsrc/(OBJS) )
19.8 小结
- 工程项目中不希望源码文件夹在编译时被改动
- 模式规则的灵活运用使得makefile具有复用性
- 变量的灵活运用使得makefile具有扩展性
- 规模较小的项目没必要使用自动生成依赖关系的解决方案(取维护性牺牲效率)
- 规模较小的项目可以直接让源文件依赖于头文件(易于维护)
第二十部分 :打造专业的编译环境(上)
20.1 大型项目的目录结构(无第三方库)

20.2 项目架构设计分析
- 项目被划分为多个不同模块
- 每个模块的代码用一个文件夹进行管理
- 文件夹由inc , src , makefile 构成
- 每个模块的对外函数声明统一放置于common/inc中
- 如: common.h xxxfunc.h
- 每个模块的代码用一个文件夹进行管理
20.3 需要打造的编译环境
- 源码文件夹在编译时不能被改动(只读文件夹)
- 在编译时自动创建文件夹( build )用于存放编译结果
- 编译过程中能够自动生成依赖关系,自动搜索需要的文件
- 每个模块可以拥有自己独立的编译方式
- 支持调试版本的编译选项
20.4 解决方案设计
第1阶段︰将每个模块中的代码编译成静态库文件

第2阶段︰将每个模块的静态库文件链接成最终可执行程序

20.5 第一阶段任务
- 完成可用于各个模块编译的makefile文件
- 每个模块的编译结果为静态库文件( .a文件)
20.6 关键的实现要点
- 自动生成依赖关系( gcc -MM )
- 自动搜索需要的文件( vpath )
- 将目标文件打包为静态库文件( ar crs )
20.7 模块makefile 中的构成

20.8 编程实验:模块的编译makefile
20.9 To be continued ...
思考︰
如何编写项目makefile使其能够触发模块makefile 的调用,并最终生成可执行程序?
第二十一部分 :打造专业的编译环境(中)
21.1 第二阶段任务
- 完成编译整个工程的makefile文件
- 调用模块makefile 编译生成静态库文件
- 链接所有模块的静态库文件,最终得到可执行程序

21.2 关键的实现要点
- 如何自动创建build文件夹以及子文件夹?
- 如何进入每一个模块文件夹进行编译?
- 编译成功后如何链接所有模块静态库?
21.3 开发中的经验假设
项目中的各个模块在设计阶段就已经基本确定,因此,在之后的开发过程中不会频繁随意的增加或减少!
21.4 解决方案设计
- 定义变量保存模块名列表(模块名变量)
- 利用Shell中的for循环遍历模块名变量
- 在for循环中进入模块文件夹进行编译
- 循环结束后链接所有的模块静态库文件
21.5 makefile 中嵌入Shell 的 for循环

21.6 注意事项
makefile 中嵌入Shell代码时,如果需要使用Shell变量的值,必须在变量名前加上$$(例: $$dir ) !
21.7 编程实验:Shell中的for循环
21.8 工程makefile 中的关键构成

21.9 链接时的注意事项
- gcc在进行静态库链接时必须遵循严格的依赖关系
- gcc -o app.out x.a y.a z.a
- 其中的依赖关系必须为:x.a y.a , y.a->z.a
- 默认情况下遵循自左向右的依赖关系
- gcc -o app.out x.a y.a z.a
- 如果不清楚库间的依赖,可以使用-Xlinker自动确定依赖关系
- gcc -o app.out -Xlinker "-(" z.a y.a x.z -Xlinker "-)"
21.10 编程实验:工程的编译makefile
21.11 To be continued ...
思考:
当前整个项目的makefile是否存在潜在的问题?是否需要重构?
第二十二部分 :打造专业的编译环境(下)
22.1 问题
当前整个项目的makefile是否存在潜在的问题?是否需要重构?
- 问题一
所有模块makefile 中使用的编译路径均为写死的绝对路径,—旦项目文件夹移动,编译必将失败!

22.2 解决方案
- 在工程makefile 中获取项目的源码路径
- 根据项目源码路径:
- 拼接得到编译文件夹的路径(DIR_BUILD)
- 拼接得到全局包含路径(DIR_COMMON_INC)
- 通过定义命令行变量将路径传递给模块makefile
22.3 编程实验:自动确定编译文件夹路径
22.4 问题二
- 所有模块makefile 的内容完全相同(复制粘贴)
- 当模块makefile 需要改动时,将涉及多处相同的改动!

22.5 解决方案
- 将模块makefile拆分为两个模板文件
- mod-cfg.mk :定义可能改变的变量
- mod-rule.mk :定义相对稳定的变量和规则
- 默认情况下
- 模块makefile复用模板文件实现功能(include)
22.6 关键问题
- 模块makefile 如何知道模板文件的具体位置?
- 解决方案:
- 通过命令行变量进行模板文件位置的传递
22.7 编程实验:模块makefile的拆分
22.8 工程makefile的重构
- 拆分命令变量,项目变量,以及其它变量和规则到不同又件
- cmd-cfg.mk :定义命令相关的变量
- pro-cfg.mk :定义项目变量以及编译路径变量等
- pro-rule.mk :定义其它变量和规则
- 最后的工程makefile通过包含拆分后的文件构成( include )
22.9 编程实验:工程makefile的拆分
22.10 小结
- 大型项目的编译环境是由不同makefile构成的
- 编译环境的设计需要依据项目的整体架构设计
- 整个项目的编译过程可以分解为不同阶段
- 根据不同的阶段有针对性的对makefile进行设计
- makefile 也需要考虑复用性和维护性等基本程序特性
第二十三部分 :模块独立编译的支持
23.1 问题
一般而言,不同工程师负责不同模块的开发;编译环境中如何支持模块的独立编译?
23.2 问题背景
- 大型项目的代码文件成干上万,完整编译的时间较长
- 编写模块代码时,可通过编译检查语法错误
- 为了提高开发效率,需要支持指定模块的独立编译

23.3 解决方案
- 将模块名( module )作为目标名(伪目标)建立规则
- 目标( module )对应的依赖为build builid/module
- 规则中的命令进入对应的模块文件夹进行编译
- 编译结果存放于build文件夹下
23.4 关键技术点
- 如何获取make命令行中指定编译的模块名?
- 预定义变量:$(MAKECMDGOALS)
- 命令行中指定的目标名(make的命令行参数)
- 预定义变量:$(MAKECMDGOALS)

23.5 编程实验:模块的独立编译make module
- makefile 中的代码复用
- 当不同规则中的命令大量重复时,可考虑自定义函数
- makefile 中的自定义函数是代码复用的一种方式

23.6 思路
- 将编译模块的命令集作为自定义函数的具体实现
- 函数参数为模块名,函数调用后编译参数指定的模块
- 在不同的规则中调用该函数

23.7 编程实验:makefile中的代码复用自定义函数
23.8 小结
- 编写模块代码时可通过模块独立编译快速检查语法错误
- 自动变量只能在规则的命令中使用,不能在依赖中使用
- makefile 中的自定义函数是代码复用的一种方式
- 当不同规则中的命令大量重复时,可考虑自定义函数
第二十四部分 :第三方库的使用支持
24.1 问题
当需要使用第三方库文件时,编译环境中的makefile 改如何修改?
24.2 经验假设
- 第三方库通过函数调用的方式提供库中的功能
- 库文件发布时都附带了声明库函数原型的头文件
- 编译阶段使用头文件,链接阶段使用库文件
24.3 第三方库在项目中的位置

24.4 第三方库的编译阶段支持
- 定义变量DIR_LIBS_INC用于指示头文件的存储位置
- DIR_LIBS_INC := $(DIR_PROJECT)/libs/inc
- 使用DIR_LIBS_INC提示make头文件的存储位置
- vpath %$(TYPE_INC) $(DIR_LIBS_INC)
- 使用DIR_LIBS_INC提示编译器头文件的存储位置
- CFLAGS += -I$(DIR_LIBS_INC)
24.5 编程实验:第一阶段编译支持
24.6 注意事项
- 定义 DIR_LIBS_LIB := libs/lib(第三方库所在路径)
- 链接时不会直接链接DIR_LIBS_LIB中的库文件
- 需要先将库文件拷贝到DIR_BUILD文件夹
- 必须考虑拷贝后的库文件和原始库文件的新旧关系

24.7 第三方库的链接阶段支持
- 定义变量EXTERNAL_LIB用于保存第三方库列表
- 目标link需要依赖于第三方库列表

第三方库名称与自己的库名称相同时,优先使用自己的库。
24.8 编程实验:第二阶段链接支持
24.9 小结
- 编译环境必须支持第三方库的使用(静态库或动态库)
- 工程开发中一般会使用特殊的文件夹存放第三方库
- 第三方库所附带的头文件用于声明库函数(编译阶段需要)
- 在链接阶段先将库文件拷贝到build文件夹,再进行链接
