How to save make argument in a variable - makefile

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

Related

What does obj_$(variable_name) do in a makefile?

I have encountered the following line in a make file:
OBJECTDIR = obj_$(TARGET)
What does the command obj_$(TARGET) do?
Unfortunately this command is not listed in the GNU make manual:
https://www.gnu.org/software/make/manual/
This is just normal variable expansion. Say that the TARGET variable has value foo, e.g. somewhere before that line you have:
TARGET := foo
Then obj_$(TARGET) will be expanded to obj_foo, which is equivalent to:
OBJECTDIR = obj_foo
I suggest you look up how to use variables in the Make manual (the expansion will work differently depending on the flavor of TARGET).

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.

Remove Make goal passed from commandline

Before beheading me for abusing make, is it possible to remove a goal passed on the command line?
Something like MAKECMDGOALS := $(wordlist 1,2,$(MAKECMDGOALS)) ?
I've tried this but it still goes on and makes the other rules anyway...
From the documentation:
MAKECMDGOALS
The targets given to make on the command line. Setting this variable has no effect on the operation of make.
(Emphasis added.)

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.

GNU Make - Extracting job parameters and passing it to another process

This is what I'm trying to do in my Makefile:
MAKE_381 := $(TOOLS)/bin/make-381
default:
cd proj && $(MAKE_381)
MAKE_381 refers to the version of make v3.81 binary. This is required since the proj directory contains Makefiles which are compatible only with v3.81 and nothing newer or older.
I need to have this Makefile always use make v3.81 to build proj independent of the version of GNU make the developer has installed on his/her system.
Invoking MAKE_381 works but with one caveat, any extra parameters like number of parallel jobs, are not passed to MAKE_381. I could not find any documentation on how to extract this information.
I'm aware of MAKEFLAGS variable in GNU make, which I understand should contain any extra parameters passed to make from command line, but somehow I find this value to be empty.
I do not wish to hardcode the number of jobs passed to MAKE_381, instead the parameter should be passed through based on the initial Makefile invoked.
All you have to do is prefix the command line with a + character, so make knows that you're invoking another sub-make process:
default:
+ cd proj && $(MAKE_381)
If your command used the variable MAKE directly then this would be automatic, but since you're using a different variable name (MAKE_381), you have to do it explicitly.

Resources