The Basics: Getting environment variables into GNU Make

[article]

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:

export FOO=bar

$(warning $(shell FOO=$(FOO) printenv | grep FOO))

all:

   @printenv | grep FOO

And get the desired result:

Makefile:3: FOO=bar

FOO=bar

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

env_shell
= $(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:

export FOO=bar
 
$(warning $(call env_shell,FOO,printenv | grep FOO))

all:

   @printenv | grep FOO

And when run you'll see the output:

Makefile:3: FOO=bar

FOO=bar

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:

export FOO=bar

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.

About the author

John Graham-Cumming's picture John Graham-Cumming

John Graham-Cumming is Co-Founder at Electric Cloud, Inc . Prior to joining Electric Cloud, John was a Venture Consultant with Accel Partners, VP of Internet Technology at Interwoven, Inc. (IWOV), VP of Engineering at Scriptics Corporation (acquired by Interwoven), and Chief Architect at Optimal Networks, Inc. John holds BA and MA degrees in Mathematics and Computation and a Doctorate in Computer Security from Oxford University. John is the creator of the highly acclaimed open source POPFile project. He also holds two patents in network analysis and has others pending.

AgileConnection is one of the growing communities of the TechWell network.

Featuring fresh, insightful stories, TechWell.com is the place to go for what is happening in software development and delivery.  Join the conversation now!

Upcoming Events

Sep 22
Sep 24
Oct 12
Nov 09