分享
 
 
 

GNU Make 使用手册!(3)

王朝other·作者佚名  2006-02-01
窄屏简体版  字體: |||超大  

目标列表指明该规则应用的目标。目标可以含有通配符,具体使用和平常的目标规则基本一样(参阅在文件名中使用通配符)。

目标的格式和依赖的格式是说明如何计算每个目标依赖的方法。从匹配目标格式的目标名中依据格式抽取部分字符串,这部分字符串称为径。将径分配到每一个依赖格式中产生依赖名。

每一个格式通常包含字符‘%’。目标格式匹配目标时,‘%’可以匹配目标名中的任何字符串;这部分匹配的字符串称为径;剩下的部分必须完全相同。如目标‘foo.o’匹配格式‘%.o’,字符串‘foo’称为径。而目标‘foo.c’和‘foo.out’不匹配格式。

每个目标的依赖名是使用径代替各个依赖中的‘%’产生。如,如果一个依赖格式为‘%.c’,把径‘foo’代替依赖格式中的‘%’生成依赖的文件名‘foo.c’。在依赖格式中不包含‘%’也是合法的,此时对所有目标来说,依赖是相同的。

在格式规则中字符‘%’可以用前面加反斜杠‘\’方法引用。引用‘%’的反斜杠也可以由更多的反斜杠引用。引用‘%’、‘\’的反斜杠在和文件名比较或由径代替它之前从格式中移走。反斜杠不会因为引用‘%’而混乱。如,格式`the\%weird\\%pattern\\'是`the%weird\' 加上字符‘%',后面再和字符串

‘pattern\\'连接。最后的两个反斜杠由于不能影响任何统配符‘%’所以保持不变。

这里有一个例子,它将对应的‘.c’文件编译成‘foo.o’和‘bar.o’。

objects = foo.o bar.o

all: $(objects)

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

$(CC) -c

$(CFLAGS) $< -o $@

这里‘$<’是自动变量,控制依赖的名称,‘$@’也是自动变量,掌握目标的名称。详细内容参阅自动变量。

每一个指定目标必须和目标格式匹配,如果不符则产生警告。如果您有一列文件,仅有其中的一部分和格式匹配,您可以使用filter函数把不符合的文件移走(参阅字符串替代和分析函数):

files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c

$(CC) -c

$(CFLAGS) $< -o $@

$(filter %.elc,$(files)): %.elc: %.el

emacs -f

batch-byte-compile $<

在这个例子中,‘$(filter %.o,$(files))'的结果是‘bar.o lose.o',第一个静态格式规则是将相应的C语言源文件编译更新为OBJ文件,‘$(filter

%.elc,$(files))' 的结果是‘foo.elc',它由‘foo.el’构造。

另一个例子是阐明怎样在静态格式规则中使用‘$*’:

bigoutput littleoutput : %output : text.g

generate

text.g -$* > $@

当命令generate执行时,$*扩展为径,即‘big’或‘little’二者之一。

4.10.2静态格式规则和隐含规则

静态格式规则和定义为格式规则的隐含规则有很多相同的地方(详细参阅定义与重新定义格式规则)。双方都有对目标的格式和构造依赖名称的格式,差异是make使用它们的时机不同。

隐含规则可以应用于任何于它匹配的目标,但它仅仅是在目标没有具体规则指定命令以及依赖可以被搜寻到的情况下应用。如果有多条隐含规则适合,仅有执行其中一条规则,选择依据隐含规则的定义次序。

相反,静态格式规则用于在规则中指明的目标。它不能应用于其它任何目标,并且它的使用方式对于各个目标是固定不变的。如果使用两个带有命令的规则发生冲突,则是错误。

静态格式规则因为如下原因可能比隐含规则更好:

l

l

对一些文件名不能按句法分类的但可以给出列表的文件,使用静态格式规则可以重载隐含规则链。

l

l

如果不能精确确定使用的路径,您不能确定一些无关紧要的文件是否导致make使用错误的隐含规则(因为隐含规则的选择根据其定义次序)。使用静态格式规则则没有这些不确定因素:每一条规则都精确的用于指定的目标上。

4.11双冒号规则

双冒号规则是在目标名后使用‘::’代替‘:’的规则。当同一个目标在一条以上的规则中出现时,双冒号规则和平常的规则处理有所差异。

当一目标在多条规则中出现时,所有的规则必须是同一类型:要么都是双冒号规则,要么都是普通规则。如果他们都是双冒号规则,则它们之间都是相互独立的。如果目标比一个双冒号规则的依赖‘旧’,则该双冒号规则的命令将执行。这可导致具有同一目标双冒号规则全部或部分执行。

双冒号规则实际就是将具有相同目标的多条规则相互分离,每一条双冒号规则都独立的运行,就像这些规则的目标不同一样。

对于一个目标的双冒号规则按照它们在makefile文件中出现的顺序执行。然而双冒号规则真正有意义的场合是双冒号规则和执行顺序无关的场合。

双冒号规则有点模糊难以理解,它仅仅提供了一种在特定情况下根据引起更新的依赖文件不同,而采用不同方式更新目标的机制。实际应用双冒号规则的情况非常罕见。

每一个双冒号规则都应该指定命令,如果没有指定命令,则会使用隐含规则。详细内容参阅使用隐含规则。

4.12 自动生成依赖

在为一个程序编写的makefile文件中,常常需要写许多仅仅是说明一些OBJ文件依靠头文件的规则。例如,如果‘main.c’通过一条#include语句使用‘defs.h’,您需要写入下的规则:

main.o: defs.h

您需要这条规则让make知道如果‘defs.h’一旦改变必须重新构造‘main.o’。由此您可以明白对于一个较大的程序您需要在makefile文件中写很多这样的规则。而且一旦添加或去掉一条#include语句您必须十分小心地更改makefile文件。

