Makefile变量作为先决条件

时间:2011-01-18 20:43:59

标签: makefile

在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

谢谢。

8 个答案:

答案 0 :(得分:144)

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

.PHONY: deploy check-env

deploy: check-env
	...

other-thing-that-needs-env: check-env
	...

check-env:
ifndef ENV
	$(error ENV is undefined)
endif

(注意,ifndef和endif没有缩进 - they control what make "sees",在Makefile运行之前生效。“$(错误”缩进了一个标签,以便它只在规则的上下文中运行。)

答案 1 :(得分:85)

您可以创建一个隐式守卫目标,用于检查词干中的变量是否已定义,如下所示:

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,那么您将收到错误,并且构建将失败。

答案 2 :(得分:38)

内嵌变体

在我的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的更复杂的答案

答案 3 :(得分: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

答案 4 :(得分:4)

到目前为止,给定答案的一个可能问题是未定义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$()只是一种明确说明我们正在与“无”进行比较的方法。

答案 5 :(得分:4)

我发现最好的答案不能作为要求,除了其他PHONY目标。如果用作实际文件的目标的依赖项,则使用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有一些可怕的警告,但对于这个简单的使用我相信它是最好的选择。

答案 6 :(得分: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

答案 7 :(得分:0)

我知道这很老了,但我想我会以自己的经验吸引以后的访客,因为恕我直言有点整洁。

通常,make将使用sh作为其默认外壳程序(set via the special SHELL variable)。在sh及其衍生物中,通过执行${VAR?Variable VAR was not set or null}exit with an error message when retrieving an environment variable if it is 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}