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
