将目标变量从模式匹配传递到静态

Joh*_*hnS 3 makefile gnu-make

我正在编写一个 Makefile 来将弹性 beantalk 应用程序的部署包装到多个环境中。这是我第一次编写“高级”makefile,遇到了一些麻烦。

我的目标如下:

  1. make deploy本身应该使用ENVIRONMENT=$(USER)和部署ENV_TYPE=staging
  2. 要部署到的环境可以通过环境变量设置。如果ENVIRONMENT=production那么ENV_TYPE=production,否则ENV_TYPE=staging
  3. 作为设置环境变量的一种简写,可以在deploy目标后缀a-和环境名称。例如:make deploy-production

给我带来最大麻烦的是第 3 点。由于任何未命名为 production 的环境都属于暂存类型,因此我尝试使用模式匹配来定义部署到暂存环境的目标。这是我目前所拥有的:

ENVIRONMENT = $(USER)
ifeq ($ENVIRONMENT, production)
ENV_TYPE=production
else
ENV_TYPE=staging
endif
DOCKER_TAG ?= $(USER)
CONTAINER_PORT ?= 8000
ES_HOST = logging-ingest.$(ENV_TYPE).internal:80

.PHONY: deploy
deploy:
    -pip install --upgrade awsebcli
    sed "s/<<ES_HOST>>/$(ES_HOST)/" < 01-filebeat.template > .ebextensions/01-filebeat.config
    sed "s/<<DOCKER_TAG>>/$(DOCKER_TAG)/" < Dockerrun.template | sed "s/<<CONTAINER_PORT>>/$(CONTAINER_PORT)/" > Dockerrun.aws.json
    eb labs cleanup-versions --num-to-leave 10 --older-than 5 --force -v --region us-west-2
    eb deploy -v coolapp-$(ENVIRONMENT)

.PHONY: deploy-%
deploy-%: ENVIRONMENT=$*
deploy-%: deploy
    @echo  # Noop required

.PHONY: deploy-production
deploy-production: ENVIRONMENT=production
deploy-production: ENV_TYPE=production
deploy-production: deploy
    @echo  # Noop required
Run Code Online (Sandbox Code Playgroud)

问题出在deploy目标的最后一步。即,$(ENVIRONMENT)似乎未设置。

示例输出:

18:42 $ make -n deploy-staging
pip install --upgrade awsebcli
sed "s/<<ES_HOST>>/logging-ingest.staging.internal:80/" < 01-filebeat.template > .ebextensions/01-filebeat.config
sed "s/<<DOCKER_TAG>>/schultjo/" < Dockerrun.template | sed "s/<<CONTAINER_PORT>>/8000/" > Dockerrun.aws.json
eb labs cleanup-versions --num-to-leave 10 --older-than 5 --force -v --region us-west-2
eb deploy -v coolapp-
echo  # Noop required
Run Code Online (Sandbox Code Playgroud)

期望输出:

18:42 $ make -n deploy-staging
pip install --upgrade awsebcli
sed "s/<<ES_HOST>>/logging-ingest.staging.internal:80/" < 01-filebeat.template > .ebextensions/01-filebeat.config
sed "s/<<DOCKER_TAG>>/schultjo/" < Dockerrun.template | sed "s/<<CONTAINER_PORT>>/8000/" > Dockerrun.aws.json
eb labs cleanup-versions --num-to-leave 10 --older-than 5 --force -v --region us-west-2
eb deploy -v coolapp-staging
echo  # Noop required
Run Code Online (Sandbox Code Playgroud)

所以我试图实现 Renaud 的递归 Make 解决方案,但遇到了一个小问题。这是我正在使用的简化 Makefile:

ENVIRONMENT ?= $(USER)
ifeq ($ENVIRONMENT, production)
ENV_TYPE = production
else
ENV_TYPE = staging
endif
ES_HOST = logging-ingest.$(ENV_TYPE).internal:80

.PHONY: deploy
deploy:
    @echo $(ENVIRONMENT)
    @echo $(ENV_TYPE)
    @echo $(ES_HOST)


.PHONY: deploy-%
deploy-%:
    @$(MAKE) --no-print-directory ENVIRONMENT=$* deploy
Run Code Online (Sandbox Code Playgroud)

当我运行时make production,看起来if围绕ENV_TYPE定义的语句不会再次运行。这是我的实际输出:

12:50 $ make -n deploy-production
/Applications/Xcode.app/Contents/Developer/usr/bin/make --no-print-directory ENVIRONMENT=production deploy
echo production
echo staging
echo logging-ingest.staging.internal:80
Run Code Online (Sandbox Code Playgroud)

