您的位置:首页 > 其它

嵌入式 书写Makefile中头文件自动依赖关系小技巧

2014-06-15 13:34 253 查看
1、首先我们了解一下Makefile中的自动化变量:

所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。 

下面是所有的自动化变量及其说明: 

$@ 

    表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。 

$% 

    仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就 是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。 

$< 

    依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。 

$? 

    所有比目标新的依赖目标的集合。以空格分隔。 

$^ 

    所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。 

$+ 

    这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。 

$*  

   这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的 值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么"$*"也就不能被推导出,但是,如果目标文 件的后缀是make所识别的,那么"$*"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所 以,"$*"的值就是"foo"。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用"$*",除非是在隐含
规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"$*"就是空值。 

当你希望只对更新过的依赖文件进行操作时,"$?"在显式规则中很有用,例如,假设有一个函数库文件叫"lib",其由其它几个object文件更新。那么把object文件打包的比较有效率的Makefile规则是: 

    lib : foo.o bar.o lose.o win.o 

            ar r lib $? 

在上述所列出来的自动量变量中。四个变量($@、$<、$%、$*)在扩展时只会有一个文件,而另三个的值是一个文件列表。这七个自动化变 量还可以取得文件的目录名或是在当前目录下的符合模式的文件名,只需要搭配上"D"或"F"字样。这是GNU make中老版本的特性,在新版本中,我们 使用函数"dir"或"notdir"就可以做到了。"D"的含义就是Directory,就是目录,"F"的含义就是File,就是文件。 

下面是对于上面的七个变量分别加上"D"或是"F"的含义: 

$(@D) 

    表示"$@"的目录部分(不以斜杠作为结尾),如果"$@"值是"dir/foo.o",那么"$(@D)"就是"dir",而如果"$@"中没有包含斜杠的话,其值就是"."(当前目录)。 

$(@F) 

    表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o","$(@F)"相当于函数"$(notdir $@)"。 

"$(*D)" 

"$(*F)" 

    和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子,"$(*D)"返回"dir",而"$(*F)"返回"foo" 

"$(%D)" 

"$(%F)" 

    分别表示了函数包文件成员的目录部分和文件部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。 

"$(<D)" 

"$(<F)" 

    分别表示依赖文件的目录部分和文件部分。 

"$(^D)" 

"$(^F)" 

    分别表示所有依赖文件的目录部分和文件部分。(无相同的) 

"$(+D)" 

"$(+F)" 

    分别表示所有依赖文件的目录部分和文件部分。(可以有相同的) 

"$(?D)" 

"$(?F)" 

    分别表示被更新的依赖文件的目录部分和文件部分。 

最后想提醒一下的是,对于"$<",为了避免产生不必要的麻烦,我们最好给$后面的那个特定字符都加上圆括号,比如,"$(<)"就要比"$<"要好一些。 

还得要注意的是,这些变量只使用在规则的命令中,而且一般都是"显式规则"和"静态模式规则"(参见前面"书写规则"一章)。其在隐含规则中并没有意义。

2、下面我们真正进入Makefile中头文件依赖的小技巧:

 

现在我们的Makefile写成这样:
all: main

main: main.o stack.o maze.o
gcc $^ -o $@

main.o: main.h stack.h maze.h
stack.o: stack.h main.h
maze.o: maze.h main.h

clean:
-rm main *.o

.PHONY: clean

按照惯例,用
all
做缺省目标。现在还有一点比较麻烦,在写
main.o
stack.o
maze.o
这三个目标的规则时要查看源代码,找出它们依赖于哪些头文件,这很容易出错,一是因为有的头文件包含在另一个头文件中,在写规则时很容易遗漏,二是如果以后修改源代码改变了依赖关系,很可能忘记修改Makefile的规则。为了解决这个问题,可以用
gcc
-M
选项自动生成目标文件和源文件的依赖关系:
$ gcc -M main.c
main.o: main.c /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/bits/wordsize.h \
/usr/include/gnu/stubs.h /usr/include/gnu/stubs-32.h \
/usr/lib/gcc/i486-linux-gnu/4.3.2/include/stddef.h \
/usr/include/bits/types.h /usr/include/bits/typesizes.h \
/usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/i486-linux-gnu/4.3.2/include/stdarg.h \
/usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h main.h \
stack.h maze.h

-M
选项把
stdio.h
以及它所包含的系统头文件也找出来了,如果我们不需要输出系统头文件的依赖关系,可以用
-MM
选项:
$ gcc -MM *.c
main.o: main.c main.h stack.h maze.h
maze.o: maze.c maze.h main.h
stack.o: stack.c stack.h main.h

