Pass variable to child make only if defined - makefile

I can pass a variable to child make using make -C VAR=$(VAR) target (preferring this to export because the variable is only meaningful for a single target). However, it appears that if VAR variable isn't defined in parent, child gets an empty string (it has VAR ?= ..., and the value of VAR is empty). How can I avoid this?
It looks like a variation on Define make variable at rule execution time should work for my specific usecase, but it requires the parent Makefile to know child's default value, which I want to avoid.

You should always use $(MAKE) when invoking sub-makes, never make explicitly. Also you're missing a directory after the -C in your examples.
What about something like:
$(MAKE) $(if $(VAR),VAR=$(VAR))

Related

Invoking a Make target in a loop with an argument

I am designing a simple makefile that defines one target which takes an argument, and I would like to define a second target that invokes the first target in a loop, once per every variable defined in an array variable at the top of the Makefile.
my_loop_var = var1 var2
my_thing:
echo $MY_VAR
all:
invoke my_thing once for each value of my_loop_var
What is the right way to solve this?
Note: $MY_VAR will probably expand as Y_VAR because make will try to expand variable M, which is probably undefined, and concatenate Y_VAR to the result. Use $(VARNAME) to expand make variable VARNAME. It is only with single-character variable names that you can expand with $X.
There is not really a "right way". There are many ways. The most straightforward from your specifications (but the less natural) would be something like:
my_loop_var := var1 var2
my_thing:
echo "$(MY_VAR)"
.PHONY: all my_thing
all:
for v in $(my_loop_var); do $(MAKE) my_thing MY_VAR="$$v"; done
The recipe of all is a shell loop that re-invokes make with the my_thing goal and with each value in my_loop_var passed as the value of make variable MY_VAR.
Note the $$v to escape the first expansion that make always performs before passing the recipe to the shell. With just $v make would expand the recipe as ... MY_VAR="" ... and the result would not be what you expect.
Note also the use of the MAKE make variable instead of directly calling make (have a look at the documentation if you want to understand why it is better like this).
all and my_thing are declared as phony because they are not real file names that their recipes create, and make needs to know this kind of things.
But a much more make-ish way would be something like:
my_loop_var := var1 var2
my_thing_targets := $(addprefix my_thing_,$(my_loop_var))
.PHONY: all $(my_thing_targets)
all: $(my_thing_targets)
$(my_thing_targets): my_thing_%:
echo "$*"
Here we define as many my_thing_something targets as there are something values in my_loop_var. And explain make how to build such targets with a static pattern rule. In the recipe of a pattern rule the $* automatic variable expands as the part that matched the % pattern. So, this static pattern rule says that if we need to build my_thing_something, the recipe is echo "something".
We declare all these my_thing_something targets as prerequisites of all such that if you type make or make all, make will build all the my_thing_something targets.
This second solution is better for at least two reasons. First, make is invoked only once, which is better, at least for performance. Second, make can parallelize the build of the my_thing_something if you use the -j N option (to allow make to run up to N jobs in parallel). This also is better for performance.
But it is also a matter of style. Very frequently if you use shell loops in your recipes, especially to invoke make again, it is the sign that you did not really understand what make is intended for and how it works. The make language is not a scripting language (even if the recipes are written in shell language, which is a scripting language); make is designed to "loop" over all targets to build, without the need for explicit loops.

How to change $(MAKE) for all sub-makes?

I would like to redefine MAKE := $(MAKE) --warn-undefined-variables and this works but only for the first level.
When the first level invokes another makes, this definition is not kept.
How could I do this?
Variable values of the top-level make can be passed to the sub-make through the environment by explicit request.
These variables are defined in the sub-make as defaults, but they do not override variables defined in the makefile used by the sub-make unless you use the ā€˜-eā€™ switch
To pass down, or export, a variable, make adds the variable and its value to the environment for running each line of the recipe. The sub-make, in turn, uses the environment to initialize its table of variable values.
As a convenience, you can define a variable and export it at the same time by doing:
export variable := value
or
variable := value
export variable
https://www.gnu.org/software/make/manual/html_node/Variables_002fRecursion.html

Does Make expand recursive-variables before exporting them?