为避免这种烦恼,现代C编译器根据原程序中的#include语句可以为您编写这些规则。如果需要使用这种功能,通常可在编译源程序时加入‘-M’开关,例如,下面的命令:

cc -M main.c

产生如下输出:

main.o : main.c defs.h

这样您就不必再亲自写这些规则,编译器可以为您完成这些工作。

注意,由于在makefile文件中提及构造‘main.o’,因此‘main.o’将永远不会被隐含规则认为是中间文件而进行搜寻,这同时意味着make不会在使用它之后自动删除它;参阅隐含规则链。

对于旧版的make程序,通过一个请求命令,如‘make depend’,利用编译器的特点生成依赖是传统的习惯。这些命令将产生一个‘depend’文件,该文件包含所有自动生成的依赖;然后makefile文件可以使用include命令将它们读入(参阅包含其它makefile文件)。

在GNU make中,重新构造makefile文件的特点使这个惯例成为了过时的东西――您永远不必具体告诉make重新生成依赖,因为GNU make总是重新构造任何过时的makefile文件。参阅Makefile文件的重新生成的过程。

我们推荐使用自动生成依赖的习惯是把makefile文件和源程序文件一一对应起来。如,对每一个源程序文件‘name.c’有一名为‘name.d’的makefile文件和它对应,该makefile文件中列出了名为‘name.o’的OBJ文件所依赖的文件。这种方式的优点是仅在源程序文件改变的情况下才有必要重新扫描生成新的依赖。

这里有一个根据C语言源程序‘name.c’生成名为‘name.d’依赖文件的格式规则:

%.d: %.c

set -e;

$(CC) -M $(CPPFLAGS) $<

| sed 's/\($*\)\.o[ :]*/\1.o $@ : /g' > $@;

[ -s $@ ] || rm -f $@

关于定义格式规则的信息参阅定义与重新定义格式规则。‘-e’开关是告诉shell如果$(CC)命令运行失败(非零状态退出)立即退出。正常情况下,shell退出时带有最后一个命令在管道中的状态(sed),因此make不能注意到编译器产生的非零状态。

对于GNU C编译器您可以使用‘-MM’开关代替‘-M’,这是省略了有关系统头文件的依赖。详细内容参阅《GNU CC使用手册》中控制预处理选项。

命令Sed的作用是翻译(例如):

main.o : main.c defs.h

到:

main.o main.d : main.c defs.h

这使每一个‘.d’文件和与之对应的‘.o’文件依靠相同的源程序文件和头文件,据此,Make可以知道如果任一个源程序文件和头文件发生变化,则必须重新构造依赖文件。

一旦您定义了重新构造‘.d’文件的规则,您可以使用使用include命令直接将它们读入,(参阅包含其它makefile文件),例如:

sources = foo.c bar.c

include $(sources:.c=.d)

(这个例子中使用一个代替变量参照从源程序文件列表‘foo.c bar.c'翻译到依赖文件列表‘foo.d bar.d'。详细内容参阅替换引用。)所以,‘.d’的makefile文件和其它makefile文件一样,即使没用您的任何进一步的指令,make同样会在必要的时候重新构建它们。参阅Makefile文件的重新生成过程。

5在规则中使用命令

规则中的命令由一系列shell命令行组成,它们一条一条的按顺序执行。除第一条命令行可以分号为开始附属在目标-依赖行后面外,所有的命令行必须以TAB开始。空白行与注释行可在命令行中间出现,处理时它们被忽略。(但是必须注意,以TAB开始的‘空白行’不是空白行,它是空命令,参阅使用空命令。)

用户使用多种不同的shell程序,如果在makefile文件中没有指明其它的shell,则使用缺省的‘/bin/sh’解释makefile文件中的命令。参阅命令执行。

使用的shell种类决定了是否能够在命令行上写注释以及编写注释使用的语法。当使用‘/bin/sh’作为shell,以‘#’开始的注释一直延伸到该行结束。‘#’不必在行首,而且‘#’不是注释的一部分。

5.1 命令回显

正常情况下make在执行命令之前首先打印命令行,我们因这样可将您编写的命令原样输出故称此为回显。

以‘@’起始的行不能回显,‘@’在传输给shell时被丢弃。典型的情况,您可以在makefile文件中使用一个仅仅用于打印某些内容的命令,如echo命令来显示makefile文件执行的进程:

@echo About to make distribution files

当使用make时给出‘-n’或‘--just-print’标志,则仅仅回显命令而不执行命令。参阅选项概要。在这种情况下也只有在这种情况下,所有的命令行都回显,即使以‘@’开始的命令行也回显。这个标志对于在不使用命令的情况下发现make认为哪些是必要的命令非常有用。

‘-s’或‘--silent’标志可以使make阻止所有命令回显,好像所有的行都以‘@’开始一样。在makefile文件中使用不带依赖的特别目标‘.SILENT’的规则可以达到相同的效果(参阅内建的特殊目标名)。因为‘@’使用更加灵活以至于现在已基本不再使用特别目标.SILENT。

5.2执行命令

需要执行命令更新目标时,每一命令行都会使用一个独立的子shell环境,保证该命令行得到执行。(实际上,make可能走不影响结果的捷径。)

请注意:这意味着设置局部变量的shell命令如cd等将不影响紧跟着的命令行;如果您需要使用cd命令影响到下一个命令,请把这两个命令放到一行,它们中间用分号隔开,这样make将认为它们是一个单一的命令行,把它们放到一起传递给shell,然后按顺序执行它们。例如:

foo : bar/lose

cd bar;

gobble lose > ../foo

