Command-line variables - in makefile - expand to weird values - makefile

From the docs:
Target-specific variables have the same priority as any other
makefile variable. Variables provided on the command line (and in the
environment if the '-e' option is in force) will take precedence.
Specifying the 'override' directive will allow the target-specific
variable value to be preferred.
So, a simple makefile, like:
# A pattern-specific variable assignment.
% : foo += file
all : x ;
# Target is a double-colon w/o dependencies, so Make will ALWAYS run its commands.
x ::
#echo '$(foo)'
Running, we get:
# Override makefile-level variables, with a command-line assignment.
$ make foo=cmd
cmd cmd cmd
# Set the value in the environment, And tell Make to prefer it over any makefile-level definitions.
$ foo=env make --environment-overrides
env file file
Returning now, to the quote above, from the documentation:
Variables provided on the command line (and in the environment if the '-e' option is in force) will take precedence.
It seems, that using either:
Command-line assignment.
Environment-set variables, AND using -e (--environment-overrides).
Have both the same effect, i.e. overrides the file-level (makefile) variable.
But, the results differ greatly. Remember that the value given in the command-line was: cmd, and the value given in the environment was: env.
Now, compare the values, given for a command-line override vs. an environment override:
cmd cmd cmd (for command-line override).
env file file (for environment override).
So, whereas for command-line, Make repeats the same value, i.e. cmd, 3 times, for environment-override, the situation is different. That is, Make will "repeat" the environment-level value: env only 1 time, and then repeats - none other - than the overridden file-level value: file.
Now, not only is the situation completely different for an "override" from command-line vs. an "override" from the environment, which is strange by itself, the problem here is much bigger.
Since, Make rules to give "priority" for a command-line (or environment) value, why does it insist to append "other" values (as in the case of environment-override, where Make appends "file file"), or in the case of a command-line override (where Make repeats the same value ***3* times). Seriously?
How does it make sense at all? And what is the justification for these inconsistent and strange results?

