Makefile变量作为先决条件

abe*_*ier 127 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!"
Run Code Online (Sandbox Code Playgroud)

如何确保设置此变量,例如:是否有办法将此makefile变量声明为部署配方的先决条件,例如:

deploy: make-sure-ENV-variable-is-set
Run Code Online (Sandbox Code Playgroud)

谢谢.

Bet*_*eta 155

如果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没有缩进 - 它们控制着什么使"看到",在Makefile运行之前生效."$(错误"用缩进键缩进,以便它只在规则的上下文中运行.)

  • 当运行**没有**check-env作为先决条件的任务时,我得到`ENV is undefined`. (12认同)
  • @esmit:是的; 我应该回答这个问题.在我的解决方案中,该行以TAB开头,因此它是`check-env`规则中的命令; 除非/直到执行规则,否则Make不会扩展它.如果它不以TAB开头(如在@ rane的例子中),则Make将其解释为不在规则中,并在运行任何规则之前对其进行评估,而不管目标如何. (8认同)
  • @rane是空格与制表符的区别? (2认同)

Cla*_*ley 90

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

guard-%:
    @ if [ "${${*}}" = "" ]; then \
        echo "Environment variable $* not set"; \
        exit 1; \
    fi
Run Code Online (Sandbox Code Playgroud)

然后,您可以在任何要声明已定义变量的位置添加guard-ENVVAR目标,如下所示:

change-hostname: guard-HOSTNAME
        ./changeHostname.sh ${HOSTNAME}
Run Code Online (Sandbox Code Playgroud)

如果您调用'make change-hostname',而不在调用中添加HOSTNAME = somehostname,那么您将收到错误,并且构建将失败.

  • 这是一个聪明的解决方案,我喜欢它:) (4认同)
  • 单行:`if [-z'$ {$ {*}}']; 然后回显'环境变量$*未设置'&&退出1; fi`:D (4认同)
  • 这应该是选定的答案.它更清洁的实施. (4认同)
  • 好.我自己找到了解决方案... @在规则命令行的开头是我的朋友... (2认同)
  • 警告,如果 guard-VARIABLENAME 是一个存在的文件,则会损坏。通过声明一个空的虚假目标然后将guard-% 隐式规则设置为取决于它来解决。[示例 .mak gist.github.com](https://gist.github.com/brimston3/fc43658bdb6882ed13d942fa584dd2de)。这迫使每次看到规则时都要重新评估它。 (2认同)

Dan*_*der 40

内联变体

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

deploy:
    test -n "$(ENV)"  # $$ENV
    rsync . $(ENV).example.com:/var/www/myapp/
Run Code Online (Sandbox Code Playgroud)

原因:

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

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

test -n ""
Makefile:3: recipe for target 'deploy' failed
make: *** [deploy] Error 1
Run Code Online (Sandbox Code Playgroud)

...强制您查找Makefile,而...

test -n ""  # $ENV
Makefile:3: recipe for target 'deploy' failed
make: *** [deploy] Error 1
Run Code Online (Sandbox Code Playgroud)

......直接解释了什么是错的

全局变体(完整性,但没有问)

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

ifeq ($(ENV),)
  $(error ENV is not set)
endif
Run Code Online (Sandbox Code Playgroud)

警告:

  • 不要在该块中使用tab
  • 谨慎使用:clean如果未设置ENV,即使目标也会失败.否则看Hudon的答案更复杂

  • 我喜欢这种测试方法。我使用了这样的东西: `@test -n "$(name)" || (echo '必须为备份定义一个名称。例如:make backup name=xyz' && exit 1)` (3认同)

Lew*_*her 8

我知道这很旧,但我想我会为未来的访客提供我自己的体验,因为恕我直言,它更整洁一些。

通常,makesh用作其默认 shell(通过特殊SHELL变量设置)。在sh及其衍生物中,如果通过执行以下操作检索环境变量未设置或为空,则退出并显示错误消息是微不足道的:${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!"
Run Code Online (Sandbox Code Playgroud)

注意事项:

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

  • 如果“ENV”包含空格,这似乎会失败(至少对我来说) (2认同)

Dan*_*her 6

您可以使用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
Run Code Online (Sandbox Code Playgroud)


ssm*_*mir 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
Run Code Online (Sandbox Code Playgroud)


Hud*_*don 5

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

make -j target
Run Code Online (Sandbox Code Playgroud)

什么时候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
    ...
Run Code Online (Sandbox Code Playgroud)

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


小智 5

我发现最好的答案不能用作要求,除了其他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"
Run Code Online (Sandbox Code Playgroud)

输出看起来像

$ make build
echo "building"
building
$ make deploy
Makefile:5: *** ENV not set.  Stop.
$ make deploy ENV="env"
echo "deploying env"
deploying env
$
Run Code Online (Sandbox Code Playgroud)

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