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:
$@. 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.