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

时间:2017-12-06 03:08:56

标签: makefile gnu-make

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

我的目标如下:

  1. make deploy本身应与ENVIRONMENT=$(USER)ENV_TYPE=staging一起部署。
  2. 可以通过环境变量设置要部署的环境。如果ENVIRONMENT=productionENV_TYPE=production,否则ENV_TYPE=staging
  3. 作为设置环境变量的简写,可以使用deploy和环境名称为-目标添加后缀。例如:make deploy-production
  4. 这是给我最麻烦的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),则会发生同样的错误。

2 个答案:

答案 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)中分配,但在扩展之后。我对编程中作用域阴影变量的类比感到震惊。

相关问题