I’ve seen way too many projects that supply a makefile that requires the
user to run make clean
and make
every single time they make a
change to some file. This is annoying and error prone, and the good news
is that it can be easily solved with this one simple trick. Use a good
build-generation system like CMake instead.
Still here? Well, since you asked, I shall tell.
The trick is to have your compiler spit out a list of dependencies for each source file.
Try it out right now. Start a terminal and go to any project that is
sufficiently complex to have multiple include chains in a C or C++
source file (foo.cc
). Then type in this command
$> g++ -M foo.cc
The output is a list of all the files that are included by the source
file, including standard library headers. If you look closer, the output
is formatted so that it follows the same syntax as make
for declaring
dependencies. But we really don’t want to be bothered with system header
files, so instead, we’ll use the g++ -MM
.
Cool, isn’t it? This means that all we now need to do is to find a way to include this information in our Makefile, and have it automatically update any time the source files are changed. So here’s the recipe.
# List of source files here.
sources = foo.cc bar.cc
# Add C++ flags, for example, if the code uses C++11 standards.
CPPFLAGS = -std=c++11
# Recipe for making .d files from .cc files
%.d: %.cc
$(CC) -MM $(CPPFLAGS) -o $@ $<
# Include the required .d files in the current Makefile.
include $(sources:.cc=.d)
# Recipe for making .o files. Here, I introduce a dependency on the
# primary source c++ file.
%.o: %.cc
$(CC) -c $(CPPFLAGS) -o $@ $(<:.d=.cc)
Of course, there’s a bug in this recipe as well. Can you spot it?
The bug is that the .d
files do not update if something changes in the
include chain. However, the GNU Make manual has an interesting
fix.
Their solution is to use sed
to introduce the same dependency chain as
for the .o
file into the .d
file. Here’s the updated Makefile with
their recipe.
# List of source files here.
sources = foo.cc bar.cc
# Add C++ flags, for example, if the code uses C++11 standards.
CPPFLAGS = -std=c++11
# Recipe for making .d files from .cc files. This has been updated from
# the GNU Make manual
%.d: %.cc
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
# Include the required .d files in the current Makefile.
include $(sources:.cc=.d)
# Recipe for making .o files. Here, I introduce a dependency on the
# primary source c++ file.
%.o: %.cc
$(CC) -c $(CPPFLAGS) -o $@ $(<:.d=.cc)
Okay, I think that should work now. But it does beg the question, why are we even writing Makefiles by hand any more? Shouldn’t we just use a configuration system like CMake or GNU Autotools that can generate correct Makefiles?