如果您喜欢将一个单一的命令分割成多个文本行,您必须用反斜杠作为每一行的结束,最后一行除外。这样,多个文本行通过删除反斜杠按顺序组成一新行,然后将它传递给shell。如此,下面的例子和前面的例子是等同的:

foo : bar/lose

cd

bar;

gobble

lose > ../foo

用作shell的程序是由变量SHELL指定,缺省情况下,使用程序‘/bin/sh’作为shell。

在MS_DOS上运行,如果变量SHELL没有指定,变量COMSPEC的值用来代替指定shell。

在MS_DOS上运行和在其它系统上运行,对于makefile文件中设置变量SHELL的行的处理也不一样。因为MS_DOS的shell,‘command.com’,功能十分有限,所以许多make用户倾向于安装一个代替的shell。因此,在MS_DOS上运行,make检测变量SHELL的值,并根据它指定的Unix风格或DOS风格的shell变化它的行为。即使使用变量SHELL指向‘command.com’ ,make依然检测变量SHELL的值。

如果变量SHELL指定Unix风格的shell,在MS_DOS上运行的make将附加检查指定的shell是否能真正找到;如果不能找到,则忽略指定的shell。在MS_DOS上,GNU make按照下述步骤搜寻shell:

1、在变量SHELL指定的目录中。例如,如果makefile指明`SHELL = /bin/sh',make将在当前路径下寻找子目录‘/bin’。

2、在当前路径下。

3、按顺序搜寻变量PATH指定的目录。

在所有搜寻的目录中,make首先寻找指定的文件(如例子中的‘sh’)。如果该文件没有存在,make将在上述目录中搜寻带有确定的可执行文件扩展的文件。例如:‘.exe', ‘.com', ‘.bat', ‘.btm', ‘.sh'文件和其它文件等。

如果上述过程中能够成功搜寻一个shell,则变量SHELL的值将设置为所发现shell的全路径文件名。然而如果上述努力全部失败,变量SHELL的值将不改变,设置shell的行的有效性将被忽略。这是在make运行的系统中如果确实安装了Unix风格的shell,make仅支持指明的Unix风格shell特点的原因。

注意这种对shell的扩展搜寻仅仅限制在makefile文件中设置变量SHELL的情况。如果在环境或命令行中设置,希望您指定shell的全路径文件名,而且全路径文件名需象在Unix系统中运行的一样准确无误。

经过上述的DOS特色的处理,而且您还把 ‘sh.exe’安装在变量PATH指定的目录中,或在makefile文件内部设置`SHELL = /bin/sh' (和多数Unix的makefile文件一样),则在MS_DOS上的运行效果和在Unix上运行完全一样。

不像其它大多数变量,变量SHELL从不根据环境设置。这是因为环境变量SHELL是用来指定您自己选择交互使用的shell程序。如果变量SHELL在环境中设置,它将影响makefile文件的功能,这是非常不划算的,参阅环境变量。然而在MS-DOS和MS-WINDOWS中在环境中设置变量SHELL的值是要使用的,因为在这些系统中,绝大多数用户并不设置该变量的值,所以make很可能特意为该变量指定要使用的值。在MS-DOS上,如果变量SHELL的设置对于make不合适,您可以设置变量MAKESHELL用来指定make使用的shell;这种设置将使变量SHELL的值失效。

5.3 并行执行

GNUmake可以同时执行几条命令。正常情况下,make一次执行一个命令,待它完成后在执行下一条命令。然而,使用‘-j’和‘--jobs’选项将告诉make同时执行多条命令。在MS-DOS上,‘-j’选项没有作用,因为该系统不支持多进程处理。

如果‘-j’选项后面跟一个整数,该整数表示一次执行的命令的条数;这称为job slots数。如果‘-j’选项后面没有整数,也就是没有对job slots的数目限制。缺省的job slots数是一,这意味着按顺序执行(一次执行一条命令)。同时执行多条命令的一个不太理想的结果是每条命令产生的输出与每条命令发送的时间对应,即命令产生的消息回显可能较为混乱。

另一个问题是两个进程不能使用同一设备输入,所以必须确定一次只能有一条命令从终端输入,make只能保证正在运行的命令的标准输入流有效,其它的标准输入流将失效。这意味着如果有几个同时从标准输入设备输入的话,对于绝大多数子进程将产生致命的错误(即产生一个‘

Broken pipe’信号)。

命令对一个有效的标准输入流(它从终端输入或您为make改造的标准输入设备输入)的需求是不可预测的。第一条运行的命令总是第一个得到标准输入流,在完成一条命令后第一条启动的另一条命令将得到下一个标准输入流,等等。

如果我们找到一个更好替换方案,我们将改变make的这种工作方式。在此期间,如果您使用并行处理的特点,您不应该使用任何需要标准输入的命令。如果您不使用该特点,任何需要标准输入的命令将都能正常工作。

最后,make的递归调用也导致出现问题。更详细的内容参阅与子make通讯的选项。

如果一个命令失败(被一个信号中止,或非零退出),且该条命令产生的错误不能忽略(参阅命令错误),剩余的构建同一目标的命令行将会停止工作。如果一条命令失败,而且‘-k’或‘--keep-going’选项也没有给出(参阅选项概要),make将放弃继续执行。如果make由于某种原因(包括信号)要中止,此时又子进程正在运行,它将等到这些子进程结束之后再实际退出。

当系统正满负荷运行时,您或许希望在负荷轻的时再添加任务。这时,您可以使用‘-|’选项告诉make根据平均负荷限制同一时刻运行的任务数量。‘-|’或‘--max-load’选项一般后跟一个浮点数。例如:

-| 2.5

将不允许make在平均负荷高于2.5时启动一项任务。‘-|’选项如果没有跟数据,则取消前面‘-|’给定的负荷限制。

