学习日记:如何写Makefile(二)——规则篇(中)
2013-01-24 05:06
337 查看
接上一篇如何写Makefile——规则篇(上)
如果将这些文件都放在根目录下,显然比较混乱。通常情况下,头文件会放到include/下,源文件被放到src/。最后,将makefile放在根目录下,整个文件系统如下所示
为了让make能够找到相应的位置,需要在makefile开头添加VPATH参数,显式的指出源文件和头文件的路径:
此外,不仅make需要知道路径,gcc同样需要,通过添加编译选项 -I 的方式,显式的告诉gcc头文件的位置:
注意1: VPATH变量可以包含一个路径列表,当make需要一个文件时会在其中搜索。这个列表既可以作为目标文件也可作为关联文件的路径,但不能作为下面命令行程序中文件的路径。这正是为什么在命令行程序中使用自动化变量的原因,避免因为路径修改而导致的命令运行错误。
注意2: 如果是因为make的相关路径配置错误,终端会输出例如:
但如果是因为gcc的头文件路径配置错误,在终端会提示,例如:
注意3: 在UNIX系统中,路径列表可以被空格或者冒号分隔开,在Windows中则是用空格或者分号。(既然两种系统都用空格,那最好就使用空格)
注意4: make会在每次需要文件的时候搜索VPATH列表中的路径,如果有两个不同路径下文件重名,则make只会使用顺序查找到的第一个。
更加准确的方式是使用 vpath 变量,它的语法是:
因此,上面makefile中的VPATH可以写做:
这样就告诉了make去src/中寻找.c和.l文件,去include中寻找.h文件。
所有的内建规则都是模式匹配规则的实例,这个makefile之所以可以使用,是因为三个内建规则。
规则一: 从.c到.o
规则二: 从.l 到.c
规则三: 从.c到无后缀名
当生成目标没有后缀名的时候(通常是可执行文件)
依照上述的模式匹配规则,make的生成过程如下:
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来分开看,或者重定向输出到文件)
下面这些例子都是合法的模式匹配:
当然,模式匹配中只包含一个百分号也是允许的,这种方式最常用的例子就是在UNIX下创建可执行文件。
静态模式规则可以显式的指出匹配列表,而不用仅仅指出后缀等匹配模式。
后缀规则包含一到两个连接起来的后缀作为目标文件:
注意:这里跟前面有一点不一样,首先写的.c实际上是依赖关系,.o才是目标文件。用前面的方式重写这段:
定义后缀规则
后缀规则的定义就像一个特别的目标文件一样:
三、 查找文件(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; }为了使这个库函数具有复用性,再添加一个counter.h作为头文件声明:
#ifndef COUNTER_H_ #define COUNTER_H_ extern void counter( int counts[4]); #endif同样的,也可以为lexer.l创建一个lexer.h的头文件:
#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最终,makefile为:
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运行make的结果为:
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
注意1: VPATH变量可以包含一个路径列表,当make需要一个文件时会在其中搜索。这个列表既可以作为目标文件也可作为关联文件的路径,但不能作为下面命令行程序中文件的路径。这正是为什么在命令行程序中使用自动化变量的原因,避免因为路径修改而导致的命令运行错误。
注意2: 如果是因为make的相关路径配置错误,终端会输出例如:
make: *** No rule to make target `count_words.c', needed by `count_words.o'. Stop.
但如果是因为gcc的头文件路径配置错误,在终端会提示,例如:
src/counter.c:1:19: fatal error: lexer.h: No such file or directory compilation terminated.
注意3: 在UNIX系统中,路径列表可以被空格或者冒号分隔开,在Windows中则是用空格或者分号。(既然两种系统都用空格,那最好就使用空格)
注意4: make会在每次需要文件的时候搜索VPATH列表中的路径,如果有两个不同路径下文件重名,则make只会使用顺序查找到的第一个。
更加准确的方式是使用 vpath 变量,它的语法是:
vpath pattern directory-list
因此,上面makefile中的VPATH可以写做:
vpath %.c src vpath %.l src vpath %.h include
这样就告诉了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的生成过程如下:
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
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
模式匹配
模式匹配规则中使用的百分号“%”与UNIX shell里面的通配符 “*”非常类似,它也可以代表任何长度的字符,并能被放在模式匹配中的任何位置,但在一个模式匹配中只能出现一次。下面这些例子都是合法的模式匹配:
%,v s%.o wrapper_%
当然,模式匹配中只包含一个百分号也是允许的,这种方式最常用的例子就是在UNIX下创建可执行文件。
静态模式规则
静态模式规则是只对一系列特定的目标生效的规则。$(OBJECT): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@与普通的模式匹配规则的唯一区别是:初始化中的“$(OBJECT):” ,这限定了文件列表。在$(OBJECT)中的每一个目标文件,会匹配到%.o,然后再通过%.c产生依赖关系。如果,目标的匹配不存在,则make会提示一个warning。
静态模式规则可以显式的指出匹配列表,而不用仅仅指出后缀等匹配模式。
后缀规则
后缀规则是一种定义隐式规则的传统方式,因为即便其他版本的make不会识别GNU make的一些模式规则语法,但后缀规则却依然会出现在其他的makefile中。因此,尽管GNU make是一个不错的选择,但我们还是应该掌握可以适应其他编译环境的makefile编写和理解方式。后缀规则包含一到两个连接起来的后缀作为目标文件:
.c.o: $(CC) $(OUTPUT_OPTION) $<
注意:这里跟前面有一点不一样,首先写的.c实际上是依赖关系,.o才是目标文件。用前面的方式重写这段:
%.o: %.c $(CC) $(OUTPUT_OPTION) $<只有当目标和依赖关系的两个文件后缀都在make的已知后缀列表中存在的时候,后缀规则才会生效。上面的后缀规则又叫双后缀规则,顾名思义它包含了两种后缀。另一种后缀规则是单后缀规则,它只包含了一个属于源文件的后缀,当然这个规则是用来创建可执行文件的.
定义后缀规则
后缀规则的定义就像一个特别的目标文件一样:
.SUFFIXES: .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l当然,你也可以自己定义其他的后缀规则,如pdf,html,xml等。要删除所有这些定义的后缀也很简单
.SUFFIXES:也可以使用命令行参数: --no-builtin-rules或者-r。
相关文章推荐
- 如何写Makefile(二)——规则篇(中)
- 如何写Makefile(二)——规则篇(下)
- 学习日记:如何写Makefile(二)——规则篇(上)
- 学习日记:如何写Makefile(二)——规则篇(下)
- 如何写Makefile(二)——规则篇(上)
- 如何编出健壮的代码 java编程30条规则
- 如何编写makefile
- 如何写makefile
- Makefile书写规则
- 如何自己编写Makefile
- Android Makefile中是 如何识别 TARGET_PRODUCT
- Makefile--基本规则(零)
- makefile详解(03) 如何工作
- workflow中如何读取外部的规则集
- 如何编写snort的检测规则
- makefile详解(07) 清空目标文件的规则
- 快速编写“专家级”makefile(1.从最简单的Makefile中了解规则)
- 如何得到VC工程的makefile文件
- [003-Makefile-笔记] Makefile的规则
- 如何把规则变为文化