Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure Makefile variable is set as a prerequisite?

Tags:

makefile

People also ask

How do I check if a variable is set in makefile?

Check if variable is defined in a Makefilecheck_defined = \ $(strip $(foreach 1,$1, \ $(call __check_defined,$1,$(strip $(value 2))))) __check_defined = \ $(if $(value $1),, \ $(error Undefined $1$(if $2, ($2)))) install: $(call check_defined, var1) $(call check_defined, var2) # do stuff here..

How do I set an environment variable in makefile?

Re: Setting Environment variable in MakefileTo get the shell to see one "$", you must use "$$" in the Makefile. 2.) Environment variables can only be inherited from parent to child processes, not vice versa. In this case, the parent process is the 'make' that is processing the Makefile.

What does $@ mean in makefile?

The variable $@ represents the name of the target and $< represents the first prerequisite required to create the output file.


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.)


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.


Inline variant

In my makefiles, I normally use an expression like:

deploy:
    test -n "$(ENV)"  # $$ENV
    rsync . $(ENV).example.com:/var/www/myapp/

The reasons:

  • it's a simple one-liner
  • it's compact
  • it's located close to the commands which use the variable

Don't forget the comment which is important for debugging:

test -n ""
Makefile:3: recipe for target 'deploy' failed
make: *** [deploy] Error 1

... forces you to lookup the Makefile while ...

test -n ""  # $ENV
Makefile:3: recipe for target 'deploy' failed
make: *** [deploy] Error 1

... explains directly what's wrong

Global variant (for completeness, but not asked)

On top of your Makefile, you could also write:

ifeq ($(ENV),)
  $(error ENV is not set)
endif

Warnings:

  • don't use tab in that block
  • use with care: even the clean target will fail if ENV is not set. Otherwise see Hudon's answer which is more complex

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})

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

One possible problem with the given answers so far is that dependency order in make is not defined. For example, running:

make -j target

when target has a few dependencies does not guarantee that these will run in any given order.

The solution for this (to guarantee that ENV will be checked before recipes are chosen) is to check ENV during make's first pass, outside of any recipe:

## Are any of the user's goals dependent on ENV?
ifneq ($(filter deploy other-thing-that-needs-ENV,$(MAKECMDGOALS)),$())
ifndef ENV 
$(error ENV not defined)
endif
endif

.PHONY: deploy

deploy: foo bar
    ...

other-thing-that-needs-ENV: bar baz bono
    ...

You can read about the different functions/variables used here and $() is just a way to explicitly state that we're comparing against "nothing".