更精确地讲,当make启动一项任务时,而它此时已经有至少一项任务正在运行,则它将检查当前的平均负荷;如果不低于‘-|’选项给定的负荷限制时,make将等待直到平均负荷低于限制或所有其它任务完成后再启动其它任务。

缺省情况下没有负荷限制。

5.4命令错误

在每一个shell命令返回后,make检查该命令退出的状态。如果该命令成功地完成,下一个命令行就会在新的子shell环境中执行,当最后一个命令行完成后,这条规则也宣告完成。如果出现错误(非零退出状态),make将放弃当前的规则,也许是所有的规则。

有时一个特定的命令失败并不是出现了问题。例如:您可能使用mkdir命令创建一个目录存在,如果该目录已经存在,mkdir将报告错误,但您此时也许要make继续执行。

要忽略一个命令执行产生的错误,请使用字符‘-’(在初始化TAB的后面)作为该命令行的开始。字符‘-’在命令传递给shell执行时丢弃。例如:

clean:

-rm -f

*.o

这条命令即使在不能删除一个文件时也强制rm继续执行。

在运行make时使用‘-i’或‘--ignore-errors’选项,将会忽略所有规则的命令运行产生的错误。在makefile文件中使用如果没有依赖的特殊目标.IGNORE规则,也具有同样的效果。但因为使用字符‘-’更灵活,所以该条规则已经很少使用。

一旦使用‘-’或‘-i’选项,运行命令时产生的错误被忽略,此时make象处理成功运行的命令一样处理具有返回错误的命令,唯一不同的地方是打印一条消息,告诉您命令退出时的编码状态,并说明该错误已经被忽略。如果发生错误而make并不说明其被忽略,则暗示当前的目标不能成功重新构造,并且和它直接相关或间接相关的目标同样不能重建。因为前一个过程没有完成,所以不会进一步执行别的命令。

在上述情况下,make一般立即放弃任务,返回一个非零的状态。然而,如果指定‘-k’或‘--keep-goning’选项,make则继续考虑这个目标的其它依赖,如果有必要在make放弃返回非零状态之前重建它们。例如,在编译一个OBJ文件发生错误后,即使make已经知道将所有OBJ文件连接在一起是不可能的,‘make -k'选项也继续编译其它OBJ文件。详细内容参阅选项概要。通常情况下,make的行为基于假设您的目的是更新指定的目标,一旦make得知这是不可能的,它将立即报告失败。‘-k’选项是告诉make真正的目的是测试程序中所有变化的可行性,或许是寻找几个独立的问题以便您可以在下次编译之前纠正它们。这是Emacs编译命令缺省情况下传递‘-k’选项的原因。

通常情况下,当一个命令运行失败时,如果它已经改变了目标文件,则该文件很可能发生混乱而不能使用或该文件至少没有完全得到更新。但是,文件的时间戳却表明该文件已经更新到最新,因此在make下次运行时,它将不再更新该文件。这种状况和命令被发出的信号强行关闭一样,参阅中断或关闭make。因此,如果在开始改变目标文件后命令出错,一般应该删除目标文件。如果.DELETE_ON_ERROR作为目标在makefile文件中出现,make将自动做这些事情。这是您应该明确要求make执行的动作,不是以前的惯例;特别考虑到兼容性问题时,您更应明确提出这样的要求。

5.5中断或关闭make

如果make在一条命令运行时得到一个致命的信号, 则make将根据第一次检查的时间戳和最后更改的时间戳是否发生变化决定它是否删除该命令要更新的目标文件。

删除目标文件的目的是当make下次运行时确保目标文件从原文件得到更新。为什么?假设正在编译文件时您键入Ctrl-c,而且这时已经开始写OBJ文件‘foo.o’,Ctrl-c关闭了该编译器,结果得到不完整的OBJ文件‘foo.o’的时间戳比源程序‘foo.c’的时间戳新,如果make收到Ctrl-c的信号而没有删除OBJ文件‘foo.o’,下次请求make更新OBJ文件‘foo.o’时,make将认为该文件已更新到最新而没有必要更新,结果在linker将OBJ文件连接为可执行文件时产生奇怪的错误信息。

您可以将目标文件作为特殊目标.PRECIOUS的依赖从而阻止make这样删除该目标文件。在重建一个目标之前,make首先检查该目标文件是否出现在特殊目标.PRECIOUS的依赖列表中,从而决定在信号发生时是否删除该目标文件。您不删除这种目标文件的原因可能是:目标更新是一种原子风格,或目标文件存在仅仅为了记录更改时间(其内容无关紧要),或目标文件必须一直存在,用来防止其它类型的错误等。

5.6递归调用make

递归调用意味着可以在makefile文件中将make作为一个命令使用。这种技术在包含大的系统中把makefile分离为各种各样的子系统时非常有用。例如,假设您有一个子目录‘subdir’,该目录中有它自己的makefile文件,您希望在该子目录中运行make时使用该makefile文件,则您可以按下述方式编写:

subsystem:

cd

subdir && $(MAKE)

或, 等同于这样写 (参阅选项概要):

subsystem:

$(MAKE)

-C subdir

您可以仅仅拷贝上述例子实现make的递归调用,但您应该了解它们是如何工作的,它们为什么这样工作,以及子make和上层make的相互关系。

为了使用方便,GNU make把变量CURDIR的值设置为当前工作的路径。如果‘-C’选项有效,它将包含的是新路径,而不是原来的路径。该值和它在makefile中设置的值有相同的优先权(缺省情况下,环境变量CURDIR不能重载)。注意,操作make时设置该值无效。

5.6.1 变量MAKE的工作方式

