Painless non-recursive Make

[article]
Summary:

In this article, Mr. Make outlines 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.

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

Pages

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!