导言
以前经常用Makefile
,但是仅是用而已,没有从头折腾过,所以,谈不上很深的理解。
最近针对一些场景做了一些实践,对于Makefile
中经常遇到的各种变量类型,和目标依赖关系,以及与make
并行编译的关系,谈点自己的后知后觉 😃
变量类型
按照较为顺畅的逻辑,从变量存在的开头到结尾,介绍Makefile
中的几种变量类型,以及它们的一些典型的使用场景。
- 环境变量
- 内置变量
CC
、CXX
编译程序变量等- 编译选项变量
CFLAGS
、链接选项变量LDFLAGS
等- 建议使用此内置变量,以利于可读性和规范性
- 入参变量
可以作为某些特性是否开启的定制,例如,是否生成DEBUG版本,
- export共享变量
可以传递给
make
子过程,包含其它组件进行集成编译
- 立即变量
var := somevalue
适合记录某个恒定值,例如,编译开始时间等
- 延迟变量
var = delayvalue
根据出现位置,每次按照变量赋值规则进行递归展开,充分变量化,例如,编译的当前时间,需要每次根据出现位置进行新的评估
- 条件变量
var ?= defaultvalue
适配外部编译框架
export
变量,以及make
命令入参变量,使得组件可以既可以独立编译,也可以被集成进行编译
变量追加操作
注意,区分立即变量和延迟变量
模式规则自动变量
- 模式规则变量、
-
- $@(目标文件名)
-
- $<(第一个依赖文件名)
-
- $^(所有依赖文件列表)
强烈建议使用,以利于命名的统一和可读性、规范性
目标依赖和并行编译
make并行编译命令
# 简单加上-j选项就可以触发并行编译,加快编译
make -j[num]
并行场景下的依赖
在Makefile
中目标之间的依赖关系非常重要,将决定Makefile
文件是否可以正确被make -j[num]
命令并行编译。
我们知道make
对于大量的源代码文件的编译,可以通过模糊匹配规则,实际上被当做多个目标,可以充分并行起来。不过,很多Makefile
在撰写的时间,经常希望将唯一性的clean
目标作为第一个被执行的目标,注意,并不是默认生成目标,以利于清除历史编译残留。
这样的逻辑在单线程make
编译时,按照依赖声明的顺序,一个个顺次执行,它并不会遇到什么问题,但是,在并行编译时将会遇到问题。
在并行编译场景下,clean
目标,以及其它源代码编译目标,被充分并行起来。这样就会出现并发问题,OBJ
目标编译的输出,可能会偶发性地被clean
目标定义的规则任务将资源清除掉了!
解决办法是明显的,就是准确描述依赖关系,让所有的依赖clean
的目标,例如,OBJ
目标,准确地描述依赖关系,而不依赖目标的声明顺序。
通过正确地声明依赖关系,就可以让
make
推导出来正确的依赖顺序,正确地并行起来!
演示例子
# Demo shows var usages in makefile
# 约定:+ 强烈建议使用make定义的内置变量和自动变量,以利于可读性和规范性
# 约定:+ 变量使用大写
# 约定:+ 可选参数需要通过条件变量提供默认处理
# 约定:+ 内部变量以下划线开头命名$(info "inherit envrionment var from host, PATH: $(PATH) ...")# 通用做法,通过内置变量定制编译选项
CFLAGS := -Wall -W -g -pipe -Wno-unused-parameter
ifeq ($(DEBUG_VERSION), yes)
# 变量转编译器可识别宏定义CFLAGS += -O0 -D_DEBUG
elseCFLAGS += -O2
endif$(info "BIN var from make command optional parameter, BIN : $(BIN) ...")
BIN ?= demo$(info "Try to set default value for BIN : $(BIN) ...")simplevar = a
$(info "After set var using = operator, value: $(simplevar)")simplevar += b
$(info "After set var using += operator, value: $(simplevar)")VAR_CURRENT_TIME = $(shell date +%Y-%m-%d_%H-%M-%S)
CONST_HUMAN_TIME := $(shell date +%Y-%m-%d_%H-%M-%S)
$(info "CONST_HUMAN_TIME using := operator : $(CONST_HUMAN_TIME)")$(shell sleep 1)CONST_START_TIME := $(shell date +%s)
NOW_TIME = $(shell date +%s)
$(info "CONST_START_TIME using := operator : $(CONST_START_TIME)")$(shell sleep 1)$(info "NOW_TIME: $(NOW_TIME)")
$(info "VAR_CURRENT_TIME: $(VAR_CURRENT_TIME)")$(shell sleep 3)
$(info "VAR_CURRENT_TIME after sleep: $(VAR_CURRENT_TIME)")vpath %.c .# 使用模糊匹配
SRCS := $(wildcard *.c)# 简单文本替换
OBJS := $(SRCS:.c=.o).PHONY: $(BIN) clean $(OBJS)
# 自顶向下为并行编译声明依赖关系
$(BIN): $(OBJS)
$(OBJS): clean# 建议使用自动变量
$(BIN):$(CC) $^ -o $@ $(LDFLAGS)@echo "Elapsed time: $(shell expr $(NOW_TIME) - $(CONST_START_TIME)) seconds"# 模糊匹配规则
$(OBJS): %.o: %.c@echo "Begin compile $< ..."$(CC) $(CFLAGS) -c $< -o $@@echo "Has finished compiling $<"clean:@echo "Begin clean old resources ..."$(RM) *.o $(BIN)@echo "Has finished clean old resources"
- 尽量使用本文介绍的实践
- 通过
make
和make -j
打印过程,可以明显看出来并行编译,但是,clean
过程被准确作为第一个目标执行