Given a Makefile:
ifeq "$(MAKELEVEL)" "0"
0 ::
#$(MAKE)
else
1 ::
#echo 'foo is: "$(foo)"'
endif
And executing, we get:
$ make foo='$#'
make[1]: Entering directory '/home/myname'
foo is: "1"
make[1]: Leaving directory '/home/myname'
$ make foo='$#' --environment-overrides
make[1]: Entering directory '/home/myname'
foo is: "0"
make[1]: Leaving directory '/home/myname'
So we have here a recursive variable foo with the value: $#, which - of course - expands to the name of the target. Now, we have two options here:
Either, Make expands first the variable, and then export the variable to a sub-make.
With this "logic", when Make runs the first makefile (MAKELEVEL = 0), it will build the target 0, Hence: expand the variable foo(and its value:$#) to 0, and then export - this already expanded value - to the sub-make.
This result, with the sub-make running its makefile, with a variable foo that has the simple value: 0.
This is in-fact the case, when we run make --environment-overrides, as you can see in the second run of the makefile, in the example above.
Another "logic" is, for Make to pass the value "verbatim". That means, with no expansion!
Hence all recursive variables will be still intact, when passed to the second Make.
For this logic, only the sub-make is allowed to expand its variables recursively, hence: any recursive-expansion will be done in the context of the sub-make.
In our example above, that we had foo with its value $#, if we are to follow this logic, Make will pass the value $# "verbatim", with no expansion at all, so the sub-make will effectively see a value $# for its foo variable, hence: when expanding in the context of its target, that happens to be 1, it will recursively expand foo(and its value: $#) to 1.
Actually, this is the "normal" behaviour that is evident in the first run of makefile, as evident in the example above.
So, for a lack of clear methodology, we are left to conclude, that this behaviour of either expand and then export or export and then expand is inconsistent.
That is, sometimes Make will choose the first method, where sometimes it will chose the second.
In our example, it was a command-line option (--environment--overrides), that acted as a deciding factor, as to what method Make has to choose.
But, can we really justify, that these - seemingly - unrelated features (i.e export/recursive vs. environment-overrides), will end up to have such a dramatic effect on each-other?
(Versions note: 4.0 and up).
make does export foo in expanded form, as can be seen here:
target:
#echo "'$$foo'"
Output:
$make foo='$#'
'target'
However, to pass its argument to sub-makes, it does not use the environment directly -- instead it stuffs everything into the variable MAKEFLAGS. And there it passes foo unexpanded:
target:
#echo "'$$MAKEFLAGS'"
Output:
$make foo='$#'
' -- foo=$$#'
To be clear: the sub-make does import foo from the environment, but that is then overridden by the setting from MAKEFLAGS.
When you specify --environment--overrides, that means that environment variables take precedence over settings in the makefile. The GNU make documentation does not explicitly specify what happens to variables passed in via MAKEFLAGS in the presence of --environment--overrides, but apparently they are overridden too.

Unset an env variable on a makefile

I have a makefile that runs some other make target by first setting some variables:
make -C somedir/ LE_VAR=/some/other/stuff LE_ANOTHER_VAR=/and/so/on
Now I need to unset LE_VAR (really unset, not just override the value with "").
Is there any way to do it so on GNU Make 3.81?
Thanks!
Assuming your makefile contains something like this to invoke a sub-make:
submake:
$(MAKE)
You need to modify the magical variable MAKEOVERRIDES, like this:
MAKEOVERRIDES := $(filter-out LE_VAR=%,$(MAKEOVERRIDES))
unexport LE_VAR
submake:
$(MAKE)
Check this out unexport variable.
From gnu manual
export variable
export variable-assignment
unexport variable
Tell make whether or not to export a particular variable to child processes
Refer https://www.gnu.org/software/make/manual/html_node/Quick-Reference.html
Thank you very much for your replies, this was quite tricky.
When executing make, and setting vars in the parameters, like:
make -C le/path install ONEVAR=one OTHERVAR=two
We have both ONEVAR and OTHERVAR on the env and the subtasks ran by the first command. This kind of puzzled me because I added to the task (at le/path) to execute a simple bash script that only did:
echo $ONEVAR
unset ONEVAR
And by my surprise the var $ONEVAR was actually "one" (so it was on the env) and the unset actually cleared it. But, adding an "echo $(ONEVAR)" on the makefile still outputs "one".. This is due to MAKEOVERRIDES, and in fact, as suggested by Communicating Options to a Sub-make:
The command line variable definitions really appear in the variable
MAKEOVERRIDES, and MAKEFLAGS contains a reference to this variable. If
you do want to pass flags down normally, but don't want to pass down
the command line variable definitions, you can reset MAKEOVERRIDES to
empty, like this:
MAKEOVERRIDES =
Or as MadScientist suggested above :)
But this was not enough, since this var was still being passed to the other subtasks below (in this situation some nodejs modules that were being compiled on a local folder, and by bad luck, both a js file from phantomjs and some other makefiles where using a var with the same name (e.g., $ONEVAR).
unexport variable Tell make whether or not to export a particular
variable to child processes.
GNU Make Appendix A Quick Reference
What I did was:
DESTDIR_BUFFER=$(DESTDIR)
MAKEOVERRIDES := $(filter-out DESTDIR=%,$(MAKEOVERRIDES))
unexport DESTDIR
And only then make npm install.
At the end of this task I export DESTDIR with the value at DESTDIR_BUFFER and all the other consequent tasks still work.
Thanks a lot for your help!

How to prevent make from communicating any variable to a submake?

I am unable to prevent make from communicating any variables to a submake. I've read the manual and I've followed their advice (resetting MAKEOVERRIDES and MAKEFLAGS) but it's still not working has I think it should.
Consider the following prototype Makefile:
${warning $(MAKEOVERRIDES)}
${warning $(MAKEFLAGS)}
${warning $(VAR)}
none:
$(MAKE) -f Makefile MAKEOVERRIDES= MAKEFLAGS= all
all:
echo done!
If I make VAR=10 none, I get the following:
Makefile:2: VAR=10
Makefile:3:
Makefile:4: 10
make -f Makefile MAKEOVERRIDES= MAKEFLAGS= all
make[1]: Entering directory `/home/adriano/sandbox/makes'
Makefile:2:
Makefile:3:
Makefile:4: 10
echo done!
done!
make[1]: Leaving directory `/home/adriano/sandbox/makes'
Meaning that make is communication VAR to the submake. Is this the correct behaviour?
I've tried unexport VAR and bash -c make ... without any luck.
EDIT: I've modified none's recipe to: bash -c "echo $$MAKEOVERRIDES $$MAKEFLAGS $$VAR" ; make ...
This way I found out that VAR is actually being passed through the environment that make creates for the commands to be executed and not through the other variables (the other variables are also passed this way to make).
I think my question now is: how can I create a fresh shell/environment to run my sub make?
EDIT: Someone asked why am I trying to this; I'll try to answer to that here.
I have a "module" which uses a variable named CONFIG. In order to build this module I need to build another partially unrelated "module" which also uses CONFIG, but with a different value. The problem is that when I try to build the "sub-module" CONFIG contains the value of the "super-module." I could specify CONFIG when making the "sub-module" however both modules use many variables with the same name and trying to specify them all would make the modules tightly coupled which is something I cannot afford.
How can this be so difficult...
This is wrong:
none:
$(MAKE) -f Makefile MAKEOVERRIDES= MAKEFLAGS= all
These variables (MAKEOVERRIDES and MAKEFLAGS) are set in the environment by the parent make to be passed down to the sub-makes. Setting overrides on these values inside the recipe won't help, because make has to set the environment for the recipe before it actually starts the commands in the recipe (of course).
You have to override/remove these values in the parent makefile, so that those changes are seen by the parent make before it constructs the sub-make's environment:
MAKEOVERRIDES =
none:
$(MAKE) -f Makefile all
There's no perfect way to do this. However, you can play a trick that will work most of the time:
unexport $(shell echo '$(MAKEOVERRIDES)' | sed 's/=[^ ]*//g')
MAKEOVERRIDES =
The first line tries to unexport all the variables in MAKEOVERRIDES and the second line resets MAKEOVERRIDES. There are a few issues with this. One is that if MAKEOVERRIDES is empty, it will use "unexport" by itself which unexports everything. That can be easily worked around by sticking some bogus variable before the shell function. The other is that if any variable's value contains whitespace, the expansion will consider it a variable to be unexported. That's probably OK, but it's odd.
I can't think of any better way to do it.
You don't really say why you want to do this. Have you considered doing something different, such as running the commands where you want to have a "vanilla" environment using env; for example if you want to run a command with a limited and specific set of env vars, you can run:
test:
env -i PATH='$(PATH)' LANG='$(LANG)' runMyCommand --with --my arguments
Unfortunately some versions of env use - instead of -i; check your man page.
Alternatively, you can try to start a login shell which will re-read the user's shell setup environment from scratch:
test:
/bin/sh -lc 'runMyCommand --with --my arguments'
EDIT: It's difficult because what you're asking to do (restrict the environment of the sub-make) is tricky.
Luckily based on your description, it doesn't seem necessary. Make has a hierarchy of importance for finding variable values. The command line is the highest level (well, there's override but we'll ignore that). After that comes variables set in the makefile itself. And last and lowest comes variables imported from the environment (well, default variables are even lower but we'll ignore that too).
So if your goal is to allow the variables in the sub-makes to not be affected by command line variables given to the upper-level makes, then all this rigmarole of getting the variables out of the environment is not necessary. Variables set in the sub-makefiles will take precedence over the values in the environment. So all you have to do is get rid of the variables set on the command line, which I've already shown how to do above, by setting MAKEOVERRIDES.

Resources