Can make determine whether an environment variable is defined? - makefile

AFAICT, ifdef in make works only for variables defined in the Makefile. Is there a way to check whether an environment variable is defined?

I don't know what you're doing (it's always best to include an actual example of what you have tried) but it definitely works:
$ cat Makefile
ifdef FOO
$(info FOO is defined)
endif
all:;#:
$ make
$ FOO=1 make
FOO is defined

Related

Is there a way to print all variables defined/visible within a makefile?

let's assume the following Makefile:
FOO=1
BAR?=test
BLAH:=$(BAR)
target:
#echo "helloworld"
Is there a way it can show me variables like this (\n or \s) ?
FOO
BAR
BLAH
bonus question:
if it is possible, is there a way to also get external variables?
make target BLABLAH=toto or export BLABLAH=toto; make target
FOO
BAR
BLAH
BLABLAH
You don't say so specifically but because of the syntax I'll assume you're using GNU make. In that case you can use the .VARIABLES special variable:
$(info Variables: $(.VARIABLES))
Note, that make target BLABLBAH=toto does not create an "external variable" (that term is not well-defined so I'm just guessing that by this you mean variables that are inherited from the environment and not set in the makefile). Setting a variable on the command line creates a real make variable and it will be shown in the list above.
Variables obtained from the environment are imported into make as makefile variables and will also appear in the above list.

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)

Include Makefile and Echo Variables Inside Multiple Makefiles

Inside a Makefile I have recently had to look at, it includes this include $(MAKERULES)
Now to me, I would think somewhere at the top of the makefile it has MAKERULES = xyz but it does not. So I print out MAKERULES
test:
#echo "Rules: $(MAKERULES)"
Then I do make test. It runs and it prints out another Makefile Location, Makefile2.
X/Y/Z/Makefile2 path. I go inside this Makefile and attempt to write out some echo statements so I can see what is printing, but nothing prints out.
Is it possible to print out variables from another Makefile (Makefile2) that my local Makefile (Makefile) references to?
Yes, it is possible. You haven't shown us your attempt to display those variables, so we can't tell you why it didn't work. (Well, I can't.)
Here is how I'd do it:
$(info the variable FOO contains $(FOO))
If you want to do this for several variables:
$(foreach X, FOO BAR BAZ, $(info $(X) is $($(X))))
And the list of all currently defined global variables is .VARIABLES, so you can use "$(.VARIABLES)" in place of "FOO BAR BAZ" to print all of them.

Why doesn't gnu make's "override" pass through to sub-makes?

