makefile 速成.docx
《makefile 速成.docx》由会员分享,可在线阅读,更多相关《makefile 速成.docx(10页珍藏版)》请在冰豆网上搜索。
![makefile 速成.docx](https://file1.bdocx.com/fileroot1/2023-1/26/e6ed2b22-5f41-4ada-a449-b3759b12fd31/e6ed2b22-5f41-4ada-a449-b3759b12fd311.gif)
makefile速成
分享一篇前短时间总结的makefile速成,教你一天搞定makefile,略加实践掌握其最核心部分。
可以从下面的几个维度来学习和理解makefile:
∙规则
∙变量
∙函数
∙命令
∙make的命令选项
∙一个大型项目的makefile例子
∙make–p的输出概览
在正式介绍makefile的以上四个方面之前,首先一句话概括一下makefile是什么:
makefile是用来描述文件依赖关系,并告诉make命令如何根据这种依赖关系,调用shell完成目标文件建立的文件。
makefile的执行时通过两步来完成的,第一步是扫描文件中的依赖关系,并藉此建立依赖关系树,然后从最底层想上来执行。
1.规则(rule)
Makefile从本质上说就是描述项目中文件依赖关系的文件。
这种依赖关系的描述就是规则。
Makefile的编写中的一切都是围绕规则来展开的,上面提到的四个方面:
规则、变量、函数、命令都是为了规则能够方便快捷的发挥作用才引入的。
一个简单规则可以表述为:
目标文件:
依赖文件(不同文件以空格分隔)
得到目标文件需要的命令
规则的常见种类有:
●∙ExplicitRule最简单的rule,明确指出了目标和依赖,以及如何有依赖得到目标。
举例:
Hello.o:
hello.cpp
g++-chello.cpp
●∙PatternRule使用了wildcard(通配符)的规则。
makefile中的通配符是百分号%,相当于bash中的*,在描述规则的时候使用的都是%。
makefile中也可以看到通配符*,这个一般是出现在命令之中,命令是要放到shell中运行的,所以要使用*作为通配符。
举例:
prog:
*.c
g++-oprog$^
●∙SuffixRule顾名思义,是只使用后缀来描述的rule,描述的时候不使用pattern,举例:
.c.o:
$(COMPILE.C)$(OUTPUT_OPTION)$<
这个suffixrule的作用和下面这个patternrule的作用是完全一样的。
区别仅在于描述依赖的时候,一个把目标放在后,一个把目标放在前面。
%.o:
%.c
$(COMPILE.C)$(OUTPUT_OPTION)$<
●∙ImplicitRule就是看不到的rule,这些rule是make的内置rule。
makefile有众多的build-inrule,这些规则为我们编写makefile带来了很大的便利。
它们可以通过make–p来查看(在“#ImplicitRules“部分)。
另外内置的规则里面还有大量的后缀规则,由于这些后缀规则在makefile中也不能直接看到,所以也可以叫做implicitrule。
上面说的rule目标文件都是实实在在的文件,还有一种目标,它不代表文件,叫做phonytarget(伪目标)。
伪目标总是未更新的。
最常用的就是clean这个phonytarget,他声明为:
.PHONY:
clean
clean:
ls;who;rm*.cpp
伪目标可以帮用来帮我们测试我们的makefile,当我们想测试一段Makefile是不是正确的时候,可以把要执行的命令放到伪目标(比如伪目标test)的下面,然后执行maketest就可以查看执行结果了。
2.变量
自动变量
刚刚在介绍规则的时候用到了$<,这东西是什么呢?
他是makefile中的automaticvariable。
这中变量是Makefile特有的变量,他是在股则的命令中自动得到的,不同规则中执行命令时使用相同的自动变量得到的结果不同,正是因为这个原因,所以叫做自动变量。
下面列出最常用的自动变量:
●∙$@ 规则的目标
●∙$% 档案文件成员(archivemember)结构中的文件名元素
●∙$< 第一个依赖文件名
●∙$^ 所有的依赖文件名(已经消重),以空格分隔
●∙$+ 所有的依赖文件名(未经消重),以空格分隔
●∙$* 所有除掉后缀的依赖文件名,以空格分隔,仅适用于模式规则。
注:
文件名包含stem和suffix,去掉suffix就剩下了stem。
比如hello.cpp的stem是hello,suffix就是cpp。
●∙$?
比目标文件新的依赖文件。
vpath和VPATH变量
vpath用来告诉make命令到什么地方去寻找文件。
如:
VPATH:
src
也可以用下面的形式,告诉make命令到指定的文件去查找对应类型的文件:
vpath%.h$(include_dirs)
vpath%.a$(lib_dir)$(extra_lib_dirs)
变量基本知识
和bash中变量赋值的情况不同,makefile里面的变量使用是$()方式的。
虽然使用${}也可以,但这种方式并不常见。
在使用$()来引用变量时,如果变量的长度为1,则可以省略括号,否则不能。
如$arg,在makefile中$arg并没有被识别为一个变量,而是变量$a和字母rg。
上面说到的自动变量$^$<都是这种情况。
另外,要特别注意的是makefile中使用的变量和bash中的变量不是一个体系,bash中的变量不能在makefile中使用。
且makefile有自己的内置变量。
如CURDIR,代表当前的目录,SHELL,代表用来执行命令使用的shell。
这些变量可以再make–p的#Variables中找到,表示为:
#default。
变量的展开和赋值
makefile中的=并不立即赋值而是要延迟到是使用变量的时候。
对于下面的makefile,运行make5.o,
结果是:
而,如果写成下面的形式:
结果就变成:
除了上面的两个赋值符号,还有两种符号是:
●∙?
= 在变量不存在的情况下进行赋值。
咽喉扩展
●∙+= appendoperator,作用和C语言中相同。
如果左边部分已经被定义则进行“立即扩展”,否则延后扩展
C/C++编译过程中用到的内置变量(make–p可以看到):
宏
变量适合用来存储单行形式的值,可是对于多行形式的值,例如命令脚本,如果我们想在不同的地方执行它,该怎办?
方法就是使用宏。
宏是封装的命令序列。
他可以内置换行符。
用下面的方式定义宏:
definecreate-jar
@echoCreating$@...
$(RM)$(TMP_JAR_DIR)
$(MKDIR)$(TMP_JAR_DIR)
$(CP)-r$^$(TMP_JAR_DIR)
cd$(TMP_JAR_DIR)&&$(JAR)$(JARFLAGS)$@.
$(JAR)-ufm$@$(MANIFEST)
$(RM)$(TMP_JAR_DIR)
endef
使用宏和使用变量的方式相同,如下:
$(UI_JAR):
$(UI_CLASSES)
$(create-jar)
3.函数
函数可以分为字符串函数、文件名函数、流程控制函数,用户还可以自定应函数。
函数调用形式为:
$(function-namearg1[,argn])
注意:
●∙函数调用使用$()括起来
●∙$(后面直接跟函数名称
●∙函数名和第一个参数之间用空格,后面的参数之间使用逗号间隔
例如:
objects=$(subst.cpp,.o,$(sources))
调用函数吧$(sources)中的.cpp替换成.o。
常用函数有:
4.命令
Ø∙必须讲明,makefile的命令是要调用shell来运行的,而到底是使用的哪个shell取决于makefile中SHELL变量的设置,默认为/bin/sh就是bash。
Ø∙在makefile中使用bash的变量。
makefile中使用$()引用的是makefile内的变量,如果想使用bash环境的变量要使用双美元引用$$,使用单个$引用的变量全部被解释为makefile的变量。
以此类推,如果要想试图用bash中的进程号码就要使用四个$,即:
$$$$。
Ø∙另外,值得强调的一点是:
makefile是一次发送一行命令,每发送一次命令,就会启动一个subshell来运行。
这意味着命令之间不能共享变量,而且bash中可以使用的for和while,在这里面也不能换行,因为每行会被单独的发送给bash来执行。
要解决这个问题,要么使用makefile中的换行符”\”,要么写成单行的形式。
如:
使用换行符:
写成单行:
再如:
$$n是用来引用bash环境中的变量n的,上面第二条有解释。
makefile的命令修饰符
有三种:
●∙@ 不打印运行的命令
●∙- 屏蔽命令出错信息
●∙+ 只打印运行的命令,不运行
5.make命令行选项
在make的命令行上可以指定make使用的变量,覆盖makefile中的赋值。
比如:
makeCXX=gcc
另外,可以使用-C运行其他文件下面的makefile。
例如:
make-Csrc/main
再如,你可能感觉make的时间特长,那么试试-j选项吧,这是make内置的并行指令
方法:
make-j8 #这就是并行8个job来完成make
6.一个大型项目makefile例子
根目录下的makefile
#初始化4个变量,用来收集子文件夹下面的信息
modules :
=src/mainsrc/utilsrc/testsrc/ext #程序包括哪些模块,或者说文件夹
programs :
= #收集excutable的
sources :
= #收集cpp的
libraries :
= #收集.o的
extra_clean :
= #
objects =$(subst.c,.o,$(sources)) #调用函数由.c的文件得到.o的文件
dependencies=$(subst.c,.d,$(sources)) #调用函数由.c的文件得到.d的文件,.d文件是用来得到子目录.o的
include_dirs :
=libinclude
CPPFLAGS +=$(addprefix-I,$(include_dirs))
vpath%.h$(include_dirs)
MV :
=mv-f
RM :
=rm-f
SED:
=sed
all:
include$(addsuffix/module.mk,$(modules)) #用函数引入src/main/module.mk等
include$(dependencies)
.PHONY:
all
all:
$(programs)
.PHONY:
libraries
libraries:
$(libraries)
.PHONY:
clean
clean:
$(RM)$(objects)$(programs)$(libraries)$(dependencies)$(extra_clean)
%.d:
%.c #后缀规则,描述.d文件的生成
$(CC)$(CFLAGS)$(CPPFLAGS)$(TARGET_ARCH)-M$<|\ #续行符,直接重定向到后面命令
$(SED)'s,\($(notdir$*)\.o\)*:
$(dir$@)\1$@:
'>$@.tmp
$(MV)$@.tmp$@
两个特殊的表达式:
\($(notdir$*)\.o\)*:
函数notdir用来取“含路径的文件名”中的文件名,匹配出的是.c文件对应的.o文件
$(dir$@)\1$@:
函数dir用来取路径,\1保存的是全面正则表达式的第一个匹配,$@是目标即.d
子目录下的module.mk
local_dir :
=lib/codec
local_lib :
=$(local_dir)/libcodec.a
local_src :
=$(addprefix$(local_dir)/,codec.c)
local_objs :
=$(subst.c,.o,$(local_src))
libraries+=$(local_lib)
sources +=$(local_src)
$(local_lib):
$(local_objs)
$(AR)$(ARFLAGS)$@$^
在最顶层目录使用make命令,整个项目的执行过程仍然是先建立依赖关系,然后从最底层来执行。
这里的情况是:
1. 由include进入,make先会到找每个目录下面找module.mk和.d文件
2. .d的产生需要.c,这在每个子文件中条件已经具备了
3. module.mk的运行需要.o,可能还有其他模块产生的的.a
4. .o文件的产生,是在生成的.d文件中描述的(由g++的-M参数实现)
5. .a文件的产生,是在module.mk文件中描述的
至此,所有条件都已经具备。
从后往前做就可以完成所有任务。
7.make–p的输出
#GNUMake3.80
#Copyright(C)2002 FreeSoftwareFoundation,Inc.
#Thisisfreesoftware;seethesourceforcopyingconditions.
#ThereisNOwarranty;notevenforMERCHANTABILITYorFITNESSFORA
#PARTICULARPURPOSE.
normalcommandexecutionoccurshere
#Makedatabase,printedonThuApr2920:
58:
132004
#Variables 自动变量、内置变量都在这里
...
#Directories 将会被make检查的目录
...
#ImplicitRules 隐含虽则,当然也是内置规则
...
#Pattern-specificvariablevalues
...
#Files 后缀规则在这里
...
#VPATHSearchPaths vpath的设置