Thursday, November 24, 2011

Dependency Generation with Subdirectories using gcc

Here I am on Thanksgiving morning working on my latest project, Hayloft. As is not uncommon, Mrs. Overclock (a.k.a. Dr. Overclock, Medicine Woman) has to work today, leaving your fearless leader to finish up the dinner preparations later in the day. So I thought I'd debug an issue that's been driving me to distraction: automatic dependency generation using the GNU gcc/g++ compilers.

In Hayloft, I have translation units (that's what the standards call C and C++ source files) organized into subdirectories, for example hayloft/Logger.cpp and s3/BucketCreate.cpp, but using just one Makefile. This makes it easier to manage the code base, with absolutely no additional effort on my part, since the make command's pattern matching causes a rule like

%.o: %.cpp
g++ -o $@ -c $<

to do exactly what I want: The % in the target %.o matches the entire file name path, for example s3/BucketCreate, and is propagated into the prerequisite %.cpp. The object file ends up in the same subdirectory. Life is good.

Alas, if I do the usual command to generate dependencies

g++ -MM -MG s3/BucketCreate.cpp

it creates a rule not with the s3/BucketCreate.o target (which is what I want) but with BucketCreate.o. Since I'm running the Makefile from the project's root directory, there will never be a BucketCreate.o nor a requirement for it. If I edit a prerequisite for s3/BucketCreate.cpp (that is, any header file that it includes), s3/BucketCreate.o will never be automatically regenerated.

So I had to write a little rule to go through the source code base, generate the dependencies for each source file individually, and prepend the directory path for that source file onto the make target. Here's what this looks like. (Apologies as usual for any Blogger editor weirdness.)

DEPENDS:=${shell find . -type f \( -name '*.c' \
-o -name '*.cpp' \) -print}

depend:
cp /dev/null dependencies.mk
for F in $(DEPENDS); do \
D=`dirname $$F | sed "s/^\.\///"`; \
echo -n "$$D/" >> dependencies.mk; \
$(CXX) $(CPPFLAGS) -MM -MG $$F \
>> dependencies.mk; \
done

-include dependencies.mk

It's a bit of a tribute to make and the shell that this is even possible.

Update (2012-03-14)

I think this rule might be a little simpler, having just now discovered the -MT option, although I had to separate the C and C++ files to handle their differing suffixes.

CFILES:=$(shell find . -type f -name '*.c' \
-print)
CXXFILES:=$(shell find . -type f -name '*.cpp' \
-print)

depend:
cp /dev/null dependencies.mk
for F in $(CFILES); do \
D=`dirname $$F`; \
B=`basename -s .c $$F`; \
$(CC) $(CPPFLAGS) -MM -MT $$D/$$B.o -MG $$F \
>> dependencies.mk; \
done

for F in $(CXXFILES); do \
D=`dirname $$F`; \
B=`basename -s .cpp $$F`; \
$(CXX) $(CPPFLAGS) -MM -MT $$D/$$B.o -MG $$F \
>> dependencies.mk; \
done


Update (2013-11-06)

I upgraded my server to a later version of Ubuntu which doesn't appear to have the -s flag on the basename command. So now I'm back to something more like the prior approach. 

No comments: