How can I set long options within a makefile - makefile

I am writing a makefile for distribution among students. To ease up their hacking experience, I would like make to warn about uninitialised variables.
I know there is the option --warn-undefined-variables to do just this, and of course, I can add an alias á la alias make="make --warn-undefined-variables" to my .bashrc. But I would like to set this option within the makefile so students will automatically profit from those warnings too, when they start to extend the makefile.
The logical way to do so would be the MAKEFLAGS variable. However, while it works for short options, I cannot get it to work with --warn-undefined-variables as described in Can make warn me, when I use unset variables?
Makefile:
MAKEFLAGS=--warn-undefined-variables
$(info MAKEFLAGS: $(MAKEFLAGS))
$(info ${BAR})
Call:
$ make
MAKEFLAGS: --warn-undefined-variables
make: *** No targets. Stop.
$ make --warn-undefined-variables
MAKEFLAGS: --warn-undefined-variables
Makefile:3: warning: undefined variable 'BAR'
make: *** No targets. Stop.
When I change the MAKEFLAGS to -d the console is flooded with debug information, so I know MAKEFLAGS is set correctly. Any suggestions?

I have GNU make 4.0 here and I cannot for the life of me get make to honor MAKEFLAGS= --warn-undefined-variables with a straightforward Makefile. However, if I make the Makefile invoke itself, then MAKEFLAGS= --warn-undefined-variables works in the child invocation!
MAKEFLAGS= --warn-undefined-variables
$(info MAKEFLAGS: $(MAKEFLAGS))
$(info $(BAR))
# This prevents a warning if we invoke make without a target...
MAKECMDGOALS?=
all:
ifndef RECURSED
$(MAKE) RECURSED=1 $(MAKECMDGOALS)
else
echo $(FOO)
endif
If I just run make, I get:
MAKEFLAGS: --warn-undefined-variables
make RECURSED=1
make[1]: Entering directory '/tmp/t1'
MAKEFLAGS: --warn-undefined-variables
Makefile:3: warning: undefined variable 'BAR'
Makefile:12: warning: undefined variable 'FOO'
echo
make[1]: Leaving directory '/tmp/t1'
Either I'm borking on something... or there's a bug in make. I'm inclined to think the latter.

Related

Makefile default rule pattern