递归调用make的命令总是使用变量MAKE,而不是明确的命令名‘make’,如下所示:

subsystem:

cd subdir

&& $(MAKE)

该变量的值是调用make的文件名。如果这个文件名是‘/bin/make’,则执行的命令是`cd subdir && /bin/make'。如果您在上层makefile文件时用特定版本的make,则执行递归调用时也使用相同的版本。

在命令行中使用变量MAKE可以改变‘-t' (‘--touch'),

‘-n' (‘--just-print'), 或

‘-q' (‘--question')选项的效果。如果在使用变量MAKE的命令行首使用字符‘+’也会起到相同的作用。参阅代替执行命令。

设想一下在上述例子中命令‘make -t’的执行过程。(‘-t’选项标志目标已经更新,但却不执行任何命令,参阅代替执行命令。)按照通常的定义,命令‘make

–t’在上例中仅仅创建名为‘subsystem’的文件而不进行别的工作。您实际要求运行‘cd subdir && make –t’干什么?是执行命令或是按照‘-t’的要求不执行命令?

Make的这个特点是这样的:只要命令行中包含变量MAKE,标志`-t',

`-n' 和 `-q'将不对本行起作用。虽然存在标志不让命令执行,但包含变量MAKE的命令行却正常运行,make实际上是通过变量MAKEFLAGS将标志值传递给了子make(参阅与子make通讯的选项)。所以您的验证文件、打印命令的请求等都能传递给子系统。

5.6.2与子make通讯的变量

通过明确要求,上层make变量的值可以借助环境传递给子make,这些变量能在子make中缺省定义,在您不使用‘-e’开关的情况下,传递的变量的值不能代替子make使用的makefile文件中指定的值(参阅命令概要)。

向下传递、或输出一个变量时,make将该变量以及它的值添加到运行每一条命令的环境中。子make,作为响应,使用该环境初始化它的变量值表。参阅环境变量。

除了明确指定外,make仅向下输出在环境中定义并初始化的或在命令行中设置的变量,而且这些变量的变量名必须仅由字母、数字和下划线组成。一些shell不能处理名字中含有字母、数字和下划线以外字符的环境变量。特殊变量如SHELL和MAKEFLAGS一般总要向下输出(除非您不输出它们)。即使您把变量MAKEFILE设为其它的值,它也向下输出。

Make自动传递在命令行中定义的变量的值,其方法是将它们放入MAKEFLAGS变量中。详细内容参阅下节。Make缺省创造的变量的值不能向下传递,子make可以自己定义它们。如果您要将指定变量输出给子make,请用export指令,格式如下:

export variable ...

您要将阻止一些变量输出给子make,请用unexport指令,格式如下:

unexport variable ...

为方便起见,您可以同时定义并输出一个变量:

export variable = value

下面的格式具有相同的效果:

variable = value

export variable

以及

export variable := value

具有相同的效果:

variable := value

export variable

同样,

export variable += value

亦同样:

variable += value

export variable

参阅为变量值追加文本。

您可能注意到export和unexport指令在make与shell中的工作方式相同,如sh。

如果您要将所有的变量都输出,您可以单独使用export:

export

这告诉make 把export和unexport没有提及的变量统统输出,但任何在unexport提及的变量仍然不能输出。如果您单独使用export作为缺省的输出变量方式,名字中含有字母、数字和下划线以外字符的变量将不能输出,这些变量除非您明确使用export指令提及才能输出。

单独使用export的行为是老板本GNU make缺省定义的行为。如果您的makefile依靠这些行为,而且您希望和老板本GNU make兼容,您可以为特殊目标.EXPORT_ALL_VARIABLES 编写一条规则代替export指令,它将被老板本GNU make忽略,但如果同时使用export指令则报错。

同样,您可以单独使用unexport告诉make缺省不要输出变量,因为这是缺省的行为,只有前面单独使用了export(也许在一个包括的makefile中)您才有必要这样做。您不能同时单独使用export和unexport指令实现对某些命令输出对其它的命令不输出。最后面的一条指令(export或unexport)将决定make的全部运行结果。

作为一个特点,变量MAKELEVEL的值在从一个层次向下层传递时发生变化。该变量的值是字符型,它用十进制数表示层的深度。‘0’代表顶层make,‘1’代表子make,‘2’代表子-子-make,以此类推。Make为一个命令建立一次环境,该值增加1。

该变量的主要作用是在一个条件指令中测试(参阅makefile文件的条件语句);采用这种方法,您可以编写一个makefile,如果递归调用采用一种运行方式,由您控制直接执行采用另一种运行方式。

您可以使用变量MAKEFILES使所有的子make使用附加的makefile文件。变量MAKEFILES的值是makefile文件名的列表,文件名之间用空格隔开。在外层makefile中定义该变量,该变量的值将通过环境向下传递;因此它可以作为子make的额外的makefile文件,在子make读正常的或指定的makefile文件前,将它们读入。参阅变量MAKEFILES。

5.6.3与子make通讯的选项

诸如‘-s’和‘-k’标志通过变量MAKEFLAGS自动传递给子make。该变量由make自动建立,并包含make收到的标志字母。所以,如果您是用‘make –ks’变量MAKEFLAGS就得到值‘ks’。

作为结果,任一个子make都在它的运行环境中为变量MAKEFLAGS赋值;作为响应,make使用该值作为标志并进行处理,就像它们作为参数被给出一样。参阅选项概要。

同样,在命令行中定义的变量也将借助变量MAKEFLAGS传递给子make。变量MAKEFLAGS值中的字可以包含‘=’,make将它们按变量定义处理,其过程和在命令行中定义的变量一样。参阅变量重载。

选项`-C', `-f', `-o', 和 ‘-W’不能放入变量MAKEFLAGS中;这些选项不能向下传递。

