MAKAO BUILD ENGINE  2.3.0
 All Groups Pages
MAKAO Build Engine

Table of Contents

Cross-platform build engine based on GNU Make scripts

Introduction

MAKAO is a set of scripts that automate building process of C/C++ projects. It is targeted mainly at embedded systems. The main advantages of this solution:

MAKAO relies on GNU make and several command line tools available for all linux and MS Windows systems.

Installation

MAKAO is distributed in source form as a set of make scripts. In order to run MAKAO the following tools should be present:

and optionally:

Basic usage

In order to build something with MAKAO you need to provide the Makefile. But compared to standard Makefiles the one you have to provide may be fairly simple.

Simplest example

The simplest Makefile may look like this:

TARGET_DEVICE = x86
SRC = main.c
MAKAO_PATH = ../makao
include $(MAKAO_PATH)/makao.mak

This Makefile will command the MAKAO to compile a single C source file 'main.c' into an executable file for the x86 architecture, using default toolchain for this platform. On Windows, MinGW will be used, and the product executable file will be named 'target.exe' by default. On linux, gcc will be used and the product executable file will be named 'target.elf'.

As you can see, the idea behind MAKAO is simple. In a project Makefile you should provide minimum information needed to build your project. The following sections will guide you through the possible options that control the build process.

The MAKAO_PATH variable

The Makefile that uses MAKAO build engine must provide the MAKAO_PATH variable with a relative or absolute path to the directory, where makao scripts are stored. See also Tips on integration with IDE or development environment. on how to make it even easier for the end user to include MAKAO in the Makefile.

Specifying target platform

MAKAO is focused on embedded systems software. It is designed to support cross-compiling and promote portable software. To resolve the target platform MAKAO expects the TARGET_DEVICE variable to be defined in the project Makefile. Look back at the Simplest example above. The TARGET_DEVICE should hold the name of the target processor or microcontroller. See list of supported devices for a complete list of supported units.

Specifying toolchain

By default MAKAO deduces the toolchain based on the supplied TARGET_DEVICE variable value. For toolchain mapping you consult the list of supported devices or you can always check it in the device.mak file. The toolchain definitions supported by MAKAO are stored within the toolchains sub-folder. The user may also specify a custom toolchain definition, providing a path to a file describing the toolchain via the TOOLCHAIN variable.

Specifying toolchain location

MAKAO allows to specify a custom path to a toolchain, through the TOOLCHAIN_DIR definition. For example, in order to use a toolchain located in c:/toolchains/arm-none-eabi/5.4.1 one can simply build the project like this:

make all TOOLCHAIN_DIR=c:/toolchains/arm-none-eabi/5.4.1

In case the TOOLCHAIN_DIR definition is not specified, MAKAO will try to use default toolchain paths, and expect them to be defined as an environment variables. The following definitions will be used:

In case neither the TOOLCHAIN_DIR definition is available, nor a default path exists, the MAKAO will not try to call the toolchain in a specific folder, but rather expect the toolchain to be in the system path, as a last resort.

Specifying products

You can override the executable output by using the OUTPUT_FILENAME variable:

TARGET_DEVICE = x86
SRC = main.c
OUTPUT_FILENAME = project.exe project.lss
MAKAO_PATH = ../makao
include $(MAKAO_PATH)/makao.mak

This will build an executable file called 'project.exe' and also create listing file called 'project.lss'. For a complete description of possible output file options consult OUTPUT_FILENAME variable.

Specifying source files

The source files are specified through the SRC variable. This variable holds the list of source files that need to be built in order to produce the product (see Specifying products). Please consult the SRC variable for examples and information on how to specify source files.

See also the information about components (below).

Specifying include files and directories