Just trying to write a conditional Makefile using this skeleton:
TARGET = test
ifeq ($(FOO),y)
$(TARGET):
#echo This is test
$(TARGET)-a:
#echo This is test-a
$(TARGET)-b:
#echo This is test-b
else
$(info FOO is disabled)
endif
When the FOO condition is true, the set of rules based on the TARGET variable (composed of one $(TARGET) and a set of $(TARGET)-substring) work as expected:
$ make test
This is test
$ make test-a
This is test-a
When the FOO condition is false, I want to define a default rule for all my targets, just to report on the screen FOO variable is disabled. I don't know the proper way to do that. Tried some options:
Option1, using the skeleton example, the string "FOO is disabled" is always printed, but it generates an error:
$ make test-a
FOO is disabled
make: *** No rule to make target 'test-a'. Stop.
$ make test
FOO is disabled
make: *** No rule to make target 'test'. Stop.
Option 2, if try to modify the false rule in this way:
else
$(TARGET)-%:
$(info FOO is disabled)
endif
Then all $(TARGET)-substring targets work as expected:
$ make test-a
FOO is disabled
make: 'test-a' is up to date.
$ make test-b
FOO is disabled
make: 'test-b' is up to date.
But this rule fails when making $(TARGET):
$ make test
make: *** No rule to make target 'test'. Stop.
Option 3, if try to remove the hyphen on the false rule defined in option2:
else
$(TARGET)%:
$(info FOO is disabled)
endif
then making $(TARGET) executes a default rule for compiling a test.o object file:
$ make test
FOO is disabled
cc test.o -o test
cc: error: test.o: No such file or directory
cc: fatal error: no input files
compilation terminated.
make: *** [<builtin>: test] Error 1
And I am becoming a little bit crazy trying so satisfy this default rule. Please some help with this would be very useful. Tnks!
There are several ways to solve this, but the simplest is probably just to add another rule:
else
$(TARGET):
#echo FOO is disabled
$(TARGET)%:
#echo FOO is disabled
endif
(I changed $(info ... to #echo ... because the latter will run only when Make execute the rule, while the former will run if the conditional defines those rules, even if the target is something else.)
EDIT: Yes, it's possible to solve this with only one rule, there is more than one way, but no perfect way.
Here is one way:
TARGET = tes
...
else
$(TARGET)%:
#echo FOO is disabled
endif
Note that the last character of test has been removed. The good news is that this rule will apply to test, test-a and test-b; the bad news is that it will also apply to tesw.
As #MadScientist says, .DEFAULT rule fixes the issue when you have a single Makefile. So this would be the final Makefile:
TARGET = test
ifeq ($(FOO),y)
$(TARGET):
#echo This is test
$(TARGET)-a:
#echo This is test-a
$(TARGET)-b:
#echo This is test-b
endif
.DEFAULT:
#echo This is the default rule
Thanks a lot for your help!

.EXPORT_ALL_VARIABLES works only when made 'phony'

The docs provides:
'.EXPORT_ALL_VARIABLES'
Simply by being mentioned as a target, this tells 'make' to export
all variables to child processes by default. *Note Communicating
Variables to a Sub-'make': Variables/Recursion.
However, the following makefiles show that only by making .EXPORT_ALL_VARIABLES a phony target, then and only then, will it have the desired effect on the makefile, i.e. to export ALL variables.
Makefile(version 1) is:
ifeq "$(MAKELEVEL)" "0"
foo=bar
.DEFAULT:;
all: .EXPORT_ALL_VARIABLES
#$(MAKE)
else
all:
#echo 'foo is: $(foo)'
endif
Running, we get:
make[1]: Entering directory '/home/myname'
foo is:
make[1]: Leaving directory '/home/myname'
Makefile(version 2) is:
ifeq "$(MAKELEVEL)" "0"
foo=bar
.DEFAULT:;
all: .EXPORT_ALL_VARIABLES
#$(MAKE)
# This line is added in THIS version.
.PHONY: .EXPORT_ALL_VARIABLES
else
all:
#echo 'foo is: $(foo)'
endif
Running, we get:
make[1]: Entering directory '/home/myname'
foo is: bar
make[1]: Leaving directory '/home/myname'
Now, the only difference between these 2 versions of makefile, is that in the 2nd version, the .EXPORT_ALL_VARIABLES was made phony.
Why is the 'phoniness' needed in order to work?
Simply by being mentioned as a target,
Why is the 'phoniness' needed in order to work?
It's not. You didn't declare .EXPORT_ALL_VARIABLES as a target, you declared it as a prerequisite:
all: .EXPORT_ALL_VARIABLES
That's a prerequisite, not a target. If you declare it as a target:
.EXPORT_ALL_VARIABLES:
then it will work and you won't have to declare it phony.
A more accurate question would be, why does declaring .EXPORT_ALL_VARIABLES as phony work even though it's not declared as a target? It happens because things that are marked phony are assumed to be targets even if they're not explicitly mentioned as such. That may or may not be a bug, depending on how you interpret the intent of .PHONY.
Your questions recently seem to follow a pattern: read the documentation, then write a makefile that does something similar to but not the same as what the documentation says, observe it doesn't work as described, then ask why not.

ifndef include guard in Gnu Make breaks on nested conditional

I’m trying to implement include guards in Gnu Make. In this Makefile, the first inclusion is OK, while the second one fails with an error.
ifndef INCLUDED
INCLUDED = 1
$(info Including)
define macro
ifneq ($(1),)
define inner_macro
macro content...
endef
else
define inner_macro
endef
endif
endef
endif
The same effect can be simulated by explicitly giving INCLUDED = 1 before the inclusion, e.g. on command line.
Gnu Make 4.1 under Gentoo says Makefile:14: *** missing separator. Stop., while Gnu Make 3.81 under Debian Wheezy says Makefile:14: *** extraneous `endef'. Stop.. On the first inclusion, they both say:
Including
make: *** No targets. Stop.
If I try $(eval $(call macro,whatever)) after the first inclusion, it defines inner_macro as expected.
I used make INCLUDED=1 and make commands respectively to get the described behavior.
The same happens when I clear the environment and disable built-in rules and variables: env -i make -rR INCLUDE=1. When I use -p to dump the database, without INCLUDED=1, the macro is defined as it should be, but with INCLUDED=1, empty inner_macro is defined. This is consistent across both the versions of Make. This hints me that when the condition is false, Make parses the Makefile differently and thinks the else inside macro’s definition belongs to the ifndef. Other condition types behave all the same.
If I remove both the definitions of inner_macro, the problem goes away.
I read the manual pages info make conditional\ syntax and info make multi-line (formerly defining), but I found no caveat there and I still think I am doing nothing wrong.
Am I correct with my conclusions?
Is this a bug in Make, or am I invoking undefined behavior?
How should I implement include guards in Gnu Make?
That's a bug. Report it on Savannah.
There's something wrong with the tracking of nested define/endef inside a not-taken ifdef/ifndef condition. If you don't use nested define/endef then it works; for example (obviously you may not be able to do this in your environment):
ifndef INCLUDED
INCLUDED = 1
$(info Including)
define macro
ifneq ($(1),)
inner_macro = macro content...
else
inner_macro =
endif
endef
endif

How to use ifeq inside of a define in GNU Make?

I'm trying to do an ifeq inside of a define within a Makefile, but I seem to be running into some errors, and I'm wondering if I'm missing something. I have the following Makefile:
$(info ---- start ----)
ifeq ("X","Y")
$(info DOES not appear_1)
endif
define TESTDEF
ifeq ("X","Y")
$(info SHOULD not appear)
# $(error DEFINITELY SHOULD not error...)
endif
endef
$(eval $(call TESTDEF, 1,2,3))
I'm getting the following error:
---- start ----
SHOULD not appear
Makefile:14: *** DEFINITELY SHOULD not error.... Stop.
Is there some trick that I'm missing? Is it possible to do ifeq's inside define? (note: this happens on both my native GNU 3.81 make, and on my mips uclibc cross-compiler)
When you call this function, Make evaluates the definition, using whatever parameters you provide (irrelevant in this case). So if the definition includes something like $(info ...) or $(error ...), even in a comment, Make will evaluate it and you'll see the result (see documentation; I've tested it in GNUMake 3.81).
To get the behavior you want, add a couple of dollar signs:
define TESTDEF
ifeq ("X","Y")
$$(info SHALL not appear)
# $$(info DEFINITELY SHALL not error...)
endif
endef
$(eval $(call TESTDEF))

Makefile calling other makefile with target, gives wrong target

I have a makefile with a target clean:
clean:
$(MAKE) -f <other makefile location> clean
When I call make clean in this makefile, it tells me that in the other makefile there is no rule 'clean-linux'. Specifically here is the output.
make -f /root/ovaldi-5.5.25-src/project/linux/Makefile clean
make[1]: Entering directory '/root/ovaldi-5.5.25-src'
make[2]: Entering directory '/root/ovaldi-5.5.25-src'
make[2]: *** No rule to make target 'clean-linux'. Stop.
make[2]: Leaving directory '/root/ovaldi-5.5.25-src'
make[1]: Leaving directory '/root/ovaldi-5.5.25-src'
Why is it giving it the clean-linux target and not just clean like I specified?
When you make (or $(MAKE)), by default you use whatever makefile is there. So here's what I think is happening.
You cd to some location.
You 'make -f Makefile_A clean'.
make runs with Makefile_A, and does '$(MAKE) -f Makefile_B clean'.
make[1] runs with Makefile_B, and does '$(MAKE) clean-linux'.
make[2] runs with whatever makefile is here which might be anything (I suspect it's Makefile_A) but whatever it is it has no rule for clean-linux.
The solution: rewrite your second makefile (the one that has clean-linux) so that clean-linux becomes a prerequisite of clean (if/when you're on a linux system). That way it won't run make[2].
ifeq ($(PLATFORM), LINUX)
clean: clean-linux
endif
ifeq ($(PLATFORM), SUNOS)
clean: clean-sunos
endif
clean:;
Just a guess but maybe the 'clean' target in the second makefile calls 'clean-linux'?
Can you post the clean target of the second makefile?
Edit:
In light of your posted clean target it seems you're just calling the clean-linux target incorrectly.
Beta has posted the correct way of dealing with your problem in their answer so I'm going to +1 that.

Resources