Checking valid values for overriden variables in GNU Make - makefile

What I'm interested in doing is checking the value of a variable that's been overridden on the command-line has one of several valid values. Specifically, I have part of my makefile that looks like so:
ifndef BUILD
BUILD ::= release
endif
# Set flags for $(CC), based on the value of $(BUILD)
CFLAGS ::= -Wall -pthread -std=c17 -I./src/include
CFLAGS.debug ::= -g
CFLAGS.release ::= -O3
CFLAGS ::= $(CFLAGS.$(BUILD)) $(CFLAGS)
My goal is to be able to run something like make BUILD=debug without having to write a seperate debug target and adding a bunch of mess to my recipies.
What I want is a way to do something like $(warn Invalid BUILD option) if something like make BUILD=production is specified. Is there a better/more idiomatic way of doing this than just nesting a bunch of ifneq?

Well, something like this will work:
BUILD ?= release
OK_BUILDS := debug release
$(if $(filter $(BUILD),$(OK_BUILDS)),,$(warn Invalid BUILD option: $(BUILD))

Related

Enabling make to expand on user-supplied macro

Is there a way to convince make to accept a user-supplied macro and then expand on it? One simple example of what I mean is that I would like the user to be able to issue:
> make CFLAGS='-g'
and then somewhere in Makefile would be a line like:
CFLAGS := $(CFLAGS) -O2
so that, altogether CFLAGS would evaluate to -g -O2. AFAICT, if the user issues that command, make takes CFLAGS as -g and won't touch it further. Of course, I could have the user issue something like
> make CFIN='-g'
and then in the Makefile put
CFLAGS := $(CFIN) -O2
but that just seems a bit "clunky" to me.
Do this:
CFLAGS :=
override CFLAGS := $(CFLAGS) -O2
The first assignment prevents the contents of CFLAGS environment variable from being imported as the Make variable with the same name. If you want this behavior, remove the first line.

Should CXXFLAGS, CPPFLAGS and LDFLAGS be overridden in a Makefile?

Situation
I am using a handwritten GNUmakefile in which CXXFLAGS, CPPFLAGS and LDFLAGS are appended to by the += assignment, as in:
CXXFLAGS += -std=c++11 $(MODENV) $(WARNINGS) $(OPTIMS)
CPPFLAGS += $(DMACROS) $(INCDIRS)
LDFLAGS += $(MODENV) $(LIBDIRS) $(EXTRA_LIBS)
Problem
When the user defines his own flags at the command-line, the appending in the Makefile will be ignored. This leaves the variables to exactly what the user set them. (And in my case, the build will fail.) The generic solution for this problem is to override the variables, as in:
override CXXFLAGS += -std=c++11 $(MODENV) $(WARNINGS) $(OPTIMS)
override CPPFLAGS += $(DMACROS) $(INCDIRS)
override LDFLAGS += $(MODENV) $(LIBDIRS) $(EXTRA_LIBS)
This way, the necessary content will be appended to the user's variable.
Questions
Is overriding variables considered bad practice?
Is setting the above flags inside the Makefile considered bad practice?
If "yes" to both questions above, then where do I put -std=c++11, if not in CXXFLAGS?
If a user overrides the variables, assume the user knows what they're doing. The system they're working on may have very different requirements and need to override the variables. Don't make the difficult impossible.
I'm not going to comment on good/bad practice, as it depends on the use case (who are the users of this makefile? Do they all work with you, so you could just tell them how it's meant to be used? Can you just document your conventions, in the makefile comments or a project README?)
But ...
where do I put -std=c++11, if not in CXXFLAGS?
If you have flags that are essential and must not be overridden by the user, you can find another way to put them in the compilation command.
For example:
CXX := g++ -std=c++11
Or:
foo.o: foo.cc
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -std=c++11 -o $# $^
Even better might be to use a variable so that it can still be set by the user (e.g. to use -std=c++14 instead) but isn't set by CXXFLAGS:
STD := -std=c++11
foo.o: foo.cc
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(STD) -o $# $^
(Or just put the -std option before CXXFLAGS so that a different std option in CXXFLAGS will take precedence).
As the use cases for build management with make are so diverse, there simply is no ground to objectively judge this as "bad practice". If you are writing OSS for a multitude of platforms and for an unknown audience and timeframe, the POLA/POLS should be applied. That said, a user who is surprised to find more flags than the ones she gave on the command line is a rather unrealistic corner case, so -override has its place. In the end the values you add are absolutely necessary for the build, aren't they?
PS: the POLA should be applied to all engineering activities of course - its just that the definition of "surprise" shifts with the task in question.