Note: This question was originally posted as a rant by a now-deleted user, but there was a valid question behind the rant; this is my attempt to provide an answer.
Given the Makefile:
ifeq "$(MAKELEVEL)" "0"
# Override the command-line specification of "foo".
override foo=replaced
export foo
all::
#echo outer: foo is "$(foo)"
#$(MAKE)
else
# Variable 'foo' was "exported" from the top-level Makefile.
all::
#echo inner: foo is "$(foo)"
endif
The expectation is that export foo will cause make to export the value defined in the override declaration. But it doesn't:
$ make -s foo=original
outer: foo is replaced
inner: foo is original
The expectation is probably reasonable, but it turns out that this is not the way Gnu make works. It could well be that the make documentation could be improved to clarify the process, but the hints all seem to be there.
How variables get their values
A variable can be set by the programmer in three ways:
On the command line with a var=value command-line argument
Explicitly in the make file
From the environment
The above list is the normal priority order; the first definition found in the list "wins". However, you can use the override directive to swap the priorities of the first two methods. (You can also use the -e flag to make to swap the priorities of the last two methods. The -e flag is required by Posix, but its use is discouraged and it does not interact will with override.)
How variables are passed to sub-makes
make is very similar to a shell in that the environment is used to pass variable values. If a variable is marked as exported, then its value is placed into the environment for any processes initiated by make, including sub-makes. As with the shell, a variable is marked as exported if its definition came from the environment or if it is explicitly marked as exported with the export directive. Variables are also exported if they were set on the command line.
However, there is another mechanism by which variables on the command-line are passed to subprocesses: the MAKEFLAGS exported variable.. MAKEFLAGS contains (most) command-line options as well as all of the command-line variable overrides. If make finds MAKEFLAGS in the environment, it merges the settings in that variable with the ones actually specified on its command line. That means that command-line variable settings in a make will also take priority in a sub-make.
Since command-line variable settings are passed through the MAKEFLAGS variable, they are not subject to any changes in the makefile. A makefile can unexport or override a variable set on the command-line, but that will only affect the value (or presence) of the variable in the environment; it does not remove or change the value from MAKEFLAGS.
Resolution
So if the intent is to override (or modify) a command-line variable both in the make itself and in the environment of sub-makes, it is necessary to use both an override and an explicit modification of MAKEFLAGS. (As explained in the make manual, MAKEFLAGS is actually recursively composed using the MAKEOVERRIDES variable, so we actually modify that variable.)
ifeq "$(MAKELEVEL)" "0"
# Override the command-line specification of "foo".
override foo=replaced
MAKEOVERRIDES += foo=replaced
all::
#echo outer: foo is "$(foo)"
#$(MAKE) -s
else
# Variable 'foo' was "exported" from the top-level Makefile.
all::
#echo inner: foo is "$(foo)"
endif
And now we get the expected result:
$ make -s foo=original
outer: foo is replaced
inner: foo is replaced
Real-life application: dealing with whitespace
The primary intention of overrides is to allow the makefile to append words to a variable possibly provided on the command line. The example provided in the gnu make manual is insisting that CFLAGS always includes the -g flag, even if it were specified on the make command line:
override CFLAGS += -g
Passing the append through to a sub-make needs a little caution; in particular, the obvious:
MAKEOVERRIDES += CFLAGS=$(CFLAGS) # Don't do this
won't work because the whitespace inside the CFLAGS variable will not be escaped when it is added to MAKEFLAGS; the result will be that MAKEFLAGS will look something like this:
-- CFLAGS=-O3 CFLAGS=-O3 -g
instead of the desired
-- CFLAGS=-O3 CFLAGS=-O3\ -g
If the value assigned to CFLAGS on the command line included whitespace, the whitespace is escaped in MAKEFLAGS. The particular escaping mechanism used is not documented, and Posix only requires that there be some mechanism; apparently, Gnu make uses backslash. It would be possible to manually backslash escape the whitespace, resulting in something like this:
# Don't do this either
MAKEOVERRIDES += CFLAGS=$(subst $(space),\ ,$(CFLAGS))
(The definition and use of space is based on an example in the gnu make manual.)
But it is actually easier to just use an append assignment in MAKEOVERRIDES, which is undocumented but appears to work. It works on the command line, too.
override CFLAGS+=-g
MAKEOVERRIDES += CFLAGS+=-g
Important Note as of make v4.1: A bit of testing revealed that the above stanza will only work if CFLAGS (or some other variable) is actually set on the command-line. I reported this bug as Savannah issue 46013, with a very simple fix in the bug report. In the meantime, if you really want to do this, use the following workaround:
override CFLAGS+=-g
MAKEOVERRIDES += CFLAGS+=-g
# This line is necessary in case there were no command-line overrides.
# In effect, it produces a command-line override, although that value
# will not be passed on to sub-makes.
MAKEFLAGS += dummy=dummy
Update May 19, 2019: Today I was informed that a fix for the bug referenced above has been committed, so it should be fixed in the next gmake release.
First of all, I want to point out that your suggestion to add to MAKEOVERRIDES, is dangerous!
And SHOULD NEVER BE DONE!!
You simply turn a recursive variable into a simple one, you will always get false results, if recursive expansion is done.
I can not believe that you got up-voted for this clearly wrong "suggestion".
And note this:
You can not even fix it with a quoted assignment, like MAKEOVERRIDES += foo=$$(bar)!!!
But, let me return to the main point of your post.
And, with which, I couldn't disagree more.
One simple example would be, if you run the very same makefile, that you have:
This one is copied verbatim, from your post:
ifeq "$(MAKELEVEL)" "0"
# Override the command-line specification of "foo".
override foo=replaced
export foo
all::
#echo outer: foo is "$(foo)"
#$(MAKE) -s
else
# Variable 'foo' was "exported" from the top-level Makefile.
all::
#echo inner: foo is "$(foo)"
endif
And running in any modern version, 4.0 and up:
# Sub-make does NOT get the value from the root-Make's command-line.
# Instead, it "inherits" the value from the root-Make's definition in the Makefile.
$ make -s foo=original -e
outer: foo is replaced
inner: foo is replaced
Now, given your assertion above:
However, there is another mechanism by which variables on the command-line are passed to subprocesses: the [MAKEFLAGS exported variable.][3]. MAKEFLAGS contains (most) command-line options as well as all of the command-line variable overrides. If make finds MAKEFLAGS in the environment, it merges the settings in that variable with the ones actually specified on its command line. That means that command-line variable settings in a make will also take priority in a sub-make.
Since command-line variable settings are passed through the MAKEFLAGS variable, they are not subject to any changes in the makefile. A makefile can unexport or override a variable set on the command-line, but that will only affect the value (or presence) of the variable in the environment; it does not remove or change the value from MAKEFLAGS.
You should get:
outer: foo is replaced
inner: foo is original
In other words, we should get for the sub-make, the value defined on the command-line (original)!
Because, you said yourself:
A makefile can't unexport or override a variable set on the command-line.
So, here, when we empower the environment over the makefile, which means that the makefile has less "power" in the total scheme of things. Right?
Sure, for such a case, you assertion will hold even stronger.

How to have variables of one included makefile available in another makefile included later?

I have the Makefile below,
include settings.mk
include main.mk
where settings.mk has the following content,
FOO=foo
BAR=bar
and main.mk is as follows:
THIS_MAKEFILE:=$(lastword $(MAKEFILE_LIST))
.PHONY: all
all:
$(MAKE) -f $(THIS_MAKEFILE) display
.PHONY: display
display:
#echo "FOO=$(FOO)"
#echo "BAR=$(BAR)"
The problem is that make all results in the following output
FOO=
BAR=
instead of
FOO=foo
BAR=bar
How to have the variables FOO and BAR available in main.mk?
When you execute just make -f main.mk (in the all target), the values are not set because you've just reread main.mk without pre-reading settings.mk.
In the Makefile, the variables are set. If you add a rule such as:
check:
#echo "FOO=$(FOO); BAR=$(BAR)"
to the Makefile, and then run make check, you'll see that FOO and BAR are indeed set.
So, the question becomes: why on earth are you doing what you are doing — and why are you expecting just make -f main.mk to know about stuff set in a makefile that the second invocation of make hasn't read? I think this is probably an XY Problem.
You could add include settings.mk to main.mk; that might make sense (but then the main Makefile would only need to contain include main.mk, leaving open the question of why you have both).
They are available in main.mk. They aren't available in the sub-make that you are spawning because you haven't exported them.
Use $(info FOO:$(FOO))/etc. in main.mk and you'll see them print out correctly.

Resources