wiki:FawkesBuildSystem
Last modified 6 years ago Last modified on 18.03.2013 15:42:47

Fawkes Build System

The Fawkes build system has evolved over time to allow for several typical use cases while leveraging the power and flexibility of GNU Make to allow for more complex scenarios as they arise. Read this to understand how to write a Makefile used with the AllemaniACs' Fawkes build system. The build system is basically meant to make the things done often very easy while making complex task possible.

After a short introduction to the different supported basic build types we will describe the basic Makefile structure and name a few conventions. Then we will give a simple example and show how to influence the build parameters.

If you start reading about the build system you most likely also want to read about the common build system patterns in FawkesBuildSystemPatterns. A a detailed description of the build process is given in FawkesBuildProcess and it is strongly recommended that you read this if want to break out of the standard patterns or if you want to customize the build system. Especially if you cannot use the standard patterns read this!

Build types

There are basically three build types in the Fawkes build system that we are going to introduce now.

  1. Sub-directories only
  2. Concrete targets
  3. Hybrid concrete targets and sub-directories

Sub-directories only

This can be considered to be an infrastructure Makefile. In general it us used on the way to the real targets, for example in the src/Makefile and src/lib/Makefile Makefiles. It contains a list of sub-directories to process and optional ordering constraint targets. See FawkesBuildSystemPatterns for a sub-directory-only Makefile pattern.

Concrete targets

These targets are used to build libs, binaries and plugins and use the basic templates provided by the build system. This is the Makefile type that you will find the most throughout the software and that we are going to show in the example below.

Hybrid Makefile

This is a combination of the two former types. This can be used if you want to build a few subdirectories and then some concrete targets. By default the concrete targets are built first, then the sub-directories are traversed. Use ordering constraints by adding dependencies to the subdirs target.

Basic structure

The basic Makefile structure is pretty simple.

BASEDIR = ../../..

OBJECT definitions
CFLAGS
LDFLAGS

OBJS_all = ...
BINS_all = ...

include $(BASEDIR)/etc/buildsys/base.mk

The BASEDIR has to be set to the relative path to the top directory of your Fawkes working copy (i.e. up to something like $(HOME)/fawkes).

The last include line is always the same and may not be changed. Now read on how to define objects that should be compiled and what possibilities you have to influence the compiler and linker parameters.

There are a couple of variables involved when defining a new application or plugin that is to be built. The parameters that can be set are discussed below, here we just discuss how to define a new build target.

In general it is a good idea to start with the Makefile from another similar directory (another plugin's Makefile if you write a plugin, another lib's Makefile if you write a lib etc.).

Conventions

There are a few conventions that you shall follow to make the thing work.

Dependency detection

Dependencies shall be detected during build and the module shall be gracefully skipped if a dependency fails.

The one exception are base libraries in src/libs. These libs and only these libs shall cause a critical failure if they cannot be built. See FawkesBuildSystemPatterns for these patterns.

Sub-directory naming

Sub-directories shall be named in all lowercase containing only alpha-numeric characters, underscore, dash and dot.

It is important to use sub-directory names that are compatible with make in the sense that they can be used as make targets. Because of this sub-directories may not be named like any special target make provides (see make manual).

QA application naming

QA applications shall always start with a qa_ prefix, followed by another module-dependent collision-free module_ prefix.

The qa_ prefix is needed to make the QA applications easy separable from the rest. The module_ prefix depends on the module name, for example the utils use utils_. So in the end your name looks like qa_mymodule_testcase.

Basic example

Assume that you want to build a new application called test which consists of two source files main.cpp and regression.cpp. We want this to be built as test in the global Fawkes bin directory. You can accomplish this by defining the objects as

OBJS_test = main.o regression.o

OBJS_all = $(OBJS_test)
BINS_all = $(BINDIR)/test

include $(BASEDIR)/etc/buildsys/base.mk

This will have the desired results.

Mandatory ending

At the end of the Makefile you then define OBJS_all as a concatenation of all objects mentioned in the file. This is needed to avoid erasion of the .o file after successful compilation! If your programs builds fast you may even leave this out which would cause the object files to be rebuilt every single time.

Binary declaration

The BINS_all line defines all applications that you want to build. It MUST be prefixed by $(BINDIR), otherwise the build system will not recognize it and will not know how to build it.

Compiler and linker flags

Now imagine that this application needs some special compiler and linker flags. In a Makefile we distuinguish two different types of parameters. First we have program-specific options and then we have object-specific options. Program-specific options include

  • LIBS_
  • LIBDIRS_

Object-specific parameters include

  • INCS_

For program-specific options you name the variable with the prefix given above and a program-specific suffix. The object-specific options are defined per object. In our example we might want regression.cpp to be compiled with a special include to an external lib that we downloaded and of course we want the program to be link against it. This could be expressed as:

SUPERLIB = $(HOME)/dev/superlib-0.4

LIBS_test = superlib
LIBDIRS_test = $(SUPERLIB)/lib

INCS_regression = $(SUPERLIB)/include

This is especially handy if you have multiple applications or plugins defined in just one Makefile. There are also global forms of this options which apply to all applications and plugins in the Makefile. Using these global options the superlib inclusion above would look like this:

SUPERLIB = $(HOME)/dev/superlib-0.4

LIBS = superlib
LIBDIRS = $(SUPERLIB)/lib
INCDIRS = $(SUPERLIB)/includes

Build system variables per module

There are several variables that influence the behavior of the build system for a specific module. These are:

VariableApplies toDescription
CFLAGScompilationglobal CFLAGS for whole module, added to CFLAGS_BASE in config
CFLAGS_Ycompilation of specific file YCFLAGS that are used only for compiling file Y
INCDIRScompilationspace-separated list of include directories, added to CFLAGS with -I prepended
INCS_Ycompilation of specific file Ylist of include directories only used for compiling file Y
LDFLAGSlinkinglinker flags used for whole module
LDFLAGS_Ylinking of module Ylinker flags used only for linking the module Y
LIBSlinkinglist of libs used for whole module
LIBS_Ylinking of module Ylist of libs to link module Y against (prepended with -l)
LIBDIRSlinkinglist of directories searched for libraries for whole module
LIBDIRS_Ylinking of module Ylist of directories searched for libraries for module Y

Basic definitions passed

There are a few defines that are passed to the compilation process by default. These are:

DefinitionMeaning
BINDIRset to the directory where the compiled binaries reside (defaults to $(BASEDIR)/bin
LIBDIRset to the directory where the compiled libraries reside (defaults to $(BASEDIR)/lib
PLUGINDIRset to the directory where the compiled plugins reside (defaults to $(BASEDIR)/plugins
CONFDIRset to the directory where the config files reside (defaults to $(BASEDIR)/cfg