3

I have a function in a makefile that I want to stop the entire make run if a file doesn't exist, or at least the target it is executed from:

vaultfile = ./vault

$(shell test -f $(1) || exit 1)

define get_token
$(shell test -f $1 && cat $1 || exit 2)
endef

a: token = $(call get_token,$(vaultfile),tokenname)
a:
        echo ==== $(token)
.PHONY=a

The above doesn't work, silently failing when the file is missing

$ rm vault
$ make
echo ==== 
====
$ echo f > vault
$ make
echo ==== f
==== f

I want this to be in the function, because most targets do not call the function (which obviously does more IRL).

How do I make this work?

2 Answers2

2

First a few things:

.PHONY=a

doesn't do anything: the variable .PHONY is not special to make. To declare a target phony you need to list it as a prerequisite of the .PHONY pseudo target:

.PHONY: a

Second, this line:

$(shell test -f $(1) || exit 1)

doesn't do anything: the make variable $(1) is not set here so the test always fails, but it doesn't matter because the exit code is ignored, see below.

The exit code from the make shell function won't cause make to fail, it's ignored. To cause make to think that a recipe failed you have to get the command line itself to exit with a non-zero value.

A good rule of thumb is, if you find yourself using the make shell function inside a recipe, you're doing something wrong and you aren't understanding how make expands variables and functions. A recipe is already going to be passed to a shell, so you don't need to use the shell function at all.

Let's look at what your recipe will be after the first step of expansion, for the token variable:

    echo ==== $(call get_token,$(vaultfile),tokenname)

Now after the call function is expanded (note that the second argument to the function, tokenname, is completely ignored) you get:

    echo ==== $(shell test -f ./vault && cat ./vault || exit 2)

Now make expands the shell function which invokes a shell to run the command and replace the expansion with the output... but the exit code is ignored. Let's say that ./vault doesn't exist: then this shell command outputs nothing, and make runs this rule:

    echo ==== 

The best way to stop an entire make run is using the error function. You can use make functions to do all the work, like this:

vaultfile = ./vault

get_token = $(if $(wildcard $1),`cat $1`,$(error File $1 does not exist))

a: token = $(call get_token,$(vaultfile),tokenname)
a:
        echo ==== $(token)

Let's look at what the results of the call expansion will be now:

    echo ==== $(if $(wildcard ,/vault),`cat ./vault`,$(error File ./vault does not exist))

Now make evaluates the if function and the condition is the wildcard function which will expand to ./vault if it exists, and the empty string if not. The if function treats a non-empty string as "true" and an empty string as "false", so if the file exists it will expand to:

    echo ==== `cat ./vault`

If the file doesn't exist it will run the error function which stops make immediately, printing that error message.

MadScientist
  • 92,819
  • 9
  • 109
  • 136
2

Thank you MadScientist!

The simplified version of what I ended up with is: (formatted for easier readability)

vaultfile = ./vault

define get_token
$(shell \
$(if $(wildcard $1), \
echo "Decoded $1" \
, \
$(error File $1 does not exist)
)
)
endef

a: token = $(call get_token,$(vaultfile))
a:
        echo ==== $(token)