- make的使用和makefile的编写
- 什么是make
- make机制概述
- make工具的最初设计目的是为了维护C程序文件,防止不必要的重新编译。make工具对于维护一些具有相互依赖关系的文件特别有用,其对,文件和命令的联系提供一套编码方法。在使用过程中只告诉make需要做什么,即提供一些规则,其他工作由make完成。
- make工具的工作是自动确定工程的那部分源程序文件需要重新编译,然后执行命令去编译他们。
- make机制的运行环境需要一个二进制命令行程序make和一个文本文件makefile
案例说明:
执行make命令进行滚编译:
- make与makefile的关系
- make是linux下的二进制程序,用来处理Makefile这种文本文件。在Linux的shell命令键入make的时候将自动寻找名称为” Makefile”作为编译文件,如果没有则是找”makefile”。找到编译文件后,根据makefile中的第一个目标自动寻找依赖关系,如果该依赖目标有依赖其他目标,make工具将一层层寻找直到找到最后一个目标文件为止。
- make工具的使用格式为:make [options参数] [target]…
表make工具参数选项
选项 | 含义 |
-f filename(常用) | 显式的指定文件作为makefile |
-C diname(常用) | 指定make在运行后的工作目录为diname |
-e(常用) | 不允许在makefile中替换环境变量的值 |
-k(常用) | 执行命令出错时,放弃当前目标继续维护其他目标 |
-n | 按实际执行的顺序模拟执行命令(包括用@开头的命令),没有实际执行效果,仅仅用于显示执行过程 |
-p | 显示makefile中所有变量的内部规则 |
-r | 忽略内部规则 |
-s(常用) | 执行但不显示命令,常用来检查Makefile的正确性 |
-S(常用) | 如果执行命令出错就退出 |
-t | 修改每个目标文件的创建日期 |
-I | 忽略make执行中的错误 |
-V | 显示make的版本号 |
make操作管理makefile的规则:
1)如果这个工程没有被编译过,那么所有C文件都需要被编译
2)如果这个工程有几个C文件被修改,则只需要编译修改过的C文件,并链接目标程序
3)如果这几个工程的头文件被改变了,则需要编译引用了这几个头文件的C文件,并链接目标程序
案例分析:
- makefile的书写规则
1)makefile有两种语法格式:
target :prerequisites command … | target :prerequisites;command command … |
target是目标文件可以,多个文件可以以空格分开,可以使用通配符“*”、“?”“[]”。一般来说,make会以UNIX的标准shell,也就是/bin/sh来执行命令
2)在规则中使用通配符
makefile中支持三种通配符
makefile通配符描述表
符号 | 案例 | 描述 | 备注 |
* | clean: rm -f *.o | 删除所有以.o为后缀名的文件 | 如果要使用*,可用转义字符“\”,即\* |
? | print:*.c lpr -p $? touch print | $?是一个自动化变量,表示比目标新的依赖文件集合 | |
[] | [a-d] | 等同于[abcd] |
通配符还可以用在变量中:
比如:objects = *.o 这里的*.o不会展开,objects的值就是*.o。
如果想让通配符在变量中展开,也就是让所有objects的值是所有.o文件的集合,那么可以这样:
objects := $(wildcard *.o)
这种用法是通过Makefile的关键字wildcard关键字指示的
3)伪目标
伪目标不是一个文件,而是一个标签。需要注意的是伪目标的取名不能和文件名重名。当然为了避免和文件名重名的情况,可以使用“.PHONY”标记伪目标,向make说明,不管是否有这个文件,这个目标都是伪目标
.PHONE:clean
伪目标没有依赖文件,但是可以为伪目标指定依赖文件。伪目标同样可以作为默认目标,只要将其放在第一个。案例如下:
4)多目标
makefile中的目标文件不止一个,其支持多目标。当makefile中多个目标同时依赖一个文件,并且生成的命令大体相同,就能把他们合并起来。
当然,多目标的生成规则的执行命令是同一个,这可能存在问题。可以使用自动化变量“$@”,该变量意味着目前规则中的目标集合。
案例如下:
5)makefile的命令
I)多条命令
Makefile中,make会按顺序一条条执行命令,每条命令必须以tab开头,除非命令是紧跟在依赖文件后的。在命令行之间的空格和空行会被忽略,但是空格或空行前以tab开头,make会认为其是一个空命令。
当依赖目标新于目标文件时,也就是当规则的最终目标需要被更新时,make会一条条执行后面的命令。如果想让上一条命令的结果应用在下一条命令上,则需要使用分号将这两个命令分离。
案例如下:
exec:
cd /home/zhangkai #进入该目录
pwd #打印当前目录
执行make exec命令后,会进入/home/zhangkai目录,但是pwd打印结果仍然是makefile的目录。
但是如果改成:
exec:
cd /home/zhangkai ; pwd
表示进入home/zhangkai,打印当前目录
II)返回码
当命令执行完毕后,make会自动检测他们的返回码,如果命令返回成功,那么make会继续执行下一条命令,直到规则中所有命令都成果返回后。如果某条命令出错,make就会终止当前规则,这将有可能终止所有规则的执行。
但有时候,命令的出错不代表规则就是错的。因此为了忽略命令的出错,可以在命令前加一个“-”,案例如下:
exec:
- cd /home/zhangkai #进入该目录,并忽略错误
pwd #打印当前目录
- makefile的中使用变量
1)变量基础
I)变量的名字可以包含字符、数字和下划线,变量是大小写敏感的。
II)变量在声明时,需要赋值,而在使用时需要在变量名前加上“$”符号,最好用“()”或“{}”括起来,如果需要用真实的$字符,那么需要用$$表示。
案例如下:
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects):defs.h
等价于:
objects = program.o foo.o utils.o
program : program.o foo.o utils.o
cc -o program program.o foo.o utils.o
program.o foo.o utils.o:defs.h
III)赋值变量
make还支持变量的嵌套定义,案例如下:
foo = $(bar) #可以使用后面定义好的变量
bar = $(ugh)
ugh = Huh?
all:
echo $(foo)
执行结果为Huh?
make中还支持另一种定义变量的方法,使用“:=”操作符,如
x := foo
y :=$(x) bar
x := later
其等价于
y :=foo bar
x := later
使用“:=”操作符使得前面的变量不能使用后面的变量,只能使用前面定义好的变量。
y := $(x) bar
x := foo
此时y的值为bar 而不是 foo bar
IV)操作符“?=”
案例:FOO?=bar
其含义是如果变量FOO没有被定义过那么其值为bar,如果先前已经定义过那么该语句什么也不做。
其等价于:
ifeq($(origin FOO),undefined)
FOO = bar
endif
这段代码中使用了ifeq条件判断语句和origin函数调用。origin函数返回函数变量FOO的出处,ifeq则判断两个参数是否相等。
V)赋值操作符“+=”
案例如下:
objects = main.o foo.o bar.o util.o
objects += anther.o
于是$(objects) 的值为main.o foo.o bar.o util.o anther.o ,其也可等效于下面这种方式:
objects = main.o foo.o bar.o util.o
objects := $(objects) anther.o
如果变量之前没有定义过,那么“+=”会自动变成“=”,如果前面有定义,那么“+=”会继承前次操作的赋值符。如果前一次是“:=”,那么“+=”会以“:=”作为其赋值符。案例如下:
variable := value
variable += more
等价于:
variable:=value
variable:=$(variable) more
- makefile中常见函数调用
1)函数基础
Makefile 可以调用函数,从而让Makefile的书写更加灵活。函数调用后,返回值可以当变量的。其语法如下:
$(<function> <arguments>) #函数名和参数使用空格隔开,参数见使用逗号隔开
或是
${<function> <arguments>}
< function >是函数名,<arguments>是函数参数,参数间可以逗号隔开,而函数名和参数之间以空格分隔。函数调用以“$”开头,以圆括号和花括号把函数名和参数括起来,如使用“$(subst,a,b,$(x))”这样的形式而不是“$(subst,a,b,${x})”
2)字符串处理函数
字符串处理函数表
函数 | 功能 | 返回值 |
$(subst,<from>,<to>,<text>) | 字符串text中的from替换成to | 字符串 |
$(patsubst <pattern>,<replacement>,<text>) | 查找text中的单词是否符合pattern,如果匹配的话,用replacement替换 | 替换后的字符串 |
$(findstring <find>,<in>) | 在字符串in中寻找find | 如果找到返回该字符串,没找到返回空字符串 |
$(filter <pattern…>,<text>) | 以pattern模式过滤text中的字符串,保留符合pattern模式的单词,多个模式间通过空格链接 | 返回符合pattern模式的单词 |
$(filter-out <pattern…>,<text>) | 以pattern模式过滤字符串中的单词,去除符合模式pattern的字符,可有多个模式 | 返回不符合模式的字符串 |
$(sort<list>) | 将字符串list中的单词按照字母升序排序,先比较单词的首字母,首字母相同比较下一字母,以此类推。当遇到相同单词时,sort将自动删除。 | 排序后的字符串 |
$(word <n>,<next>) | 从字符串text中取出第n个单词,计数从1开始 | 返回第n个单词,如果n大于字符串的长度则返回空字符串 |
$(wordlist <s>,<e>,<text>) | 从字符串中取出从s开始到e结束的单词串,s和e是一个数字 返回:返回从s到e的字符串。如果s比text的单词数大,那么返回空字符串,如果e大于text的单词数,那么返回从s开始到结束的单词串 | 返回从s到e的字符串。如果s比text的单词数大,那么返回空字符串,如果e大于text的单词数,那么返回从s开始到结束的单词串 |
$(words <text>) | 统计text字符串中单词个数,计数从1开始 | 返回text中的单词数 |
$(firstword <text>) | 取出text中的首个单词 | 返回text中的首个单词 |
I)字符串替换函数subst
格式:$(subst,<from>,<to>,<text>)
功能:把字符串text中的from替换成to
返回:返回替换后的字符串
II)模式字符串替换函数patsubst
格式:$(patsubst <pattern>,<replacement>,<text>)
功能:查找text中的单词是否符合pattern,如果匹配的话,用replacement替换
返回:替换后的字符串
案例:
$(patsubst %.c,%.o,x.c.c bar.c)
把字符串x.c.c bar.c符合模式%.c的单词替换成%.o,函数返回结果是:
x.c.o bar.o
III)查找字符串函数findstring
格式:$(findstring <find>,<in>)
功能:在字符串in中寻找find
返回:如果找到返回该字符串,没找到返回空字符串
IV)过滤函数filter
格式:$(filter <pattern…>,<text>)
功能:以pattern模式过滤text中的字符串,保留符合pattern模式的单词,多个模式间通过空格链接
返回:返回符合pattern模式的单词
案例:
sources :=foo.c bar.c baz.c ugh.h
foo:$(sources)
cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources)的返回值是foo.c bar.c baz.c
V)反过滤函数filter-out
格式:$(filter-out <pattern…>,<text>)
功能:以pattern模式过滤字符串中的单词,去除符合模式pattern的字符,可有多个模式
返回:返回不符合模式的字符串
案例:
objects = main1.o foo.o mian2.o bar.o
mains = main1.o main2.o
$(filter-out $(mains),$(objects))
返回值为foo.o bar.o
VI)排序函数sort
格式:$(sort<list>)
功能:将字符串list中的单词按照字母升序排序,先比较单词的首字母,首字母相同比较下一字母,以此类推。当遇到相同单词时,sort将自动删除。
返回:排序后的字符串
案例:
$(sort lose foo bar lost)
返回值为bar foo lose lost
VII)取单词函数word
格式:$(word <n>,<next>)
功能:从字符串text中取出第n个单词,计数从1开始
返回:返回第n个单词,如果n大于字符串的长度则返回空字符串
案例:
$(word 2, programing linux C programing)
返回值:linux
VIII)去除单词串函数wordlist
格式:$(wordlist <s>,<e>,<text>)
功能:从字符串中取出从s开始到e结束的单词串,s和e是一个数字
返回:返回从s到e的字符串。如果s比text的单词数大,那么返回空字符串,如果e大于text的单词数,那么返回从s开始到结束的单词串
案例:
$(wordlist 2,4,I like linux C programing)
返回值:like linux C
IX)单词个数统计函数words
格式:$(words <text>)
功能:统计text字符串中单词个数,计数从1开始
返回:返回text中的单词数
案例:
$(words,I like linux C programing)
返回值为5
X)首单词函数firstword
格式:$(firstword <text>)
功能:取出text中的首个单词
返回:返回text中的首个单词
案例:
$(firstword I like linux c programing)
返回值:I
3)文件名操作函数
I)取目录函数dir
格式: $(dir <name…>)
功能: 从文件名序列(一个或多个)取出目录部分。目录部分是最后一个/之前的部分,如果没有/则返回“./”
返回:返回文件名序列的目录部分
案例:
$(dir usr/src/linux-2.4/Makefile hello.c)
返回值为: usr/src/linux-2.4/
II)取文件名函数notdir
格式:$(notdir<name…>)
功能:从文件名序列(一个或多个)中取出文件名部分。文件名部分为最后一个/之后的部分
返回:文件名序列中的文件名
案例:
$(dir usr/src/linux-2.4/Makefile hello.c)
返回值为: Makefile hello.c
III)取后缀名函数suffix
格式:$(suffix <name…>)
功能:从文件名序列name中取出各个文件名的后缀
返回:返回文件名序列name的后缀名序列,如果没有前缀则返回空字符串
案例:$(suffix usr/src/linux – 2.4/Makefile hello.c foo.s)
返回值:.c .s
IV)名称取前缀函数basename
格式:$(basename <names…>)
功能:从文件名序列中取出各个文件名的前缀部分
返回:返回文件名序列的前缀序列,如果文件没有前缀则返回空字符串
案例:$( basename usr/src/linux – 2.4/Makefile hello.c home/hacks)
返回值:usr/src/linux – 2.4/Makefile hello home/hacks
V)加后缀函数addsuffix
格式:$(addsuffix <suffix>,<names…>)
功能:将后缀suffix逐个加到names的单词后面
返回:返回已经加后缀的文件名序列
案例:$(addsuffix .c foo bar hello)
返回值:foo.c bar.c hello.c
VI)加前缀函数addprefix
格式:$(addprefix <prefix>,<names…>)
功能:把前缀prefix加到文件名序列names的前面
返回:返回加入前缀的文件名序列
案例:$( addprefix usr/src/linux – 2.4/kernel/,exit.c time.c)
返回值:usr/src/linux – 2.4/kernel/ exit.c usr/src/linux – 2.4/kernel/ time.c
4)循环函数
foreach循环函数
格式:$(foreach <var> ,<list>,<text>)
功能:将参数list中的单词逐一放到参数var中所指定的变量中,然后再执行text中的表达式。每一次text会返回一个字符串,循环过程中,text的所返回的每个字符串以空格分隔,最后当整个循环结束后,text返回的每个字符串以空格为分隔符组成新的字符串是foreach的返回值。所以var最好是个变量名,<list>可以是个表达式,而text一般会使用var这个参数来依次枚举list的单词。
案例:
names := a b c d
files :=$(foreach n,$(names),$(n).o)
返回值:names中的每个单词将会挨个取出,并赋值给n。$(n).o每次计算出一个值,最后将所有值以空格为分隔符组成新字符串,并赋值给files。
其值为a.o b.o c.o d.o
5)条件判断函数
条件判断函数表
关键字 | 典型表达形式 | 含义 |
ifeq | ifeq(<arg1>,<arg2>) | 比较参数arg1和arg2是否相等,相等时表达式为真 |
ifneq | ifneq(<arg1>,<arg2>) | 比较参数arg1和arg2是否相等,不相等时表达式为真 |
ifdef | ifdef(variable-name) | 如果变量variable-name的值为非空,则表达式为真。variable-name可以是一个函数的返回值。ifdef只是测试一个变量是否有值,并不会把变量扩展到当前位置 |
ifndef | ifndef(variable-name) | 如果变量variable-name的值为空,则表达式为真。 |
I)if函数
格式:
$(if <condition>,<then-part>)
或者:
$(if <condition>,<then-part>,<else-part>)
功能:当condition为真(非空字符串)时,执行then-part,否则执行else-part,最后进行返回
返回:返回then-part 或else-part
- makefile的隐式规则
隐式规则,即makefile中约定好的规则,不用写出来的规则。
案例:
foo:foo.o bar.o
cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
可以看到这里并没有说明如何生成foo.o 和bar.o,此时make会自动推导这两个目标的规则和命令,也就是说完全没有必要写出下面这两条规则
foo.o:foo.c
cc -c foo.c $(LDFAGS)
bar.o:bar.c
cc -c bar.c $(LDFAGS)
1)隐式规则中的变量
隐式规则中的变量可分为两种,一种是和命令相关的,一种是和参数相关的。
案例如下:
$(CC) -c $(CFLAGS) $(CPPFALGS)
make的默认编译命令是cc,如果将$(CC)重定义为gcc,把变量$(CFLAGS)重定义为-g,那么隐式规则中的命令会以gcc -c -g $(CPPFALGS)的样子执行。
与命令相关的表
变量 | 含义 |
AR | 数据库打包程序,默认命令是ar |
AS | 汇编语言编译程序,默认命令是as |
CC | C语言编译程序,默认命令是cc |
CXX | C++编译程序,默认命令是g++ |
CO | 从RCS文件中扩展文件程序,默认命令是co |
CPP | C程序的预处理器,默认命令是$(CC) -E |
FC | Fortran和Ratfor的编译器和预处理程序。默认命令是f77 |
GET | 从SCCS文件中扩展文件的程序,默认命令是get |
LEX | Lex方法分析程序,针对C或Ratfor.默认命令是lex |
PC | Pascal语言编译程序,默认命令是pc |
YACC | yacc文法分析器,针对于C程序,默认命令为yacc |
YACCR | yacc文法分析器,针对于Ratfor程序,默认命令为yacc -r |
MAKEINFO | 转换Texinfo源文件(.texi)到Info文件程序。默认命令为makeinfo |
TEX | 从Tex源文件创建Tex DVI文件程序。默认命令是tex |
WEAVE | 传唤web到Tex程序。默认命令是weave |
TEXI2DVI | 从Texinfo源文件创建Tex DVI文件程序,默认命令为texi2dvi |
CWEAVE | 转换Web到Tex语言的程序,默认命令是cweave |
TANGLE | 转换Web到Pascal语言程序,默认命令是tangle |
CTANGLE | 转换C Web到C。默认命令是ctangle |
RM | 删除文件,默认命令是rm -f |
2)模式规则
I)模式规则基础
可以使用模式规则定义隐式规则,模式规则与一般规则类似,只是在模式规则中,目标(目标文件)的定义需要有%符。%定义对文件名的匹配,表示任意长度的字符串。在依赖目标中同样可以使用%,只是依赖目标的取值,取决于其目标文件。
在模式规则中,至少在规则中的目标文件中要包含%,并且目标文件中%的值将决定,依赖文件中%的值。
案例:
%.o:%.c;<command>
其含义是,指出了所有的.c文件都将生成对应的.o文件的规则。如果要生成的文件是a.o b.o,那么%.c就是a.c b.c
II)模式规则中的自动化变量
模式规则中的自动化变量,是为了通过同一个命令完成从不同依赖文件生成相应目标。这种自动化变量会把模式中所有定义的文件自动取出,直至所有符合模式的文件都取完了。且这种自动化变量只应出现在规则的命令中 。
Makefile中共有21个自动化变量,在表1中,后面的14个变量只是在前面7个变量后分别加上字母D和F。D:Directory F:file
另外,对于变量$<,为了避免不必要的麻烦,可以写成$(<),以免造成歧义
表1 Makefile中的自动化变量
变量 | 含义 |
$@ | 表示规则中的所有目标文件集合。在模式中如果有多个目标,$@就是匹配于目标中模式定义的集合 |
$% | 仅当目标文件是库函数文件时,表示规则中的目标成员名,如果目标不是库函数文件(unix是.a windows是.lib),其值为空 |
$< | 依赖目标中的第一个目标名字,如果依赖目标是以模式(%)定义的,则$<是符合模式的一系列文件集 |
$? | 所有比目标文件新的依赖文件集合,以空格分隔 |
$^ | 所有依赖目标的集合,以空格分隔。如果依赖目标中有多个重复的,则自动去除重复的依赖文件,只保留一份 |
$+ | 同$^但是不去除重复部分 |
$* | 目标模式中%及其之前的部分 |
$(@D) | %@目录部分(不以斜杠作为结尾),如果$@中没有包含斜杠,其值为 . |
$(@F) | $@的文件部分,相当于函数$(notdir $@) |
$(*D) | 同$(@D),取出文件目录部分 |
$(*F) | 同$(@F),取出文件部分,但不带有后缀 |
$(%D) | 函数包含文件成员的目录部分 |
$(%F) | 函数包含文件成员的文件名部分 |
$(<D) | 依赖目标中的第一个目标的目录部分 |
$(<F) | 依赖目标的第一个目标的文件名部分 |
$(^D) | 所有依赖目标的目录部分(无重复) |
$(^F) | 所有依赖目标的文件名部分(无重复) |
$(+D) | 所有依赖目标的目录部分(可以有重复) |
$(+F) | 所有依赖目标的文件名部分(可以有重复) |
$(?D) | 所有被更新文件的目录部分 |
$(?F) | 所有被更新的文件名部分 |