i am jack’s minimal recursive makefile

Today’s simple Debian packaging task was adding a little C program and Makefile into an existing package that didn’t already compile anything. Through the magic of debian/rules, all I had to do was to write a top-level Makefile in the package directory, which would simply recurse into the subdirectory and invoke make there.

Assume a directory structure like so:
 

$ pwd
foo
$ ls -F
bar/ baz/

 

The goal is to write a top-level Makefile in foo/ that will recurse into bar/ and baz/ and call the makefiles there.

There are lots of examples on how to write such a Makefile, but I thought they were kinda kludgey. For example, this page has this solution:

 

DIRS    = bar baz
OBJLIBS = libbar.a libbaz.a

all: $(OBJLIBS)
libbar.a: force_look
        cd bar; $(MAKE) 
libbaz.a : force_look
        cd baz; $(MAKE) 
install :
        -for d in $(DIRS); do (cd $$d; $(MAKE) install ); done
clean :
        -for d in $(DIRS); do (cd $$d; $(MAKE) clean ); done
force_look :
        true

 

That works, but is a little ugly. They define $DIRS but only use it for the install and clean targets; a manual ‘cd bar ; cd baz’ is still required to build the libs. That’s unnecessary repetition. And the fake force_look target is definitely hacky.

It could be cleaned up a little. Here is an attempt at refinement. It assumes that the makefiles in bar/ and baz/ will properly build libbar.a and libbaz.a.

 

DIRS    = bar baz

all: $(DIRS)
$(DIRS): force_look
        for d in $@ ; do (cd $$d ; make ) ; done
install:
        -for d in $(DIRS) ; do (cd $$d; $(MAKE) install ); done
clean :
        -for d in $(DIRS); do (cd $$d; $(MAKE) clean ); done
force_look :
        true

 

The second attempt is much shorter and avoids repeating ourselves. We just need to specify the subdirectories, and issue make appropiately, depending on the action we want.

However, this still has a problem; the phony target section of the manual says it’s fragile, since you can’t detect errors in your submakes. Plus, typing out that loop several times just feels wrong. And we still have that ugly force_look target.

Here’s my final attempt:

 

DIRS = bar baz

$(DIRS):
	$(MAKE) -C $@
all: $(DIRS)
install: MAKE = make install
install: $(DIRS)
clean: MAKE = make clean
clean: $(DIRS)
.PHONY: clean install $(DIRS)

 

It’s 8 lines of real work vs 9 lines in the 2nd example and 11 lines in the initial attempt. It feels a lot cleaner than typing out the for loop several times. And using the -C option to properly recurse and invoke make, we can detect errors in our sub-makes.

I’m not a huge fan of redefining $(MAKE) so if anyone out there has better suggestions, please do let me know.