‘-j’选项是一个特殊的例子(参阅并行执行)。如果您将它设置为一些数值‘N’,而且您的操作系统支持它(大多数Unix系统支持,其它操作系统不支持),父make和所有子make通讯保证在它们中间同时仅有‘N’个任务运行。注意,任何包含递归调用的任务(参阅代替执行命令)不能计算在总任务数内(否则,我们仅能得到‘N’个子make运行,而没有多余的时间片运行实在的工作)。

如果您的操作系统不支持上述通讯机制,那么‘-j 1’将放到变量MAKEFLAGS中代替您指定的值。这是因为如果‘-j’选项传递给子make,您可能得到比您要求多很多的并行运行的任务数。如果您给出‘-j’选项而没有数字参数,意味着尽可能并行处理多个任务,这样向下传递,因为倍数的无限制性所以至多为1。

如果您不希望其它的标志向下传递,您必须改变变量MAKEFLAGS的值,其改变方式如下:

subsystem:

cd

subdir && $(MAKE) MAKEFLAGS=

该命令行中定义变量的实际上出现在变量MAKEOVERRIDES中,而且变量MAKEFLAGS包含了该变量的引用值。如果您要向下传递标志,而不向下传递命令行中定义的变量,这时,您可以将变量MAKEOVERRIDES的值设为空,格式如下:

MAKEOVERRIDES =

这并不十分有用。但是,一些系统对环境的大小有固定限制,而且该值较小,将这么多的信息放到变量MAKEFLAGS的值中可能超过该限制。如果您看到‘Arg list too long'的错误信息,很可能就是由于该问题造成的。(按照严格的POSIX.2的规定,如果在makefile文件定义特殊目标‘.POSIX’,改变变量MAKEOVERRIDES的值并不影响变量MAKEFLAGS。也许您并不关心这些。)

为了和早期版本兼容,具有相同功能的变量MFLAGS也是存在的。除了它不能包含命令行定义变量外,它和变量MAKEFLAGS有相同的值,而且除非它是空值,它的值总是以短线开始(MAKEFLAGS只有在和多字符选项一起使用时才以短线开始,如和‘--warn-undefined-variables’连用)。变量MFLAGS传统的使用在明确的递归调用make的命令中,例如:

subsystem:

cd

subdir && $(MAKE) $(MFLAGS)

但现在,变量MAKEFLAGS使这种用法变得多余。如果您要您的makefile文件和老版本的make程序兼容,请使用这种方式;这种方式在现代版本make中也能很好的工作。

如果您要使用每次运行make都要设置的特定选项,例如‘-k’选项(参阅选项概要),变量MAKEFLAGS十分有用。您可以简单的在环境中将给变量MAKEFLAGS赋值,或在makefile文件中设置变量MAKEFLAGS,指定的附加标志可以对整个makefile文件都起作用。(注意:您不能以这种方式使用变量MFLAGS,变量MFLAGS存在仅为和早期版本兼容,采用其它方式设置该变量make将不予解释。)

