Makefile变量作为前提条件


134

在Makefile中,deploy配方需要设置一个环境变量ENV以正确执行自身,而其他配方则不在乎,例如:

ENV = 

.PHONY: deploy hello

deploy:
    rsync . $(ENV).example.com:/var/www/myapp/

hello:
    echo "I don't care about ENV, just saying hello!"

我如何确定设置此变量,例如:是否可以将此makefile变量声明为部署配方的先决条件,例如:

deploy: make-sure-ENV-variable-is-set

谢谢。


您是什么意思,“确保已设置此变量”?您是要验证还是确保?如果以前没有设置过,应该make设置它,还是发出警告,或者产生致命错误?
Beta

1
该变量必须由用户自己指定-因为他是唯一知道自己的环境(开发,生产...)的用户-例如通过调用,make ENV=dev但是如果他忘记了ENV=dev,则deploy配方将失败...
abernier

Answers:


171

如果ENV未定义并且需要某些内容,则将导致致命错误(无论如何,在GNUMake中)。

.PHONY:部署check-env

部署:检查环境
	...

其他需要的环境:检查环境
	...

检查环境:
ifndef ENV
	$(错误ENV未定义)
万一

(请注意,ifndef和endif不缩进- 它们控制make“ sees”的内容,并在运行Makefile之前生效。“ $(error”)带有制表符的缩进,以便仅在规则的上下文中运行。)


12
我正在ENV is undefined运行没有以check-env为先决条件的任务。
2013年

@rane:这很有趣。您能举一个简单的完整例子吗?
2013年

2
@rane是空格与制表符之间的区别?
esmit 2013年

8
@esmit:是的;我应该对此回答。在我的解决方案中,该行以TAB开头,因此这是check-env规则中的命令;除非/直到执行规则,否则Make不会展开它。如果它不是以TAB开头(例如@rane的示例),则Make会将其解释为不在规则中,并在运行任何规则之前对其进行评估,而与目标无关。
2013年