接下来的问题是怎么把这些规则包含到Makefile中,GNU 
make
的官方手册建议这样写:
all: main

main: main.o stack.o maze.o
gcc $^ -o $@

clean:
-rm main *.o

.PHONY: clean

sources = main.c stack.c maze.c

include $(sources:.c=.d)

%.d: %.c
set -e; rm -f $@; \
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$

sources
变量包含我们要编译的所有
.c
文件,
$(sources:.c=.d)
是一个变量替换语法,把
sources
变量中每一项的
.c
替换成
.d
,所以
include
这一句相当于:
include main.d stack.d maze.d

类似于C语言的
#include
指示,这里的
include
表示包含三个文件
main.d
stack.d
maze.d
,这三个文件也应该符合Makefile的语法。如果现在你的工作目录是干净的,只有
.c
文件、
.h
文件和
Makefile
,运行
make
的结果是:
$ make
Makefile:13: main.d: No such file or directory
Makefile:13: stack.d: No such file or directory
Makefile:13: maze.d: No such file or directory
set -e; rm -f maze.d; \
cc -MM maze.c > maze.d.$$; \
sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \
rm -f maze.d.$$
set -e; rm -f stack.d; \
cc -MM stack.c > stack.d.$$; \
sed 's,\(stack\)\.o[ :]*,\1.o stack.d : ,g' < stack.d.$$ > stack.d; \
rm -f stack.d.$$
set -e; rm -f main.d; \
cc -MM main.c > main.d.$$; \
sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.$$ > main.d; \
rm -f main.d.$$
cc -c -o main.o main.c
cc -c -o stack.o stack.c
cc -c -o maze.o maze.c
gcc main.o stack.o maze.o -o main

一开始找不到
.d
文件,所以
make
会报警告。但是
make
会把
include
的文件名也当作目标来尝试更新,而这些目标适用模式规则
%.d: %c
,所以执行它的命令列表,比如生成
maze.d
的命令:
set -e; rm -f maze.d; \
cc -MM maze.c > maze.d.$$; \
sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \
rm -f maze.d.$$

注意,虽然在Makefile中这个命令写了四行,但其实是一条命令,
make
只创建一个Shell进程执行这条命令,这条命令分为5个子命令,用
;
号隔开,并且为了美观,用续行符
\
拆成四行来写。执行步骤为:

set -e
命令设置当前Shell进程为这样的状态:如果它执行的任何一条命令的退出状态非零则立刻终止,不再执行后续命令。

把原来的
maze.d
删掉。

重新生成
maze.c
的依赖关系,保存成文件
maze.d.1234
(假设当前Shell进程的id是1234)。注意,在Makefile中
$
有特殊含义,如果要表示它的字面意思则需要写两个$,所以Makefile中的四个$传给Shell变成两个$,两个$在Shell中表示当前进程的id,一般用它给临时文件起名,以保证文件名唯一。

这个
sed
命令比较复杂,就不细讲了,主要作用是查找替换。
maze.d.1234
的内容应该是
maze.o: maze.c maze.h main.h
,经过
sed
处理之后存为
maze.d
,其内容是
maze.o maze.d: maze.c maze.h main.h


最后把临时文件
maze.d.1234
删掉。

不管是Makefile本身还是被它包含的文件,只要有一个文件在
make
过程中被更新了,
make
就会重新读取整个Makefile以及被它包含的所有文件,现在
main.d
stack.d
maze.d
都生成了,就可以正常包含进来了(假如这时还没有生成,
make
就要报错而不是报警告了),相当于在Makefile中添了三条规则:
main.o main.d: main.c main.h stack.h maze.h
maze.o maze.d: maze.c maze.h main.h
stack.o stack.d: stack.c stack.h main.h

如果我在
main.c
中加了一行
#include "foo.h"
,那么:

1、
main.c
的修改日期变了,根据规则
main.o main.d: main.c main.h stack.h maze.h
要重新生成
main.o
main.d
。生成
main.o
的规则有两条:
main.o: main.c main.h stack.h maze.h
%.o: %.c
# commands to execute (built-in):
$(COMPILE.c) $(OUTPUT_OPTION) $<

第一条是把规则
main.o main.d: main.c main.h stack.h maze.h
拆开写得到的,第二条是隐含规则,因此执行
cc
命令重新编译
main.o
。生成
main.d
的规则也有两条:
main.d: main.c main.h stack.h maze.h
%.d: %.c
set -e; rm -f $@; \
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$

因此
main.d
的内容被更新为
main.o main.d: main.c main.h stack.h maze.h foo.h


2、由于
main.d
被Makefile包含,
main.d
被更新又导致
make
重新读取整个Makefile,把新的
main.d
包含进来,于是新的依赖关系生效了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: