1 Makefile规则#
1 | 目标(target)…: 依赖(prerequiries)… |
如果“依赖文件”
比“目标文件”
更加新,那么执行“命令”
来重新生成“目标文件”
。
命令被执行的 2 个条件:依赖文件比目标文件新,或是 目标文件还没生成。
2 一步一步完善 Makefile#
第 1 个 Makefile,简单粗暴,效率低:
1 | test : main.c sub.c sub.h |
第 2 个 Makefile,效率高,相似规则太多太啰嗦,不支持检测头文件:
1 | test : main.o sub.o |
第 3 个 Makefile,效率高,精炼,不支持检测头文件:
1 | test : main.o sub.o |
第 4 个 Makefile,效率高,精炼,支持检测头文件(但是需要手工添加头文件规则):
1 | test : main.o sub.o |
2.1 模式规则#
1 | %.o:%.c |
前面第3第4个Makefile都用到了模式规则。
2.2 自动变量#
1 | $@--目标文件 |
2.2.1 函数传参#
函数传参也属于自动变量。上图的make结果为:
2.3 立即变量和延时变量#
1 | A := $(C) //立即变量 |
2.3 变量导出#
A makefile
中的变量无法在B makefile
识别,因此要用export
导出如:
1 | export CC = $(CROSS_COMPILE)gcc |
2.4 Makefile 中使用 shell 命令#
1 | PWD=$(shell pwd) |
2.5 伪目标#
.PHONY
表示伪目标。表示无条件执行目标。makefile将不会判断该目标是否存在或者该目标是否需要更新。
1 | clean: |
2.6 产生依赖文件#
第5个Makefile。效率高,精炼,支持自动检测头文件:
1 | objs := main.o sub.o |
3 常用函数#
3.1 字符串相关#
3.1.1 subst
#
1 | $(subst from,to,text) |
在文本text
中使用to
替换每一处from
。
比如:
1 | $(subst ee,EE,feet on the street) |
结果为fEEt on the strEEt
3.1.2 patsubst
#
1 | $(patsubst pattern,replacement,text) |
寻找text
中符合格式pattern
的字,用replacement
替换它们。
pattern
和replacement
中可以使用通配符。
比如:
1 | $(patsubst %.c,%.o,x.c.c bar.c) |
结果为: x.c.o bar.o
3.1.3 strip
#
1 | $(strip string) |
去掉前导和结尾空格,并将中间的多个空格压缩为单个空格。
比如:
1 | $(strip a b c ) |
结果为:foo.c bar.c baz.s
3.1.4 findstring
#
1 | $(findstring find,in) |
在字符串in
中搜寻find
,如果找到,则返回值是find
,否则返回值为空。
比如:
1 | $(findstring a,a b c) |
将分别产生值a
和(空字符串)
3.1.5 filter
1 | $(filter pattern...,text) |
返回在text
中由空格隔开且匹配格式pattern...
的字,去除不符合格式pattern...
的字。
比如:
1 | $(filter %.c %.s,foo.c bar.c baz.s ugh.h) |
结果为foo.c bar.c baz.s
。
3.1.6 filter-out
#
1 | $(filter-out pattern...,text) |
返回在text
中由空格隔开且不匹配格式pattern...
的字,去除符合格式pattern...
的字。它是函数 filter 的反函数。
比如:
1 | $(filter %.c %.s,foo.c bar.c baz.s ugh.h) |
结果为ugh.h
。
3.1.7 sort
#
1 | $(sort list) |
将list
中的字按字母顺序排序,并去掉重复的字。输出由单个空格隔开的字的列表。
比如:
1 | $(sort foo bar lose) |
返回值是bar foo lose
3.2 文件名相关#
3.2.1 dir
#
1 | $(dir names...) |
抽取names...
中每一个文件名的路径部分,文件名的路径部分包括从文件名的首字符到最后一个斜杠(含斜杠)之前的一切字符。
比如:
1 | $(dir src/foo.c hacks) |
结果为src/ ./
。
3.2.2 notdir
#
1 | $(notdir names...) |
抽取names...
中每一个文件名中除路径部分外一切字符(真正的文件名)。
比如:
1 | $(notdir src/foo.c hacks) |
结果为foo.c hacks
。
3.2.3 suffix
#
1 | $(suffix names...) |
抽取names...
中每一个文件名的后缀。
比如:
1 | $(suffix src/foo.c src-1.0/bar.c hacks) |
结果为.c .c
。
3.2.4 basename
#
1 | $(basename names...) |
抽取names...
中每一个文件名中除后缀外一切字符。
比如:
1 | $(basename src/foo.c src-1.0/bar hacks) |
结果为src/foo src-1.0/bar hacks
。
3.2.5 addsuffix
#
1 | $(addsuffix suffix,names...) |
参数 names...
是一系列的文件名,文件名之间用空格隔开; suffix 是一个后缀名。将 suffix(后缀)的值附加在每一个独立文件名的后面,完成后将文件名串联起来,它们之间用单个空格隔开。
比如:
1 | $(addsuffix .c,foo bar) |
结果为foo.c bar.c
。
3.2.6 addprefix
#
1 | $(addprefix prefix,names...) |
参数 names
是一系列的文件名,文件名之间用空格隔开; prefix 是一个前缀名。将 preffix(前缀)的值附加在每一个独立文件名的前面,完成后将文件名串联起来,它们之间用单个空格隔开。
比如:
1 | $(addprefix src/,foo bar) |
结果为src/foo src/bar
。
3.2.7 wildcard
#
1 | $(wildcard pattern) |
参数pattern
是一个文件名格式,包含有通配符(通配符和 shell 中的用法一样)。函数 wildcard 的结果是一列和格式匹配的且真实存在的文件的名称,文件名之间用一个空格隔开。
比如若当前目录下有文件 1.c、 2.c、 1.h、 2.h,则:
1 | c_src := $(wildcard \*.c) |
结果为1.c 2.c
3.2.8 join
#
$(join list1,list2)
逐个地将list2中的元素链接到list1。
1 | LIST1 := foo bar |
结果为:foo.c bar.p
3.2.9realpath
#
$(realpath names…)
对names中
的每个文件,求其绝对路径,当目标为链接时,将解析链接。
3.2.10 abspath
#
$(abspath names…)
对names中
的每个文件,求其绝对路径。不解析链接。
3.2.11 file
#
$(file op filename[,text])
向文件执行文本的输入输出.
1 | TEXT := "hello world" |
前目录下存在test文件时,"hello world"
被写入到test中,当不存在test文件时,文件被创建且同时写入"hello world"
.
3.3 其他函数#
3.3.1 foreach
#
1 | $(foreach var,list,text) |
比如:
1 | dirs := a b c d |
这里text
是$(wildcard $(dir)/*)
,它的扩展过程如下:
第一个赋给变量dir
的值是a
, 扩展结果为$(wildcard a/*)
;
第二个赋给变量dir
的值是b
, 扩展结果为$(wildcard b/*)
;
第三个赋给变量dir
的值是c
, 扩展结果为$(wildcard c/*)
;
如此继续扩展。
这个例子和下面的例有共同的结果:
1 | files := $(wildcard a/* b/* c/* d/*) |
3.3.2 origin
#
1 | $(origin variable) |
变量variable
是一个查询变量的名称,不是对该变量的引用。所以,不能采用$
和圆括号的格式书写该变量,当然,如果需要使用非常量的文件名,可以在文件名中使用变量引用。
函数origin
的结果是一个字符串,该字符串变量是这样定义的:
例如定义编译时用verbose
还是quiet
打印,verbose
表示输出详细过程,quiet
输出简略信息。
将所有的信息都输出到同一个文件中:
1 | make xxx > build_output_all.txt 2>&1 |
3.3.3 word
#
$(word n,text)
返回text列表中第n个元素.
1 | TEXT := foo.c foo.h bar.c |
结果:foo.h
3.3.4wordlist
#
$(wordlist s,e,text)
返回text列表中指定的由s(start)开始由e(end)结尾的元素集合.
3.3.5 words
#
$(words text)
返回text列表中的元素数量。
3.3.6 firstword/lastword
#
$(firstword names…) \$(lastword names…)
返回names列表中的第一个、最后一个元素.
3.3.7 call
#
1 | func = $1.$2 |
结果:a.b
, 子函数调用,数的参数会被赋值给临时参数·=\$1,\$2,\$0
则代表函数名本身.
3.3.8 info
#
1 | ifeq ($(KERNEL_DIR), ) |
可以执行目标前先打印信息。
3.4 VPATH选项#
VPATH中添加的目录,即使是文件处于其他目录,我们也可以像操作当前目录一样操作其他目录的文件,例如:
1 | VPATH += src |
等效于:
1 | all:src/foo.c |
但是写成下面这样是不行的:
1 | VPATH += src |
这是因为:VPATH
是makefile中
的语法规则,而命令部分是由shell
解析,所以shell
并不会解析VPATH
。
3.5 make环境变量#
1 | AR 打包程序,默认值为ar,对目标文件进行打包,封装静态库 |
3.6 make编译选项#
1 | ARFLAGS 指定$(AR)运行时的参数,默认值为"ar" |
4 通用型makefile
#
本makefile
是参考linux内核的makefile
框架改编简化,大家可以参考Linux内核中built-in.o
的产生过程来进一步了解该流程。
顶层目录下,存在Makefile
和Makefile.build
两个文件。这两个文件非常重要,make命令能递归查找每个子目录,就是这2个Makefile
文件的功劳。
4.1 顶层Makefile
#
1 | # 延时变量, 只有第一次定义赋值才成功.而该变量在/etc/profile中. 已定义为arm-linux-gnueabihf- |
顶层Makefile
的作用:
- 提供项目make命令的执行入口,提供所有编译target目标。
- 定义全局变量,编译选项,链接选项等。
- 通过
obj-y
指定要搜索的子目录。 - 切换目录,递归执行make命令,并执行根目录的
Makefile.build
文件
4.2 Makefile.build
#
1 | PHONY := __build echo_obj |
Makefile.build
作用:
- 包含引用顶层Makefile。
- 取出每个子Makefile中定义的
.o
文件,再根据%.o:%.c
模式规则,自动寻找.c
源码文件。 - 取出每个子Makefile中定义的子目录,再用
make -C
切换到子目录,从而实现递归目录编译。 - 为每个
.o
文件生成依赖文件(.d)
,并包含进Makefile.build。 - 为每个子目录(含有Makefile)生成一个
built-in.o
文件,便于根目录下的Makefile文件编译、链接。 - 设置伪目标。
4.3 Make过程举例#
项目目录展开如下:
1 | . |
4.3.1 子目录MakeFIle-display
为例#
1 | EXTRA_CFLAGS := |
4.3.2 编译详细输出日志#
顶层目录输入make all V=1
来看详细的编译过程:
点击查看代码
start_recursive_build
obj-y = display/ ui/ page/ config/ business/
make -C ./ -f /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/Makefile.build
make[1]: Entering directory '/media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command'
******************** echo_obj *************************
obj-y: display/ ui/ page/ config/ business/
__subdir-y:display ui page config business
subdir-y: display ui page config business
subdir_objs:display/built-in.o ui/built-in.o page/built-in.o config/built-in.o business/built-in.o
cur_objs:
**********************************************************
subdir-y = display
make -C display -f /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/Makefile.build
make[2]: Entering directory '/media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/display'
******************** echo_obj *************************
obj-y: disp_manager.o framebuffer.o
__subdir-y:
subdir-y:
subdir_objs:
cur_objs:disp_manager.o framebuffer.o
**********************************************************
gcc -Wall -O2 -g -I /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/include -Wp,-MD,.disp_manager.o.d -c -o disp_manager.o disp_manager.c
gcc -Wall -O2 -g -I /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/include -Wp,-MD,.framebuffer.o.d -c -o framebuffer.o framebuffer.c
ld -r -o built-in.o disp_manager.o framebuffer.o
make[2]: Leaving directory '/media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/display'
subdir-y = ui
make -C ui -f /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/Makefile.build
make[2]: Entering directory '/media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/ui'
******************** echo_obj *************************
obj-y: button.o
__subdir-y:
subdir-y:
subdir_objs:
cur_objs:button.o
**********************************************************
gcc -Wall -O2 -g -I /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/include -Wp,-MD,.button.o.d -c -o button.o button.c
ld -r -o built-in.o button.o
make[2]: Leaving directory '/media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/ui'
subdir-y = page
make -C page -f /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/Makefile.build
make[2]: Entering directory '/media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/page'
******************** echo_obj *************************
obj-y: main_page.o page_manager.o
__subdir-y:
subdir-y:
subdir_objs:
cur_objs:main_page.o page_manager.o
**********************************************************
gcc -Wall -O2 -g -I /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/include -Wp,-MD,.main_page.o.d -c -o main_page.o main_page.c
main_page.c:198:16: warning: ‘GetButtonByInputEvent’ defined but not used [-Wunused-function]
198 | static Button* GetButtonByInputEvent(InputEvent *pInputEvent)
| ^~~~~~~~~~~~~~~~~~~~~
main_page.c:103:13: warning: ‘GenerateButtons’ defined but not used [-Wunused-function]
103 | static void GenerateButtons(void)
| ^~~~~~~~~~~~~~~
gcc -Wall -O2 -g -I /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/include -Wp,-MD,.page_manager.o.d -c -o page_manager.o page_manager.c
ld -r -o built-in.o main_page.o page_manager.o
make[2]: Leaving directory '/media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/page'
subdir-y = config
make -C config -f /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/Makefile.build
make[2]: Entering directory '/media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/config'
******************** echo_obj *************************
obj-y: config.o
__subdir-y:
subdir-y:
subdir_objs:
cur_objs:config.o
**********************************************************
gcc -Wall -O2 -g -I /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/include -Wp,-MD,.config.o.d -c -o config.o config.c
ld -r -o built-in.o config.o
make[2]: Leaving directory '/media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/config'
subdir-y = business
make -C business -f /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/Makefile.build
make[2]: Entering directory '/media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/business'
******************** echo_obj *************************
obj-y: main.o
__subdir-y:
subdir-y:
subdir_objs:
cur_objs:main.o
**********************************************************
gcc -Wall -O2 -g -I /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/include -Wp,-MD,.main.o.d -c -o main.o main.c
main.c: In function ‘main’:
main.c:12:9: warning: unused variable ‘err’ [-Wunused-variable]
12 | int err;
| ^~~
ld -r -o built-in.o main.o
make[2]: Leaving directory '/media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command/business'
ld -r -o built-in.o display/built-in.o ui/built-in.o page/built-in.o config/built-in.o business/built-in.o
make[1]: Leaving directory '/media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/31_improve_command'
gcc -o test built-in.o -lpthread -lfreetype -lm
test has been built!
4.3.3 编译入口#
首先编译目标start_recursive_build
, 列出目标、要进行编译的子目录模块,进入当前Makefile所在目录,按照顶层Makefile.build
的规则编译,打印如下:
1 | start_recursive_build |
4.3.4 编译__build
#
执行顶层Makefile.build
,执行目标__build
,执行echo_obj
, 打印:
1 | make -C ./ -f /media/cvitek/robin.lee/my_test/study/weidongshan/imx6study/project/electronic_test_tools/ |
4.3.4.1 编译$(subdir-y)
#
在每个子目录产生build-in.o
:
执行$(subdir-y)
, 又$(subdir-y)=display ui page config business
,因此目标编译规则展开如下:
1 | display ui page config business: |
打印如下:
1 | subdir-y = display |
分析:目标是display
,进入display
目录,按照顶层Makefile.build
的规则编译,再次递归调用__build
,可以看到只有当子目录subdir-y
为空了,才不会递归进去,那么此时会执行built-in.o
目标。
1 | built-in.o : $(cur_objs) $(subdir_objs) |
此时按照推导规则进行disp_manager.o, framebuffer.o
的编译,在dispaly
目录下打包成build-in.o
。
同理,ui
目录编译:
1 | subdir-y = ui |
同理,page
目录编译:
1 | subdir-y = page |
同理,config
目录编译:
1 | subdir-y = config |
同理,business
目录编译:
1 | subdir-y = business |
4.3.4.2 打包成总的built-in.o
#
最后子目录的built-in.o
都生成了,再来返回顶层Makefile
,此时subdir_objs和cur_objs
如下:
1 | subdir_objs:display/built-in.o ui/built-in.o page/built-in.o config/built-in.o business/built-in.o |
那么继续:
1 | built-in.o : $(cur_objs) $(subdir_objs) |
1 | ld -r -o built-in.o display/built-in.o ui/built-in.o page/built-in.o config/built-in.o business/built-in.o |
至此,__build
目标执行完毕。
4.3.5 编译出口#
回到顶层makefile
的start_recursive_build
,回到all
目标,执行$(TARGET)
目标。
1 | gcc -o test built-in.o -lpthread -lfreetype -lm |
最终回到all
目标,打印:
1 | test has been built! |
5 通用型makefile2
-裸机版#
5.1 文件工程目录#
1 | book@100ask:~/ftp/openedv/bak_drv_prj/05_ledc_bsp$ tree |
5.2 lds链接脚本#
1 | SECTIONS{ |
5.3 Makefile
#
1 | CROSS_COMPILE ?= arm-linux-gnueabihf- |
打印出目标和依赖文件:
1 | book@100ask:~/ftp/openedv/bak_drv_prj/05_ledc_bsp$ make print |
编译结果如下:
1 | book@100ask:~/ftp/openedv/bak_drv_prj/05_ledc_bsp$ make |