makefile深入之编写规则.
接 上篇扩展部分如何写makefile
还有哪些makefile编写规则
多目标规则
多目标,也就是冒号前面可以有多个目标,个人觉得这种只是简化写法,目的是提高可维护性,虽然简化了但学习的门槛要提高了。比如:@$表示什么?要是不学习肯定不懂。@$是一个特殊的变量,也叫“自动化变量”表示表示着目前规则中所有的目标的集合。为了更好阅读别人的makefile,这种多目标语法还是要掌握的。基本的语法如下:
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...
targets:表示一系列的目标文件
target-pattern:匹配的方法文件,一般指链接文件
prereq-patterns:上面匹配的方法文件再一次依赖,专用名词叫“依赖目标集”,如下面的例子
objects = foo.o bar.o #定义了object变量
all: $(objects) #定义了依赖 目标all要依赖于object
$(objects): %.o: %.c #object又要从.o结尾文件获取,.o文件又依赖于.c结尾的文件
$(CC) -c $(CFLAGS) $< -o $@ #cc是编译命令,不说了
#$<也是一个自动化变量,表示所有的依赖目标集
#$@,前面已经说了是目标集,并且是输出-o
#等价于
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
代码中的$(CC) -c $(CFLAGS) foo.c -o foo.o,包含了makefile文件的3种隐式规则,第1种为代码链接规则,第2种为源代码编译规则,第3种为汇编代码编译规则。编译器常用的链接命令选项含义如下:
- -o file 输出生成的 file文件
- -c 编译或汇编程序文件,但不会执行链接操作
- -T script 使用script脚本来分配内存
- -W1,option 给链接器发送一个选项,比如生成地址映射表,-Wl,-Map,output.map
- -mcpu=name 规定目标处理器的型号
- -Wall 使能所有警告调试信息输出
- -glevel 要求带调试信息的等级,-g0代表不产生调试信息,-g1代表产生最小的调试信息用来跟踪程序的运行,但不包括本地变量,-g3包含了一些额外的调试信息比如程序的宏定义等。
- -I dir 增加头文件的搜索路径,比如 –I…/header
- -D name 预先定义一个宏定义,比如 –DMPC564xB 表示定义一个宏 MPC564xB
- -m 自动找寻源文件中包含的头文件,并生成一个依赖关系。
自动依赖性规则
根据上面的编译器命令-m ,main文件中的#include def.h,实际make时会输出:main.o : main.c def.h
这其实就是一种自动依赖关系的生成。如何应用到makefile文件中呢?
GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个“name.c”的文件都生成一个“name.d”的Makefile文件,[.d]文件中就存放对应[.c]文件的依赖关系。于是,我们可以写出[.c]文件和[.d]文件的依赖关系,并让make自动更新或自成[.d]文件,并把其包含在我们的主Makefile中,这样,我们就可以自动化地生成每个文件的依赖关系了。生成[.d]文件的规则如下:
%.d: %.c #所有的[.d]文件依赖于[.c]文件
@set -e; rm -f $@; / #rm -f $@”的意思是删除所有的目标,也就是[.d]文件
$(CC) -M $(CPPFLAGS) $< > $@.; / #为每个依赖文件“$<”,也就是[.c]文件生成依赖文件,“$@”表示模式“%.d”文件
sed 's,/($*/)/.o[ :]*,/1.o $@ : ,g' < $@.> $@; / #使用sed命令作了一个替换。
rm -f $@. #删除临时的文件
有了.d文件,上面的make就可以这么写 main.o main.d : main.c defs.h
makefile使用的命令
前面已经说了,使用的是标准的shell命令
显示命令
@echo+信息 #会只打印信息,不会输出前面的命令
echo+信息 #会输出命令+信息 echo+信息,换行 信息
make参数“-s”或“–slient”则是全面禁止命令的显示
make参数“-n”或“–just-print”,那么其只是显示命令,但不会执行命令,这样可以调试指令的执行顺序。
命令执行
当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。如:
exec:
cd /home/hchen; pwd
变量使用
在Makefile中的定义的变量,就像是C/C++语言中的宏一样,他代表了一个文本字串,在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。
变量使用规则
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。如果你要使用真实的“$”字符,那么你需要用“$$”来表示。
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
变量值替换
我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。
还是看一个示例吧:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
这个示例中,我们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。
变量值再当成变量
x = y
y = z
a := $($(x))
在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,而不是“x=$(y)”)
override 指示符
Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:
override <variable> = <value>
override <variable> := <value>
define关键字
设置变量的值可以有换行.define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef关键字结束。其工作方式和“=”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开头,所以如果你用define定义的命令变量中没有以[Tab]键开头,那么make就不会把其认为是命令。
define two-lines
echo foo
echo $(bar)
endef
条件判断
语法
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
其中<conditional-directive>表示条件关键字
ifeq关键字
ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"
比较参数\“arg1”和“arg2”的值是否相同。当然,参数中我们还可以使用make的函数。如:
ifeq ($(strip $(foo)),)
<text-if-empty>
endif
这个示例中使用了“strip”函数,如果这个函数的返回值是空(Empty),那么<text-if-empty>就生效。
ifneq关键字
ifneq (<arg1>, <arg2>)
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"
其比较参数“arg1”和“arg2”的值是否相同,如果不同,则为真。
ifdef关键字
ifdef <variable-name>
ifndef关键字
其语法是:
ifndef <variable-name>
在<conditional-directive>这一行上,多余的空格是被允许的,但是不能以[Tab]键做为开始(不然就被认为是命令)。而注释符“#”同样也是安全的。“else”和“endif”也一样,只要不是以[Tab]键开始就行了。
特别注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,所以,你最好不要把自动化变量(如“$@”等)放入条件表达式中,因为自动化变量是在运行时才有的。
而且,为了避免混乱,make不允许把整个条件语句分成两部分放在不同的文件中。
函数
函数调用语法
函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:
$(<function> <arguments>)
或是
${<function> <arguments>}
这里,<function>就是函数名,make支持的函数不多。<arguments>是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。感觉很像一个变量,是不是?函数中的参数可以使用变量,为了风格的统一,函数和变量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。因为统一会更清楚,也会减少一些不必要的麻烦。
还是来看一个示例:
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
在这个示例中,$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格,$(foo)的值是“a b c”,$(bar)的定义用,调用了函数“subst”,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把$(foo)中的空格替换成逗号,所以$(bar)的值是“a,b,c”
常用函数
foreach 函数
foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的foreach函数几乎是仿照于Unix标准Shell(/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的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函数当中。
call函数
call函数是唯一一个可以用来创建新的参数化的函数;你可以用call函数来向这个表达式传递参数。其语法是:
$(call <expression>,<parm1>,<parm2>,<parm3>...)
当make执行这个函数时,<expression>参数中的变量,如$(1),$(2),$(3)等,会被参数<parm1>,<parm2>,<parm3>依次取代。而<expression>的返回值就是call函数的返回值。例如:
reverse = $(1) $(2)
foo = $(call reverse,a,b)
那么,foo的值就是“a b”。当然,参数的次序是可以自定义的,不一定是顺序的,如:
reverse = $(2) $(1)
foo = $(call reverse,a,b)
此时的foo的值就是“b a”。
origin函数
origin函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的?其语法是:
$(origin <variable>)
注意,<variable>是变量的名字,不应该是引用。所以你最好不要在<variable>中使用“$”字符
总结
总体来说,make还有很多的规则,也不是一时就能掌握的,只能通过不断练习来一步步掌握,以上只是个人认为当前来说是有用的部分,记录下来,以后用到了,再补充