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

Makefile 将目标变量从模式匹配传递到静态,makefile,gnu-make,Makefile,Gnu Make,我正在编写一个Makefile来包装弹性beanstalk应用程序在多个环境中的部署。这是我第一次编写“高级”makefile,我遇到了一些麻烦 我的目标如下: makedeploy本身应该使用ENVIRONMENT=$(USER)和ENV\u TYPE=staging进行部署 可以通过环境变量设置要部署到的环境。如果ENVIRONMENT=production则ENV_TYPE=production,否则ENV_TYPE=staging 作为设置环境变量的简写,可以在deploy目标后面加上-

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

我的目标如下:

  • makedeploy
    本身应该使用
    ENVIRONMENT=$(USER)
    ENV\u TYPE=staging
    进行部署
  • 可以通过环境变量设置要部署到的环境。如果
    ENVIRONMENT=production
    ENV_TYPE=production
    ,否则
    ENV_TYPE=staging
  • 作为设置环境变量的简写,可以在
    deploy
    目标后面加上
    -
    和环境名称。例如:
    make-deploy-production
  • 给我带来最大麻烦的是3号。由于任何未命名为production的环境都属于staging类型,因此我尝试使用模式匹配来定义部署到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
    
    期望输出:

    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
    
    当我运行
    makeproduction
    时,如果围绕
    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
    ),也会发生相同的错误。

    由于与RenaudPacalet的对话,我已经了解到,我的方法之所以有效,主要是因为
    deploy-%
    规则中定义的变量在任何地方都没有使用,而是在配方中使用……在配方中,它们会在传递到shell之前延迟展开。这使我可以在变量定义中使用
    $*
    ,因为变量定义直到第二阶段才展开,此时
    $*
    实际上有一个值

    设置
    ENV_TYPE
    的方法使用
    patsubst
    的技巧,通过从
    $ENVIRONMENT
    的内容中去掉
    “production”
    一词,为
    if
    生成条件;在此上下文中,空字符串选择
    else
    大小写。因此,如果
    $ENVIRONMENT
    正好等于
    “production”
    ,则
    patsubst
    生成一个空字符串,
    if
    计算结果为
    production
    ,否则计算结果为
    staging

    对于
    deploy-
    ,在底部有一个显式规则,因为该目标会调用一些疯狂的隐式模式规则,试图编译
    deploy-.o

    发现这也使我考虑了可能出现的其他错误情况,所以前几行定义了一个函数,以确保如果用户同时指定<代码>环境= x并使用后缀<代码> y>代码>,则会有一个适当的错误消息(而不仅仅是后缀获胜)。您可以将对该函数的调用视为

    deploy-%
    配方的第一行。如果将
    $ENVIRONMENT
    定义为具有多个单词,则存在另一个潜在问题;第二个
    deploy:
    行实现了一个在这种情况下会出错的测试——它使用与上面相同的
    patsubst/if
    技巧测试
    $ENVIRONMENT
    中的字数是否正好是
    1

    还应注意,此生成文件假定实际工作将在
    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)
    
    #定义一个函数来检测ENV和后缀之间的不匹配
    原始环境:=$(环境)
    环境检查=$(如果$(原始环境),$(如果$(子$(原始环境),,$(环境))\
    $(错误$$环境不同于部署-),),)
    环境?=$(用户)
    .冒牌货:部署
    部署:部署-$(环境)
    部署:$(如果$(patsubst 1%,%,$(words$(ENVIRONMENT)),$(error Bad$$ENVIRONMENT:“$(ENVIRONMENT)”,)
    .虚假:部署-%
    部署-%:环境=$*
    部署-%:环境类型=$(如果$(patsubst生产%,%,$(环境)),暂存,生产)
    部署-%:
    $(调用环境检查)
    @回声环境:$(环境)
    @回声环境类型:$(环境类型)
    #如果用户指定:
    #环境=进行部署
    #或
    #部署-
    .冒牌货:部署-
    部署-:
    $(无法使用空的$$环境生成错误)
    
    给予

    $USER=匿名进行部署
    环境:匿名
    环境类型:暂存
    $ENVIRONMENT=生产制造部署
    环境:生产
    环境类型:生产
    $ENVIRONMENT=testmakedeploy
    环境:测试
    环境类型:暂存
    $makedeployfoo
    环境:富
    环境类型:暂存
    $make部署生产
    环境:生产
    环境类型:生产
    $ENVIRONMENT=foo使部署生产
    Makefile:14:**$环境不同于部署-。。停止
    $ENVIRONMENT=make-deploy
    Makefile:24:**Bad$环境:“”。停止
    $make部署-
    Makefile:24:**无法使用空$ENVIRONMENT生成。停止
    $ENVIRONMENT=“越多越好”进行部署
    Makefile:10:**Bad$环境:“越多越好”。停止
    

    回想一下这是怎么回事,一点也不简单。对
    $ENVIRONMENT
    有各种解释……例如在
    部署:部署-$(ENVIRONMENT)
    行中,
    $ENVIRONMENT
    的意思是从shell的环境中得到的(如果不存在,可能被设置为
    $(USER)
    )。配方行
    @echo-ENVIRONMENT:$(ENVIRONMENT)
    中还有另一种含义,它将是在上面的
    deploy-%:ENVIRONMENT=$*
    中分配的,但在扩展之后。我对编程中变量的作用域或阴影的类比印象深刻。

    您的问题来自于特定于目标的变量值被目标先决条件继承的方式。你想干什么
    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
    
    # 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.
    
    $ 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
    
    $ 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-%: ENVIRONMENT := $*
    
    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
    
    $ 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