GUN学习笔记之make变量

来源:互联网 发布:网络大v引导舆情走向 编辑:程序博客网 时间:2024/05/19 06:14

在之前的学习中,我们曾使用到变量,现在,让我们回顾曾在范例中出现的一些变量。其中最简单的变量语法如下:

$(variable-name)

这表明我们想扩展名字为variable-name的变量。变量可以包含任何文本,变量名可以包含多数字符包括点字符(.)。例如,包含C编译命令的COMPILE.c变量。通常,变量名必须放在$()括号里,才能被make识别。一个例外是:单个字符变量名则不需要括号。


通常,makefile定义了许多变量,不过其中有许多变量是make自动定义的。一些变量可以被用户设置,以方便用户控制make的行为,其余的变量则是供makemakefile通信使用的。


自动变量


当规则匹配时,make会定义自动变量。它们提供了对目标和前提条件列表的访问,这样你就不必显示知名任何文件名。这是避免代码重复的有效方式,但更重要的是定义更为一般的模式规则(稍后讨论)。


有六种核心的自动变量($^$+视为一个):

变量

作用

$@

目标文件名称

$%

归档成员规范的文件名元素

$<

第一个前提条件的文件名称

$?

由空格隔开的,时间比目标新的前提条件的所有文件名称

$^

由空格隔开的,所有的前提条件的文件名称,这个列表删除了重复的文件名,因为,对于多数使用来说,例如编译、复制等,并不想要重复的文件

$+

类似于$^,但包含重复的文件名称,传给连接器时有用

$*

目标的主文件名。文件的主文件名不包含后缀,不要在模式规则以外使用


除此之外,为了与其他make兼容,上面的每个变量都包含两个变体。一个变体是返回值的目录部分。它通过在变量后添加字符D实现,$(@D)$(<D)等。另一个变体返回值的文件部分。它通过在变量后添加字符F实现,$(@F)$(<F)等。注意到,这些变体名字长度超过两个字符,因此必须用放在括号里。GUNmake提供了dirnotdir函数以提高可读性。我们将在后面介绍。


在规则匹配后,make使用目标和前提条件设置自动变量,因此,这些变量只能在该规则的命令脚本中使用。


现在,我们的makefile使用自动变量替代文件名:

count_words: count_words.o counter.o lexer.o -lflgcc $^ -o $@count_words.o: count_words.cgcc -c $<counter.o: counter.cgcc -c $<lexer.o: lexer.cgcc -c $<lexer.c lexer.lflex -t $< > $@


通过VPATHvpath查找文件


目前,我们的范例非常简单,makefile和源文件位于同一单一目录。现实中程序是复杂的。现在让我们重构先前的范例,进行较为实际的文件布局。我们可以通过将main重构成一个名为counter的函数来修改我们先前的单词计数程序。

<pre name="code" class="plain">#include <lexer.h>#include <counter.h>void counter(int counts[4]){while (yylex())/* nothing to do */;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放在父目录。现在,范例程序的布局如下图1


1源代码树

因为我们的源文件现在包含头文件,新产生的依存关系需要记录在makefile中,这样,头文件修改后,相应的目标文件会被更新。

count_words: count_words.o couter.o lexer.o -lflgcc $^ -o $@count_words.o: count_words.c include/counter.hgcc -c $<counter.o: counter.c include/counter.h include/lexer.hgcc -c $<lexer.o: lexer.c include/lexer.hgcc -c $<lexer.c: lexer.lflex -t $< > $@

现在,当执行我们的makefile文件时,得到:

$ makemake:*** No rule to make `counter_words.c', needed by `count_words.o'.Stop.


发生了什么?makefile尝试更新count_words.c,但是这是源文件。让我们来扮演make。我们的第一个前提条件是count_words.o。我们看到不存在这个文件,但有创建它的规则。创建count_words.o引用count_words.c。但是,为什么make没有找到源文件呢?因为源文件在src目录而不在当前目录。除非高诉make,否则make只会在当前目录查找目标和前提条件文件。我们怎样让make查找src目录呢?或者,更为一般的,我们怎样告知make我们的源代码在哪儿?


你可以通过使用VPATHvpath特性告诉make在不同的目录查找源文件。为了修复我们当前的问题,我们可以在makefile中加入VPATH赋值:

VPATH= src


这表明,如果需要的文件不在当前目录,make应该在src目录查找。现在,我们运行makefile,我们得到:

$ makegcc-c src/count_words.c -o count_words.osrc/count_words.c:2:21:counter.h: No such file or directorymake:*** [count_words.o] Error 1


注意,现在我们可以编译第一个文件,因为填入了源代码的相对路径。使用自动变量的另一个原因是:如果你使用具体的文件名,make不能使用源代码适当的路径。不幸的是,编译失败了因为gcc不能找到头文件。我们可以解决这个问题通过自定义隐含编译规则的-I选项:

CPPFLAGS= -I include

此时的makefile文件如下:

VPATH = src includeCPPFLAGS = -I includecount_words: count_words.o couter.o lexer.o -lflgcc $^ -o $@count_words.o: count_words.c include/counter.hgcc $(CPPFLAGS) -c $<counter.o: counter.c include/counter.h include/lexer.hgcc $(CPPFLAGS) -c $<lexer.o: lexer.c include/lexer.hgcc $(CPPFLAGS)  -c $<lexer.c: lexer.lflex -t $< > $@

现在可以成功编译了:

$ makegcc-I include -c src/count_words.c -o count_words.ogcc-I include -c src/counter.c -o counter.oflex-t src/lexer.l > lexer.cgcc-I include -c lexer.c -o lexer.ogcccount_words.o counter.o lexer.o /lib/libfl.a -o count_words


VPATH变量包含make查找文件的目录列表。这个列表目录同时用于目标和前提条件文件的搜索,但是不包含在命令脚本的文件中。在Unix系统上,列表目录由空格和冒号分割;在Windows系统上,列表目录由空格和分号分割。我更倾向使用空格,因为它在所有的系统上都适用,这样可以避免在冒号和分号之间纠结。此外,使用空格更容易阅读。


VPATH很好的解决了上面文件搜索的问题。但是,它也有很大的不足。make会对它需要的文件在每个目录中进行查找。如果一个相同的文件在不同的地方多次出现且都在VPATH列表中,make会丢弃前面的。有时这会导致问题产生。


vpath指令以更精确的方式实现我们的目标。vpath指令的语法如下:

vpath pattern directory-list

因此,我们之前的VPATH可以重写为如下形式:

vpath %.c src

vpath %.h include


现在,我们告诉make应该在src目录下查找.c文件,include目录下查找.h文件(因此,我们可以移除前提条件中的include/)。在更复杂的应用中,这种控制可以解决一些头痛的问题和调试时间。


范例中,我们使用vpath指令解决分布在多个目录的源文件问题。怎样构建应用程序与此类似但又不同,目标文件写入二进制树中,然而源文件保存在源文件树中。正确使用vpath可以解决这个新问题,但是这个任务变得非常复杂,并且单独vpath是不足够。在后面我们会详细的讨论这个问题。

0 0