How to use conditions inside a Makefile define function

Viewed 63

I'm trying to reduce the number of function calls by having a single function with conditions inside, but my code doesn't work without showing any errors.

define say
    $(if $(filter hi,  $(1)), $(echo "Hello   ${2}"))
    $(if $(filter bye, $(1)), $(echo "Goodbye ${2}"))
endef

.SILENT:

test:
    $(call say, hi, "John Smith")

Besides, I initially used an ifeq/else structure, but I received syntax errors. I also noticed that I cannot declare variables in this scope.

define say
    ifeq ($(1), hi)
        echo "Hello ${2}"
    else ifeq ($(1), bye)
        echo "Goodbye ${2}"
    endif
endef

.SILENT:

test:
    $(call say, hi, "John Smith")

I also tried another approach to solve the same problem:

HI  = "Hello"
BYE = "Goodbye"

define say
    echo "${${1}} ${2}"
endef

.SILENT:

test:
    $(call say, HI, "John Smith")

But this one doesn't work either, mainly because the variable-variable ($($(1))) doesn't work as expected.

1 Answers

First, why are you adding .SILENT: to your makefiles? You should never do that at least until the makefile is working. Adding .SILENT: then trying to understand why your makefile doesn't work is like running your program with its output sent to /dev/null and trying to understand what's wrong with it.

All of your issues are related to not having a clear distinction in your mind between makefiles, which use makefile syntax, and recipes, which are shell scripts and use shell script syntax.

A makefile is a combination of two different languages. All commands in recipes are expanded and sent to the shell. All commands outside of recipes are makefile commands. You can't use makefile commands (like ifeq etc.) in a shell script. And you can't use shell commands (like $(echo ...)) in the makefile (outside of a recipe).

When you want to use the $ in a shell script, you have to escape it as $$ in the makefile recipe so that it won't be interpreted as a make macro.

So, given that:

define say
    $(if $(filter hi,  $(1)), $(echo "Hello   ${2}"))
    $(if $(filter bye, $(1)), $(echo "Goodbye ${2}"))
endef

This would work, except you use $(echo ...) which is interpreted by make as a make variable with a really weird name. Make will look it up, see it's not defined, and expand to nothing. You can do this:

define say
    $(if $(filter hi,  $(1)), echo "Hello   ${2}")
    $(if $(filter bye, $(1)), echo "Goodbye ${2}")
endef

This will expand to and echo command, which will then be passed to the shell (because you called it from within a recipe), and the shell will run it.

For this one:

define say
    ifeq ($(1), hi)
        echo "Hello ${2}"
    else ifeq ($(1), bye)
        echo "Goodbye ${2}"
    endif
endef

this can't work because you invoke it from within a recipe and, as we've mentioned above, ifeq is not a shell command and so the shell won't recognize it. call does NOT "run" a set of make commands. call expands a string, with some extra variables in-scope for the arguments.

For this one:

define say
    echo "${${1}} ${2}"
endef

This one is due to a bug in GNU make. Generally the rule is that preceding whitespace is ignored and trailing whitespace is not ignored. Unfortunately it seems that the call function doesn't do this correctly for arguments, so when you write:

$(call say, HI, "John Smith")

the first argument has the value HI (including the space). So make tries to expand the variable ${ HI} and that, of course, is not a valid variable name. If you remove the whitespace it will work:

$(call say,HI,"John Smith")

I also have to point out that all this quoting is totally unnecessary. make doesn't care about quotes at all; they're just another character to it, like a or b. These quotes will be passed verbatim to the shell.

Related