Painless non-recursive Make

Summary:

One of the common objections to using non-recursive Make is that with recursive Make its possible to go to anywhere in the source code tree and type 'make'.  Doing so typically builds the objects that are defined by the Makefile at that level in the tree (and possibly below
if the Makefile recurses).

Oftentimes, non-recursive Make systems (based on include statements instead of make
invocations) do not offer this flexibility and Make must be run from the top-level directory.  Even though non-recursive Make is typically more efficient (meaning that running from the top-level directory should be quick), it's important to be able to give developers the same
level of functionality as a recursive Make system.

In this article I outline a pattern for non-recursive Make systems that supports the familiar make-anywhere style common to recursive Make systems.  Typing 'make' in a directory will build everything in that directory and below.

A simple recursive Make

Imagine a simple project with the following sub-directories:

/src/

/src/library/

/src/executable/

/src/ is the top level directory and is where you'd type 'make' to get a full build.   Inside /src/ there's the library/ directory which build a library called lib.a from source files lib1.c and lib2.c:

/src/library/lib1.c

/src/library/lib2.c

The /src/executable/ directory builds an executable file called exec from two source files (foo.c and bar.c) and the library lib.a:

/src/executable/foo.c

/src/executable/bar.c

The classic recursive Make solution to this means putting a Makefile in each subdirectory that contains the rules to build the objects and a top-level Makefile that recurses into each sub-directory.  Here's the contents of /src/Makefile

SUBDIRS = library executable

.PHONY: all

all:

   for dir in $(SUBDIRS); do \

       $(MAKE) -C $$dir;     \

   done

It enters each directory in turn and runs Make to build first the library and then the executable.   The dependency between the executable and the library (i.e. the fact that the library needs to be built before the exectuable) is implicit in the order in which the directories are specified in SUBDIRS.

An improvement on that is to unwind the loop inside the rule for all, create separate rules for each sub-directory and then explicitely specify the dependency between executable and library.

Here's how to do that using phony targets for each directory:

SUBDIRS = library executable

.PHONY: $(SUBDIRS)

$(SUBDIRS):

    $(MAKE) -C $@

.PHONY: all

all: $(SUBDIRS)

executable: library

That's much clearer, but it's still recursive with separate Make invocations for each sub-directory.

A flexible non-recursive Make system

When moving to non-recursive Make the ideal top-level Makefile would look like this:

SUBDIRS = library executable

include $(addsuffix /Makefile,$(SUBDIRS))

That simply says to include the Makefile in each sub-directory.  The trick is to make that work!  Before showing you how I make that actually work here are the skeletons of the contents of the Makefiles in the library and executable sub-directories.

# /src/library/Makefile

include root.mak

include top.mak

SRCS := lib1.c lib2.c

BINARY := lib

BINARY_EXT := $(_LIBEXT)

include bottom.mak

and

# /src/executable/Makefile

include root.mak

include top.mak

SRCS := foo.c foo.c

BINARY := exec

BINARY_EXT := $(_EXEEXT)

include bottom.mak

Each of those Makefiles specifies the source files to be built (in the SRCS macro), the name of the final linked binary (in the BINARY macro) and the type of the binary (using the BINARY_EXT macro which is set from special macros _LIBEXT and _EXEEXT).

To actually make this work they both include common Makefiles root.mak, top.mak and bottom.mak which are located in the /src/ directory.

Since the .mak included Makefiles are not in the sub-directories GNU Make needs to go
looking for them.  The simplest way to do that is to use the [t]-I[/tt] command-line option to GNU Make that adds a directory to the include search path.   To find the .mak files in the /src you can do:

make -I /src

It's unfortunate to ask a user to add anything to the Make command-line so I use a simple method of automatically walking up the source tree to find the .mak files.  Here's the actual Makefile for /src/library:

sp :=

sp +=

_walk = $(if $1,$(wildcard /$(subst $(sp),/,$1)/$2) $(call _walk,$(wordlist 2,$(words $1),x $1),$2))

_find = $(firstword $(call _walk,$(strip $(subst /, ,$1)),$2))

_ROOT := $(patsubst %/root.mak,%,$(call _find,$(CURDIR),root.mak))

include $(_ROOT)/root.mak

include $(_ROOT)/top.mak

SRCS := lib1.c lib2.c

BINARY := lib

BINARY_EXT := $(_LIBEXT)

include $(_ROOT)/bottom.mak

The _find function walks up a directory tree starting from the directory in $1 looking for

About the author

John Graham-Cumming's picture
John Graham-Cumming

John Graham-Cumming is Co-Founder at Electric Cloud, Inc . Prior to joining Electric Cloud, John was a Venture Consultant with Accel Partners, VP of Internet Technology at Interwoven, Inc. (IWOV), VP of Engineering at Scriptics Corporation (acquired by Interwoven), and Chief Architect at Optimal Networks, Inc. John holds BA and MA degrees in Mathematics and Computation and a Doctorate in Computer Security from Oxford University. John is the creator of the highly acclaimed open source POPFile project. He also holds two patents in network analysis and has others pending.