如何写Makefile(二)——规则篇(中)

来源:互联网 发布:淘宝网发货地址怎么改 编辑:程序博客网 时间:2024/05/16 11:36

三、 查找文件(VPATH)

上一篇所使用的例子中,makefile和源文件都是在同一个简单目录下,但真正的程序往往会复杂很多。让我们重新修改整个程序,添加一个叫做counter的函数,同时添加counter.c:

[cpp] view plaincopy
  1. #include <lexer.h>  
  2. #include <counter.h>  
  3.   
  4. void counter( int counts[4]) {  
  5.         while ( yylex() )  
  6.                 ;  
  7.   
  8.         counts[0] = fee_count;  
  9.         counts[1] = fie_count;  
  10.         counts[2] = foe_count;  
  11.         counts[3] = fum_count;  
  12. }  
为了使这个库函数具有复用性,再添加一个counter.h作为头文件声明:
[cpp] view plaincopy
  1. #ifndef COUNTER_H_  
  2. #define COUNTER_H_  
  3.   
  4. extern void counter( int counts[4]);  
  5. #endif   
同样的,也可以为lexer.l创建一个lexer.h的头文件:
[cpp] view plaincopy
  1. #ifndef LEXER_H_  
  2. #define LEXER_H_  
  3.   
  4. extern int fee_count, fie_count, foe_count, fum_count;  
  5. extern int yylex( void );  
  6. #endif  

如果将这些文件都放在根目录下,显然比较混乱。通常情况下,头文件会放到include/下,源文件被放到src/。最后,将makefile放在根目录下,整个文件系统如下所示


为了让make能够找到相应的位置,需要在makefile开头添加VPATH参数,显式的指出源文件和头文件的路径:

[plain] view plaincopy
  1. VPATH = src include  

此外,不仅make需要知道路径,gcc同样需要,通过添加编译选项 -I 的方式,显式的告诉gcc头文件的位置:

[plain] view plaincopy
  1. CPPFLAGS = -I include  
最终,makefile为:
[plain] view plaincopy
  1. VPATH=src include  
  2. CC = gcc  
  3. CPPFLAGS = -I include  
  4. count_words: count_words.o counter.o lexer.o -lfl  
  5.         $(CC) $^ -o $@  
  6. count_words.o: count_words.c counter.h  
  7.         $(CC) $(CPPFLAGS) -c $<   
  8. counter.o: counter.c counter.h lexer.h  
  9.         $(CC) $(CPPFLAGS) -c $<  
  10. lexer.o: lexer.c include/lexer.h  
  11.         $(CC) $(CPPFLAGS) -c $<  
  12. lexer.c: lexer.l  
  13.         flex -t $< > $@  
  14. .PHONY: clean  
  15. clean:   
  16.         rm *.o lexer.c count_words  
运行make的结果为:

<span style="font-family:Microsoft YaHei;">gcc -I include -c src/count_words.c;gcc -I include -c src/counter.cflex -t src/lexer.l > lexer.cgcc -I include -c lexer.cgcc count_words.o counter.o lexer.o /usr/lib/x86_64-linux-gnu/libfl.so -o count_words</span>

注意1: VPATH变量可以包含一个路径列表,当make需要一个文件时会在其中搜索。这个列表既可以作为目标文件也可作为关联文件的路径,但不能作为下面命令行程序中文件的路径。这正是为什么在命令行程序中使用自动化变量的原因,避免因为路径修改而导致的命令运行错误。

注意2: 如果是因为make的相关路径配置错误,终端会输出例如:

<span style="font-family:Microsoft YaHei;">make: *** No rule to make target `count_words.c', needed by `count_words.o'. Stop.</span>

但如果是因为gcc的头文件路径配置错误,在终端会提示,例如:

<span style="font-family:Microsoft YaHei;">src/counter.c:1:19: fatal error: lexer.h: No such file or directorycompilation terminated.</span>

注意3: 在UNIX系统中,路径列表可以被空格或者冒号分隔开,在Windows中则是用空格或者分号。(既然两种系统都用空格,那最好就使用空格)

注意4: make会在每次需要文件的时候搜索VPATH列表中的路径,如果有两个不同路径下文件重名,则make只会使用顺序查找到的第一个

更加准确的方式是使用 vpath 变量,它的语法是:

<span style="font-family:Microsoft YaHei;">vpath pattern directory-list</span>

因此,上面makefile中的VPATH可以写做:

<span style="font-family:Microsoft YaHei;">vpath %.c srcvpath %.l srcvpath %.h include</span>

这样就告诉了make去src/中寻找.c和.l文件,去include中寻找.h文件。

四、 模式匹配规则

通常情况下,编译器会将带有它可以识别后缀名的文件编译成相应的目标文件。例如,C语言的编译器会将.c后缀名的文件编译成带有.o后缀名的目标文件。再比如,前面的用到过的flex使用.l后缀名文件作为输入,输出则是.c的文件。事实上,这样一些约定可以根据文件名模式,通过内建规则来进行处理。例如,用内建规则,之前的makefile可以简写做:

[plain] view plaincopy
  1. VPATH=src include  
  2. CC = gcc  
  3. CPPFLAGS = -I include  
  4.   
  5. count_words: counter.o lexer.o -lfl  
  6. count_words.o: counter.h  
  7. counter.o: counter.h lexer.h  
  8. lexer.o: lexer.h  
  9. .PHONY: clean  
  10. clean:   
  11.         rm *.o lexer.c count_words  


