Makefile `export` Changes Variable Type From Simply-Expanded to Recursively-Expanded

12 Views Asked by At

Consider the following simple example:

ARGS := -a -b
$(info args = $(ARGS))

DIR := foo
ARGS += ../$(DIR)/lib.a
$(info args = $(ARGS))

DIR := bar
$(info args = $(ARGS))

This outputs (make in current directory) (as I expect):

args = -a -b
args = -a -b ../foo/lib.a
args = -a -b ../foo/lib.a
make: *** No targets.  Stop.

That is, ARGS is a Simply-Expanded Variable, and so there's no re-substitution. It doesn't pick up that DIR changed from "foo" to "bar". So far, so good.


However, now I try doing it with recursive makefiles. In the parent makefile, I have the first block with the export command (which exports all variables to the children) and a recursive call:

ARGS := -a -b
$(info args = $(ARGS))

export

default:
    $(MAKE)   -C child/

In the child makefile, I have the rest of it, unmodified:

DIR := foo
ARGS += ../$(DIR)/lib.a
$(info args = $(ARGS))

DIR := bar
$(info args = $(ARGS))

However, now the output is:

args = -a -b
make   -C child/
make[1]: Entering directory '[...]/child'
args = -a -b ../foo/lib.a
args = -a -b ../bar/lib.a
make[1]: *** No targets.  Stop.
make[1]: Leaving directory '[...]/child'
make: *** [Makefile:14: default] Error 2

Now, it does pick up on DIR changing from "foo" to "bar", re-substituting in. That is, somehow the export command changed the type of ARGS (in the child) into a Recursively-Expanded Variable!

The description of export (again, here) seems to suggest this is impossible. Specifically, it contrasts export := vs. export = and says they have a different effect. We can also test that directly by replacing the code in the parent makefile with something very explicit:

export ARGS := -a -b

default:
    $(MAKE)   -C child/

Yet this produces the same unwanted recursive expansion in the child as before!

make --version reports itself as "GNU Make 4.3".


What's going on and how do I export variables so that they stay simply-expanded in the children?

1

There are 1 best solutions below

0
MadScientist On

You're reading too much into the manual. The manual says that:

export foo := bar

is identical to the behavior of:

foo := bar
export foo

and that's true. But this says absolutely nothing about the behavior of foo in the sub-make.

In fact all make variables that are exported are exported through the standard POSIX environment, as standard POSIX environment variables. There's no such thing in an environment variable as "simple" vs. "recursive" variables, there are just variables.

When make is ready to invoke a command in a recipe (regardless of whether that command is a compiler or another instance of make or anything else: it's all just a new child process to make) it will go through all exported variables, expand them, and add them to the environment of the subprocess, then invoke the subprocess.

If the subprocess is another instance of make, then make will retrieve all the environment variables (as it always does) and import them as recursive variables with whatever value they had as make variables. Make has no idea that the parent instance used simple variables and there's no way for the parent make to tell the child make about it through the environment.

If you wanted to enforce the simple-ness of the variable you can use assignment instead of appending:

ARGS := $(ARGS) ../$(DIR)/lib.a