Tips and Tricks From the Automatic Dependency Generation Masters

[article]
Mr. Make
Summary:

Make's dependency syntax is flawed because it incorporates both foo.o must be updated if header.h, system.h or foo.c are changed and foo.o is the result of compiling foo.c.  Thus, anything to the right of the : is a prerequisite, but the first prerequisite where there's a rule body (i.e. commands) is special: it's the prerequisite that will be passed to the compiler (or other command) to actually generate the target.

GNU Make's mixed dependency syntax
Even Make's dependency syntax is flawed because it incorporates both 'foo.o must be updated if header.h, system.h or foo.c are changed' and 'foo.o is the result of compiling foo.c'.  Thus, anything to the right of the : is a prerequisite, but the first prerequisite where there's a rule body (i.e. commands) is special: it's the prerequisite that will be passed to the compiler (or other command) to actually generate the target.

Take a look at this example:

foo.o: foo.c header.h system.h
@echo Compiling $@ from $<...

which outputs:

Compiling foo.o from foo.c...

Here foo.o is built if foo.c, header.h or system.h change, but the rule also states that foo.o is made from foo.c.  If the example were written like this:

foo.o: foo.c
foo.o: header.h system.h
@echo Compiling $@ from $<...

the output would be

Compiling foo.o from header.h...

which is clearly wrong.   

A simple example
The biggest problem of all is generating these rules for a large project.  The rest of this article uses the following contrived example Makefile as a starting point:

.PHONY: all
all: foo.o bar.o baz.o

foo.o: foo.c foo.h common.h header.h
bar.o: bar.c bar.h common.h header.h ba.h
baz.o: baz.c baz.h common.h header.h ba.h

Three object files (foo.o, bar.o and baz.o) are built from corresponding C files (foo.c, bar.c and baz.c).  Each .o file has dependencies on various different header files as expressed in the Makefile.   The Makefile uses GNU Make's built-in rules to perform compilation using the system's compiler.

There's no mention here of the final executable being built since this article concentrates on dealing with dependencies between sources and objects, and relationships between objects are usually easier to maintain by hand as there are fewer of them and the relationships are part of the product design.

makedepend and make depend
Since maintaining any real Makefile by hand is impossible many projects use the widely available makedepend program.  makedepend reads C and C++ files looking at the #include statements, follows the #includes and builds the dependency lines for you.   A basic way of incorporating makedepend in a project is a special depend target:

.PHONY: all
all: foo.o bar.o baz.o

SRCS = foo.c bar.c baz.c

DEPENDS = dependencies.d
.PHONY: depend
depend:
@makedepend -f - $(SRCS) > $(DEPENDS)

-include $(DEPENDS)

Doing make depend with this Makefile causes the depend rule to execute, which runs makedepend on the sources (defined in the SRCS variable) and outputs the dependency lines to dependencies.d (defined by DEPENDS variable).

The Makefile includes the dependencies lines in its final line.   dependencies.d would look like this:

# DO NOT DELETE

foo.o: foo.h header.h common.h
bar.o: bar.h header.h common.h ba.h
baz.o: baz.h header.h common.h ba.h

You'll notice that makedepend doesn't try to define the relationship between an object file (e.g. foo.o) and the source file it is made from (foo.c); in this case GNU Make's standard rules will find the related .c file automatically.

Automating makedepend and removing make depend
There are two problems with the make depend style: running make depend can be slow, as every source file has to be searched even if there are no changes, and it's a manual step: before every make the user will have to do make depend to ensure that the dependencies are correct.  The answer to these problems is automation.

Here's a version of the Makefile that still uses makedepend to generate dependencies but automates the process and only runs makedepend for sources that have changed:

.PHONY: all
all: foo.o bar.o baz.o

SRCS = foo.c bar.c baz.c

%.d : %.c
@makedepend -f - $< | sed 's,\($*\.o\)[ :]*,\1 $@ : ,g' >

About the author

AgileConnection is one of the growing communities of the TechWell network.

Featuring fresh, insightful stories, TechWell.com is the place to go for what is happening in software development and delivery.  Join the conversation now!