There are two variables that deal with files that should be included during build. The INC variable hold a list of include files that should be included during every source file build. The INC_DIR variable holds a list of directories that should be searched for the include files (#include directive).

Specifying preprocessor definitions

The preprocessor definitions that are made available to the compiler during build process can be specified using the DEF variable. Here's an example

DEF = NDEBUG ENABLE_LOG=1

Specifying link-time libraries

The link-time libraries, that the executable is linked against can be specified using the LIB variable. In addition it is possible to specify a list of paths, where the libraries will be searched for, using the LIB_DIR variable. The following example illustrates these ideas:

LIB = myLib.a myOtherLib.a
LIB_DIR = ../myLibs /usr/lib/myOtherLibs

Specifying output directory

It is possible to specify an output directory for the build, by providing the OUTPUT_DIR definition.

OUTPUT_DIR = output

Note however, that the OUTPUT_DIR only supports a single directory name, that will be the subdirectory of the location where the build process starts.

File and directory paths

One of the key strengths of MAKAO is the support of relative paths. In the example above the source file 'main.c' is searched in the directory, where the Makefile is placed. You can write it also like this:

SRC = ./main.c

But other directories can be specified easily, with relative paths, like this:

SRC = main.c ../../common.c ./uart/driver.c

All these paths are always relative to the Makefile, they are defined in. This is also true in component definitions.

Components

MAKAO supports component-oriented programming by providing means of enclosing parts of your program into standalone components.

How to define components

A software component is made of the same ingredients as a regular program: source files, include files, libraries + definitions and description of how to build it all together. All this information along with some information about the component and it's purpose is placed in a special *.cd file. Each component should have it's own *.cd file.

A simple makao file may look like this:

NAME := UART_DRIVER
VERSION := 1.0
SRC := uart.c
INC_DIR := .

The descriptive part of a *.cd file consists of two definitions: NAME and VERSION. The NAME definition provides name for the component. The NAME MUST NOT include any spaces or other white characters. The VERSION definition is used to note the version of the component.

The definitions that follow the NAME and VERSION are exactly the same as in a standard MAKAO makefile.

How to use components

It's easy. Once you have a *.cd file with description of the component, all you need to do is add the COMPONENTS variable to your Makefile. This variable holds a list of directories, where component description files are placed. MAKAO searches these paths for *.cd files. Components represented by *.cd files are included into build process automatically.

Example

A simple makao file that uses components may look like this:

TARGET_DEVICE = x86
SRC = main.c
OUTPUT_FILENAME = project.exe project.lss
COMPONENTS = ../externals/some_lib ../externals/some_other_lib
MAKAO_PATH = ../makao
include $(MAKAO_PATH)/makao.mak

On-line component repository

Since version 1.2.0 MAKAO supports on-line component repository. The idea is to have the components in a version control system from where they can be imported to a project. The components may be stored in various places. Because of that, MAKAO needs a single list, that gathers all the required information.

Let's start with the format of such list. Below is a simple example:

crc https://my.svn.server/components/crc - CRC calculation routines
hash https://my.svn.server/components/hash - HASH calculation routines
unittest https://my.other.svn.server/components - Unit unit test framework

Each row on that list denotes a single component. There are 3 items in each row, separated by single spaces

Such list should be placed somewhere on-line, where MAKAO can access it (download it). Let's assume, that the list is placed under this URL: http://my.server/makao.components We need to define a variable MAKAO_COMPONENTS_URL, and filling it with the URL

MAKAO_COMPONENTS_URL = http://my.server/makao.components

Our on-line component repository is now set up and ready for MAKAO to use it.

Importing components using MAKAO

Since version 1.2.0 it is possible to import pre-defined components using MAKAO. The components are imported from the on-line repositories, so you need to have an Internet connection. To list all possible components that can be imported this way use the 'import' make target:

make import

This should print the list of available components. To import a particular component use the following syntax:

make import c=thecomponent

Where thecomponent is the name of the component you would like to import.

Importing a component basically means, that the source code of the component will be fetched into the project. The imported component will be placed in a folder called 'externals'. However, depending on whether this folder is already in place and under version control two things may happen:

Component-specific import behavior

Each component may define a special action (GNU Make rule), that will be invoked right after the component is imported. Typically, this action provides additional information about the component. To do that, the component description file must include a rule named: 'after_install_<name>' where '<name>' is the name of the component. Below is an example for a component called 'uart_driver':

NAME := UART_DRIVER
VERSION := 1.0
SRC := uart.c
INC_DIR := .
after_install_uart_driver:
@echo "You need to configure the module by providing a file called uart_config.h"
@echo "Consult the documentation"
@echo "Have a nice day!"

Please note, that the space before the echo commands should be made with a TAB, not spaces! This is how GNU Make requires the rules to be written.

Component bootstrapping

Often, the imported component needs additional configuration, in order to be used in the project. Sometimes it is just convenient for the component to provide some template files to the project, in order to speed up the integration. We will call this process bootstrapping and since MAKAO 1.2.0 it is possible to invoke a component-specific bootstrap action.

First the component must define the bootstrapping action, by providing a rule called 'bootstrap_<name>' where '<name>' is the name of the component. Below is an example for a component called 'uart_driver':

NAME := UART_DRIVER
VERSION := 1.0
SRC := uart.c
INC_DIR := .
after_install_uart_driver:
@echo "This component supports bootstrapping. Type: 'make bootstrap c=uart_driver'"
bootstrap_uart_driver:
@echo "Bootstrapping component UART_DRIVER. This will add the following files to your project:"
@echo + uart_config.h : UART configuration file
@echo "If this file already exists in your project, you will be asked (y/n) if it is OK to overwrite it."
@cp -i $(UART_DRIVER_PATH)/bootstrap/uart_config.h $(ROOT_PATH)/uart_config.h

Please note, that the space before the echo commands should be made with a TAB, not spaces! This is how GNU Make requires the rules to be written. The exemplary rule prints some information and then tries to copy the file 'uart_config.h' from the boostrap subfolder of the component to the root path of the project. The '-i' option causes the copy tool to ask for permission to overwrite the file.

Subcomponents

Since version 1.2.0 it is possible for a component to import another component. This way it is possible to create a hierarchy of components and sub-components. To do that, we simply use the COMPONENTS variable in the component description file.

An exemplary component description file that imports a sub-component may look like this

NAME := UART_DRIVER
VERSION := 1.0
COMPONENTS := path/to/subcomponent1 path/to/subcomponent2

Toolchain setup

In order to build anything, MAKAO must know how to invoke build tools such as assembler, compiler and linker. The options

Standard MAKAO toolchain settings

All toolchain definition files shipped with MAKAO share some standard settings that the user can pass through a Makefile.

Language standard

For the GCC based toolchains, there are 3 definitions that pass the language standard settings:

In the following example the main.cpp file will be compiled using C++11 standard, and lib.c using C99

TARGET_DEVICE = x86
SRC = main.cpp lib.c
CSTD = c99
CPPSTD = c++11
MAKAO_PATH = ../makao
include $(MAKAO_PATH)/makao.mak

Optimization

For the GCC based toolchains, the definition that controls the optimization level is called OPTIMIZATION.

In the following example the main.c file will be compiled with optimization set to size (-Os).

TARGET_DEVICE = x86
SRC = main.c
OPTIMIZATION = s
MAKAO_PATH = ../makao
include $(MAKAO_PATH)/makao.mak

Listing suppression

By default MAKAO does not instruct the toolchain to generate assembly listings in order to speed up the build and consume less disk space. Listing generation can be enabled by providing the NOLST definition, disabling the listing suppression

NOLST = 0

Pedantic warnings

By default MAKAO instructs the toolchain to issue only common warnings. To enable all additional ones the PEDANTIC definition can be set to 1

PEDANTIC = 1

Pedantic warnings are useful as an additional static code analysis.

Custom toolchain flags

For the GCC based toolchains, it is possible to add custom flags to the invocation of compiler and linker. The flags specified in CFLAGS definition will be appended to the invocation of the C compiler. The flags specified in CPPFLAGS definition will be appended to the invocation of the C++ compiler. The flags specified in LDFLAGS definition will be appended to the invocation of the linker.

In the following example the main.c file will be compiled with -pedantic-errors options.

TARGET_DEVICE = x86
SRC = main.c
CFLAGS = -pedantic-errors
MAKAO_PATH = ../makao
include $(MAKAO_PATH)/makao.mak

Integration with GCOV (GNU Coverage)

In order to enable GCOV (GNU Coverage) during build, specify the USE_GCOV flag as 1:

USE_GCOV = 1;

Build targets

How to check what is built

One useful feature of MAKAO is the possibility to list all information about the building process using make target called simply "makao". In order to see use this simply run make with "makao" as argument in the folder, where your main Makefile is placed:

make makao

As a result all information about the build process, including source files, folders, components, paths and settings will be printed out.

Building

Everything works as with standard makefiles, so:

make all

..builds everything

make clean

..cleans everything

Emitted definitions

The MAKAO build engine injects some preprocessor definitions into the C/C++ build. The following definitions are available:

__MAKAO_HOST_OS__Name of the hosting operating system, for example: windows, linux
__MAKAO_DEVICE__Name of the target MCU, for example: atmega128, stm32f103rbt, str711
__MAKAO_CPU__Name of the CPU core, for example: cortex-m3, avr, x86
__MAKAO_CPU_WORD_SIZE__Word size of the CPU, for example 8, 16, 32
__MAKAO_MCU_FAMILY__Name of the MCU family, for example: stm32f4, efm32gg
__MAKAO_TOOLCHAIN__Name of the toolchain, for example: mingw, arm-none-eabi

In addition, for each component the following definitions will be injected:

NAME_VERSIONComponent version as defined in the *.cd file
NAME_REVISIONComponent revision taken from SVN - this will be emitted only if project is an SVN working copy

For example, if the component is a SVN working copy and its description file is as following:

NAME := UART_DRIVER
VERSION := 1.0
SRC := uart.c
INC_DIR := .

The following definitions will be emitted: UART_DRIVER_VERSION and UART_DRIVER_REVISION

The MAKAO engine also emits a variable called MAKAO_VERSION. It will be visible in the component description files during the build.

Controlling log verbosity

By default, the log from the MAKAO build process is fairly minimal providing only the most important information. In order to include information about each command line invoked, set the VERBOSE definition to true:

make all VERBOSE=true

Printing size information

Before every build, the size of the target executable is printed. It is also printed after the build so that is is possible to easily see the difference. In order to print out a detailed information about the size of each object file, use printsize make target:

make printsize

Flashing the microcontroller

Most target devices supported by MAKAO define the default way in which they are programmed (flashed). In order to run the default flashing algorithm run the following make target:

make flash

Please note however that most target devices will require additional definitions specifying the path to the programming tool.

Running the application

In PC environment, the executable that has been build can be easily run using the following make target:

make run

Custom build steps

In is possible to define custom build steps that will be executed right before the build and/or right after. The PRE_BUILD_STEP definition lists all make targets that shall be built before the actual build process begins, whereas the POST_BUILD_STEP lists all make targets that shall be built after the process. Here's an example:

pre:
echo "This will be run before the build"
post:
echo "This will be run after the build"
PRE_BUILD_STEP = pre
POST_BUILD_STEP = post
Also, since MAKAO 2.1 it is possible to define a custom clean step, executed before the actual clean
(during make clean command) by defining the \ref clean_step variable. Here's an example:
custom-clean:
echo "This will be run before the cleaning"
rm -f myOutputFile
rm -f -r myOutputDirectory
CLEAN_STEP = custom-clean

Custom build settings

Many times the build will use some custom MAKE definitions, to parameterize the build. For example you may want to inject some variable to the MAKE process to control the target platform, like:

make all MY_TARGET_DEVICE=ultrabox-500 MY_TARGET_DEVICE_VERSION=1.3

While MAKAO will not try to interpret these settings, it can print it out in the build log. To do that, you want to tell MAKAO, which variables are your build settings, using the SETTINGS variable like so:

SETTINGS := MY_TARGET_DEVICE MY_TARGET_DEVICE_VERSION

Now during the build, these settings will be printed in the log along with the other crucial build-related information.

Build cache

Starting from version 2.x MAKAO supports automatic caching to speed up the build process.

Why is caching needed?

In large projects, setting up the build can be a time consuming process. If your project has multiple components then gathering information about what to build from these sources may take quite a lot of time. Instead of doing this at every build, MAKAO first prepares a cache file (called .makao-cache) and saves there all the crucial information on how to build things the way you wanted. Each sequential build can then use this information to save time.

How does it work?

In order to understand how caching works, we first need to acknowledge that there are 3 sources of information that MAKAO processes in order to figure out how to build your project. These are:

Based on this information, MAKAO produces a set of rules to build the elements of your project and saves these rules in the .makao-cache file inside your project. Now, when you run the build the second time MAKAO then uses the file dependencies mechanism to check if your Makefile of component description files were changed. It also checks if your command line has changed (it uses .makao-last-command file in your project to do this). If none of these sources has changed (which is a common case!) MAKAO will run the build using the cache file, which yields significant time savings. If you tweaked the Makefile, component description files or the command line the cache is first rebuilt, and then the build is executed.

When else is the cache rebuild?

As stated before, the cache is rebuilt if you change your Makefile, component description files or command line arguments. However, the cache is also rebuild if you change the MAKAO files - for example if you upgrade to a newer version or you tweak something in the toolchain definition.

Does it support parallel jobs?

Absolutely! MAKAO builds will use as many jobs as you provided using the GNU Make -j switch.

Forcing cache rebuild and clear

In rare cases you may want to clear the cache or force it to be rebuilt.

To clear the cache simply delete the .makao-cache file or run this command:

make clear-cache

To force MAKAO to rebuild cache use this command:

make rebuild-cache

Nameplate support

Starting from version 1.1.0 MAKAO supports the so called nameplate records generated by a custom tool: nameplate.exe

In order to use this feature, a path to this executable must be specified in the NAMEPLATE_EXE variable. The software nameplate is generated from a JSON file specified by the NAMEPLATE_SW_JSON variable. The contents of the software nameplate can be built based on a binary file specified by the NAMEPLATE_SW_BINARY_FILE variable. The software revision is specified by NAMEPLATE_SW_REV variable.

The generated nameplate can be merged with an output binary file. In such case the following two variables need to be included: NAMEPLATE_SW_ADDR which specified the target address, under which the nameplate will be placed and ELF_TO_MERGE_WITH_NAMEPLATE which specifies an *.elf file, to which the nameplat will be injected.

A typical usage of this feature is to prepare a JSON file, specifying basic properties of a given software. Next, in the Makefile we specify the following set of variables:

NAMEPLATE_SW_JSON = my_nameplate_sw.json
NAMEPLATE_SW_BINARY_FILE = my_output.bin
NAMEPLATE_SW_REV = $(shell svnversion | cut -d: -f2 | tr -d MSP)

The given NAMEPLATE_SW_REV definition uses subversion to poll for the actual software revision number from the version control repository. In addtition, in order to merge the namplate with output file, the following two definitions may be used:

NAMEPLATE_SW_ADDR = 0x7ff80
ELF_TO_MERGE_WITH_NAMEPLATE = my_output.elf

By specifying these variables in a Makefile, the following make targets become available:

make nameplate_sw.bin

generates software nameplate

make output+nameplate.elf

generates software nameplate and merges it with my_output.elf. You can also use:

make output+nameplate.bin

Please note that the generated nameplate_sw.bin and output+nameplate.elf.bin file names are fixed and always the same.

Support for SonarQube.

Starting from version 2.2 MAKAO includes support for SonarQube code analysis. This support includes only generation of the "sonar-project.properties" file, that is used by SonarQube parser. In order for this to work the following two definitions must be set: SONAR_PROJECT_KEY and SONAR_LOGIN_TOKEN MAKAO will pass to the SonarQube all the files and components that are used to build the project. This can be narrowed down by using SONAR_EXCLUDE_DIR and SONAR_EXCLUDE_FILES In example:

SONAR_LOGIN_TOKEN := 1234567890123456789012345678901234567890
SONAR_PROJECT_KEY := my-software
SONAR_EXCLUDE_DIR := externals/hal/hal_ports externals/hal/unit_testing
SONAR_EXCLUDE_FILES := test.c

Support for ccache.

Starting from version 2.3 MAKAO includes support for ccache tool (https://github.com/ccache/ccache) This tool in many cases speeds up the builds by not rebuilding the files that are already built in the same or other workspace. The support is ready out of the box. The only thing needed is to setup an environment variable that MAKAO can see called:

MAKAO_DEFAULT_CCACHE_EXE

with the path to the ccache executable.

Tips on integration with Subversion.

The component feature of MAKAO integrates nicely with subversion "externals" feature. The idea is to have components in separate common repository or repositories. Other projects may "link" the requested components using svn:externals folder property and check out the whole source tree at once. For example, lets assume that project "coffee machine" uses a "lcd_driver" and "buttons" components. The project is stored in a dedicated repository, and the components are placed in globally available component repository. Inside the project tree we create a folder called "externals" and add svn:externals property to it. The property value may look like this:

lcd_driver https://svn.reposerver.com/svn/components/lcd_driver
buttons https://svn.reposerver.com/svn/components/buttons

Now, during project checkout, the external components will also be checked out automatically, giving the whole source tree ready for building.

Since MAKAO version 1.2.0, the components can be automatically imported from an on-line repository using the 'make import' command. See Importing components using MAKAO for details.

Please consult the SVN book for details on how to use svn:externals property feature.

Tips on integration with IDE or development environment.

In order to make things even easier for the end user, you may want to consider simplifying MAKAO inclusion into the Makefile. You may want to provide default values for the following variables (in example - using environment variables):

and in addition, define an environment variable called MAKAO, that will hold a complete path to the main MAKAO file: makao.mak

This way, the user Makefile will just require a single line to include MAKAO:

include $(MAKAO)