1
在我的解决方案中,该行以TAB开头,因此它是check-env规则中的命令;```您在谈论哪一行?就我而言,即使ifndef之后的行以TAB开头,每次也会评估if条件
Dhawal

103

您可以创建一个隐式的保护目标,以检查是否已定义主干中的变量,如下所示:

guard-%:
    @ if [ "${${*}}" = "" ]; then \
        echo "Environment variable $* not set"; \
        exit 1; \
    fi

然后,您guard-ENVVAR可以在要断言定义了变量的任何地方添加目标,如下所示:

change-hostname: guard-HOSTNAME
        ./changeHostname.sh ${HOSTNAME}

如果您致电make change-hostname,而未添加HOSTNAME=somehostname通话,则会收到错误消息,并且构建将失败。


5
这是一个聪明的解决方案,我喜欢它:)
Elliot Chance

我知道这是一个古老的答复,但是也许有人仍在观看,否则我可能会将此问题重新发布为一个新问题……我正在尝试实现此隐式目标“后卫”以检查设置的环境变量,并且该方法有效原则上,但是实际上“ guard-%”规则中的命令已打印到外壳程序上。我想抑制这一点。这怎么可能?
genomicsio 2014年

2
好。我自己找到了解决方案……@在规则命令行的开头是我的朋友……
genomicsio 2014年

4
一内胆:if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fi:d
c24w

4
这应该是选定的答案。其更清洁的实现。
sb32134 '16

46

内联变体

在我的makefile中,我通常使用类似以下的表达式:

deploy:
    test -n "$(ENV)"  # $$ENV
    rsync . $(ENV).example.com:/var/www/myapp/

原因:

  • 这是一个简单的单线
  • 它很紧凑
  • 它位于使用该变量的命令附近

不要忘记对调试很重要的注释:

test -n ""
Makefile:3: recipe for target 'deploy' failed
make: *** [deploy] Error 1

...强迫您在...时查找Makefile

test -n ""  # $ENV
Makefile:3: recipe for target 'deploy' failed
make: *** [deploy] Error 1

...直接说明出了什么问题

全局变量(出于完整性考虑,但未要求)

在您的Makefile上,您还可以编写:

ifeq ($(ENV),)
  $(error ENV is not set)
endif

警告:

  • 不要在该块中使用制表符
  • 谨慎使用:clean如果未设置ENV,则即使目标也会失败。否则请看更复杂的休顿答案

哇。直到看到“不要在该块中使用选项卡”,我对此感到麻烦。谢谢!
Alex K

这是一个很好的选择,但是我不喜欢即使成功(整个行
Jeff

@Jeff这是makefile的基础知识。只需在行前面加上即可@。-> gnu.org/software/make/manual/make.html#Echoing
Daniel Alder

我尝试过,但是如果失败,则不会显示错误消息。嗯,我会再试一次。肯定地支持您的答案。
杰夫

1
我喜欢测试方法。我使用的是这样的:@test -n "$(name)" || (echo 'A name must be defined for the backup. Ex: make backup name=xyz' && exit 1)
swampfox357

6

到目前为止,给定答案的一个可能问题是未定义make中的依赖顺序。例如,运行:

make -j target

什么时候target有一些依赖关系并不能保证它们将以任何给定的顺序运行。

解决此问题的方法(以确保在选择配方之前将对ENV进行检查)是在任何配方之外的make首次通过期间检查ENV:

## Are any of the user's goals dependent on ENV?
ifneq ($(filter deploy other-thing-that-needs-ENV,$(MAKECMDGOALS)),$())
ifndef ENV 
$(error ENV not defined)
endif
endif

.PHONY: deploy

deploy: foo bar
    ...

other-thing-that-needs-ENV: bar baz bono
    ...

您可以阅读有关此处使用的不同函数/变量的信息$()这只是一种明确声明我们要与“无”进行比较的方法。


6

我发现最好的答案不能用作要求,除了其他PHONY目标。如果用作实际文件目标的依赖项,则使用check-env将强制重建该文件目标。

其他答案是全局的(例如,Makefile中的所有目标都需要变量)或使用外壳程序,例如,如果缺少ENV,make将终止于目标。

我发现这两个问题的解决方案是

ndef = $(if $(value $(1)),,$(error $(1) not set))

.PHONY: deploy
deploy:
    $(call ndef,ENV)
    echo "deploying $(ENV)"

.PHONY: build
build:
    echo "building"

输出看起来像

$ make build
echo "building"
building
$ make deploy
Makefile:5: *** ENV not set.  Stop.
$ make deploy ENV="env"
echo "deploying env"
deploying env
$

value 有一些可怕的警告,但是对于这种简单的用法,我相信它是最佳选择。


5

如我所见,命令本身需要ENV变量,因此您可以在命令本身中进行检查:

.PHONY: deploy check-env

deploy: check-env
    rsync . $(ENV).example.com:/var/www/myapp/

check-env:
    if test "$(ENV)" = "" ; then \
        echo "ENV not set"; \
        exit 1; \
    fi

问题在于,deploy不一定是唯一需要此变量的配方。使用此解决方案,我必须测试ENV每个状态的状态……而我想将其作为一个(某种)先决条件来处理。
abernier 2011年

4

我知道这很古老,但我想我会以自己的经验吸引未来的访问者,因为IMHO有点整洁。

通常,make它将sh用作其默认外壳程序(通过特殊SHELL变量设置)。在sh及其衍生物中,如果未设置环境变量或通过执行以下操作返回null则在检索环境变量时退出并显示一条错误消息很容易${VAR?Variable VAR was not set or null}

扩展这一点,我们可以编写一个可重用的make目标,如果未设置环境变量,该目标可用于使其他目标失效:

.check-env-vars:
    @test $${ENV?Please set environment variable ENV}


deploy: .check-env-vars
    rsync . $(ENV).example.com:/var/www/myapp/


hello:
    echo "I don't care about ENV, just saying hello!"

注意事项:

  • 需要使用转义的美元符号($$)将扩展扩展到外壳,而不是扩展到外壳make
  • 的使用test只是为了防止shell尝试执行其中的内容VAR(它没有其他重要作用)
  • .check-env-vars可以简单地扩展以检查更多的环境变量,每个环境变量仅添加一行(例如@test $${NEWENV?Please set environment variable NEWENV}

如果ENV包含空格,这似乎将失败(至少对我而言)
eddiegroves

2

您可以使用ifdef而不是其他目标。

.PHONY: deploy
deploy:
    ifdef ENV
        rsync . $(ENV).example.com:/var/www/myapp/
    else
        @echo 1>&2 "ENV must be set"
        false                            # Cause deploy to fail
    endif

嘿,谢谢您的回答,但由于您的建议生成了重复的代码,因此无法接受……而且,deploy这不是唯一必须检查ENV状态变量的方法。
abernier 2011年

然后重构。在ifdef块之前使用.PHONY: deployand deploy:语句,并删除重复项。(顺便说一句,我已经编辑了答案以反映正确的方法)
德怀特·斯宾塞
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.