最后两行应该说production而不是staging,暗示我的条件有问题,但我没有在早期版本中编辑该条件,所以我很困惑。如果我ENVIRONMENT手动调用 make 和set会发生同样的错误(例如ENVIRONMENT=production make deploy)。

Ren*_*let 5

您的问题来自于目标先决条件继承目标特定变量值的方式。您尝试执行的操作适用于明确的特定于目标的变量:

$ cat Makefile
ENVIRONMENT = default

deploy:
    @echo '$@: ENVIRONMENT = $(ENVIRONMENT)'

deploy-foo: ENVIRONMENT = foo
deploy-foo: deploy
    @echo '$@: ENVIRONMENT = $(ENVIRONMENT)'
$ make deploy
deploy: ENVIRONMENT = default
$ make deploy-foo
deploy: ENVIRONMENT = foo
deploy-foo: ENVIRONMENT = foo
Run Code Online (Sandbox Code Playgroud)

因为deploy-foo-specificENVIRONMENT变量foo在第一个扩展阶段(在此期间扩展了特定于目标的变量分配、目标列表和先决条件列表)被赋值。所以,deploy继承了这个值。

但它不适用于使用$*自动变量的特定于模式的变量:

$ cat Makefile
ENVIRONMENT = default

deploy:
    @echo '$@: ENVIRONMENT = $(ENVIRONMENT)'

deploy-%: ENVIRONMENT = $*
deploy-%: deploy
    @echo '$@: ENVIRONMENT = $(ENVIRONMENT)'
$ make deploy
$ deploy: ENVIRONMENT = default
$ make deploy-foo
deploy: ENVIRONMENT = 
deploy-foo: ENVIRONMENT = foo
Run Code Online (Sandbox Code Playgroud)

原因是deploy-foo-specificENVIRONMENT变量在 make 方言中称为递归扩展变量(因为您使用了=赋值运算符)。它被递归扩展,但仅在 make 需要其值时,而不是在分配时。因此,在 的上下文中deploy-foo,它被分配$*,而不是模式词干。ENVIRONMENT按原样传递给deploy先决条件的上下文,在此上下文中,$(ENVIRONMENT)递归扩展到$*空字符串,然后再扩展到空字符串,因为deploy规则中没有模式。您可以尝试简单的扩展(非递归)变量风格:

deploy-%: ENVIRONMENT := $*
Run Code Online (Sandbox Code Playgroud)

立即扩展,但结果将相同,因为$*在第一次扩展期间扩展为空字符串。它仅在第二次扩展期间设置,因此只能在配方中使用(即在第二阶段扩展)。

一个简单(但不是超级高效)的解决方案是再次调用 make:

deploy-%:
    @$(MAKE) ENVIRONMENT=$* deploy
Run Code Online (Sandbox Code Playgroud)

例子:

$ cat Makefile
ENVIRONMENT = default

deploy:
    @echo '$(ENVIRONMENT)'

deploy-%:
    @$(MAKE) --no-print-directory ENVIRONMENT=$* deploy
$ make
default
$ make deploy
default
$ make deploy-foo
foo
Run Code Online (Sandbox Code Playgroud)

注意:GNU make 支持二次扩展,可以认为可以用它来解决这个问题。不幸的是不是:二次扩展只扩展了先决条件,而不是特定于目标的变量定义。

如上所述,这种递归 make 效率不高。如果效率至关重要,则最好使用单个 make 调用。如果您在单个模式规则的配方中移动所有变量处理,则可以完成此操作。如果 make 使用的 shell 是 bash 的示例:

$ cat Makefile
deplo%:
    @{ [[ "$*" == "y" ]] && ENVIRONMENT=$(USER); } || \
    { [[ "$*" =~ y-(.*) ]] && ENVIRONMENT=$${BASH_REMATCH[1]}; } || \
    { echo "$@: unknown target" && exit 1; }; \
    echo "$@: ENVIRONMENT = $$ENVIRONMENT" && \
    <do-whatever-else-is-needed>
$ USER=bar make deploy
deploy: ENVIRONMENT = bar
$ make deploy-foo
deploy-foo: ENVIRONMENT = foo
$ make deplorable
deplorable: unknown target
make: *** [Makefile:2: deplorable] Error 1
Run Code Online (Sandbox Code Playgroud)

不要忘记通过 make ( $$ENVIRONMENT)来逃避配方扩展。