当make解释变量MAKEFLAGS值的时候(不管在环境中定义或在makefile文件中定义),如果该值不以短线开始,则make首先为该值假设一个短线;接着将该值分割成字,字与字间用空格隔开,然后将这些字进行语法分析,好像它们是在命令行中给出的选项一样。(‘-C', ‘-f',‘-h',‘-o',‘-W'选项以及它们的长名字版本都将忽略,对于无效的选项不产生错误信息。)

如果您在环境中定义变量MAKEFLAGS,您不要使用严重影响make运行,破坏makefile文件的意图以及make自身的选项。例如‘-t', ‘-n', 和‘-q'选项,如果将它们中的一个放到变量MAKEFLAGS的值中,可能产生灾难性的后果,或至少产生让人讨厌的结果。

5.6.4 ‘--print-directory’选项

如果您使用几层make递归调用,使用‘-w’或‘--print-directory’选项,通过显示每个make开始处理以及处理完成的目录使您得到比较容易理解的输出。例如,如果使用‘make –w’命令在目录‘/u/gnu/make’中运行make,则make将下面格式输出信息:

make: Entering directory `/u/gnu/make'.

说明进入目录中,还没有进行任何任务。下面的信息:

make: Leaving directory `/u/gnu/make'.

说明任务已经完成。

通常情况下,您不必具体指明这个选项,因为make已经为您做了:当您使用‘-C’选项时,‘-w’选项已经自动打开,在子make中也是如此。如果您使用‘-s’选项,‘-w’选项不会自动打开,因为‘-s’选项是不打印信息,同样使用`--no-print-directory'选项‘-w’选项也不会自动打开。

5.7定义固定次序命令

在创建各种目标时,相同次序的命令十分有用时,您可以使用define指令定义固定次序的命令,并根据这些目标的规则引用固定次序。固定次序实际是一个变量,因此它的名字不能和其它的变量名冲突。

下面是定义固定次序命令的例子:

define run-yacc

yacc $(firstword $^)

mv y.tab.c $@

endef

run-yacc是定义的变量的名字;endef标志定义结束;中间的行是命令。define指令在固定次序中不对变量引用和函数调用扩展;字符‘$’、圆括号、变量名等等都变成您定义的变量的值的一部分。定义多行变量一节对指令define有详细解释。

在该例子中,对于任何使用该固定次序的规则,第一个命令是对其第一个依赖运行Yacc命令,Yacc命令执行产生的输出文件一律命名为‘y.tab.c’;第二条命令,是将该输出文件的内容移入规则的目标文件中。

在使用固定次序时,规则中命令使用的变量应被确定的值替代,您可以象替代其它变量一样替代这些变量(详细内容参阅变量引用基础)。因为由define指令定义的变量是递归扩展的变量,所以在使用时所有变量引用才扩展。例如:

foo.c : foo.y

$(run-yacc)

当固定次序‘run-yacc’运行时,‘foo.y’将代替变量‘$^’,‘foo.c’将代替变量‘$@’。

这是一个现实的例子,但并不是必要的,因为make有一条隐含规则可以根据涉及的文件名的类型确定所用的命令。参阅使用隐含规则。

在命令执行时,固定次序中的每一行被处理为和直接出现在规则中的命令行一样,前面加上一个Tab,make也特别为每一行请求一个独立的子shell。您也可以在固定次序的每一行上使用影响命令行的前缀字符(`@', `-',和 `+'),参阅在规则中使用命令。例如使用下述的固定次序:

@echo "frobnicating target $@"

frob-step-1 $< -o $@-step-1

frob-step-2 $@-step-1 -o $@

endef

make将不回显第一行,但要回显后面的两个命令行。

另一方面,如果前缀字符在引用固定次序的命令行中使用,则该前缀字符将应用到固定次序的每以行中。例如这个规则:

frob.out: frob.in

@$(frobnicate)

将不回显固定次序的任何命令。具体内容参阅命令回显。

5.8 使用空命令

定义什么也不干的命令有时很有用,定义空命令可以简单的给出一个仅仅含有空格而不含其它任何东西的命令即可。例如:

target: ;

为字符串‘target’定义了一个空命令。您也可以使用以Tab字符开始的命令行定义一个空命令,但这由于看起来空白容易造成混乱。

也许您感到奇怪,为什么我们定义一个空命令?唯一的原因是为了阻止目标更新时使用隐含规则提供的命令。(参阅使用隐含规则以及定义最新类型的缺省规则)

也许您喜爱为实际不存在的目标文件定义空命令,因为这样它的依赖可以重建。然而这样做并不是一个好方法,因为如果目标文件实际存在,则依赖有可能不重建,使用假想目标是较好的选择,参阅假想目标。

6 使用变量

变量是在makefile中定义的名字,其用来代替一个文本字符串,该文本字符串称为该变量的值。在具体要求下,这些值可以代替目标、依赖、命令以及makefile文件中其它部分。(在其它版本的make中,变量称为宏(macros)。)

在makefile文件读入时,除规则中的shell命令、使用‘=’定义的‘=’右边的变量、以及使用define指令定义的变量体此时不扩展外,makefile文件其它各个部分的变量和函数都将扩展。

变量可以代替文件列表、传递给编译器的选项、要执行的程序、查找源文件的目录、输出写入的目录,或您可以想象的任何文本。

变量名是不包括‘:',‘#',‘='、前导或结尾空格的任何字符串。然而变量名包含字母、数字以及下划线以外的其它字符的情况应尽量避免,因为它们可能在将来被赋予特别的含义,而且对于一些shell它们也不能通过环境传递给子make(参阅与子make通讯的变量)。变量名是大小写敏感的,例如变量名‘foo', ‘FOO', 和 ‘Foo'代表不同的变量。

使用大写字母作为变量名是以前的习惯,但我们推荐在makefile内部使用小写字母作为变量名,预留大写字母作为控制隐含规则参数或用户重载命令选项参数的变量名。参阅变量重载。

一少部分的变量使用一个标点符号或几个字符作为变量名,这些变量是自动变量,它们又特定的用途。参阅自动变量。

6.1 变量引用基础

写一个美元符号后跟用圆括号或大括号括住变量名则可引用变量的值:‘$(foo)' 和 ‘${foo}'都是对变量‘foo’的有效引用。‘$’的这种特殊作用是您在命令或文件名中必须写‘$$’才有单个‘$’的效果的原因。

变量的引用可以用在上下文的任何地方:目标、依赖、命令、绝大多数指令以及新变量的值等等。这里有一个常见的例子,在程序中,变量保存着所有OBJ文件的文件名:

objects = program.o foo.o utils.o

program : $(objects)

cc -o

program $(objects)

$(objects) : defs.h

变量的引用按照严格的文本替换进行,这样该规则

foo = c

prog.o : prog.$(foo)

$(foo)$(foo) -$(foo) prog.$(foo)

可以用于编译C语言源程序‘prog.c’。因为在变量分配时,变量值前面的空格被忽略,所以变量foo的值是‘C’。(不要在您的makefile文件这样写!)

美元符号后面跟一个字符但不是美元符号、圆括号、大括号,则该字符将被处理为单字符的变量名。因此可以使用‘$x’引用变量x。然而,这除了在使用自动变量的情况下,在其它实际工作中应该完全避免。参阅自动变量。

6.2 变量的两个特色

在GNU make中可以使用两种方式为变量赋值,我们将这两种方式称为变量的两个特色(two

flavors)。两个特色的区别在于它们的定义方式和扩展时的方式不同。

变量的第一个特色是递归调用扩展型变量。这种类型的变量定义方式:在命令行中使用‘=’定义(参阅设置变量)或使用define指令定义(参阅定义多行变量)。变量替换对于您所指定的值是逐字进行替换的;如果它包含对其它变量的引用,这些引用在该变量替换时(或在扩展为其它字符串的过程中)才被扩展。这种扩展方式称为递归调用型扩展。例如:

foo = $(bar)

bar = $(ugh)

ugh = Huh?

all:;echo $(foo)

将回显‘Huh?':‘$(foo)’扩展为‘$(bar)’,进一步扩展为‘$(ugh)’,最终扩展为‘Huh?’。

这种特色的变量是其它版本make支持的变量类型,有缺点也有优点。大多数人认为的该类型的变量的优点是:

CFLAGS = $(include_dirs) -O

include_dirs = -Ifoo -Ibar

即能够完成希望它完成的任务:当‘CFLAGS’在命令中扩展时,它将最终扩展为‘-Ifoo -Ibar’。其最大的缺点是不能在变量后追加内容,如在:

CFLAGS = $(CFLAGS) -O

在变量扩展过程中可能导致无穷循环(实际上make侦测到无穷循环就会产生错误信息)。

它的另一个缺点是在定义中引用的任何函数时(参阅文本转换函数)变量一旦展开函数就会立即执行。这可导致make运行变慢,性能变坏;并且导致通配符与shell函数(因不能控制何时调用或调用多少次)产生不可预测的结果。

为避免该问题和递归调用扩展型变量的不方便性,出现了另一个特色变量:简单扩展型变量。

简单扩展型变量在命令行中用‘:=’定义(参阅设置变量)。简单扩展型变量的值是一次扫描永远使用,对于引用的其它变量和函数在定义的时候就已经展开。简单扩展型变量的值实际就是您写的文本扩展的结果。因此它不包含任何对其它变量的引用;在该变量定义时就包含了它们的值。所以:

x := foo

y := $(x) bar

x := later

等同于:

y := foo bar

x := later

引用一个简单扩展型变量时,它的值也是逐字替换的。这里有一个稍复杂的例子,说明了‘:=’和shell函数连接用法(参阅函数shell)。该例子也表明了变量MAKELEVEL的用法,该变量在层与层之间传递时值发生变化。(参阅与子make通讯的变量,可获得变量MAKELEVEL关于的信息。)

ifeq (0,${MAKELEVEL})

cur-dir :=

$(shell pwd)

whoami :=

$(shell whoami)

host-type := $(shell arch)

MAKE := ${MAKE} host-type=${host-type}

whoami=${whoami}

endif

按照这种方法使用‘:=’的优点是看起来象下述的典型的‘下降到目录’的命令:

${subdirs}:

${MAKE}

cur-dir=${cur-dir}/$@ -C $@ all

简单扩展型变量因为在绝大多数程序设计语言中可以象变量一样工作,因此它能够使复杂的makefile程序更具有预测性。它们允许您使用它自己的值重新定义(或它的值可以被一个扩展函数以某些方式处理),它们还允许您使用更有效的扩展函数(参阅文本转换函数)。

您可以使用简单扩展型变量将控制的前导空格引入到变量的值中。前导空格字符一般在变量引用和函数调用时被丢弃。简单扩展型变量的这个特点意味着您可以在一个变量的值中包含前导空格,并在变量引用时保护它们。象这样:

nullstring :=

space := $(nullstring) # end of the line

这里变量space的值就是一个空格,注释‘# end of the line’包括在这里为了让人更易理解。因为尾部的空格不能从变量值中分离出去,仅在结尾留一个空格也有同样的效果(但是此时相当难读),如果您在变量值后留一个空格,象这样在行的结尾写上注释清楚表明您的打算是很不错的主意。相反,如果您在变量值后不要空格,您千万记住不要在行的后面留下几个空格再随意放入注释。例如:

dir := /foo/bar

# directory to put the frobs in

这里变量dir的值是‘/foo/bar ’(四个尾部空格),这不是预期的结果。(假设‘/foo/bar’是预期的值)。

另一个给变量赋值的操作符是‘?=’,它称为条件变量赋值操作符,因为它仅仅在变量还没有定义的情况下有效。这声明:

FOO ?= bar

和下面的语句严格等同(参阅函数origin)

ifeq ($(origin FOO), undefined)

FOO = bar

endif

注意,一个变量即使是空值,它仍然已被定义,所以使用‘?=’定义无效。

6.3变量引用高级技术

本节内容介绍变量引用的高级技术。

6.3.1替换引用

替换引用是用您指定的变量替换一个变量的值。它的形式‘$(var:a=b)’(或‘${var:a=b}’),它的含义是把变量var的值中的每一个字结尾的a用b替换。

我们说‘在一个字的结尾’,我们的意思是a一定在一个字的结尾出现,且a的后面要么是空格要么是该变量值的结束,这时的a被替换,值中其它地方的a不被替换。例如:

foo := a.o b.o c.o

bar := $(foo:.o=.c)

将变量‘bar’的值设为‘a.c

b.c c.c’。参阅变量设置。

替换引用实际是使用扩展函数patsubst的简写形式(参阅字符串替换和分析函数)。我们提供替换引用也是使扩展函数patsubst与make的其它实现手段兼容的措施。

另一种替换引用是使用强大的扩展函数patsubst。它的形式和上述的‘$(var:a=b)’一样,不同在于它必须包含单个‘%’字符,其实这种形式等同于‘$(patsubst a,b,$(var))’。有关于函数patsubst扩展的描述参阅字符串替换和分析函数。例如:

foo := a.o b.o c.o

bar := $(foo:%.o=%.c)

社值变量‘bar'的值为‘a.c b.c c.c'。

6.3.2嵌套变量引用(计算的变量名)

嵌套变量引用(计算的变量名)是一个复杂的概念,仅仅在十分复杂的makefile程序中使用。绝大多数情况您不必考虑它们,仅仅知道创建名字中含有美元标志的变量可能有奇特的结果就足够了。然而,如果您是要把一切搞明白的人或您实在对它们如何工作有兴趣,请认真阅读以下内容。

变量可以在它的名字中引用其它变量,这称为嵌套变量引用(计算的变量名)。例如:

x = y

y = z

a := $($(x))

第一頁    上一頁    第3頁/共9頁    下一頁    最後頁
第01頁 第02頁 第03頁 第04頁 第05頁 第06頁 第07頁 第08頁 第09頁 
 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有