Handling dependencies in Makefiles

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?

Related

comments powered by Disqus