Allowing users to override CFLAGS, CXXFLAGS and friends

Typical makefiles often use the built-in variables CFLAGS, CXXFLAGS, CPPFLAGS and so on1 to set the flags passed to the C, C++ or other compilers/tools. In principle, this sometimes even lets you avoid writing a compilation recipe entirely since the various built-in rules use these flags.
In general, a makefile might add things to the FLAGS variables that are required for the code to compile, such as include directories, arguments indicating which language standard to use and so on. The variables might also include "optional" or "default" arguments, such as optimization level, warning level and other settings that might validly be altered or removed.
Since CFLAGS and fields are "well known" variables, they are also apparently a configuration point for end users. For example, if a project compiles without debug information by default, it is expected that CFLAGS=-g on the make command line causes -g to be added to the $(CC) compiler command line and hence cause debug info to be produced. Similarly for other options the end user might want to control, such as the optimization level, the -march setting on gcc, and so on.
However, these two uses seem incompatible to me. If the user overrides $(CFLAGS) they will obliterate any internal "required" flags as described above, and the project either may not compile or may compile incorrectly.
Is there a best practice for handling this? The same problem doesn't really arise for "single value" variables like $(CC) since they generally have exactly one value: in this example, the C compiler to use. If the user overrides it, you use their value. Things like $(CFLAGS) are in principle a list of values, some of which are internal and shouldn't be overridden, an others which a user may want to override.
Intuitively, a solution seems to be to leave $(CFLAGS) and friends empty and unused in your makefile, preferring say CFLAGS_INTERNAL for in-makefile arguments, and then put both on the command line. I'm curious, however, if there is a best practice around this or if I'm missing something obvious.
1 For the rest of this question I will often simply refer to $(CFLAGS) with the understanding that this is simply a convenient representative of the whole family of well known compiler flag variables such as $(CPPFLAGS), $(CXXFLAGS) and so on.
I am faced with the same problem. For the time being my solution is to provide "non-standard" flags such as OPTIMS, WARNINGS, MODENV which will be appended to the "standard" CXXFLAGS internally.
If the user defines CXXFLAGS from the command-line it is assumed that he wants to override it, and if that's what he wants, that's what he should get: an override. Ironically this means I'm not using override CXXFLAGS += ... in the Makefile.
I don't want advanced users to pull their hairs out because I insist on appending/prepending my stuff to their flags, so in my opinion the final situation is like this:
GOOD: require advanced users to pass intricate custom flags
BAD: require advanced users to patch the Makefile
Just stumbled upon the same question while building an RPM with debuginfo package.
The requirement for debuginfo generation is to pass -g in CFLAGS while preserving whatever CFLAGS the software has in its Makefile.
So if you want to add some extra bits to CFLAGS, without overwriting the ones present in Makefile, you can simply use CFLAGS as an environment variable. But only as long as the Makefile in question uses CFLAGS += ... notation.
Example, suppose that you have software with Makefile having:
CFLAGS += $(ARCH) -O3 -std=gnu11 -Wall ...
To have it build with all those flags and -g, you will do:
CFLAGS='-g' make
Note that passing it as an argument to make won't work, as in: make CFLAGS='-g' is wrong, because it will overwrite internal CFLAGS.
More on the solution to pass -g for building debuginfo packages properly
Here's reference on make: appending to variables.
The approach I prefer is to provide sensible default values to these common variables, but let users provide their own - overriding the default values.
include $(wildcard makefile.in Makefile.in)
BUILD ?= build
CFLAGS ?= -O2 -fPIC -pedantic -Wall -Wextra -Wconversion
This can be done by either environment variables, command line parameters like make CFLAGS=-g or persistently in a makefile.in.
I am aware that this doesn't exactly pick up the issue you described in the questions, but I found use cases in which users want to compile a project with non-default flags should be able to
Define these variables to their needs
Check their defaults, preferably at the top of the makefile
Maybe adjust the definitions in accordance to the defaults
If someone wants to build with some special flags and is incapable of these steps, there will be some more serious problems anyhow.
This approach will not scale well when the build becomes more involved and the defaults are set across a larger makefile and dependent on other conditions.
The override directive may be what you are looking for:
$ cat Makefile
override CFLAGS += -foobar
all:
$(info CFLAGS = $(CFLAGS))
$ make
CFLAGS = -foobar
make: 'all' is up to date.
$ make CFLAGS=-g
CFLAGS = -g -foobar
make: 'all' is up to date.
Note that you can also use:
$ make CFLAGS+=-g
on the command line but it behaves just like:
$ make CFLAGS=-g

