Wednesday, 17 August 2022
Handling CFLAGS
One question that comes up quite often is, “What is the right way of handling CFLAGS within autosetup (also autoconf, automake, etc.) and the Makefiles for the best user experience?” – stack overflow. automake spends some time on this – 26.6 Flag Variables Ordering however it doesn’t answer all the questions that we might want answered. Below is my attempt to untangle the confusion and provide a canonical best way to handle CFLAGS with autosetup and make (or similar).
One fundamental consideration is that CFLAGS (and CPPFLAGS, CXXFLAGS) are all user-specified and the user’s wishes should be respected above all else. This means that overwriting the user’s settings by changing CFLAGS or adding additional flags after the user’s settings is not acceptable. With this in mind, let’s look at all the points where flags to the C compiler could be specified.
First the user can provide flags in the environment or on the command
line to configure
that we will name (1) and (2).
$ CFLAGS=-Dd1 ./configure CFLAGS=-Dd2
Next the user can provide flags in the environment or on the commandline to make or equivalent that we will name (3) and (4).
$ CFLAGS=-Dd3 make CFLAGS=-Dd4
Finally if no explicit CFLAGS are provided by the user, use -g -O2
, which we will name (0).
In addition, configure
may wish to add additional flags based on configure
tests or user
options, but these should not affect CFLAGS.
# If supported, add this flag to compiler flags cc-check-flags -std=c99
And also we may we wish to add to CFLAGS within the makefile based on settings. (This example could have been done in auto.def instead - it is just indicative). Simlarly this should not affect the user’s CFLAGS settings.
ifeq(@SHARED@,1) # Except we don't want to append to CFLAGS CFLAGS += -dynamic endif
Now what behaviour provides the best user experience? It is reasonable for (2) to override (1), especially as CFLAGS may simply be set in the environment, so the explicit setting on the command line should take precedence. Similarly, (4) should override (3) for similar reasons. It is also clear that (4) should take precedence over (1) and (2) as this is a later phase. One thing that is not entirely clear is whether (3) should override (2). For simplicity we will say it should as otherwise we would need to distiguish beteen (1) and (2) in the Makefile. Finally, for CFLAGS all of (1) - (4) should override (0). Now as we said that the user’s selection is the highest precedence, our command line should clearly be.
cc <other-flags> $CFLAGS -c ... -o ...
With the user’s selection last, overriding other flags that we will discuss shortly, and where CFLAGS is defined as below, where the notation means that the first value that is set is chosen.
CFLAGS := (4) || (3) || (2) || (1) || (0)
Note that CPPFLAGS can be specified by the user in all the same ways, except that there is no default if not specified. The ordering between CFLAGS and CPPFLAGS is not clear, but we may as well follow the gnu make approach:
# default COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
So then we have:
cc <other-flags> $CFLAGS $CPPFLAGS -c ... -o ...
Now we only need to decide how configure-derived flags are set and made available to make.
While most autosetup checks don’t do anything with CFLAGS by default (it is up to the auto.def
developer to make the flags available), cc-check-flags
unfortunately (as of autosetup 0.7.0)
modifieds CFLAGS. This is not OK as CFLAGS is a user value, and should not be touched by autosetup.
Therefore, as of autosetup 0.7.1, cc-check-flags
now adds to AS_CFLAGS instead (autosetup CFLAGS).
This makes the recommendation of how to handle CFLAGS in auto.def simple. All flags are added to AS_CFLAGS
(or AS_CPPFLAGS or AS_CXXFLAGS). e.g.
# Adds to AS_CFLAGS cc-check-flags -std=c99 # Add profiling if selected if {[opt-bool profiling]} { define-append AS_CFLAGS -pg }
And finally we need to determine how flags can be added in the Makefile. It is simplest if we use the same AS_CFLAGS as autosetup. e.g.
ifeq(@SHARED@,1) AS_CFLAGS += -dynamic endif
Now the only remaining challenge is to ensure that the Makefile implements our desired command line:
cc $(AS_CFLAGS) $(AS_CPPFLAGS) $CFLAGS $CPPFLAGS -c ... -o ...
We could start by trying this:
CFLAGS += @AS_CFLAGS@ @AS_CPPFLAGS@ @CFLAGS@ @CPPFLAGS@
However this does not work as these will take precedence over the user flags. As there is no way to prepend flags, we could try the cumbersome:
override CFLAGS := @AS_CFLAGS@ @AS_CPPFLAGS@ $(CFLAGS)
But my preference is for the simpler solution to either take over the implicit rule entirely, or replace the compile line. Therefore in gnu make we can do:
# Use configure versions if not set during make CFLAGS ?= @CFLAGS@ CPPFLAGS ?= @CPPFLAGS@ # ensure we get the ordering we need COMPILE.c = $(CC) @AS_CFLAGS@ @AS_CPPFLAGS@ $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
(Note that we could changed to CFLAGS = @CFLAGS@
if we wanted to ignore (3))
Alternatively replace the rule entirely:
# This may be gnu make only %.o: %.c $(CC) @AS_CFLAGS@ @AS_CPPFLAGS@ $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ # This may work better for bsd make .c.o: $(CC) @AS_CFLAGS@ @AS_CPPFLAGS@ $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
This is implemented in examples/typical/Makefile.in If you have a suggestion why one Makefile approach is better than the others, please make a comment in Discussions.