我正在编写一个Makefile来将弹性beanstalk应用程序的部署包装到多个环境中。这是我第一次写一个“高级”makefile,我遇到了一些麻烦。
我的目标如下:
make deploy
本身应与ENVIRONMENT=$(USER)
和ENV_TYPE=staging
一起部署。ENVIRONMENT=production
则ENV_TYPE=production
,否则ENV_TYPE=staging
。deploy
和环境名称为-
目标添加后缀。例如:make deploy-production
。这是给我最麻烦的3号。由于任何未命名为production的环境都是staging类型,我尝试使用模式匹配来定义用于部署到登台环境的目标。这就是我目前所拥有的:
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
问题出在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
期望的输出:
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
所以我试图实现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
当我运行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
最后两行应该说production
而不是staging
,这意味着我的条件有问题,但我没有在早期版本编辑条件时编辑它,所以我是一个但很困惑。如果我手动设置ENVIRONMENT
调用make(例如ENVIRONMENT=production make deploy
),则会发生同样的错误。
答案 0 :(得分:2)
您的问题来自目标先决条件继承目标特定变量值的方式。您尝试做的事情适用于明确的特定于目标的变量:
$ 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
因为deploy-foo
- 特定ENVIRONMENT
变量在第一个扩展阶段(特定于目标的变量分配,目标列表和先决条件列表)之间被分配了值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
原因是deploy-foo
- 特定ENVIRONMENT
变量在make dialect中被称为递归扩展变量(因为您使用了=
赋值运营商)。它是递归扩展的,但只有当make需要它的值时才会扩展,而不是在它被赋值时。因此,在deploy-foo
的上下文中,它被赋予$*
,而不是模式词干。 ENVIRONMENT
按原样传递给deploy
先决条件的上下文,在此上下文中,$(ENVIRONMENT)
递归地扩展为$*
,然后到空字符串,因为那里deploy
规则中没有模式。您可以尝试简单扩展的(非递归)变量风格:
deploy-%: ENVIRONMENT := $*
立即展开,但结果是相同的,因为$*
在第一次展开期间作为空字符串展开。它仅在第二次扩展期间设置,因此只能用于配方(在第二阶段进行扩展)。
一个简单(但不是超级高效)的解决方案在于再次调用make:
deploy-%:
@$(MAKE) ENVIRONMENT=$* deploy
示例:
$ cat Makefile
ENVIRONMENT = default
deploy:
@echo '$(ENVIRONMENT)'
deploy-%:
@$(MAKE) --no-print-directory ENVIRONMENT=$* deploy
$ make
default
$ make deploy
default
$ make deploy-foo
foo
注意:GNU make支持secondary expansion,可以认为它可以用来解决这个问题。不幸的是:二级扩展只扩展了先决条件,而不是特定于目标的变量定义。
如上所述,这种递归制作效率不高。如果效率至关重要,那么单个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
不要忘记通过make($$ENVIRONMENT
)来逃避配方扩展。
答案 1 :(得分:1)
由于与@ RenaudPacalet的对话,我了解到我的方法主要起作用,因为deploy-%
规则中定义的变量除了配方之外的任何地方都没有使用......它们在很晚才扩展传递到shell。这允许我在变量定义中使用$*
,因为变量定义不会扩展到第二阶段,$*
实际上有一个值。
设置ENV_TYPE
的方法使用patsubst
的技巧,通过从if
的内容中删除单词"production"
来为$ENVIRONMENT
生成条件;在此上下文中,空字符串选择else
大小写。因此,如果$ENVIRONMENT
完全等于"production"
,那么patsubst
会生成一个空字符串,而if
会计算为production
,否则会计算为staging
deploy-
底部有一条明确的规则,因为该目标会调用一些试图编译的疯狂隐式模式规则deploy-.o
发现这让我也考虑了可能出现的其他错误情况,因此前几行定义了一个函数,以确保如果用户同时指定ENVIRONMENT=X
并使用后缀Y
,有一个适当的错误信息(而不是只有后缀赢)。您可以将对该函数的调用视为deploy-%
配方的第一行。如果将$ENVIRONMENT
定义为包含多个单词,则还有另一个潜在问题;第二个deploy:
行实现了一个在这种情况下会出错的测试 - 它使用相同的$ENVIRONMENT
技巧测试1
中的字数是否正好patsubst/if
如上所述。
还应该注意,这个makefile假定实际工作将在deploy-%
下的配方中实现。
# define a function to detect mismatch between ENV and suffix
ORIG_ENV := $(ENVIRONMENT)
ENV_CHECK = $(if $(ORIG_ENV),$(if $(subst $(ORIG_ENV),,$(ENVIRONMENT)),\
$(error $$ENVIRONMENT differs from deploy-<suffix>.),),)
ENVIRONMENT ?= $(USER)
.PHONY: deploy
deploy: deploy-$(ENVIRONMENT)
deploy: $(if $(patsubst 1%,%,$(words $(ENVIRONMENT))),$(error Bad $$ENVIRONMENT: "$(ENVIRONMENT)"),)
.PHONY: deploy-%
deploy-%: ENVIRONMENT=$*
deploy-%: ENV_TYPE=$(if $(patsubst production%,%,$(ENVIRONMENT)),staging,production)
deploy-%:
$(call ENV_CHECK)
@echo ENVIRONMENT: $(ENVIRONMENT)
@echo ENV_TYPE: $(ENV_TYPE)
# keep it from going haywire if user specifies:
# ENVIRONMENT= make deploy
# or
# make deploy-
.PHONY: deploy-
deploy-:
$(error Cannot build with empty $$ENVIRONMENT)
给出
$ USER=anonymous make deploy
ENVIRONMENT: anonymous
ENV_TYPE: staging
$ ENVIRONMENT=production make deploy
ENVIRONMENT: production
ENV_TYPE: production
$ ENVIRONMENT=test make deploy
ENVIRONMENT: test
ENV_TYPE: staging
$ make deploy-foo
ENVIRONMENT: foo
ENV_TYPE: staging
$ make deploy-production
ENVIRONMENT: production
ENV_TYPE: production
$ ENVIRONMENT=foo make deploy-production
Makefile:14: *** $ENVIRONMENT differs from deploy-<suffix>.. Stop.
$ ENVIRONMENT= make deploy
Makefile:24: *** Bad $ENVIRONMENT: "". Stop.
$ make deploy-
Makefile:24: *** Cannot build with empty $ENVIRONMENT. Stop.
$ ENVIRONMENT="the more the merrier" make deploy
Makefile:10: *** Bad $ENVIRONMENT: "the more the merrier". Stop.
反思这是如何工作的,这根本不简单。有$ENVIRONMENT
的各种解释......例如在行deploy: deploy-$(ENVIRONMENT)
中,$ENVIRONMENT
的意义是从shell的环境中获取的那个(可能被设置为{{1}如果没有)。在配方行$(USER)
中还有另一种感觉,它将在上面的@echo ENVIRONMENT: $(ENVIRONMENT)
中分配,但在扩展之后。我对编程中作用域或阴影变量的类比感到震惊。