Make: Override a flag

I was a little confused with the responses to Quick way to override -Werror flag?
So I ask my specific question here.
I have multiple Makefiles working together and CFLAGS has been set along the way to (-Werror -Wall .. and many others)
But in one of the Makefiles, I wish that the errors not be treated as warnings and so I would like to remove -Werror flag.
What would be the best way to achieve this, so that only for this Makefile, -Werror flag is removed and for the others normal execution takes place?
Thanks,
Sunny
The right way to do this is with the filter-out function.
Put
CFLAGS := $(filter-out -Werror,$(CFLAGS))
in the Makefile where you want to override this, and the -Werror part of CFLAGS will be removed in that Makefile.
You can even use this to override flags for a single target by using target-specific variable values:
CFLAGS = -Werror
all: foo bar
foo:
echo cc $(CFLAGS) -o $#
bar: CFLAGS := $(filter-out -Werror,$(CFLAGS))
bar:
echo cc $(CFLAGS) -o $#
foo will be built with the default CFLAGS containing -Werror, but bar will be built without.
This is a general-purpose solution that works for all arguments to all programs, rather than requiring each program to supply a --no-foo for every --foo option.
Because it can’t be done from Make command-line, it doesn’t directly answer the question you linked to. But overriding Make variables from the command-line to force stuff to build is a pretty good way to make your unbuildable code even less maintainable!
Simpler way
It looks like you can invoke
gcc -c ... -Werror ... -Wno-error ...
without having GCC complain (GCC 4.7.1). So, you can add -Wno-error to the CFLAGS set up elsewhere in the one makefile where you need it. If you're using GNU make, in the one makefile, you can add:
CFLAGS += -Wno-error
possibly for just the single target that needs it.
Harder way
Otherwise, you need a system for building CFLAGS from components. What I have in the makefile I use for testing answers to questions on SO is:
WFLAG1 = -Wall
WFLAG2 = -Wextra
WFLAG3 = -Wmissing-prototypes
WFLAG4 = -Wstrict-prototypes
WFLAG5 = -Wold-style-definition
WFLAG6 =
WFLAGS = ${WFLAG1} ${WFLAG2} ${WFLAG3} ${WFLAG4} ${WFLAG5} ${WFLAG6}
SFLAGS = -std=c99
GFLAGS = -g
OFLAGS = -O3
UFLAGS =
IFLAG1 = -I${HOME}/inc
IFLAGS = # ${IFLAG1}
CFLAGS = ${OFLAGS} ${GFLAGS} ${IFLAGS} ${SFLAGS} ${WFLAGS} ${UFLAGS}
The main point is that each flag is independently adjustable; I can control the warning flags by setting any of ${WFLAG1} to ${WFLAG6}, or by setting ${WFLAGS} wholesale on the command line, or (indeed) by setting ${CFLAGS}. But because each is individually adjustable, and can tune the warnings relatively easily (the main hassle being determining which WFLAGn needs clobbering).
The UFLAGS is 'user flags' and is only set on the command line; I can add more flags to my command line by setting it.
This way is 'harder' because it requires you to modify the central part of your makefile system where you set CFLAGS. It is also less likely to be understood by your colleagues at first sight.
You can see an example of variable overriding in Git Makefile with CFLAGS which now can be tweaked when invoking Make while using DEVELOPER=YesPlease, with Git 2.22 (Q2 2019)
DEVELOPER (in Git Makefile) is a variable to group more compiler warning.
See commit 6d5d4b4, commit 71a7894, commit 8fb2a23, commit 65260a4, commit 9559f8f, commit 4f14a8c (22 Feb 2019) by Ævar Arnfjörð Bjarmason (avar).
(Merged by Junio C Hamano -- gitster -- in commit 3cef676, 20 Mar 2019)
Makefile: allow for combining DEVELOPER=1 and CFLAGS="..."
Ever since the DEVELOPER=1 facility introduced there's been no way to have custom CFLAGS (e.g. CFLAGS="-O0 -g -ggdb3") while still benefiting from the set of warnings and assertions DEVELOPER=1 enables.
This is because the semantics of variables in the Makefile are such
that the user setting CFLAGS overrides anything we set, including what
we're doing in config.mak.dev.
So let's introduce a "DEVELOPER_CFLAGS" variable in config.mak.dev and
add it to ALL_CFLAGS. Before this the ALL_CFLAGS variable
would (basically, there's some nuance we won't go into) be set to:
$(CPPFLAGS) [$(CFLAGS) *or* $(CFLAGS) in config.mak.dev] $(BASIC_CFLAGS) $(EXTRA_CPPFLAGS)
But will now be:
$(DEVELOPER_CFLAGS) $(CPPFLAGS) $(CFLAGS) $(BASIC_CFLAGS) $(EXTRA_CPPFLAGS)
The reason for putting DEVELOPER_CFLAGS first is to allow for
selectively overriding something DEVELOPER=1 brings in.
On both GCC and Clang later settings override earlier ones.
E.g. "-Wextra -Wno-extra" will enable no "extra" warnings, but not if those two
arguments are reversed.
Examples of things that weren't possible before, but are now:
# Use -O0 instead of -O2 for less painful debuggng
DEVELOPER=1 CFLAGS="-O0 -g"
# DEVELOPER=1 plus -Wextra, but disable some of the warnings
DEVELOPER=1 DEVOPTS="no-error extra-all" CFLAGS="-O0 -g -Wno-unused-parameter"
The reason for the patches leading up to this one re-arranged the
various *FLAGS assignments and includes is just for readability.
The Makefile supports assignments out of order, e.g.:
$ cat Makefile
X = $(A) $(B) $(C)
A = A
B = B
include c.mak
all:
#echo $(X)
$ cat c.mak
C=C
$ make
A B C

