217

How could I abort a make/makefile execution based on a makefile's variable not being set/valued?

I came up with this, but works only if caller doesn't explicitly run a target (i.e. runs make only).

ifeq ($(MY_FLAG),)
abort:   ## This MUST be the first target :( ugly
    @echo Variable MY_FLAG not set && false
endif

all:
    @echo MY_FLAG=$(MY_FLAG)

I think something like this would be a good idea, but didn't find anything in make's manual:

ifndef MY_FLAG
.ABORT
endif
Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
Caruccio
  • 3,489
  • 4
  • 18
  • 14

6 Answers6

398

TL;DR: Use the error function:

ifndef MY_FLAG
$(error MY_FLAG is not set)
endif

Note that the lines must not be indented. More precisely, no tabs must precede these lines.


Generic solution

In case you're going to test many variables, it's worth defining an auxiliary function for that:

# Check that given variables are set and all have non-empty values,
# die with an error otherwise.
#
# Params:
#   1. Variable name(s) to test.
#   2. (optional) Error message to print.
check_defined = \
    $(strip $(foreach 1,$1, \
        $(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
    $(if $(value $1),, \
      $(error Undefined $1$(if $2, ($2))))

And here is how to use it:

$(call check_defined, MY_FLAG)

$(call check_defined, OUT_DIR, build directory)
$(call check_defined, BIN_DIR, where to put binary artifacts)
$(call check_defined, \
            LIB_INCLUDE_DIR \
            LIB_SOURCE_DIR, \
        library path)


This would output an error like this:

Makefile:17: *** Undefined OUT_DIR (build directory).  Stop.

Notes:

The real check is done here:

$(if $(value $1),,$(error ...))

This reflects the behavior of the ifndef conditional, so that a variable defined to an empty value is also considered "undefined". But this is only true for simple variables and explicitly empty recursive variables:

# ifndef and check_defined consider these UNDEFINED:
explicitly_empty =
simple_empty := $(explicitly_empty)

# ifndef and check_defined consider it OK (defined):
recursive_empty = $(explicitly_empty)

As suggested by @VictorSergienko in the comments, a slightly different behavior may be desired:

$(if $(value $1) tests if the value is non-empty. It's sometimes OK if the variable is defined with an empty value. I'd use $(if $(filter undefined,$(origin $1)) ...

And:

Moreover, if it's a directory and it must exist when the check is run, I'd use $(if $(wildcard $1)). But would be another function.

Target-specific check

It is also possible to extend the solution so that one can require a variable only if a certain target is invoked.

$(call check_defined, ...) from inside the recipe

Just move the check into the recipe:

foo :
    @:$(call check_defined, BAR, baz value)

The leading @ sign turns off command echoing and : is the actual command, a shell no-op stub.

Showing target name

The check_defined function can be improved to also output the target name (provided through the $@ variable):

check_defined = \
    $(strip $(foreach 1,$1, \
        $(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
    $(if $(value $1),, \
        $(error Undefined $1$(if $2, ($2))$(if $(value @), \
                required by target `$@')))

So that, now a failed check produces a nicely formatted output:

Makefile:7: *** Undefined BAR (baz value) required by target `foo'.  Stop.

check-defined-MY_FLAG special target

Personally I would use the simple and straightforward solution above. However, for example, this answer suggests using a special target to perform the actual check. One could try to generalize that and define the target as an implicit pattern rule:

# Check that a variable specified through the stem is defined and has
# a non-empty value, die with an error otherwise.
#
#   %: The name of the variable to test.
#   
check-defined-% : __check_defined_FORCE
    @:$(call check_defined, $*, target-specific)

# Since pattern rules can't be listed as prerequisites of .PHONY,
# we use the old-school and hackish FORCE workaround.
# You could go without this, but otherwise a check can be missed
# in case a file named like `check-defined-...` exists in the root 
# directory, e.g. left by an accidental `make -t` invocation.
.PHONY : __check_defined_FORCE
__check_defined_FORCE :

Usage:

foo :|check-defined-BAR

Notice that the check-defined-BAR is listed as the order-only (|...) prerequisite.

Pros:

  • (arguably) a more clean syntax

Cons:

I believe, these limitations can be overcome using some eval magic and secondary expansion hacks, although I'm not sure it's worth it.

Eldar Abusalimov
  • 24,387
  • 4
  • 67
  • 71
  • What exactly? Never used Mac, though I guess it has another implementation of Make installed by default (e.g. BSD Make instead of GNU Make). I'd suggest you to check `make --version` as the first step. – Eldar Abusalimov Sep 06 '15 at 09:41
  • 3
    This doesn't seem to be working in make 3.81. It always errors, even if the variable is defined (and can be echoed). – OrangeDog Jun 14 '16 at 10:10
  • Ah, you need to structure it exactlt as in the linked duplicate. – OrangeDog Jun 14 '16 at 10:12
  • @EldarAbusalimov how do I make the requirement `build_target` specific? – Bibek Shrestha Jul 22 '16 at 13:16
  • 1
    @bibstha I added the options that come in mind, please read the updated answer. – Eldar Abusalimov Jul 24 '16 at 01:22
  • 2
    I'd add a clarification-for-noobies (like me) that ifndef needs to be not indented :) I found that tip somewhere else and suddenly all my errors made sense. – helios Jun 25 '17 at 03:45
  • If 'ifndef needs to be not indented' comment was buried for me. It would be helpful if the example had a little more to it to show the indentation problem. – gageorge Oct 30 '17 at 16:59
  • 1. `$(if $(value $1)` tests if the value is non-empty. It's sometimes OK if the variable is defined with an empty value. I'd use `$(if $(filter undefined,$(origin $1)) ...` – Victor Sergienko Feb 01 '19 at 02:01
  • 2. Moreover, if it's a directory and it must exist when the check is run, I'd use `$(if $(wildcard $1))`. But would be another function. – Victor Sergienko Feb 01 '19 at 02:02
  • 3. In `$(foreach`, naming a variable `1` when you already have one (wink) complicates reading. Making it, say, `DIR` or `VALUE` would improve the readability a bit. – Victor Sergienko Feb 01 '19 at 02:04
  • @VictorSergienko 1 and 2. The `$(if $(value $1)` check reflects behavior of the `ifdef` directive, that is, an empty value is also considered undefined. – Eldar Abusalimov Feb 15 '19 at 10:43
  • @VictorSergienko 3. Naming the iteration variable `DIR` would break a totally correct `$(call check_defined, DIR)` because the iteration variable would shadow the variable being tested. Shadowing the argument 1 seems to me the least evil in this case. – Eldar Abusalimov Feb 15 '19 at 10:48
  • @VictorSergienko I added some notes, please check that out. Thank you! – Eldar Abusalimov Feb 15 '19 at 11:05
  • When I use "Generic solution", make warns me `warning: undefined variable '1'` and then `*** Undefined . Stop.`. I can't get what I'm doing wrong. – German Lashevich Nov 09 '20 at 16:19
  • @GermanLashevich do you use it as suggested, i.e. through `$(call check_defined,...)`, and not $(__check_defined) or something? – Eldar Abusalimov Dec 03 '20 at 20:59
  • @EldarAbusalimov I run it as `$(call check_defined, MY_FLAG)`. After some debug I figured out that the error occurs if there is `export` directive in the Makefile. And the warning occurs only if `warn-undefined-variables` flag is set. – German Lashevich Dec 03 '20 at 22:28
  • The TLDR; solution always errors on `GNU Make 4.2.1` when checking against an externally defined environment variable. – grmmgrmm Mar 02 '22 at 10:28
56

Use the shell function test:

foo:
    test $(something)

Usage:

$ make foo
test 
Makefile:2: recipe for target 'foo' failed
make: *** [foo] Error 1
$ make foo something=x
test x
Messa
  • 24,321
  • 6
  • 68
  • 92
  • 4
    This is what I used when faced with the same issue - thanks, Messa! I made two slight modifications: 1) I created a `checkforsomething` target that only had the `test` in it and made `foo` dependent on that, and 2) i changed the check to `@if test -z "$(something)"; then echo "helpful error here"; exit 1; fi` instead. That gave me the ability to add a useful error, and allowed me to make the name of the new target a little more indicative of what went wrong as well. – Brian Gerard Jul 13 '17 at 15:11
  • With this I get `Makefile:5: *** missing separator. Stop.` – silgon Apr 16 '18 at 20:58
  • 15
    For compactness I used `test -n "$(something) || (echo "message" ; exit 1)` to avoid the explicit `if`. – user295691 Aug 22 '18 at 15:22
  • @silgon you are probably indenting using spaces rather than a tab. – eweb Jun 13 '19 at 10:03
19

You can use an IF to test:

check:
        @[ "${var}" ] || ( echo ">> var is not set"; exit 1 )

Result:

$ make check
>> var is not set
Makefile:2: recipe for target 'check' failed
make: *** [check] Error 1
raittes
  • 5,271
  • 3
  • 30
  • 27
  • 1
    `[` is an alias for the command `test`, so this is the same answer as @Messa above. This is more compact, though, and includes the error message generation. – user295691 Aug 22 '18 at 15:27
  • This really seems like the cleanest answer for most circumstances. – Dave Kerr Oct 15 '20 at 04:01
  • I appreciate the simplicity of this answer. The next person reading my Makefile will know what this does, even if they don't know how it works. – TomOnTime Nov 05 '22 at 13:24
12

Another option:

MY_FLAG = $(error Please set this flag)

Attempting to use this variable anywhere will cause an error, unless it's overriden from the command line.

To accept environment variables as well, use ?=:

MY_FLAG ?= $(error Please set this flag)
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
8

Use the shell error handling for unset variables (note the double $):

$ cat Makefile
foo:
        echo "something is set to $${something:?}"

$ make foo
echo "something is set to ${something:?}"
/bin/sh: something: parameter null or not set
make: *** [foo] Error 127


$ make foo something=x
echo "something is set to ${something:?}"
something is set to x

If you need a custom error message, add it after the ?:

$ cat Makefile
hello:
        echo "hello $${name:?please tell me who you are via \$$name}"

$ make hello
echo "hello ${name:?please tell me who you are via \$name}"
/bin/sh: name: please tell me who you are via $name
make: *** [hello] Error 127

$ make hello name=jesus
echo "hello ${name:?please tell me who you are via \$name}"
hello jesus
kesselborn
  • 533
  • 5
  • 7
8

For simplicity and brevity:

$ cat Makefile
check-%:
        @: $(if $(value $*),,$(error $* is undefined))

bar:| check-foo
        echo "foo is $$foo"

With outputs:

$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something
bsimpson53
  • 485
  • 5
  • 9
  • can be fooled with `$ touch check-foo; make bar` ... need the check_defined_FORCE trick in other answer – qneill Feb 12 '21 at 18:34