On Mon, 2026-05-18 at 02:06 +0000, Gunter Woytowitz wrote:
> In GNU Make 4.4 (confirmed on 4.4.1), "export VAR ?= $(shell
> COMMAND)" re-evaluates $(shell) in every recursive sub-make, even
> though the variable was already evaluated and exported by the top
> make and the sub-make's $(origin VAR) correctly reports
> "environment".

You have definitely found a problem, but your diagnosis isn't accurate
(unfortunately: if it were the problem would be a simple bug, rather
than a deep issue that's hard to resolve).

I think you're getting confused by the facilities you are choosing in
your makefile to show the problem.  If you choose a simpler reproducer
you'll get a clearer view of the issue.

Consider this makefile:

  export FOO ?= $(shell echo foo)
  export BAR ?= $(shell echo bar)

  $(info origin FOO = $(origin FOO))
  $(info FOO = $(value FOO))

  $(info origin BAR = $(origin BAR))
  $(info BAR = $(value BAR))

  all: ; echo $$FOO $$BAR

Note this things: first, we use $(info ...) rather than echo to show
information directly.  Second, we use $(value ...) to show the actual
value of the variable, which is what make will evaluate when it expands
the variable.

Finally, we don't bother with recursion which adds confusion to the
output; this is not needed since we can simply set the variables in the
environment before invoking make.

So, no matter which version of make you use you get the expected
result:

  $ make
  origin FOO = file
  FOO = $(shell echo foo)
  origin BAR = file
  BAR = $(shell echo bar)
  echo $FOO $BAR
  foo bar

However, if you check how many instances of the shell are invoked in
version 4.3 you'll see (this is on GNU/Linux of course):

  $ strace make-4.3 2>&1 | grep clone | wc -l
  3

whereas in 4.4.1 you'll see:

  $ strace make-4.4.1 2>&1 | grep clone | wc -l
  5

Now, two extra clones doesn't seem like much but as you observed, this
value goes up very quickly with the number of exported variables that
contain shell calls.  If I add a new line to the makefile:

  export BAZ ?= $(shell echo baz)

then the clone() call count in make-4.4.1 goes up to 16!

But we can see that your guess that the problem is that make is still
expanding values that are overridden in the environment, is not correct
by providing environment variables:

  $ FOO=env1 BAR=env2 make
  origin FOO = environment
  FOO = env1
  origin BAR = environment
  BAR = env2
  echo $FOO $BAR
  env1 env2

and now the clone count:

  $ FOO=env1 BAR=env2 strace make-4.3 2>&1 | grep clone | wc -l
  1

  $ FOO=env1 BAR=env2 strace make-4.4.1 2>&1 | grep clone | wc -l
  1

So, no more work is done here.

The problem you are running into is a result of this change from the
NEWS file:

* WARNING: Backward-incompatibility!
  Previously makefile variables marked as export were not exported to commands
  started by the $(shell ...) function.  Now, all exported variables are
  exported to $(shell ...).  If this leads to recursion during expansion, then
  for backward-compatibility the value from the original environment is used.
  To detect this change search for 'shell-export' in the .FEATURES variable.

Resolving https://savannah.gnu.org/bugs/?10593


Unfortunately that has the unanticipated side effect that EVERY time
$(shell ...) is invoked, EVERY exported variable is expanded.  This
gives the behavior you are seeing.

There is a still-open issue we are using to consider how to alleviate
this but so far no brilliant ideas have surfaced:
https://savannah.gnu.org/bugs/index.php?64746

One thing we did do is add support for a "?:=" assignment operator that
does conditional assignment but results in a simple variable
assignment, not a recursive variable assignment.  That would fix the
issue above (in the next release of GNU Make) but of course it's not
backward-compatible.

> Replacing ?= with := (immediate assignment, computed once at top-make
> parse time) avoids the problem at the cost of breaking command-line
> variable override.

There are other ways, of course.  You can do something like:

  default_TOP  := $(shell pwd)
  export TOP   ?= $(default_TOP)

That will force the shell to run every time, even when overridden, but
it will only run once (per invocation of make) not multiple times.

Or, you can get very tricksy and use my hack for "deferred simple
variable expansion":

https://make.mad-scientist.net/deferred-simple-variable-expansion/

Although not discussed in that blog post this works with ?= as well.

If you use that trick in my example above and repeat the experiment
you'll see that make-4.4.1 does the same number of clone() calls as
make-4.3, including only one when variables are inherited from the
environment.

-- 
Paul D. Smith <[email protected]>            Find some GNU Make tips at:
https://www.gnu.org                       http://make.mad-scientist.net
"Please remain calm...I may be mad, but I am a professional." --Mad
Scientist



Reply via email to