I believe the answer here is related to the answer to this other question of yours. (And possibly a bug in the env override version.)
The global variables and the target-specific variables are distinct variables.
The cmd cmd cmd result is because when you write %: foo += file make stores that as an addition to the current value of variable foo of the target-specific value of the variable foo which is file.
However, when you set foo on the command line make overrides the value of the target-specific variable foo to be cmd instead of file. So when make concats the variable each time it gets cmd cmd cmd.
That explanation should, I think, get you env env env then and I'm not sure why it doesn't. This could be a bug or it could be some other detail about how env override variables and target-specific variable values work. I'm not sure.
(Check the output of make -p for both these cases to see what I mean about the target-specific variable's value.)

Related

Make variable that is only passed from the command line

I know that I can pass variables to a Makefile in two ways:
make <target> FOO=bar
export FOO=bar
make <target>
and that both of these will make a variable FOO available in the makefile with value bar.
However, is there a way to require that the variable only comes from the command line? I want the passing of variables to be explicit in order to avoid certain potential overlaps of environment variables, so I want to ensure make only receives the variable if passed from the command line, and to disregard it if it's only set in the environment so that the value it uses must be defined by the user when calling make.
EDIT: I realize after researching it a bit more that environment variables are not actually accessed the way I thought they are, they're actually used within make as ${FOO} so as long as I don't define FOO at any point in the makefile, its only set value will be from the command line (as noted in this answer).
These methods to pass variables to GNU make aren't equivalent.
Variables that come from the environment don't override the assignments in makefile, unless make is invoked with -e option. See variables from the environment. This is because depending on environment variables is poor practice in terms of build reproducibility (someone forgets to set the environment variable and the build is different):
It is not wise for makefiles to depend for their functioning on environment variables set up outside their control, since this would cause different users to get different results from the same makefile. This is against the whole purpose of most makefiles.
Variables that come from make command line do override assignments in makefile, unless override is specified. See the override directive.
Hence, the recommended practice is to explicitly set all your variables to their default values in the makefile unconditionally, so that only the assignments from the command line override them.
As far as I know, there's no difference between ${FOO} and $(FOO), regardless of the way FOO is defined.
If you're using GNU make, there's a function origin that allows you to make the distinction: it will return command line for a variable defined on the command line and environment for a variable exported by the environment (more info in the manual)
with the following Makefile:
foo ?= foo
default:
#echo ${foo}, comes from $(origin foo)
make prints foo, comes from file
make foo=bla prints bla, comes from command line
(export foo=bar; make) prints bar, comes from environment
expanding on #Virgile answer, you could add the following kind of check at the start of the makefile. It is a lot to repeat for each variable you wish to check, although all such checks could reside in a dedicated makefile that is then included from main makefile
foo ?= foo
# check origin
ifdef foo
ifneq "$(origin foo)" "command line"
$(error foo: must come from command line)
endif
else
$(error foo not defined)
endif
default:
#echo ${foo}, comes from $(origin foo)

How to save make argument in a variable

I am working on a (GNU) Makefile and need to save arguments in a variable.
Like if I give the command make projectX, then I need to assign projectX to some variable.
I tried to assign projectX like this, assuming that argument 1 would be projectX.
PRODUCT := "$1"
But this does not work.
What is the best way to assign make arguments in a variable?
From the GNU make manual:
Make will set the special variable MAKECMDGOALS to the list of goals you specified on the command line. If no goals were given on the command line, this variable is empty. Note that this variable should be used only in special circumstances.
And note that usually you would/should/must use $# to refer to the target of a rule.
You can also assign variables in make command line:
make PRODUCT=bla
Which is often used for debug/release builds:
make # builds debug version
make MODE=release # builds release version

Makefile: how to set a LOGFILE=`date +'test_%m.%d_%H.%M.%S.log'` variable only once?

I like to log output of test program to a log file with time stamp.
I created following Makefile, but it doesn't work. The "make" seems to calculate LOGFILE at the last moment as needed.
Makefile
LOGFILE=`date +'test_%m.%d_%H.%M.%S.log'`
export DLOG=$(LOGFILE)
test2:
echo DLOG=$$DLOG
echo DLOG=${DLOG}
sleep 2
echo DLOG=${DLOG}
make test2
echo DLOG=$DLOG
DLOG=`date +'test_%m.%d_%H.%M.%S.log'`
echo DLOG=`date +'test_%m.%d_%H.%M.%S.log'`
DLOG=test_10.22_10.28.04.log
sleep 2
echo DLOG=`date +'test_%m.%d_%H.%M.%S.log'`
DLOG=test_10.22_10.28.06.log
I like to find someway to have the "make" calculate the LOGFILE or DLOG variable only once and I can use the same value everywhere in the makefile. Is it possible?
This is because of the flavor of your variable.
The manual section in question is The Two Flavors of Variables.
Specifically
The first flavor of variable is a recursively expanded variable. Variables of this sort are defined by lines using ‘=’ (see Setting Variables) or by the define directive (see Defining Multi-Line Variables). The value you specify is installed verbatim; if it contains references to other variables, these references are expanded whenever this variable is substituted (in the course of expanding some other string). When this happens, it is called recursive expansion.
and
To avoid all the problems and inconveniences of recursively expanded variables, there is another flavor: simply expanded variables.
Simply expanded variables are defined by lines using ‘:=’ or ‘::=’ (see Setting Variables). Both forms are equivalent in GNU make; however only the ‘::=’ form is described by the POSIX standard (support for ‘::=’ was added to the POSIX standard in 2012, so older versions of make won’t accept this form either).
So you want to use := on either the LOGFILE assignment or on the DLOG assignment (or both).
You also need to use the make $(shell) function to have make execute the command instead of using backticks to make the shell (run for the recipe line) to do it.
LOGFILE=$(shell date +'test_%m.%d_%H.%M.%S.log')
export DLOG:=$(LOGFILE)
test2:
echo DLOG=$$DLOG
echo DLOG=${DLOG}
sleep 2
echo DLOG=${DLOG}
An important thing to note here is that this will cause make to run the command at make parse time and not at recipe execution time.
If you don't want that (because you may not run that target) or you want it to count recipe execution time then you need to do the command execution in the recipe and either use a single shell (via line-continuation or .ONESHELL) or you need to use $(eval) in the recipe to force make to expand the simply-expanded make-level variable only at recipe execution time.

Overwrite variable from makefile

In my makefile I have a variable FOO:
FOO = /path/to/bar
Is it possible to overwrite this variable during the makefile call? Somthing like the following:
FOO=/path/to/foo make all
Specify them as Var=Value before you specify the target, like make FOO=/path/to/foo all.
$ cat Makefile
Foo = asdf
all:
echo $(Foo)
$ make all
echo asdf
asdf
$ make Foo=bar all
echo bar
bar
The ways that variables get assigned values is specified in the How Variables Get Their Values section of the GNU make Manual.
Variables can get values in several different ways:
You can specify an overriding value when you run make. See Overriding Variables.
You can specify a value in the makefile, either with an assignment (see Setting Variables) or with a verbatim definition (see Defining Multi-Line Variables).
Variables in the environment become make variables. See Variables from the Environment.
Several automatic variables are given new values for each rule. Each of these has a single conventional use. See Automatic Variables.
Several variables have constant initial values. See Variables Used by Implicit Rules.
So, as Colonel Thirty Two indicates, you can override variables set in the makefile on the command line.
You can also, if you expect this to be a variable that you want to set persistently, use the ?= assignment and then environment values for that variable will be used.

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