条件指令可以根据变量的值,让 makefile 的一部分被执行或被忽略。条件可以把某个变量的值与另一个变量的值进行比较,也可以把变量的值与固定的字符串进行比较。条件控制的是 make 在 makefile 中实际「看到」的内容。因此,它不能用来控制命令 (recipe) 执行时刻的行为。
下面这个条件示例告诉 make:如果变量 CC 是「gcc」就使用某一组库,否则使用另一组库。它是通过控制规则所用的两行命令中使用哪一行来实现的。其结果是,把「CC=gcc」作为 make 的参数传入时,不仅会改变所使用的编译器,还会改变所链接的库。
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
这个条件用到了 ifeq、else、endif 这三个指令。
ifeq 指令标志条件的开始,并指定条件。它接受两个参数,参数之间用逗号分隔,整体用括号括起来。会对两个参数都进行变量替换,然后再加以比较。ifeq 之后的 makefile 各行,在两个参数一致时被执行,不一致时被忽略。
else 指令会在前一个条件不成立时,让其后的各行被执行。在上面的例子中,这意味着只要第一条链接命令未被使用,就一定会使用第二条作为替代的链接命令。在条件中是否放置 else 是可选的。
endif 指令结束条件。所有条件都必须以 endif 结束。其后接续与条件无关的普通 makefile 文本。
正如这个例子所示,条件是在文本层面起作用的。也就是说,条件中包含的各行会根据条件而被当作 makefile 的一部分,或被忽略。这正是为什么像规则这样更大的 makefile 语法单位,可以跨越条件的开始或结束。
当变量 CC 的值为「gcc」时,上面的例子具有如下效果:
foo: $(objects)
$(CC) -o foo $(objects) $(libs_for_gcc)
当变量 CC 取其他任何值时,效果如下:
foo: $(objects)
$(CC) -o foo $(objects) $(normal_libs)
同样的结果也可以用另一种方式得到,即把对变量的赋值本身作为条件的对象,而该变量则无条件地使用:
libs_for_gcc = -lgnu
normal_libs =
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo: $(objects)
$(CC) -o foo $(objects) $(libs)
不带 else 的简单条件的写法如下:
conditional-directive text-if-true endif
text-if-true 可以是任意若干行,当条件为真时会被当作 makefile 的一部分。当条件为假时,则不使用任何文本作为替代。
复杂条件的写法如下:
conditional-directive text-if-true else text-if-false endif
或者:
conditional-directive-one text-if-one-is-true else conditional-directive-two text-if-two-is-true else text-if-one-and-two-are-false endif
形如「else conditional-directive」的子句可以根据需要放置任意多个。一旦某个条件为真,就使用其 text-if-true,其余子句都不再使用。如果没有任何条件为真,则使用 text-if-false。text-if-true 和 text-if-false 可以是任意行数的文本。
conditional-directive 的写法,无论是简单条件还是复杂条件,也无论是否在 else 之后,都是相同的。有四种检测不同条件的指令。把它们汇总成表,如下所示:
ifeq (arg1, arg2)ifeq 'arg1' 'arg2'ifeq "arg1" "arg2"ifeq "arg1" 'arg2'ifeq 'arg1' "arg2"展开 arg1 和 arg2 中所有的变量引用,然后加以比较。如果两者完全相同,则 text-if-true 生效;否则,text-if-false(如果有的话)生效。
你常常想要检测某个变量是否具有非空的值。当该值是变量与函数复杂展开的结果时,你认为应当为空的展开结果,实际上可能含有空白字符,因而不会被视为空。不过,你可以使用 strip 函数(请参见用于字符串替换与分析的函数),以避免把空白解释为非空的值。例如,可以这样写:
ifeq ($(strip $(foo)),) text-if-empty endif
这样写的话,即使 $(foo) 的展开结果含有空白字符,text-if-empty 也会被求值。
ifneq (arg1, arg2)ifneq 'arg1' 'arg2'ifneq "arg1" "arg2"ifneq "arg1" 'arg2'ifneq 'arg1' "arg2"展开 arg1 和 arg2 中所有的变量引用,然后加以比较。如果两者不同,则 text-if-true 生效;否则,text-if-false(如果有的话)生效。
ifdef variable-name
ifdef 这种形式以变量的名字作为参数,而不是对变量的引用。如果该变量的值是非空的值,则 text-if-true 生效;否则,text-if-false(如果有的话)生效。从未被定义过的变量具有空的值。variable-name 这段文本会被展开,因此它可以是一个会展开成变量名的变量或函数。例如:
bar = true foo = bar ifdef $(foo) frobozz = yes endif
变量引用 $(foo) 被展开后得到 bar,它被当作变量名。变量 bar 本身不会被展开,但会检查它的值以判定是否非空。
请注意,ifdef 只检测变量是否具有值。它不会展开变量来查看该值是否非空。因此,使用 ifdef 进行的判定,除了像 foo = 这样的定义之外,对所有定义都返回真。想要检测是否为空值时,请使用 ifeq ($(foo),)。例如,如果这样写:
bar = foo = $(bar) ifdef foo frobozz = yes else frobozz = no endif
「frobozz」会被设置为「yes」。而如果这样写:
foo = ifdef foo frobozz = yes else frobozz = no endif
「frobozz」会被设置为「no」。
ifndef variable-name
如果变量 variable-name 是空的值,则 text-if-true 生效;否则,text-if-false(如果有的话)生效。variable-name 的展开与判定规则,与 ifdef 指令完全相同。
条件指令行的行首可以放置多余的空格,它们会被忽略,但不允许放置制表符(Tab)。(如果某行以制表符开头,它就会被当作某条规则命令的一部分。)除此之外,除了指令名内部或参数内部以外,在任何位置插入多余的空格或制表符都没有任何影响。行尾可以放置以「#」开头的注释。
在条件中发挥作用的另外两个指令是 else 和 endif。它们都只用一个单词书写,不接受参数。行首可以放置多余的空格,它们会被忽略,行尾也可以放置空格或制表符。行尾可以放置以「#」开头的注释。
条件会影响 make 使用 makefile 的哪些行。如果条件为真,make 会把 text-if-true 的各行作为 makefile 的一部分读入;如果条件为假,make 会完全忽略这些行。由此可知,像规则这样的 makefile 语法单位,即使跨越条件的开始或结束而被分割,也是安全的。
make 在读入 makefile 时就会对条件求值。因此,不能在条件的判定中使用自动变量,因为它们要到命令运行时才会被定义(请参见自动变量)。
为防止无法收拾的混乱,不允许在某个 makefile 中开始一个条件,而在另一个 makefile 中结束它。不过,你可以在条件中写 include 指令,只要不在被包含的文件中试图结束该条件即可。
把变量 MAKEFLAGS 与 findstring 函数(请参见用于字符串替换与分析的函数)组合使用,就可以编写检测像「-t」这样的 make 命令标志的条件。当仅靠 touch 不足以让文件看起来是最新的时,这很有用。
请回想一下,MAKEFLAGS 会把所有单字符选项(例如「-t」)汇集到第一个单词里,而且当没有给出任何单字符选项时,那个单词会是空的。为了应对这一点,在开头加上某个值以确保单词一定存在,会很方便。例如写成「-$(MAKEFLAGS)」。
findstring 函数判定某个字符串是否作为另一个字符串的子串出现。如果想检测「-t」标志,就把「t」指定为第一个字符串,把 MAKEFLAGS 的第一个单词指定为另一个。
例如,要安排使用「ranlib -t」来完成把归档文件 (archive) 标记为最新的收尾工作,可以这样做:
archive.a: …
ifneq (,$(findstring t,$(firstword -$(MAKEFLAGS))))
+touch archive.a
+ranlib -t archive.a
else
ranlib archive.a
endif
「+」这个前缀会把那些命令行标记为「递归的」,这样即使使用了「-t」标志,那些行也会被执行。请参见make 的递归用法。