如何确保将 Makefile 变量设置为先决条件?
Posted
技术标签:
【中文标题】如何确保将 Makefile 变量设置为先决条件?【英文标题】:How to ensure Makefile variable is set as a prerequisite? 【发布时间】:2011-06-11 08:35:39 【问题描述】: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!"
如何确保设置了这个 ENV
变量?有没有办法将此 makefile 变量声明为部署配方的先决条件?例如,
deploy: make-sure-ENV-variable-is-set
【问题讨论】:
你是什么意思,“确保这个变量被设置”?你的意思是验证还是确保?如果之前没有设置,应该make
设置,还是给出警告,还是产生致命错误?
这个变量必须由用户自己指定——因为他是唯一知道他的环境(开发、产品...)的人——例如通过调用make ENV=dev
但如果他忘记了ENV=dev
, deploy
配方将失败...
【参考方案1】:
您可以使用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
状态的配方变量。
然后重构。在 ifdef 块之前使用 .PHONY: deploy
和 deploy:
语句并删除重复项。 (顺便说一句,我已经编辑了答案以反映正确的方法)【参考方案2】:
正如我所见,命令本身需要 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
的状态......而我想将其作为一个(某种)先决条件来处理。【参考方案3】:
如果 ENV
未定义并且需要它(无论如何在 GNUMake 中),这将导致致命错误。
(注意 ifndef 和 endif 没有缩进 - they control what make "sees",在 Makefile 运行之前生效。“$(error” 用制表符缩进,因此它只在规则的上下文中运行。)
【讨论】:
在运行没有将 check-env 作为先决条件的任务时,我收到了ENV is undefined
。
@rane:这很有趣。你能举一个最小的完整例子吗?
@rane 是空格还是制表符的区别?
@esmit:是的;我应该回答这个问题。在我的解决方案中,该行以 TAB 开头,因此它是 check-env
规则中的命令;除非/直到执行规则,否则 Make 不会扩展它。如果它不以 TAB 开头(如 @rane 的示例),Make 会将其解释为不在规则中,并在运行任何规则之前对其进行评估,而不管目标如何。
``` 在我的解决方案中,该行以 TAB 开头,因此它是 check-env 规则中的一个命令;``` 你在说哪一行?在我的情况下,即使 ifndef 之后的行以 TAB 开头,每次都会评估 if 条件【参考方案4】:
您可以创建一个隐式保护目标,检查词干中的变量是否已定义,如下所示:
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
,那么你会得到一个错误,并且构建会失败。
【讨论】:
我知道这是一个古老的回复,但也许有人仍在观看,否则我可能会将其重新发布为一个新问题......我正在尝试实现这个隐含目标“守卫”检查设置的环境变量,它原则上可以工作,但是“guard-%”规则中的命令实际上是打印到 shell 的。这是我想压制的。这怎么可能? 好的。自己找到了解决方案...规则命令行开头的@是我的朋友... @genomicsio 不错的建议;纳入答案。 单线:if [ -z '$$*' ]; then echo 'Environment variable $* not set' && exit 1; fi
:D
警告,如果guard-VARIABLENAME 是一个存在的文件,则会损坏。通过声明一个空的虚假目标然后将 guard-% 隐式规则设置为依赖于它来解决。 example .mak gist.github.com。这迫使它在每次看到规则时都重新评估它。【参考方案5】:
到目前为止,给定答案的一个可能问题是未定义 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
...
您可以阅读here 和$()
使用的不同函数/变量,这只是一种明确声明我们正在与“无”进行比较的方式。
【讨论】:
【参考方案6】:内联变体
在我的 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
警告:
不要在该块中使用标签 谨慎使用:如果未设置 ENV,即使clean
目标也会失败。否则,请参阅更复杂的 Hudon 的答案
【讨论】:
是一个不错的选择,但我不喜欢即使成功也出现“错误消息”(打印整行) @Jeff 这是 makefile 的基础知识。只需在该行前加上@
。 -> gnu.org/software/make/manual/make.html#Echoing
我试过了,但是在失败的情况下不会出现错误消息。嗯,我会再试一次。肯定赞成你的答案。
我喜欢测试方法。我用过这样的东西:@test -n "$(name)" || (echo 'A name must be defined for the backup. Ex: make backup name=xyz' && exit 1)
【参考方案7】:
我发现最好的答案不能用作要求,除了其他 PHONY 目标。如果用作实际文件目标的依赖项,则使用check-env
将强制重建该文件目标。
其他答案是全局的(例如,Makefile 中的 all 目标需要该变量)或使用 shell,例如如果 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
有一些可怕的警告,但对于这个简单的用途,我相信它是最好的选择。
【讨论】:
【参考方案8】:我知道这已经过时了,但我想我会为未来的访客提供自己的经验,因为恕我直言,它更简洁一些。
通常,make
将使用 sh
作为其默认 shell (set via the special SHELL
variable)。在sh
及其派生词中,exit with an error message when retrieving an environment variable if it is not set or 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!"
注意事项:
需要转义的美元符号 ($$
) 才能将扩展推迟到 shell,而不是在 make
内
test
的使用只是为了防止 shell 尝试执行 VAR
的内容(它没有其他重要用途)
.check-env-vars
可以简单地扩展以检查更多环境变量,每个环境变量只添加一行(例如@test $$NEWENV?Please set environment variable NEWENV
)
【讨论】:
如果ENV
包含空格,这似乎会失败(至少对我而言)以上是关于如何确保将 Makefile 变量设置为先决条件?的主要内容,如果未能解决你的问题,请参考以下文章