which results in a loop as FOO would have to be placed in the environment of the $(shell) which requires getting the value of FOO and so on...
GNU Make's developers opted for an easy way: they just don't even try to fix the bug (for the moment).
Given that this bug isn't going away for the moment, we need a work around. Luckily, most decent shells have a way to set an environment variable inline. So I can modify the first Makefile in this section to:
$(warning $(shell FOO=$(FOO) printenv | grep FOO))
[email protected] | grep FOO
And get the desired result:
This works by setting the value of FOO within the shell used for the $(shell) with the string FOO=$(FOO). Since the argument to $(shell) gets expanded before execution that string becomes FOO=bar (taking its value from the value of FOO set in the Makefile).
The technique works fine if there's just one extra variable needed in the environment, but if there are many it's problematic. A more comprehensive solution to the problem is to write a replacement for the $(shell) command that does export variables. Here's env_shell that does just that:
env_file = /tmp/env
= $(shell rm -f $(env_file))$(foreach V,$1,$(shell echo export $V=$($V)
>> $(env_file)))$(shell echo '$2' >> $(env_file))$(shell
/bin/bash -e $(env_file))
Before explaining how it works I'll use it to modify the Makefile above. All that's necessary is to changing the $(shell) to $(call env_shell). The first argument of env_shell
is the list of variables that need to be added to the environment and the second argument is that command to be executed. Here's the updated Makefile with FOO exported:
$(warning $(call env_shell,FOO,printenv | grep FOO))
[email protected] | grep FOO
And when run you'll see the output:
Now back to how it works. It's very simple. env_shell makes a shell script that adds all the variables from its first argument to the environment and then executes the command. By default the shell script is stored in the file named in the env_file macro (which was set to /tmp/env above).
In the example above /tmp/env ends up containing:
printenv | grep FOO
When you call env_shell it first deletes /tmp/env (that's the $(shell rm -f $(env_file)) part). Next it adds to that file lines containing the definition of each of the variables named in the first argument, $1 (the $(foreach V,$1,$(shell echo export $V=$($V) >> $(env_file))) loop does the work).
Finally, it appends the actual command to execute (which was in the second argument $2 (the $(shell echo '$2' >> $(env_file)) bit) and then runs the /tmp/env file with a call to the shell (in this case I used bash) with the -e option (the $(shell /bin/bash -e $(env_file)) bit).
It's not perfect (because it would be nice if it just figured out what
should be in the environment), but it's a workable solution until GNU
Make's coders fix the bug.