如何确保将 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: deploydeploy: 语句并删除重复项。 (顺便说一句,我已经编辑了答案以反映正确的方法)【参考方案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 中),这将导致致命错误。

.PHONY:部署检查环境 部署:检查环境 ... 其他需要环境的东西:检查环境 ... 检查环境: ifndef ENV $(错误 ENV 未定义) 万一

(注意 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,而不是在 maketest 的使用只是为了防止 shell 尝试执行 VAR 的内容(它没有其他重要用途) .check-env-vars 可以简单地扩展以检查更多环境变量,每个环境变量只添加一行(例如@test $$NEWENV?Please set environment variable NEWENV

【讨论】:

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

以上是关于如何确保将 Makefile 变量设置为先决条件?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 NMake 设置环境变量

makefile条件判断语句

GCC学习 如何编写makefile

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

makefile.am 可以为一组目标设置 LDADD 吗?

Makefile 在目标中设置变量