三、 查找文件(VPATH)
上一篇所使用的例子中,makefile和源文件都是在同一个简单目录下,但真正的程序往往会复杂很多。让我们重新修改整个程序,添加一个叫做counter的函数,同时添加counter.c:
- #include <lexer.h>
- #include <counter.h>
- void counter( int counts[4]) {
- while ( yylex() )
- ;
- counts[0] = fee_count;
- counts[1] = fie_count;
- counts[2] = foe_count;
- counts[3] = fum_count;
- }
- #ifndef COUNTER_H_
- #define COUNTER_H_
- extern void counter( int counts[4]);
- #endif
- #ifndef LEXER_H_
- #define LEXER_H_
- extern int fee_count, fie_count, foe_count, fum_count;
- extern int yylex( void );
- #endif
如果将这些文件都放在根目录下,显然比较混乱。通常情况下,头文件会放到include/下,源文件被放到src/。最后,将makefile放在根目录下,整个文件系统如下所示
为了让make能够找到相应的位置,需要在makefile开头添加VPATH参数,显式的指出源文件和头文件的路径:
- VPATH = src include
此外,不仅make需要知道路径,gcc同样需要,通过添加编译选项 -I 的方式,显式的告诉gcc头文件的位置:
- CPPFLAGS = -I include
- VPATH=src include
- CC = gcc
- CPPFLAGS = -I include
- count_words: count_words.o counter.o lexer.o -lfl
- $(CC) $^ -o $@
- count_words.o: count_words.c counter.h
- $(CC) $(CPPFLAGS) -c $<
- counter.o: counter.c counter.h lexer.h
- $(CC) $(CPPFLAGS) -c $<
- lexer.o: lexer.c include/lexer.h
- $(CC) $(CPPFLAGS) -c $<
- lexer.c: lexer.l
- flex -t $< > $@
- .PHONY: clean
- clean:
- rm *.o lexer.c count_words
<span style="font-family:Microsoft YaHei;">gcc -I include -c src/count_words.c; gcc -I include -c src/counter.c flex -t src/lexer.l > lexer.c gcc -I include -c lexer.c gcc 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 directory compilation 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 src vpath %.l src vpath %.h include </span>
这样就告诉了make去src/中寻找.c和.l文件,去include中寻找.h文件。
四、 模式匹配规则
通常情况下,编译器会将带有它可以识别后缀名的文件编译成相应的目标文件。例如,C语言的编译器会将.c后缀名的文件编译成带有.o后缀名的目标文件。再比如,前面的用到过的flex使用.l后缀名文件作为输入,输出则是.c的文件。事实上,这样一些约定可以根据文件名模式,通过内建规则来进行处理。例如,用内建规则,之前的makefile可以简写做:
- VPATH=src include
- CC = gcc
- CPPFLAGS = -I include
- count_words: counter.o lexer.o -lfl
- count_words.o: counter.h
- counter.o: counter.h lexer.h
- lexer.o: lexer.h
- .PHONY: clean
- clean:
- rm *.o lexer.c count_words
所有的内建规则都是模式匹配规则的实例,这个makefile之所以可以使用,是因为三个内建规则。
规则一: 从.c到.o
- %.o: %.c
- $(COMPILE.c) $(OUTPUT_OPTION) $<
规则二: 从.l 到.c
- %.c: %.l
- @$(RM) $@
- $(LEX.l) $< > $@
规则三: 从.c到无后缀名
当生成目标没有后缀名的时候(通常是可执行文件)
- %: %.c
- $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
依照上述的模式匹配规则,make的生成过程如下:
<span style="font-family:Microsoft YaHei;">gcc -I include -c -o count_words.o src/count_words.c gcc -I include -c -o counter.o src/counter.c lex -t src/lexer.l > lexer.c gcc -I include -c -o lexer.o lexer.c gcc count_words.o counter.o lexer.o /usr/lib/x86_64-linux-gnu/libfl.so -o count_words rm 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来分开看,或者重定向输出到文件)
- make --print-data-base
模式匹配
<span style="font-family:Microsoft YaHei;">%,v s%.o wrapper_% </span>
静态模式规则
- $(OBJECT): %.o: %.c
- $(CC) -c $(CFLAGS) $< -o $@
后缀规则
<span style="font-family:Microsoft YaHei;">.c.o: $(CC) $(OUTPUT_OPTION) $< </span>
- %.o: %.c
- $(CC) $(OUTPUT_OPTION) $<
- .SUFFIXES: .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l
- .SUFFIXES: