Makefile variable as prerequisite

后端 未结 8 606
清歌不尽
清歌不尽 2020-12-07 09:45

In a Makefile, a deploy recipe needs a environment variable ENV to be set to properly execute itself, whereas others don\'t care, e.g.:



        
相关标签:
8条回答
  • 2020-12-07 09:55

    As I see the command itself needs the ENV variable so you can check it in the command itself:

    .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
    
    0 讨论(0)
  • 2020-12-07 10:02

    This will cause a fatal error if ENV is undefined and something needs it (in GNUMake, anyway).

    .PHONY: deploy check-env
    
    deploy: check-env
    	...
    
    other-thing-that-needs-env: check-env
    	...
    
    check-env:
    ifndef ENV
    	$(error ENV is undefined)
    endif
    

    (Note that ifndef and endif are not indented - they control what make "sees", taking effect before the Makefile is run. "$(error" is indented with a tab so that it only runs in the context of the rule.)

    0 讨论(0)
  • 2020-12-07 10:02

    I've found with the best answer cannot be used as a requirement, except for other PHONY targets. If used as a dependency for a target that is an actual file, using check-env will force that file target to be rebuilt.

    Other answers are global (e.g. the variable is required for all targets in the Makefile) or use the shell, e.g. if ENV was missing make would terminate regardless of target.

    A solution I found to both issues is

    ndef = $(if $(value $(1)),,$(error $(1) not set))
    
    .PHONY: deploy
    deploy:
        $(call ndef,ENV)
        echo "deploying $(ENV)"
    
    .PHONY: build
    build:
        echo "building"
    

    The output looks like

    $ make build
    echo "building"
    building
    $ make deploy
    Makefile:5: *** ENV not set.  Stop.
    $ make deploy ENV="env"
    echo "deploying env"
    deploying env
    $
    

    value has some scary caveats, but for this simple use I believe it is the best choice.

    0 讨论(0)
  • 2020-12-07 10:02

    I know this is old, but I thought I'd chime in with my own experiences for future visitors, since it's a little neater IMHO.

    Typically, make will use sh as its default shell (set via the special SHELL variable). In sh and its derivatives, it's trivial to exit with an error message when retrieving an environment variable if it is not set or null by doing: ${VAR?Variable VAR was not set or null}.

    Extending this, we can write a reusable make target which can be used to fail other targets if an environment variable was not set:

    .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!"
    

    Things of note:

    • The escaped dollar sign ($$) is required to defer expansion to the shell instead of within make
    • The use of test is just to prevent the shell from trying to execute the contents of VAR (it serves no other significant purpose)
    • .check-env-vars can be trivially extended to check for more environment variables, each of which adds only one line (e.g. @test $${NEWENV?Please set environment variable NEWENV})
    0 讨论(0)
  • 2020-12-07 10:05

    You can create an implicit guard target, that checks that the variable in the stem is defined, like this:

    guard-%:
        @ if [ "${${*}}" = "" ]; then \
            echo "Environment variable $* not set"; \
            exit 1; \
        fi
    

    You then add a guard-ENVVAR target anywhere you want to assert that a variable is defined, like this:

    change-hostname: guard-HOSTNAME
            ./changeHostname.sh ${HOSTNAME}
    

    If you call make change-hostname, without adding HOSTNAME=somehostname in the call, then you'll get an error, and the build will fail.

    0 讨论(0)
  • 2020-12-07 10:10

    You can use ifdef instead of a different target.

    .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
    
    0 讨论(0)
提交回复
热议问题