CFLAGS vs CPPFLAGS

I understand that CFLAGS (or CXXFLAGS for C++) are for the compiler, whereas CPPFLAGS is used by the preprocessor.
But I still don't understand the difference.
I need to specify an include path for a header file that is included with #include -- because #include is a preprocessor directive, is the preprocessor (CPPFLAGS) the only thing I care about?
Under what circumstances do I need to give the compiler an extra include path?
In general, if the preprocessor finds and includes needed header files, why does it ever need to be told about extra include directories? What use is CFLAGS at all?
(In my case, I actually found that BOTH of these allow me to compile my program, which adds to the confusion... I can use CFLAGS OR CPPFLAGS to accomplish my goal (in autoconf context at least). What gives?)
The implicit make rule for compiling a C program is
%.o:%.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $# $<
where the $() syntax expands the variables. As both CPPFLAGS and CFLAGS are used in the compiler call, which you use to define include paths is a matter of personal taste. For instance if foo.c is a file in the current directory
make foo.o CPPFLAGS="-I/usr/include"
make foo.o CFLAGS="-I/usr/include"
will both call your compiler in exactly the same way, namely
gcc -I/usr/include -c -o foo.o foo.c
The difference between the two comes into play when you have multiple languages which need the same include path, for instance if you have bar.cpp then try
make bar.o CPPFLAGS="-I/usr/include"
make bar.o CFLAGS="-I/usr/include"
then the compilations will be
g++ -I/usr/include -c -o bar.o bar.cpp
g++ -c -o bar.o bar.cpp
as the C++ implicit rule also uses the CPPFLAGS variable.
This difference gives you a good guide for which to use - if you want the flag to be used for all languages put it in CPPFLAGS, if it's for a specific language put it in CFLAGS, CXXFLAGS etc. Examples of the latter type include standard compliance or warning flags - you wouldn't want to pass -std=c99 to your C++ compiler!
You might then end up with something like this in your makefile
CPPFLAGS=-I/usr/include
CFLAGS=-std=c99
CXXFLAGS=-Weffc++
The CPPFLAGS macro is the one to use to specify #include directories.
Both CPPFLAGS and CFLAGS work in your case because the make(1) rule combines both preprocessing and compiling in one command (so both macros are used in the command).
You don't need to specify . as an include-directory if you use the form #include "...". You also don't need to specify the standard compiler include directory. You do need to specify all other include-directories.
You are after implicit make rules.
To add to those who have mentioned the implicit rules, it's best to see what make has defined implicitly and for your env using:
make -p
For instance:
%.o: %.c
$(COMPILE.c) $(OUTPUT_OPTION) $<
which expands
COMPILE.c = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
This will also print # environment data. Here, you will find GCC's include path among other useful info.
C_INCLUDE_PATH=/usr/include
In make, when it comes to search, the paths are many, the light is one... or something to that effect.
C_INCLUDE_PATH is system-wide, set it in your shell's *.rc.
$(CPPFLAGS) is for the preprocessor include path.
If you need to add a general search path for make, use:
VPATH = my_dir_to_search
... or even more specific
vpath %.c src
vpath %.h include
make uses VPATH as a general search path so use cautiously. If a file exists in more than one location listed in VPATH, make will take the first occurrence in the list.
I installed httpd on Ubuntu 18.04 using the CPPFLAGS variable for the -DLINUX flag. When run, CPPFLAGS scans the code from top to bottom, file by file, looking for directives before compiling, and will not be extended by other meaningful things like size optimization, flags that do not increase the size of the output file; under the type of processor; to reduce the size of the code and speed up the program; disable all variables except case. The only difference between CPPFLAGS and CFLAGS is that CFLAGS can be set to specify additional switches to be passed to the compiler. That is, the CFLAGS environment variable creates a directory in the installation path (eg CFLAGS=-i/opt/include) to add debugging information to the executable target's path: include general alarm messages; turning off alarm information; independent location generation; display compiler driver, preprocessor, compiler version number.
Standard way to set CPPFLAGS:
sudo ./configure --enable-unixd=DLINUX #for example
list of some known variables:
CPPFLAGS - is the variable name for flags to the C preprocessor.
CXXFLAGS - is the standard variable name for flags to the C++ compiler.
CFLAGS is - the standard name for a variable with compilation flags.
LDFLAGS - should be used for search flags/paths (-L) - i.e. -L/usr/lib (/usr/lib are library binaries).
LDLIBS - for linking libraries.
CPPFLAGS seems to be an invention of GNU Make, referenced in some of its built-in recipes.
If your program is built by some Free software distributions, you may find that some of them require packages to interpolate this variable, using CPPFLAGS for passing down options like -D_WHATEVER=1 for passing down a macro definition.
This separation is a poor idea and completely unnecessary in the GNU environment because:
There is a way to run gcc to do preprocessing only (while ignoring compiler options unrelated to preprocessing).
The stand-alone GNU cpp is tolerant to compiler options, such as -W warnings that do not pertain to preprocessing and even code generation options like -fstrict-aliasing and the linker-pass through like -Wl,--whatever.
So generally speaking, build systems that need to call the stand-alone preprocessor for whatever reason can just pass it $(CFLAGS).
As an application developer writing a Makefile, you cannot rely on the existence of CPPFLAGS. Users who are not insider experts in open source building won't know about CPPFLAGS, and will do things like make CFLAGS=-Dfoo=bar when building your program. If that doesn't work, they will be annoyed.
As a distro maintainer, you cannot rely on programs to pull in CPPFLAGS; even otherwise well-behaved ones that pull in CFLAGS, LDFLAGS and LDLIBS.
It's easy enough for the application developers to write GNU Make code to separate preprocessor flags out of $(CFLAGS):
cpp_only_flags := $(foreach arg, \
$(CFLAGS), \
$(or $(filter -D%,$(arg)), \
$(filter -U%,$(arg)), \
$(filter -I%,$(arg)), \
$(filter -iquote%,$(arg)), \
$(filter -W%,$(arg)), \
$(filter -M%,$(arg)))) \
$(CPPFLAGS) # also pull this in
all:
#echo cpp_only_flags == $(cpp_only_flags)
Demo:
$ make CFLAGS="-Wall -I/path/to/include -W -UMAC -DFOO=bar -o foo.o -lm"
cpp_only_flags == -Wall -I/path/to/include -W -UMAC -DFOO=bar
In the case of the GNU compiler and preprocessor, this is probably unnnecessary; but it illustrates a technique that could be used for non-GNU compilers and preprocessors, in a build system based on GNU Make.

Resources