Simplified Makefile System

When I first started developing on Linux and using Makefiles, I started with simple Makefile scripts and later built upon each preceding one. What eventuated is the attached set of files that should make compilation of multiple sources into a single target an easier exercise. There are, of course, many other alternatives, such as Automake, Bakefiles and CMake (all of which are far more sophisticated). If their 'sophistication' causes too much of a headache when you simply want to compile a couple of files, this could help - or could aid you in writing another Makefile-based solution for yourself.

The files are:

  1. defs.mk contains all the 'code' and implicit compilation rules to make the script run (the input arguments are mentioned at the top of the file)
  2. shared.mk contains shared variables that should be accessible to all Makefiles in your project and will/could be processed by the code in defs.mk
  3. Makefile: this is a sample file. Note that your project might have a number of components, each in their own directory. Each component can have its own Makefile (in its own directory) that could reference one copy of the above two files. Then a master Makefile (say, in the project's root directory) could invoke all the lower-level Makefiles to build the entire solution.

The simplest Makefile must contain something akin to the following:

# Makefile
 
include shared.mk
 
TARGET      = <myapp>
 
SOURCES     = \
    a.cpp \
    b.cpp
 
include defs.mk
 
# 'sinclude' tells Make to include the file if it exists. If not, then it should
#   run Make with the goal by the same name (which should create the file) and then include it.
sinclude depend

The top include and bottom include & sinclude are mandatory, and everything else must be inbetween.

defs.mk has the following input arguments that can be defined either in shared.mk or in the invoking Makefile:

#   ADDITIONAL_INCLUDEPATHS
#   ADDITIONAL_LIBRARIES
#   ADDITIONAL_LIBRARYPATHS
#   OBJPATH                 (override default)
#   ADDITIONAL_PREREQPATHS  (adds to VPATH)
#   TARGETDIR               (override default)
#   SOURCES                 (list of sources files)
#   NOOBJPATH               (set to override creation of OBJPATH)
#   TARGET                  (binary target without directory)
#   CVSIGNORE               (additional files to add to .cvsignore)
#   EXTERNALOBJECTS         (external object files with path)
#   SKIPDEPEND              (set to anything to skip DEPEND)
#   CUSTOMCLEAN             (additional commands to be run with 'clean')
#   CUSTOMDISTCLEAN         (additional commands to be run with 'distclean')
#   SO_TARGET               (if we're compiling a shared object)
#   NOCVS                   (don't create cvs files)
#   NOTARGETDIR             (don't use the default target directory)
#   NODELETEALL             (don't delete OBJPATH, only the sources' .o files inside)
#   SO_PARAMS               (parsed in from directive in a RDF)
#   DEPEND_SOURCES          (only create dependency file for these sources, not all sources)
#
# Available implicit source rules:
#   cpp  -> o
#   c    -> o

Some of these are 'booleans' in that if the variable is declared (the value is irrelevant) then a certain action will be taken (or not), eg: NOCVS.

shared.mk should at the very least least contain:

# Sets various global variables
 
DEPEND          = depend

This could also be an opportunity to define some global include directories that your current target/down-level Makefiles might also need. A more complete example could be:

# Sets various global variables
 
SERVERDIR        = server
CLIENTDIR        = client
GENERATORDIR     = generator
IDLDIR           = idl
INCLUDEDIR       = include
SRCDIR           = src
RULESDIR         = rules
 
DEPEND           = depend
 
ifndef SE3010DIR
    SE3010DIR = /home/se3010/soft
endif # SE3010DIR
 
ifndef ACE_ROOT
    ACE_ROOT = $(SE3010DIR)/src/ACE_wrappers
endif # ACE_ROOT
 
ifndef TAO_ROOT
    TAO_ROOT = $(ACE_ROOT)/TAO
endif # TAO_ROOT
 
XMLINCPATH = $(SE3010DIR)/include/libxml++-2.6/ \
             $(SE3010DIR)/include/libxml2/ \
             $(SE3010DIR)/include/libxml++-2.6/libxml++ \
             $(SE3010DIR)/include/libxml2/libxml \
             $(SE3010DIR)/include/glibmm-2.3/ \
             $(SE3010DIR)/lib/glibmm-2.3/include/ \
             /usr/include/glib-2.0/ \
             /usr/lib/glib-2.0/include/
 
XMLLIBRARIES = glibmm-2.3 xml++-2.5 xml2

Now say that the server, client, etc, components are in lower-level directories by the same name (as can be seen at the top of the above sample code), which contain their own Makefiles. The top-level master Makefile could contain:

# Makefile for the BART System
 
include shared.mk
 
SUBDIRS    = $(SERVERDIR) $(CLIENTDIR) $(GENERATORDIR) $(IDLDIR)
 
# This is supposed to contain all the global rules in defs.mk
all depend install clean distclean: $(SUBDIRS)
 
# If this Makefile is supplied with a goal that is actually a sub-directory,
#   make that sub-directory, but with an *empty* goal (so it makes the default
#   goal for that sub-directory!)
$(filter-out $(IDLDIR), $(SUBDIRS)):
    $(MAKE) -C $@ $(filter-out $(SUBDIRS), $(MAKECMDGOALS))
 
$(IDLDIR):
    $(MAKE) -C $@ $(filter-out $(SUBDIRS), $(filter-out depend, $(MAKECMDGOALS)))
 
# Dependencies:
$(DEPEND): idl
$(SERVERDIR): idl
$(CLIENTDIR): idl
$(GENERATORDIR): idl
 
# Empty goal for first deliverable
#generator:
 
.PHONY:    $(SUBDIRS) all depend install clean distclean

This sets up a nice way of calling the down-level Makefiles automatically or manually, depending on the make arguments.

The client's Makefile, for example, could contain:

# Makefile for BARTClient
 
include ../shared.mk
 
PREFIX      = ../
 
TARGET      = ../src/BARTClient
 
SOURCES     = Main.cpp ClientCommandParser.cpp ../src/ClientInitialiser.cpp $(PREFIX)$(SRCDIR)/Log.cpp
 
ADDITIONAL_INCLUDEPATHS =
ADDITIONAL_LIBRARYPATHS = $(TAO_ROOT)/tao $(ACE_ROOT)/ace
ADDITIONAL_LIBRARIES    = TAO TAO_PortableServer TAO_CosNaming
 
EXTERNALOBJECTS = $(PREFIX)$(IDLDIR)/$(OBJPATH)/BARTServiceC.o
 
include ../defs.mk
 
# 'sinclude' tells Make to include the file if it exists. If not, then it should
#   run Make with the goal by the same name (which should create the file) and then include it.
sinclude depend

AttachmentSize
SimpleMakefileSystem.zip2.66 KB