Skip to main content

GCC Makefiles

While there are many pages that describe Makefiles, none seemed to quite cover what I wanted to do, so this describes a relatively clean Makefile that I constructed that met a number of fairly specific requirements

  • Separate debug and release builds
  • All generated files to be in separate Debug and Release directories (as done within the Eclipse CDT make system)
  • Some source files are to be found in a location elewhere in the diretory structure and not in a subdirectory under the Makefile directory. I have covered the specific rule changes for this here

This approach has been used on Windows using cygwin, Apple mac and a linux server.

Standard definitions

Sets up common build rules and flags for both the debug and release builds. In this case the -std flag indicates that we are using the 2011 C++ definitions.

################################################################################
#
# Make all or Make debug makes the debug version
# Make release to make the release version
#
################################################################################
# Standard rules and flags
CCC = g++
CCCALLFLAGS= -std=c++0x

The definitions for C files are here.

Directory information

We are going to include object files from the local cisgenomeLib directory, which also includes some header files, and a static library from ../cisgenome_core, and header files from both, so set up the paths

################################################################################
# Directory information

CISGENOMEDIR = ../cisgenome_core
INCLUDES=-IcisGenomeLib -I$(CISGENOMEDIR)/cfiles

Setting Debug and release build variants

$(MAKECMDGOALS ) contains the parameters passed into make (e.g. release, clean etc). While the use of this string is often discouraged, I found it a simple way of being able to use common definitions for both debug and release builds in the vast majority of the makefile. The $(BUILD) variable is set to the identity of the directory where the generated files will go, and we add additional options to the build flag strings, turning optimisation off and debug on for the debug build and adding all warnings (with a few exceptions)

################################################################################
# Setup debug and release variants
RELDIR = Release
DEBUGDIR = Debug

ifeq "$(findstring release, $(MAKECMDGOALS))" ""
BUILD=$(DEBUGDIR)
CCCALLFLAGS += -g3 -O0 -D_DEBUG -D_GLIBCXX_DEBUG -Wall -Wno-unknown-pragmas -Wno-format
else
BUILD=$(RELDIR)
CCCALLFLAGS += -O3
endif

Setting the build rules

These are the rules for creating the .o object files from the .cpp files. By putting $(BUILD) before the obect file name we ensure that the object files are put into the Debug and Release directories as appropriate. If the .cpp file is in a subdirectory then the .o file will go into a matching subdirectory within the $(BUILD) directory.

##################################################################
#
# Instructions for building object files These are dependant on the Makefile so Makefile changes
# force a rebuild. Rule for files in the CISGENOMETOOLSDIR ensures that the .o is placed
# in the local object directory to keep it neat

$(BUILD)/%.o : %.cpp Makefile
$(CCC) -c $(CCCALLFLAGS) $(INCLUDES) -o $@ $<

The rules for building C files are here.

Specify the build outputs

In this case there is only one executable output. Add additional definitions for additional outputs, all of which should be appended to TARGS.

################################################################################
# Outputs of this Makefile


ifeq ($(OS),Windows_NT)
EXE = .exe
endif

BISULPHITETOOL = $(BUILD)/bisulphiteTool$(EXE)

TARGS = $(BISULPHITETOOL)

The approach for making a static library are described here.

Library dependancies

The executable is dependent on the libcisgenome.a static library in the ../cisgenome_core/Debug or ../cisgenome_core/Release directory. This approach allows different libraries to be used for different build types.

################################################################################
# Libraries to be linked

LIBS = -lcisGenome
LIBPATH = -L$(CISGENOMEDIR)/$(BUILD)
LIBDEPENDS = $(CISGENOMEDIR)/$(BUILD)/libcisGenome.a

Common library files

The main executable is built from bisTool/bisulphiteTool.cpp. There are also additional library files at cisGenomeLib that are to be compiled and linked in.

################################################################################
# Source files and the build specific outputs

CISGENOMECORE = cisGenomeLib/genomeFile.cpp cisGenomeLib/fastaFile.cpp
CISGENOMECOREOBJS := $(CISGENOMECORE:%.cpp=$(BUILD)/%.o)

LIBOBJS = $(CISGENOMECOREOBJS)

There are also simple techniques that can be used for

Building the output directories

This build approach requires a new directory structure under the Debug and Release direcories. gcc/g++ does not create them if they do not exist so they have to be done by the Makefile. There are many suggestions on the internet on how to do this. Approachs include the one on John Graham-Cumming's webpage the something_needs_directory_xxx option on this page This is slightly different and makes all of the directories a dependency of 'all:'. Using $@ means that only the directories that are not currently present are built. In this example, the cisGenomeLib and bisTool subdirectories must match the subdirectories within which the source files are found. In the second option for creating the DIR variable the sort method removes duplicates which would otherwise throw up warning messages during the build.

################################################################################
# For building necessary outout directories

DIRS = $(addprefix $(BUILD)/,cisGenomeLib bisTool )
# or alternatively, if a list of COREOBJS to be built has been created
DIRS = $(sort $(dir $(COREOBJS) ) )

$(DIRS):
mkdir -p $@

The target definitions

The earlier rules means that the same rules are used for both the debug and release build. The dependancy list starts with $(DIRS) to ensure that we create the directories before we start trying to put files into them. Because all of the output files go into a new directory, the definition of 'make clean' is particularly simple and brutal.

################################################################################
# The main builds

debug : all
release : all

all: $(DIRS) $(TARGS)
@echo "%% $(BUILD) bisulphiteTool built"

$(BISULPHITETOOL) : $(BUILD)/bisTool/bisulphiteTool.o $(LIBOBJS) $(LIBDEPENDS)
$(CCC) $(LIBOBJS) $< -o $@ $(CCCALLFLAGS) $(LIBPATH) $(LIBS)

clean :
rm -r -f $(RELDIR)
rm -r -f $(DEBUGDIR)

A rule for making a simple static library is described here

Make depends

While there are multiple ways of dealing with dependancies, this approach to creating the Makefile is compatible with the classic makedepend approach. The trick is to add the -p'$$(BUILD)/' parameter to the makedepend call which prefixes all of the dependencies with $(BUILD). This ensures that the dependancy list indicates that the object files is either in the Release or Debug directory

# do 'make depend' from the command line to update dependancies.
# This makes a dependancy list that is dynamically dependant
# on the build type.

depend :
makedepend -Y $(CCCAALLFLAGS) $(INCLUDES) $(SOURCES) -p'$$(BUILD)/'

# DO NOT DELETE THIS LINE -- make depend depends on it.

$(BUILD)/cisGenomeLib/genomeFile.o: ../cisgenome_tools/cisGenomeLib/pch.h
$(BUILD)/cisGenomeLib/genomeFile.o: ../cisgenome_core/cfiles/StringLib.h
$(BUILD)/cisGenomeLib/genomeFile.o: ../cisgenome_core/cfiles/SequenceLib.h