所有的内建规则都是模式匹配规则的实例,这个makefile之所以可以使用,是因为三个内建规则。

规则一: 从.c到.o

[plain] view plaincopy
  1. %.o: %.c  
  2.         $(COMPILE.c) $(OUTPUT_OPTION) $<  

规则二: 从.l 到.c

[plain] view plaincopy
  1. %.c: %.l  
  2.         @$(RM) $@  
  3.         $(LEX.l) $< > $@  

规则三: 从.c到无后缀名

当生成目标没有后缀名的时候(通常是可执行文件)

[plain] view plaincopy
  1. %: %.c  
  2.         $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@  

依照上述的模式匹配规则,make的生成过程如下:

<span style="font-family:Microsoft YaHei;">gcc  -I include   -c -o count_words.o src/count_words.cgcc  -I include   -c -o counter.o src/counter.clex  -t src/lexer.l > lexer.cgcc  -I include   -c -o lexer.o lexer.cgcc   count_words.o counter.o lexer.o /usr/lib/x86_64-linux-gnu/libfl.so   -o count_wordsrm lexer.c</span>

STEP 1: make根据makefile中的内容,将默认目标设置为count_words(如果命令行中特别指出,则为其它,如clean)。根据依赖关系,分别是count_words.o(虽然没有在makefile显式的指出,但make会根据隐式规则自动填充), counter.o, lexer.o 和 -lfl。

STEP 2:根据依赖关系列表中的顺序,make会先找到count_words.o,由于count_words.o的依赖关系没有后续更新,因此make只需要找到count_word.c并进行编译。在当前目录下,没有count_word.c的情况下,make会根据VPATH变量继续寻找,直到在src/中找到。接下来,counter.o的编译过程也是一样的。

STEP3: 编译lexer.o的过程比前面多了一步:因为工程中并不存在lexer.c,于是make发现了从lexer.l生成lexer.c的模式匹配规则。

STEP4: make检查-lfl库的具体位置,本人用的是Ubuntu12.04 64bit, 因此对应的路径为: /usr/lib/x86_64-linux-gnu/libfl.so,这个路径跟操作系统和make的版本有关,其实它具体在哪都不影响make的编译(只要是make可以找到的地方)。

STEP5: make已经准备好了生成count_words所需的所有依赖文件,生成。

STEP6:注意到,make创建的lexer.c是一个中间文件,makefile中并没有要生成它,因此在编译完成后将它删除。

DONE!

事实上,每一个makefile都有一个专有的内置规则库,在相应目录下可以使用下面的命令查看这个库(注意内容偏多,可以用more来分开看,或者重定向输出到文件)

[plain] view plaincopy
  1. make --print-data-base  

模式匹配

模式匹配规则中使用的百分号“%”与UNIX shell里面的通配符 “*”非常类似,它也可以代表任何长度的字符,并能被放在模式匹配中的任何位置,但在一个模式匹配中只能出现一次。
下面这些例子都是合法的模式匹配:
<span style="font-family:Microsoft YaHei;">%,vs%.owrapper_%</span>
当然,模式匹配中只包含一个百分号也是允许的,这种方式最常用的例子就是在UNIX下创建可执行文件。

静态模式规则

静态模式规则是只对一系列特定的目标生效的规则。
[plain] view plaincopy
  1. $(OBJECT): %.o: %.c  
  2.         $(CC) -c $(CFLAGS) $< -o $@  
与普通的模式匹配规则的唯一区别是:初始化中的“$(OBJECT):” ,这限定了文件列表。在$(OBJECT)中的每一个目标文件,会匹配到%.o,然后再通过%.c产生依赖关系。如果,目标的匹配不存在,则make会提示一个warning。
静态模式规则可以显式的指出匹配列表,而不用仅仅指出后缀等匹配模式。

后缀规则

后缀规则是一种定义隐式规则的传统方式,因为即便其他版本的make不会识别GNU make的一些模式规则语法,但后缀规则却依然会出现在其他的makefile中。因此,尽管GNU make是一个不错的选择,但我们还是应该掌握可以适应其他编译环境的makefile编写和理解方式。
后缀规则包含一到两个连接起来的后缀作为目标文件:
<span style="font-family:Microsoft YaHei;">.c.o:        $(CC) $(OUTPUT_OPTION) $<</span>
注意:这里跟前面有一点不一样,首先写的.c实际上是依赖关系,.o才是目标文件。用前面的方式重写这段:
[plain] view plaincopy
  1. %.o: %.c  
  2.         $(CC) $(OUTPUT_OPTION) $<  
只有当目标和依赖关系的两个文件后缀都在make的已知后缀列表中存在的时候,后缀规则才会生效。上面的后缀规则又叫双后缀规则,顾名思义它包含了两种后缀。另一种后缀规则是单后缀规则,它只包含了一个属于源文件的后缀,当然这个规则是用来创建可执行文件的.

定义后缀规则
后缀规则的定义就像一个特别的目标文件一样:
[plain] view plaincopy
  1. .SUFFIXES: .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l  
当然,你也可以自己定义其他的后缀规则,如pdf,html,xml等。要删除所有这些定义的后缀也很简单
[plain] view plaincopy
  1. .SUFFIXES:  
也可以使用命令行参数: --no-builtin-rules或者-r。

转自:http://blog.csdn.net/reenigne/article/details/8536434
0 0