3.4 e Makefile 语法 3.4.1 初识 Makefile Makefile 文件是由一些列的规则组合而成的,格式如下: target(目标文件) ...: prerequisites(依赖的文件) ... command(命令) ... ... 比如 3.3.2 中写的 Makefile 的规则: main.o:main.c gcc -c main.c 这条规则的 main.o 是目标文件(将要生成的文件),main.c 是依赖的文件(生成 main.o 需要的文件),“gcc -c main.c”是生成 main.o 需要运行的命令。e Makefile 中每行的脚本如果有缩进的情况,必须使用“ Tab ” 键缩进,切记不能使用空格缩进(这是 e Makefile 的语法要求),大家一定要切记!下面我们来分析一下图 3.3.2 章节中写的 Makefile 文件,脚本如下: 1 main:main.o calc.o 2 2 gcc -o main main.o calc.o 3 3 main.o:main.c 4 4 gcc -c main.c 5 5 calc.o:calc.c 6 6 gcc -c calc.c 7 7 8 8 clean: 9 9 rm -rf *.o 10 rm -rf main 从上图的运行结果可以看到最后的结果等于 10 了,和我们程序的设计结果是一样的。 该脚本一共有 4 条规则,1、2 行是第一条规则,3、4 行是第二条规则,5、6 是第三条规则 8、9、10是第四条规则。我们在运行 make 命令的时候,会解析当前目录下的这个 Makefile 文件里面的规则,首先解析第一条规则,第一条规则中的目标文件是 main,只要完成了该目标文件的更新,整个 Makefile 的功能 就完成了。在第一次编译的时候,由于目标文件 main 不存在,则会解析第一条规则,第一条规则依赖文件main.o、calc.o,make 命令会检查当前目录下是否有这两个.o 文件,经过检查发现没有,然后 make 会在Makefile 中查找分别以 main.o、calc.o 为目标的规则(第二条,第三条规则)。执行第二条规则依赖的文件是 main.c,make 命令检查发现当前目录下有这个文件,然后执行第二条规则的命令“gcc -c main.c”生成 main.o 文件。然后执行第三条规则,第三条规则的目标文件是 calc.o,依赖的文件是 calc.c,make命令检查发现当前目录下存在该文件,然后执行第三条规则的命令“gcc -c calc.c”生成 calc.o 文件,至此第一条规则依赖的 main.o、calc.o;两个文件已经生成了,然后运行第一条规则的命令“gcc -o mainmain.o calc.o”生成 main 文件。因为 make 命令运行的时候会从 Makefile 的第一条规则开始解析,然后根据第一条规则的依赖文件去遍历文件中的“对应规则”,然后在根据“对应规则”的依赖文件去遍历“对应的规则”,采用这样递归的方式会遍历出完成第一条规则所需要的所有规则。下面我们来看看第四条规则的目标文件是 clean,我们通过查看发现该规则与第一条规则没有关联,所以我们在运行 make 命令的时候,不会遍历到该规则。我们可以在终端输入“make clean”命令来运行第四条规则,第四条规则没有依赖的文件,所以执行运行命令“rm -rf *.o”和“rm -rf main”,这两条命令的功能是删除以.o 问结尾的所有文件,删除文件 main,运行如下图所示: 通过上图可以看到 main.o、mcalc.o 和 main 三个文件已经删除了。通过该规则我们可以清除编译产生的文件,实现工程的清理。 我们再来总结一下 make 命令的执行过程: 1.make 命令会在当前目录下查找以 Makefile 命名的文件 2.找到 Makefile 文件,就会按照 Makefile 里面的规则去编译,并生成最终文件 3.当发现目标文件不存在或者所依赖的文件比目标文件新(修改时间),就会执行规则对应的命令来更新。我们可以看到 make 是一个工具,他会通过 Makefile 文件里面的内容来执行具体的编译过程。 3.4.2 Makefile 的变量 在 3.3.2 章节中的 Makefile 第一条规则: main:main.o calc.o gcc -o main main.o calc.o 在该规则中 main.o、calc.o 这两个文件我们输入了两次,由于我们的额 Makefile 文件内容比较少,如果 Makefile 复杂的情况下,这种重复的输入就会非常占用时间,而且修改起来也会很麻烦,为了解决这个问题,Makefile 可以使用变量。Makefile 的变量是一个字符串。比如上面的规则我们声明一个变量,叫objects,objs 或者是 OBJ,反正不管是什么,只要能够表示 main.o、calc.o 就行了,我们修改上面的规则 1 objects = main.o calc.o 2 2 main:$( objects) 3 3 gcc -o main $( objects) 我们来分析下修改后的规则,首先第一行是我们定义了一个变量 objects,并给赋值“main.o calc.o”,第二行、第三行用到了变量 objects。Makefile 中的变量引用方式是“$(变量名)”,变量 objects 的赋值使用“=”,Makefile 中变量的赋值还可以使用“:=”、“?=”、“+=”,这四种赋值的区别如下: 1. “= = ” 赋值符 我们先在用户根目录的 work 目录下创建一个 Makefile 脚本,输入下面的内容: 1 ceshi1 = test 2 ceshi2 = $(ceshi1) 3 ceshi1 = temp 4 5 out: 第一行我们定义了变量并赋值“test”,第二行定义了变量 ceshi2 并赋值变量 ceshi1,第三行修改变量ceshi1 的值为“temp”,第五行、第六行是输出变量 ceshi2 的值。我们在终端输入“make out”命令,如下图所示: 在上图可以看到变量 ceshi2 的值是 temp,也就是变量 ceshi1 最后一次的赋值。 2. “ := ” 赋值符 我们修改“=”赋值符中的代码,第二行的“=”改成“:=”,代码如下: 1 ceshi1 = test 2 ceshi2 := $(ceshi1) 3 ceshi1 = temp 4 5 out: 6 @echo ceshi2:$(ceshi2) 我们在终端输入“make out”命令,如下图所示: file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml10068\wps4.png 我们可以看到上图的运行结果输出变量 ceshi2 的值是 test,虽然在第三行我们修改了变量 ceshi1 的 值,通过本实验我们可以看到“:=”赋值符的功能了。 3. “ ?= ” 赋值符 ceshi ?= test “?=”赋值符的作用是如果前面没有给变量 ceshi 赋值,那么变量就赋值“test”,如果前面已经赋值了,就使用前面的赋值。 4. “ += ” 赋值符 objs = main.o objs += calc.o 上面的脚本最后变量 objs 的值是“main.o calc.o”,“+=”赋值符的功能是实现变量的追加。 3.4.3 条件判断 使用条件判断,可以让 make 根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。其语法有下面两种: 1. <条件比较> [条件为真时执行的脚本] endif 2. <条件比较> [条件为真时执行的脚本] else [条件为假时执行的脚本] endif 条件比较用到的比较关键字有:ifeq、ifneq、ifdef、ifndef。 ifeq 表示如果比较相等,语法如下: ifeq(<参数 1>, <参数 2>) ifneq 表示如果不相等,语法如下: ifneq(<参数 1>, <参数 2>) ifdef 表示如果定义了变量,语法如下: ifdef <变量名> ifndef 表示如果没有定义变量,语法如下: ifndef <变量名> 3.4.4 使用函数 在 Makefile 中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make 所支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。 函数的调用很像变量的使用,也是以“$”来标识的,语法如下: $(<函数名> <参数集合>) 或者: ${<函数名> <参数集合>} 函数名和参数集合之间以空格分隔,参数集合的参数通过逗号分隔。函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。感觉很像一个变量。函数中的参数可以使用变量。为了风格的统一,函数和变量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。 因为统一会更清楚,也会减少一些不必要的麻烦。 接下来我们介绍几个常用的函数,其它的函数可以参考文档《跟我一起写 Makefile》。 t 1.subst 函数 $(subst <from>,<to>,<text>) 此函数的功能是把字串<text>中的<from>字符串替换成<to>,函数返回被替换过后的字符串。如下示例: $(subst ee,EE,feet on the street) 以上脚本实现把字符串“feet on the street”中的“ee”字符串替换成“EE”字符串,替换后的字符串 为“feet on the strEEt”。 . 2. t patsubst 函数 $(patsubst <pattern>,<replacement>,<text>) 此函数的功能是查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。这里<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。(可以用“\”来转义,以“\%” 来表示真实含义的“%”字符)。函数返回被替换过后的字符串。如下示例: $(patsubst %.c,%.o,x.c bar.c) 以上脚本实现把字串“x.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.o bar.o” 3.strip 函数 $(strip <string>) 此函数的功能是去掉<string>字串中开头和结尾的空字符,函数返回被去掉空格的字符串值。如下示例: $(strip a b c ) 以上脚本实现把字串“a b c ”去掉开头和结尾的空格,结果是“a b c”。 . 4. g findstring 函数 $(findstring <find>,<in>) 此函数的功能是在字串<in>中查找<find>字串,如果找到,那么返回<find>,否则返回空字符串,如下示 例: $(findstring a,a b c) $(findstring a,b c) 以上脚本,第一个返回“a”字符串,第二个返回空字符串。 r 5.dir 函数 $(dir <names...>) 此函数的功能是从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部 分。如果没有反斜杠,那么返回“./”。返回文件名序列<names>的目录部分,如下示例: $(dir src/foo.c hacks) 以上脚本运行结果返回“src/”。 . 6. r notdir 函数 $(notdir <names...>) 此函数的功能是从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后 的部分,返回文件名序列<names>的非目录部分,如下示例: $(notdir src/foo.c) 以上脚本返回字符串“foo.c” . 7. h 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) 以上脚本实现$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以$(files)的值是“a.o b.o c.o d.o”。(注意,foreach 中的<var>参数是一个临时的局部变量,foreach 函数执行完后,参数<var>的变量将不在作用,其作用域只在 foreach 函数当中)。 3.4.5 在规则中使用通配符 如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make 命令支持三种通配符:“*”,“?”和“[...]”,这是和 Unix 的 B-Shell 是相同的。“~”字符在文件名中也有比较特殊的用途。如果是“~/test”,这就表示当前用户根目录下的 test 文件。而“~admin/test”则表示用户 admin 根目录下的 test 文件。通配符代替了一系列的文件,如“*.c”表示所有后缀为 .c 的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如:“*”,那么可以用转义字符“\”,如“\*” 来表示真实的“*”字符,而不是任意长度的字符串。 下面我们来看几个具体的示例: clean: rm -rf *.o 上面这个示例说明通配符可以在规则的命令中使用。 print: *.c 上面这个示例说明通配符可以在规则的依赖中使用 objects = *.o 上面这个示例表示了,通符同样可以用在变量中。并不是说[*.o]会展开,objects 的值就是“*.o”。Makefile中的变量其实就是 C/C++中的宏。如果你要让通配符在变量中展开,也就是让 objects 的值是所有[.o]的文件名的集合,那么,你可以这样: objects := $(wildcard *.o) 这种用法由关键字“wildcard”指出,关于 Makefile 的关键字可以参考文档《跟我一起写 Makefile》。 关于 Makefile 的相关内容我们就介绍到这里,本节只是对 Makefile 做了基本的讲解,Mkaefile 还有大量 完结,更多内容关注:
|