Reputation: 28198
A Makefile deploy
recipe needs an environment variable ENV
to be set to properly execute itself, whereas other recipes don't care, e.g.,
ENV =
.PHONY: deploy hello
deploy:
rsync . $(ENV).example.com:/var/www/myapp/
hello:
echo "I don't care about ENV, just saying hello!"
How can I make sure this ENV
variable is set? Is there a way to declare this makefile variable as a prerequisite of the deploy recipe? e.g.,
deploy: make-sure-ENV-variable-is-set
Upvotes: 198
Views: 133028
Reputation: 893
Like @philo's answer but with less PHONY. I used the name of my Makefile as a rule name. This is valid.
Advantage: I do not need to list _check-make-vars-defined as dependency for every recipe in the Makefile. It will run with any recipe invocation.
# Force the _check-make-vars-defined recipe to always run. Verify our make variables have been defined.
# While Makefile will not usually have changed, its prerequisite will have to run regardless.
# Do not use .PHONY on the Makefile rule.
Makefile: _check-make-vars-defined
.PHONY: _check-make-vars-defined
_check-make-vars-defined:
@#Verify our make variables have been defined.
ifndef _GITLAB_USER
$(error _GITLAB_USER is not set)
endif
Upvotes: 0
Reputation: 7095
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
Upvotes: 6
Reputation: 7784
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.
Upvotes: 147
Reputation: 659
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:
$$
) is required to defer expansion to the shell instead of within make
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}
)Upvotes: 19
Reputation: 99084
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.)
Upvotes: 270
Reputation: 91
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.
Upvotes: 9
Reputation: 5372
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:
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:
clean
target will fail if ENV is not set. Otherwise see Hudon's answer which is more complexUpvotes: 66
Reputation: 1674
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".
Upvotes: 7
Reputation: 1542
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
Upvotes: 8