Search code examples
androidmakefilegnu-makerecipe

Why can I not create variables in a GNU make 'define' directive


I'm trying to build a GNU Make Canned Recipe, like so:

define this-will-fail
my_var=1
$(info your variable is $(my_var))
endef

$(this-will-fail)

This causes an error *** missing separator. Stop.

However, the following works as expected:

define why-does-this-work
$(eval my_var=1)
$(info your variable is $(my_var))
endef

$(why-does-this-work)

by printing your variable is 1.

I'm looking at AOSP's build system and frequently see usage of eval paired with define. What's the relationship between these two items, and why can I not create variables "normally" when using define?


Solution

  • The answer stems from a simple point with big ramifications - a makefile has two dialects, 1) the make language itself and 2) the shell language you use for stuff like invoking gcc.

    A reminder of the normal make "rule" syntax:

    targets : prerequisites
            recipe
    

    While the make target and prerequisites is in make syntax, the stuff in the recipe is in the shell syntax, not make syntax.

    This explains why you cannot do stuff like assign make variables as part of a recipe, as so:

    all: the_dependencies_of_all
        this_will_not_work := because_the_shell_does_not_know_what_this_line_means
    

    In the shell syntax bits, make does three special text manipulations, and then the remaining string is passed verbatim to the shell. The three steps are

    1. deal with escaped newlines (e.g. backslash followed by newline)
    2. run any make functions (like $(filter ....) and $(eval ..)
    3. expand any make target variables (like $@)

    In the normal case, this 3-steps-then-shell processs happens once per recipe line, so each line of your makefile recipe is run in a different shell (hence why some calls to make will spawns many /bin/sh sub-processes).

    Manipulation #2 above explains why you can use eval inside a recipe to add some make syntax to your recipe. Eval will turn into an empty string, which will cause no problems with the shell, but make will have evaluated the string inside your statement, so stuff like variable definitions is possible.

    The precise moment this evaluation happens is a bit tricky to pin down - it seems to happen when the variable is first expanded, which itself depends upon where you reference that variable in your makefile. Perhaps someone can clarify this a bit more, since when this happens has been ramifications for invocations of make that use the -j flag to run multiple recipe lines in parallel

    More Info: