It has once again been ages since the last programming grab bag article was published, so let's dive right into another one, shall we? This time around, we'll be looking at some simple tricks involving GNU make.
1. Let Make Construct Your Object List
One common inefficiency in many Makefiles I've seen is having a manual list of the object files you are interested in building. Let's work with the following example makefile (I realize that this makefile has a number of design issues; it's a simple, contrived example for the sake of this discussion). I've highlighted the list of objects below (line 2):
CFLAGS = -Wall
OBJS = class_a.o class_b.o my_helpers.o my_program.o
all: my_program
my_program: $(OBJS)
gcc -o my_program $(OBJS)
class_a.o: class_a.cpp
gcc $(CFLAGS) -c class_a.cpp
class_b.o: class_b.cpp
gcc $(CFLAGS) -c class_b.cpp
my_helpers.o: my_helpers.cpp
gcc $(CFLAGS) -c my_helpers.cpp
my_program.o: my_program.cpp
gcc $(CFLAGS) -c my_program.cpp
For very small projects, maintaining a list like this is doable, even if it is a bother. When considering larger projects, this approach rarely works. Why not let make do all this work for us? It can generate our list of object files automatically from the cpp
files it finds. Here's how:
OBJS = $(patsubst %.cpp,%.o,$(wildcard *.cpp))
We are using two built-in functions here: patsubst and wildcard. The first function will do a pattern substitution: the first parameter is the pattern to match, the second is the substitution, and the third is the text in which to do the substitution.
Note that, in our example, the third parameter to the patsubst
function is a call to the wildcard
function. A call to wildcard
will return a space separated list of file names that match the given pattern (in our case, *.cpp
). So the resulting string in our example would be: class_a.cpp class_b.cpp my_helpers.cpp my_program.cpp
. Given this string, patsubst
would change all .cpp
instances to .o
instead, giving us (at execution time): class_a.o class_b.o my_helpers.o my_program.o
. This is exactly what we wanted!
The obvious benefit of this technique is that there's no need to maintain our list anymore; make will do it for us!
2a. Use Pattern Rules Where Possible
One other obvious problem in our example makefile above is that all the object targets are identical in nature (only the file names are different). We can solve this maintenance problem by writing a generic pattern rule:
%.o: %.cpp
gcc -c $< -o $@
Pretty ugly syntax, huh? This rule allows us to build any foo.o
from a corresponding foo.cpp
file. Again, the %
characters here are wildcards in the patterns to match. Note also that the command for this rule uses two special variables: $<
and $@
. The former corresponds to the name of the first prerequisite from the rule, while the latter corresponds to the file name of the target of this rule.
Combining this pattern rule with the automatic list generation from tip #1 above, results in the following updated version of our example makefile:
CFLAGS = -Wall
OBJS = $(patsubst %.cpp,%.o,$(wildcard *.cpp))
all: my_program
my_program: $(OBJS)
gcc -o my_program $(OBJS)
%.o: %.cpp
gcc $(CFLAGS) -c $< -o $@
This is much more maintainable than our previous version, wouldn't you agree?
2b. Potential Problems With This Setup
Astute readers have undoubtedly noticed that my sample makefile has no header (.h
) files specified as dependencies. In the real world, it's good to include them so that updates to said files will trigger a build when make is executed. Suppose that our example project had a header file named class_a.h
. As the makefile is written now, if we update this header file and then call make, nothing will happen (we would have to make clean, then make again, to pick up the changes).
Header file dependencies aren't likely to be a one-to-one mapping. Fortunately, we can get make to automatically generate our dependencies for us. Furthermore, we can get make to include those automatic dependencies at execution time, without any recursive calls! The process for doing this is above and beyond the scope of this article, but I will be writing an article on this very subject in the near future (so stay tuned).
3. Target-Specific Variables Can Help
Suppose that we want to build a debug version of our program using a target. Wouldn't it be nice to be able to modify some of our variable values given that specific target? Well, it turns out that we can do just that. Here's how (the added lines have been highlighted):
CFLAGS = -Wall
OBJS = $(patsubst %.cpp,%.o,$(wildcard *.cpp))
all: my_program
debug: CFLAGS += -g3
debug: my_program
my_program: $(OBJS)
gcc -o my_program $(OBJS)
%.o: %.cpp
gcc -c $< -o $@
In this example, when we type make debug
from the command line, our CFLAGS
variable will have the appropriate debug option appended (in this case, -g3
), and then the program will be built using the specified dependencies. Being able to override variables in this manner can be quite useful in the right situations.
Do you have your own make tips? If so, leave a comment! I'll be posting more about doing automatic dependency generation with make and gcc in the near future.