如果变量未设置,如何中止makefile?

219

如果一个makefile的变量没有被设置或赋值,我该如何中止make/makefile执行?

我想到了这个方法,但只有在调用者没有显式运行目标(即仅运行make)时才起作用。

ifeq ($(MY_FLAG),)
abort:   ## This MUST be the first target :( ugly
    @echo Variable MY_FLAG not set && false
endif

all:
    @echo MY_FLAG=$(MY_FLAG)

我认为这样做是个好主意,但在 make 的手册中没有找到任何相关内容:

ifndef MY_FLAG
.ABORT
endif

6
可能是Makefile变量作为先决条件的重复问题。 - Keith Smiley
最简单的答案 https://dev59.com/E2gv5IYBdhLWcg3wFs6m#71243186 - kenberkeley
相关功能请求:https://savannah.gnu.org/bugs/?63737 - guettli
6个回答

402

简而言之: 使用error函数

ifndef MY_FLAG
$(error MY_FLAG is not set)
endif

请注意,这些行不能缩进。更准确地说,这些行之前不能有制表符。

通用解决方案

如果您要测试多个变量,值得定义一个辅助函数:

# Check that given variables are set and all have non-empty values,
# die with an error otherwise.
#
# Params:
#   1. Variable name(s) to test.
#   2. (optional) Error message to print.
check_defined = \
    $(strip $(foreach 1,$1, \
        $(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
    $(if $(value $1),, \
      $(error Undefined $1$(if $2, ($2))))

以下是如何使用它的方法:

$(call check_defined, MY_FLAG)

$(call check_defined, OUT_DIR, build directory)
$(call check_defined, BIN_DIR, where to put binary artifacts)
$(call check_defined, \
            LIB_INCLUDE_DIR \
            LIB_SOURCE_DIR, \
        library path)

这会输出如下错误信息:
Makefile:17: *** Undefined OUT_DIR (build directory).  Stop.

注:

真正的检查在这里进行:

$(if $(value $1),,$(error ...))

这反映了ifndef条件的行为,因此将变量定义为空值也视为“未定义”。但这仅适用于简单变量和明确为空的递归变量:
# ifndef and check_defined consider these UNDEFINED:
explicitly_empty =
simple_empty := $(explicitly_empty)

# ifndef and check_defined consider it OK (defined):
recursive_empty = $(explicitly_empty)

根据评论区@VictorSergienko的建议,可能需要略微不同的行为:

$(if $(value $1)测试值是否非空。如果变量定义为空,则有时可以使用它。我会使用$(if $(filter undefined,$(origin $1)) ...

并且:

此外,如果它是一个目录并且在运行检查时必须存在,我会使用$(if $(wildcard $1))。但这将是另一个函数。

特定于目标的检查

还可以扩展解决方案,以便只能在调用某个特定目标时要求变量。

$(call check_defined, ...)从配方内部

只需将检查移至配方中:

foo :
    @:$(call check_defined, BAR, baz value)

领先的@符号关闭命令回显,:是实际命令,一个shell no-op stub

显示目标名称

check_defined函数可以改进,还可以输出目标名称(通过$@变量提供):

check_defined = \
    $(strip $(foreach 1,$1, \
        $(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
    $(if $(value $1),, \
        $(error Undefined $1$(if $2, ($2))$(if $(value @), \
                required by target `$@')))

因此,现在一个失败的检查会产生一个格式良好的输出:
Makefile:7: *** Undefined BAR (baz value) required by target `foo'.  Stop.

check-defined-MY_FLAG 特殊目标

个人而言,我会使用上面简单直接的解决方案。但是,例如这个答案建议使用特殊目标来执行实际检查。可以尝试将其概括,并将目标定义为隐式模式规则:

# Check that a variable specified through the stem is defined and has
# a non-empty value, die with an error otherwise.
#
#   %: The name of the variable to test.
#   
check-defined-% : __check_defined_FORCE
    @:$(call check_defined, $*, target-specific)

# Since pattern rules can't be listed as prerequisites of .PHONY,
# we use the old-school and hackish FORCE workaround.
# You could go without this, but otherwise a check can be missed
# in case a file named like `check-defined-...` exists in the root 
# directory, e.g. left by an accidental `make -t` invocation.
.PHONY : __check_defined_FORCE
__check_defined_FORCE :

用法:

foo :|check-defined-BAR

注意,check-defined-BAR被列为order-only(|...)的先决条件。
优点:
  • (可以说)更简洁的语法
缺点:

我认为,可以使用一些eval魔法和secondary expansion技巧克服这些限制,尽管我不确定这是否值得。


什么意思?虽然我从未使用过Mac,但我猜它默认安装了另一种Make实现(例如BSD Make而不是GNU Make)。我建议您首先检查make --version - Eldar Abusalimov
3
在make 3.81版本中好像无法正常工作。即使变量被定义并且可以被输出,它始终会报错。 - OrangeDog
1
@bibstha 我添加了想到的选项,请阅读更新后的答案。 - Eldar Abusalimov
2
我想为新手(像我一样)添加一个澄清说明,即 ifndef 不应缩进 :) 我在其他地方找到了这个提示,突然间所有的错误都变得有意义了。 - helios
如果 'ifndef 不需要缩进' 的注释对我来说被掩埋了。如果示例有更多内容以显示缩进问题,那将是有帮助的。 - gageorge
显示剩余12条评论

57

使用shell函数test

foo:
    test $(something)

使用方法:

$ make foo
test 
Makefile:2: recipe for target 'foo' failed
make: *** [foo] Error 1
$ make foo something=x
test x

4
这是我在面对同样问题时所使用的方法 - 感谢Messa! 我进行了两个轻微的修改:1)我创建了一个 checkforsomething 目标,其中只包含 test 并使 foo 依赖于它,2)我将检查改为了 @if test -z“$(something)” ; then echo“有用的错误信息”; exit 1; fi。 这使我能够添加一个有用的错误,并允许我使新目标的名称更具指示性以表明出错的原因。 - Brian Gerard
使用这个命令,我得到了 Makefile:5: *** missing separator. Stop. - silgon
15
为了紧凑,我使用了 test -n "$(something) || (echo "message" ; exit 1) 来避免使用显式的 if。请注意,这是一句英文原文,我已经将其翻译成中文,但仍然尽可能保留原意,使其易于理解。 - user295691
@silgon 你可能是在使用空格进行缩进而不是制表符。 - eweb
@user295691 你可能想要使用花括号 { ... } 而不是圆括号 ( ... ),后者会在子shell中执行。 - undefined

19

你可以使用 IF 来进行测试:

check:
        @[ "${var}" ] || ( echo ">> var is not set"; exit 1 )

结果:

$ make check
>> var is not set
Makefile:2: recipe for target 'check' failed
make: *** [check] Error 1

2
[ 是命令 test 的别名,因此与 @Messa 上面的答案相同。虽然更紧凑,但包括错误消息的生成。 - user295691
这似乎是大多数情况下最干净的答案。 - Dave Kerr
我很欣赏这个答案的简洁明了。即使下一个阅读我的Makefile的人不知道它是如何工作的,他们也会知道它的作用。 - TomOnTime

12

另一种选择:

MY_FLAG = $(error Please set this flag)

如果未在命令行中重写该变量,则尝试在任何地方使用此变量都会导致错误。

要接受环境变量,请使用?=

MY_FLAG ?= $(error Please set this flag)

这应该是最好的答案。 - kenberkeley
这是一个干净的代码答案,但是你能不能在不设置这个变量的情况下调用clean目标呢? - Ezbob
1
@Ezbob 是的。只有在尝试读取变量时才会出现错误。 - HolyBlackCat
那么,我想这显然是最好的答案。 - Ezbob
那么,我想这显然是最好的答案。 - undefined

9

为简洁明了起见:

$ cat Makefile
check-%:
        @: $(if $(value $*),,$(error $* is undefined))

bar:| check-foo
        echo "foo is $$foo"

输出结果为:

$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something

可以通过 $ touch check-foo; make bar 进行欺骗...需要使用其他答案中的 check_defined_FORCE 技巧。 - qneill

8

使用Shell错误处理来处理未设置的变量(注意双重$):

$ cat Makefile
foo:
        echo "something is set to $${something:?}"

$ make foo
echo "something is set to ${something:?}"
/bin/sh: something: parameter null or not set
make: *** [foo] Error 127


$ make foo something=x
echo "something is set to ${something:?}"
something is set to x

如果您需要自定义错误消息,在?后添加即可:

$ cat Makefile
hello:
        echo "hello $${name:?please tell me who you are via \$$name}"

$ make hello
echo "hello ${name:?please tell me who you are via \$name}"
/bin/sh: name: please tell me who you are via $name
make: *** [hello] Error 127

$ make hello name=jesus
echo "hello ${name:?please tell me who you are via \$name}"
hello jesus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接