如何确保将Makefile变量设置为先决条件?

如何确保将Makefile变量设置为先决条件?,makefile,Makefile,Makefiledeployrecipe需要设置一个环境变量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变量声明为部署配

Makefile
deploy
recipe需要设置一个环境变量
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变量声明为部署配方的先决条件?e、 g

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

您可以使用
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

正如我看到的,命令本身需要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

如果
ENV
未定义且某些东西需要它(无论如何,在GNUMake中),这将导致致命错误

.PHONY:部署检查环境 部署:检查环境 ... 其他需要环境的东西:检查环境 ... 检查环境: ifndef环境 $(错误ENV未定义) 恩迪夫
(请注意,ifndef和endif未缩进-,在运行Makefile之前生效。”$(error”缩进一个制表符,以便它仅在规则上下文中运行。)

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

guard-%:
    @ if [ "${${*}}" = "" ]; then \
        echo "Environment variable $* not set"; \
        exit 1; \
    fi
change-hostname: guard-HOSTNAME
        ./changeHostname.sh ${HOSTNAME}
然后,将
guard ENVVAR
目标添加到要断言已定义变量的任何位置,如下所示:

guard-%:
    @ if [ "${${*}}" = "" ]; then \
        echo "Environment variable $* not set"; \
        exit 1; \
    fi
change-hostname: guard-HOSTNAME
        ./changeHostname.sh ${HOSTNAME}

如果调用
make change hostname
,但未在调用中添加
hostname=somehostname
,则会出现错误,生成将失败。

到目前为止,给定答案的一个可能问题是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
    ...

您可以阅读有关使用的不同函数/变量的信息,
$()
只是一种明确表示我们在与“无”进行比较的方式。

内联变量

在makefiles中,我通常使用如下表达式:

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
…强制您在

test -n ""  # $ENV
Makefile:3: recipe for target 'deploy' failed
make: *** [deploy] Error 1
…直接解释了问题所在

全局变量(完整性要求,但不要求)

在Makefile的顶部,您还可以编写:

ifeq ($(ENV),)
  $(error ENV is not set)
endif
警告:

  • 不要在那个街区用tab键
  • 小心使用:如果未设置ENV,即使清洁目标也会失败。否则请参阅Hudon更复杂的答案

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

其他答案是全局的(例如,Makefile中的所有目标都需要该变量)或使用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
有一些可怕的警告,但对于这个简单的用法,我相信它是最好的选择。

我知道这是旧的,但我想我会为未来的访问者提供我自己的经验,因为它更整洁一些

通常,
make
将使用
sh
作为其默认shell()。在
sh
及其派生中,这样做很简单:
${VAR?变量VAR未设置或为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?请设置环境变量NEWENV}

问题在于,
deploy
不一定是唯一需要此变量的配方。使用此解决方案,我必须测试每个变量的
ENV
状态……而我希望将其作为单个变量(某种程度上)处理先决条件。嘿,谢谢你的回答,但因为你的建议生成了重复的代码,所以不能接受。更重要的是,
deploy
不是唯一一个必须检查
ENV
状态变量的配方。然后只需重构。在ifdef块和remo之前使用
.PHONY:deploy
deploy:
语句我已经编辑了答案以反映正确的方法。“确保设置了此变量”是什么意思?您的意思是验证还是确保?如果之前未设置,是否应设置该变量,或发出警告,或生成致命错误?此变量必须由用户自己指定-因为他是唯一了解其环境的人(开发人员、生产人员…)-例如,通过调用
make ENV=dev
,但是如果他忘记了
ENV=dev
deploy
配方将失败…我发现
ENV是未定义的
当运行一个不以check ENV为先决条件的任务时。@rane:这很有趣。你能给出一个最小的完整示例吗?@rane是空格和制表符?@esmit:是的;我应该回答这个问题。在我的解决方案中,行以制表符开头,因此它是
check env
规则中的一个命令;Make不会展开它,除非/直到执行规则。如果它不是以制表符开头(如@rane的示例),Make会将其解释为不在规则中,并在之前对其求值