Painless non-recursive Make

[article]

the file named $2.   The actual find is achieved by calling the _walk function which walks up the tree finding every instead of the file $2 in each of the successively shorter paths from $1.

The block of code at the start of the Makefile finds the location of root.mak (which is in the same directory as top.mak and bottom.mak (i.e. /src/) and saves that directory in _ROOT.

Subsequently, the Makefile can use $(_ROOT)/ to include the root.mak, top.mak and bottom.mak Makefiles without any need to type anything other than make.

Here's the contents of the first included Makefile (root.mak):

_push = $(eval _save$1 := $(MAKEFILE_LIST))

_pop = $(eval MAKEFILE_LIST := $(_save$1))

_INCLUDE = $(call _push,$1)$(eval include $(_ROOT)/$1/Makefile)$(call _pop,$1)

DEPENDS_ON = $(call _INCLUDE,$1)

DEPENDS_ON_NO_BUILD = $(eval _NO_RULES := T)$(call _INCLUDE,$1)$(eval _NO_RULES :=)

For the moment we'll ignore its contents and return to what these functions are used for when we look at dependencies between modules.  The real work begins with top.mak:

_OUTTOP ?= /tmp/out

.PHONY: all

all:

_MAKEFILES := $(filter %/Makefile,$(MAKEFILE_LIST))

_INCLUDED_FROM
:= $(patsubst $(_ROOT)/%,%,$(if $(_MAKEFILES),$(patsubst
%/Makefile,%,$(word $(words $(_MAKEFILES)),$(_MAKEFILES)))))

ifeq ($(_INCLUDED_FROM),)

_MODULE := $(patsubst $(_ROOT)/%,%,$(CURDIR))

else

_MODULE := $(_INCLUDED_FROM)

endif

_MODULE_PATH := $(_ROOT)/$(_MODULE)

_MODULE_NAME := $(subst /,_,$(_MODULE))

$(_MODULE_NAME)_OUTPUT := $(_OUTTOP)/$(_MODULE)

_OBJEXT := .o

_LIBEXT := .a

_EXEEXT :=

The _OUTTOP macro defines the top-level directory into which all binary output (object files etc.) will be placed.  It's defined with a ?= so that it can be overriden on the command-line, and I've given it a default vaule of /tmp/out/.

Next top.mak sets up the default target for Make as the classic all.  Here is has no dependencies, but we'll add them later on for each module that's going to be built.

After that a number of macros end up setting the _MODULE_PATH to the full path to the module directory being built.  For example, when building the library module _MODULE_PATH would be /src/library. 
Setting this macro is complex because determining the module directory
has to be independent of the directory from which GNU Make was executed
(so that the library can be built from the top-level, for a 'make all',
or from the individual library directory, for an individual developer
build, or even can be included as a dependency on a different module).

The _MODULE_NAME is simple the path relative to the root of the tree with / replace by _.  For the simple example the two modules have _MODULE_NAME's library and exectuable.  But if library had a sub-directory containing a module called sublibrary then its _MODULE_NAME would be library_sublibrary.

The _MODULE_NAME is also used to create the special macro $(_MODULE_NAME)_OUTPUT which has a computed name based on _MODULE_NAME.  So, for the library module the macro library_OUTPUT is created with the full path of the directory into which library's object files should be written.   The output path is based on _OUTTOP and the relative path to the module being built.  In that way the /tmp/out/ tree mirrors the source tree.

Finally, some standard definitions of extensions used on file names are set up. 
Here I've shown definitions for Linux systems, but these can easily be changed for systems such as Windows that don't use .o for an object file or .a for a library.

bottom.mak uses these macros to set up the rules that will actually build the module:

$(_MODULE_NAME)_OBJS := $(addsuffix $(_OBJEXT),$(addprefix $($(_MODULE_NAME)_OUTPUT)/,$(basename $(SRCS)))) $(DEPS)

$(_MODULE_NAME)_BINARY := $($(_MODULE_NAME)_OUTPUT)/$(BINARY)$(BINARY_EXT)

ifneq ($(_NO_RULES),T)

ifneq ($($(_MODULE_NAME)_DEFINED),T)

all: $($(_MODULE_NAME)_BINARY)

.PHONY: $(_MODULE_NAME)

$(_MODULE_NAME): $($(_MODULE_NAME)_BINARY)

_IGNORE := $(shell mkdir -p $($(_MODULE_NAME)_OUTPUT))

_CLEAN := clean-$(_MODULE_NAME)

.PHONY: clean $(_CLEAN)

clean: $(_CLEAN)

$(_CLEAN):

   rm -rf $($(patsubst clean-%,%,$@)_OUTPUT)

$($(_MODULE_NAME)_OUTPUT)/%.o: $(_MODULE_PATH)/%.c

   @$(COMPILE.c) -o '$@' '$<'

$($(_MODULE_NAME)_OUTPUT)/$(BINARY).a: $($(_MODULE_NAME)_OBJS)

   @$(AR) r '$@' $^

   @ranlib '$@'

$($(_MODULE_NAME)_OUTPUT)/$(BINARY)$(_EXEEXT): $($(_MODULE_NAME)_OBJS)

   @$(LINK.cpp)

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.

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!

Upcoming Events

Nov 09
Nov 09
Apr 13
May 03