为了使得rtems的源代码在自己的编译器上能够编译通过,不得不写makefile,否则对每个C文件的改动都要手动去编译,没有makefile只会感到越来越麻烦。
一。刚开始,没想那么多,就用sed提取出所有的C文件的文件名,然后借助sed的强大之处,一条sed命令生成冗长的一个makefile,比如有一个xxx.c文件,我就用sed命令把他处理成:
xxx.o: xxx.c
cc -c xxx.c
这样一来,有多少个c文件就有多少个以上操作,想想吧,一个makefile有几十k。
二。这样虽然看起来也简单,但是当我去静下心来看下makefile的相关文章之后,发现了自己的愚蠢之处,一条语句就可以解决的,这就是所谓的静态模式,例如,我们有a.c,b.c这两个源文件,我就可以这样写:
objects = a.o b.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $< -o $@
上面的规则等价于以下规则:
a.o : a.c
$(CC) -c a.c -o a.o
b.o : b.c
$(CC) -c b.c -o b.o
这样一来,我们的makefile就一条规则就搞定了,只有1k的大小。解释一下上面的代码:上面的例子中,指明了我们的目标从$objects中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“a.o b.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“a b”,并为其加下“.c”的后缀,于是,我们的依赖目标是“a.c b.c”。而命令中的“$<”和“$@”则是自动化变量,“$<”表示所有的依赖目标集(也就是“a.c b.c”),“$@”表示目标集(也就是“a.o b.o“)。
三。以上所述部分,我没有提及头文件。我们现在要修改a.c,make一下,makefile会很智能的去执行命令
$(CC) -c a.c -o a.o
而不会去管其他没有被修改的文件。但是,在我们写的程序中,特别是一个操作系统中,头文件肯定是少不了的,如果a.c中包含a.h,b.c中包含b.h,现在修改a.h或者b.h试试看,make一下,结果会发现makefile啥都没做,但是我明明修改了文件啊,为什么make它没反应呢??对!!可以,让a.o和b.o分别依赖各自的头文件啊:
a.o : a.c a.h
$(CC) -c a.c -o a.o
b.o : b.c b.h
$(CC) -c b.c -o b.o
确实,再试试,修改一个头文件,make一下,makefile会去做我们想要它做的工作了。
然而,这样些会有很多的麻烦。
首先,这样写岂不是又回到了,我第一条说的,又把一个makefile没必要的写成了冗长冗长的样子。有人可能会说,可以用隐含规则去减少行数,比如将以上代码修改如下:
a.o : a.c a.h
b.o : b.c b.h
这样真的很简洁明了,代码量又少了。
那么,第二个麻烦,恐怕你就没办法解决了。如果有成千上百个c文件,每个c文件里都会包含不同的头文件,你要一个c文件一个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
GNU make的官方手册建议这样写:(这个解决之道来自”linux c编程一站式学习“,至于现在的官方手册是不是还是这样写,我就不清楚了)
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 $@.$$$$
用如上的代码,我在ubuntu下试了下,确实是可以行的,能够解决头文件的依赖关系,也就是说,我修改一个头文件,那些依赖这个头文件的c文件都要重新编译。
上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变量$(sources)所有[.c]的字串都替换成[.d]。当然,你得注意次序,因为include 是按次来载入文件,最先载入的[.d]文件中的目标会成为默认目标。
解释:(也是来自linux c编程一站式学习)
这个命令写了四行,但其实是一条命令,make只创建一个Shell进程执行这条命令,这条命令分为5个子命令,用分号隔开,并且为了美观,用续行符\拆成四行来写。执行步骤为:
1. set -e命令设置当前Shell进程为这样的状态:如果它执行的任何一条命令的退出状态非零则立刻终止,不再执行后续命令。
2. 把原来的maze.d删掉。
3. 重新生成maze.c的依赖关系,保存成文件maze.d.1234(假设当前Shell进程的id是1234)。注意,在Makefile中$有特殊含义,如果要表示它的字面意思则需要写两个$,所以Makefile中的四个$传给Shell变成两个$,两个$在Shell中表示当前进程的id,一般用它给临时文件起名,以保证文件名唯一。
4. 这个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。
5. 最后把临时文件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。
2. 现在main.d的内容更新为main.o main.d: main.c main.h stack.h maze.h foo.h。
3. 由于main.d被Makefile包含,main.d被更新又导致make重新读取整个Makefile,把新的main.d包含进来,于是新的依赖关系生效了。
这个所谓的官方提供的方案中的sed命令跟我平时用的语法有点不同,看起来很不爽,我就把它修改了我自己习惯的sed语句了:
四。悲催的是,我们自己的编译器是跑在windows下的,虽然支持makefile,但是不支持sed。