您的位置:首页 > 其它

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

2014-10-18 13:35 239 查看

三、 查找文件(VPATH)

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

[cpp] view
plaincopy

#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作为头文件声明:

[cpp] view
plaincopy

#ifndef COUNTER_H_

#define COUNTER_H_



extern void counter( int counts[4]);

#endif

同样的,也可以为lexer.l创建一个lexer.h的头文件:

[cpp] view
plaincopy

#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参数,显式的指出源文件和头文件的路径:

[plain] view
plaincopy

VPATH = src include

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

[plain] view
plaincopy

CPPFLAGS = -I include

最终,makefile为:

[plain] view
plaincopy

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的结果为:

<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可以简写做:

[plain] view
plaincopy

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

[plain] view
plaincopy

%.o: %.c

$(COMPILE.c) $(OUTPUT_OPTION) $<

规则二: 从.l 到.c

[plain] view
plaincopy

%.c: %.l

@$(RM) $@

$(LEX.l) $< > $@

规则三: 从.c到无后缀名
当生成目标没有后缀名的时候(通常是可执行文件)

[plain] view
plaincopy

%: %.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来分开看,或者重定向输出到文件)

[plain] view
plaincopy

make --print-data-base

模式匹配

模式匹配规则中使用的百分号“%”与UNIX shell里面的通配符 “*”非常类似,它也可以代表任何长度的字符,并能被放在模式匹配中的任何位置,但在一个模式匹配中只能出现一次。
下面这些例子都是合法的模式匹配:
<span style="font-family:Microsoft YaHei;">%,v
s%.o
wrapper_%
</span>

当然,模式匹配中只包含一个百分号也是允许的,这种方式最常用的例子就是在UNIX下创建可执行文件。

静态模式规则

静态模式规则是只对一系列特定的目标生效的规则。

[plain] view
plaincopy

$(OBJECT): %.o: %.c

$(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

%.o: %.c

$(CC) $(OUTPUT_OPTION) $<

只有当目标和依赖关系的两个文件后缀都在make的已知后缀列表中存在的时候,后缀规则才会生效。上面的后缀规则又叫双后缀规则,顾名思义它包含了两种后缀。另一种后缀规则是单后缀规则,它只包含了一个属于源文件的后缀,当然这个规则是用来创建可执行文件的.

定义后缀规则
后缀规则的定义就像一个特别的目标文件一样:

[plain] view
plaincopy

.SUFFIXES: .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l

当然,你也可以自己定义其他的后缀规则,如pdf,html,xml等。要删除所有这些定义的后缀也很简单

[plain] view
plaincopy

.SUFFIXES:

也可以使用命令行参数: --no-builtin-rules或者-r。

转自:http://blog.csdn.net/reenigne/article/details/8536434
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: