Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic Makefile variable assignment

Tags:

makefile

I have the following Makefile and I want to create "debug" and "optimal" targets which affect the values in CPPFLAGS and CFLAGS, like so:

include Makefile.inc

DIRS    = applib
EXE_APPFS       = appfs
EXE_APPMOUNT    = appmount
EXE_APPINSPECT  = appinspect
EXE_APPCREATE   = appcreate
BUILD_APPFS     =
BUILD_APPMOUNT  = -DAPPMOUNT
OBJS_APPFS      = main.o appfs.o
OBJS_APPMOUNT   = main.o appmount.o
OBJS_APPINSPECT = appinspect.o
OBJS_APPCREATE  = appcreate.o
OBJLIBS = libapp.a
LIBS    = -L. -lpthread -lstdc++ -ldl -lrt -largtable2 -lm ./libapp.a     /usr/lib64/libfuse.a

# Optimization settings.
debug: CPPFLAGS=$(CPPFLAGS_DEBUG)
debug: CFLAGS=$(CFLAGS_DEBUG)
debug:
    @true

optimal: CPPFLAGS=$(CPPFLAGS_OPTIMAL)
optimal: CFLAGS=$(CFLAGS_OPTIMAL)
optimal:
    @true

appfs: appfs.o $(OBJLIBS)
    @echo "stuff is done here"

appmount: appmount.o $(OBJLIBS)
    @echo "stuff is done here"

appmount_optimal: optimal appmount

The problem I'm having is that the variable assignments inside "debug" and "optimal" don't carry over to other targets (though if I put @echo $(CPPFLAGS) inside optimal that works). Neither doing "make optimal appmount" nor "make appmount_optimal" gets me the results I'm expecting.

Surely there is a way to define CPPFLAGS and CFLAGS depending on whether you want debugging or not, right?

like image 522
June Rhodes Avatar asked May 23 '26 23:05

June Rhodes


2 Answers

If you are using GNU make, you have two options (in addition to the recursive make invocation, which has the problems outlined above).

The first option is to use target-specific variables. You are using them in your original example:

debug: CPPFLAGS=$(CPPFLAGS_DEBUG)
debug: CFLAGS=$(CFLAGS_DEBUG)

optimal: CPPFLAGS=$(CPPFLAGS_OPTIMAL)
optimal: CFLAGS=$(CFLAGS_OPTIMAL)

The thing you're missing is that target-specific variables are inherited by their prerequisites. So you need to declare prerequisites of "debug" and "optimal" (they don't have to have recipes themselves; in fact, they can be declared .PHONY). So for example:

debug: CPPFLAGS=$(CPPFLAGS_DEBUG)
debug: CFLAGS=$(CFLAGS_DEBUG)
debug: appfs appmount

optimal: CPPFLAGS=$(CPPFLAGS_OPTIMAL)
optimal: CFLAGS=$(CFLAGS_OPTIMAL)
optimal: appfs appmount

Now if you run "make debug" it will build both appfs and appmount with the debug settings for CPPFLAGS and CFLAGS; if you run "make optimal" it will use the optimal settings.

However this has the same downside as the recursive invocation of make; if you run "make appfs" directly then NEITHER of the settings will be used; target-specific variables are inherited from the parent(s) that led to the target being built in this invocation of make. If neither of those targets are in the parent target list then their target-specific variables won't be used.

The second option, which gives you pretty much exactly the interface you were looking for, is to use the MAKECMDGOALS variable to decide whether the user asked for optimal or debug builds. For example something like this:

CPPFLAGS_debug = <debug CPPFLAGS>
CFLAGS_debug = <debug CFLAGS>

CPPFLAGS_optimal = <optimal CPPFLAGS>
CFLAGS_optimal = <optimal CFLAGS>

STYLE := $(firstword $(filter debug optimal,$(MAKECMDGOALS)))
$(if $(STYLE),,$(error No style "debug" or "optimal" set))

CPPFLAGS = $(CPPFLAGS_$(STYLE))
CFLAGS = $(CFLAGS_$(STYLE))

debug optimal:
.PHONY: debug optimal

Or if you prefer you can choose a default behavior if one isn't given, instead of throwing an error; this chooses "debug" by default for example:

STYLE := $(firstword $(filter optimal,$(MAKECMDGOALS)) debug)

However, it's important to note that the reason this seems tricky to do is that what you're asking for is inherently flawed. Suggesting that a single derived file in a directory should be built in one of two different ways based on a build-time parameter is asking for trouble. How do you know which variation was used last? Suppose you run make with optimization, then you modify some files, then you run make again this time with debug... now some of your files are optimized and some are debug.

The right way to handle different build variations of code is to ensure that the derived files are unique. This means that the debug targets are written to one directory, and the optimized targets are written to a different directory. Once you make this distinction the rest of it falls out easily: you simply use the debug flags when you write the rules for the debug targets and the optimal flags when you write the rules for the optimized targets.

like image 62
MadScientist Avatar answered May 26 '26 11:05

MadScientist


One ghastly but moderately effective technique is:

# Optimization settings.
debug:
    +$(MAKE) CPPFLAGS="$(CPPFLAGS_DEBUG)" CFLAGS="$(CFLAGS_DEBUG)"

optimal:
    +$(MAKE) CPPFLAGS="$(CPPFLAGS_OPTIMAL)" CFLAGS="$(CFLAGS_OPTIMAL)"

This means that make debug reinvokes make with the debug flags set on the command line, and make optimal reinvokes make with the optimal flags set on the command line.

This is far from perfect; it means the default rule will be run when make is reinvoked.

You could vary this with:

# Optimization settings.
debug:
    +$(MAKE) CPPFLAGS="$(CPPFLAGS_DEBUG)" CFLAGS="$(CFLAGS_DEBUG)" debug_build

optimal:
    +$(MAKE) CPPFLAGS="$(CPPFLAGS_OPTIMAL)" CFLAGS="$(CFLAGS_OPTIMAL)" optimal_build

This now runs two different build targets for the debug build and the optimal build. Other rules, such as clean or depend don't get the special treatment.

The + notation on the command lines is POSIX's way of saying 'run this rule even under make -n', which is probably what you want. The $(MAKE) notation may also achieve the same effect. If your make does not like the + signs, try removing them.


This doesn't let me do 'make debug appmount' to build the appmount target in debug mode though does it? It only allows for 'make debug' which builds all the targets in debug.

You're correct; that's the sort of reason why it is not perfect. If you're lucky, someone else will come up with a better solution. There is a slightly devious way to more or less achieve what you want:

BUILD_TARGET = all

# Optimization settings.
debug:
    +$(MAKE) CPPFLAGS="$(CPPFLAGS_DEBUG)" CFLAGS="$(CFLAGS_DEBUG)" $(BUILD_TARGET)

optimal:
    +$(MAKE) CPPFLAGS="$(CPPFLAGS_OPTIMAL)" CFLAGS="$(CFLAGS_OPTIMAL)" $(BUILD_TARGET)

Now when you run make debug, it will rerun and build the all target by default with the debug options. But, you can change that with:

make optimal BUILD_TARGET="appmount totherprog"

This will build the two named targets with the optimal flags. The initial command line is not dreadfully elegant, but it would get you to your destination - just about. Careful choice of defaults and the ability to override them should get you where you need to go.

like image 20
Jonathan Leffler Avatar answered May 26 '26 12:05

Jonathan Leffler



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!