scripts/rules.mk

This is the main file of the build system, and it contains the generic rules and macros used by all the Makefiles. The behaviour of this file is modified by the macros:

  • TOOL: Selects the toolchain used for building.

  • SYS: Selects the target system for building.

  • ARCH: Selects the target architecture for building.

  • KBUILD_DEFCONFIG: Selects the Kconfig file used for configuration.

All the Makefiles are able to include this file based in the macro PROJECTDIR, as it is the first thing defined in any Makefile:

PROJECTDIR = ../
include $(PROJECTDIR)/scripts/rules.mk

rules.mk defines the common locations used by all the Makefiles:

  • INCDIR: Directory for include files.

  • BINDIR: Directory for binary executables.

  • LIBDIR: Directory for libraries.

  • SCRIPTDIR: Directory for shared scripts.

These locations are used in several places, for example, in the generic rules for the -I options. Every rule can be customized by the following macros:

  • PROJ_CPPFLAGS: Flags used by the preprocessor.

  • PROJ_CFLAGS: Flags used by the C compiler.

  • PROJ_LDFLAGS: Flags used by the linker.

  • PROJ_ASFLAGS: Flags used by the assembler.

  • PROJ_ARFLAGS: Flags used by the library archiver.

  • PROJ_RLFLAGS: Flags used by the library randomizer.

  • PROJ_LDLIBS: Flags used by the linker to include libraries.

All these macros are built based on four categories:

  • MORE_XXXX: Flags added by specific Makefiles.

  • SYS_XXXX: Flags defined for the target system (such as Linux or Nuttx).

  • TOOL_XXXX: Flags defined for the toolchain (such as gnu or clang).

  • User flags: Flags controlled by the user to customize the build:

    • CFLAGS: For the compiler command.

    • LDFLAGS: For the linker command.

    • LIBS: For the additional libraries added to the linker command.

    • ARFLAGS: For the library archiver command.

    • RLFLAGS: For the library index builder command.

    • ASFLAGS: For the assembler command.

For example, a Makefile can do:

PROJECTDIR = ../
include $(PROJECTDIR)/scripts/rules.mk

MORE_CPPFLAGS = -I./local_include

and all the objects compiled by that Makefile will include that option.

In the same way, a user can run the make with:

make CFLAGS=-g

and all the objects will be compiled adding the option -g to the C compiler flags.

rules.mk also defines the rules to maintain the recursive structure. The macro DIRS must be defined by the Makefile before including rules.mk and then make will apply automatically the recursive targets to them. For example:

PROJECTDIR = ../
DIRS = libevp-agent libevp-app-sdk
include $(PROJECTDIR)/scripts/rules.mk

all: libevp-agent libevp-app-sdk

defines libevp-agent and libevp-app-sdk as recursive targets and when the target all is required to build then make will move unconditionally to them and it will build the target all on them.

In the same way, when the special targets clean and distclean are invoked, the build system iterates unconditionally to the directories specified in DIRS, invoking the target on each directory, before invoking the target in the current directory.

rules.mk contains the more common clean and distclean actions that usually are required by the Makefiles, and the Makefiles don't have to define them unless they need special actions. For example:

clean:
        rm -f MQTT-C/*.o

will remove all the artifacts generated in the current directory and it will remove all the objects in the MQTT-C directory.

Toolchains and cross compilation

The toolchain is selected by the Make macro TOOL and it defines all the macros required for a specific toolchain. The file scripts/rules.mk defines the default values of all the tools using:

CXX = $(CROSS_COMPILE)$(COMPXX)
CC = $(CROSS_COMPILE)$(COMP)
AS = $(CROSS_COMPILE)$(ASM)
LD = $(CROSS_COMPILE)$(LINKER)
AR = $(CROSS_COMPILE)$(ARCHIVE)
CPP = $(CROSS_COMPILE)$(PRECOMP)
NM = $(CROSS_COMPILE)$(NAMES)
RANLIB = $(CROSS_COMPILE)$(RLIB)
OBJCOPY = $(CROSS_COMPILE)$(OCOPY)
OBJDUMP = $(CROSS_COMPILE)$(ODUMP)

All the tools are defined prepending the macro CROSS_COMPILE, which is used for cross compilation. For example, if the build is for linux aarch32 and musl libc using a PC which has installed a GNU arm toolchain for that configuration it can cross compile using something like:

make config
make TOOL=gnu CROSS_COMPILE=arm-linux-musleabi- ARCH=armel

which will use the cross compiler and will also select the desired target architecture for Toolchain for WASM modules. The first make execution with the target config configures the build (see config target for the config target and Architecture definition for the definition of the ARCH macro).

Every toolchain is expected to define the following macros:

  • COMPXX: C++ compiler.

  • COMP: C compiler.

  • ASM: Assembler program.

  • LINKER: Linker program.

  • ARCHIVE: Program used to create library archives.

  • PRECOMP: C preprocessor.

  • NAMES: Nm compatible program.

  • RLIB: Ranlib compatible program.

  • OCOPY: Objcopy compatible program.

  • ODUMP: Objdump compatible program.

Optionally, toolchains can define the following macros

  • TOOL_CFLAGS: Flags added by the toolchain to compile C files.

  • TOOL_CXXFLAGS: Flags added by the toolchain to compile C++ files.

  • TOOL_LDFLAGS: Flags added by the toolchain to to link programs.

  • TOOL_ASFLAGS: Flags added by the toolchain to assembly files.

  • TOOL_ARFLAGS: Flags added by the toolchain to create libraries.

  • TOOL_RLFLAGS: Flags added by the toolchain to create library indexes.

  • TOOL_LDLIBS: Libraries added by the toolchain to link programs.

TOOL can take one of the following values:

  • gnu: It is the default value and defines all the values for the GNU toolchain.

  • clang: Defines all the values for the clang toolchain.

  • wasi: Defines all the values for the Toolchain for WASM modules used to compile to wasm.

  • cppcheck: Toolchain that extends the GNU toolchain to perform static analysis.

For example, if static analysis using cppcheck is required, it is possible to run:

make config
make TOOL=cppcheck analysis

The gnu and clang toolchains can be customized using two macros:

  • SANITIZER: Setting it to ENABLED enables the sanitizer options.

  • COVERAGE: It can select a coverage tool for the build process.

    • ccov: Coverage using clang options.

    • gcov: Coverage using gcc options.

While ccov is tied to clang, gcov can be used with both toolchains in some systems. Both coverage tools add a coverage target that generates the coverage information. For example:

make config
make TOOL=clang SANITIZER=ENABLED COVERAGE=ccov test
make TOOL=clang COVERAGE=ccov coverage

will compile and execute the tests (see test target). with the sanitizer options and with the clang coverage instrumentation and the last make execution with the coverage target generates a directory called coverage that contains all the html and coverage information. The ccov coverage tool is also used in the ci and contains a special target coverage-ci that filters the coverage information to cover only the libevp-agent library.

Architecture definition

ARCH is not usually required, because it is usually derived from the output of uname, but it cannot be derived in that way for cross compilation or in systems that lack uname. In that case it is required and the list of accepted values for ARCH is:

System definition

As there are some build flags that depend of the target system the build system can be customized using the macro SYS. This macro is usually sets by default using uname, but it can be required in some systems lacking that tool or when cross compilation is used. The target build systems supported are:

  • posix: It defines the required options for fully POSIX complaint systems.

  • nuttx: It defines the required options for NuttX.

  • wasm: It defines the required options compiling for WASM target (used by modules).

Default rules

There is a set of rules that are shared between all the Makefiles and rules.mk contains the common definition for all of them.

FORCE:
.PHONY: FORCE

The target FORCE can be used in any rule to force a build of the target. It brings the same behavior of the common extension rule .PHONY, but it is pure POSIX without needing the GNU extension.

.s.o:
        $(AS) $(PROJ_ASFLAGS) $< -o $@

This rule generates an object file from an assembly file without applying the C preprocessor, while the rule

.S.o:
        $(CPP) $(PROJ_CPPFLAGS) $< | $(AS) $(PROJ_ASFLAGS) -o $@

uses the C preprocessor before assembling the file.

It contains rules to generate an object file from a C or C++ file:

.c.o:
        $(CC) $(PROJ_CFLAGS) -o $@ -c $<

.cpp.o:
        $(CC) $(PROJ_CXXFLAGS) -o $@ -c $<

It has a rule that can be used to generate an executable elf file from an object of the same name

.o.elf:
        $(CC) $(PROJ_LDFLAGS) -o $@ $< $(PROJ_LDLIBS)

that for example will generate hello from hello.o.

It also contains a special rule that compiles a C file into a wo file that is required when native and wasm applications are required in the same directory:

.c.wo:
        $(CC) $(PROJ_CFLAGS) -o $@ -c $<

.wo.wasm:
        $(CC) $(PROJ_LDFLAGS) -o $@ $<

rules.mk also contains a set of rules for debugging:

.c.s:
        $(CC) $(PROJ_CFLAGS) -S -o $@ $<

.c.i:
        $(CPP) $(PROJ_CPPFLAGS) -o $@ $<

.o.dump:
        trap "rm -f $$$$.dump" EXIT QUIT INT TERM;\
        $(OBJDUMP) -D $< > $$$$.dump && mv $$$$.dump $@

.elf.dump:
        trap "rm -f $$$$.dump" EXIT QUIT INT TERM;\
        $(OBJDUMP) -D $< > $$$$.dump && mv $$$$.dump $@

.o.lst:
        trap "rm -f $$$$.lst" EXIT QUIT INT TERM;\
        $(NM) $< > $$$$.lst && mv $$$$.lst $@

.elf.lst:
        trap "rm -f $$$$.lst" EXIT QUIT INT TERM;\
        $(NM) $< > $$$$.lst && mv $$$$.lst $@

.a.lst:
        trap "rm -f $$$$.lst" EXIT QUIT INT TERM;\
        $(NM) -A $< > $$$$.lst && mv $$$$.lst $@

Allowing such actions as:

  • Generating an assembly file from a C file.

  • Generating the output of the C preprocessor.

  • Generating a disassembly of an object file.

  • Generating a dissasembly of an elf file.

  • Generating a symbol list form an object.

  • Generating a symbol list form an elf file.

  • Generating a symbol list from a library archive.

It also defines rules for common targets, for example clean and distclean, which will have common command lines between the different directories, removing the generated artifacts by the previous commented rules. They also consider the definition of the macro DIRS and apply them in a recursive way.

It also contains a few rules that makes easier the integration with CMake:

# CMake rules
%/build/Makefile: %/CMakeLists.txt
        CC=$(CC) \
        CXX=$(CXX) \
        SYS=$(SYS) \
        ARCH=$(ARCH) \
        CFLAGS="$(CFLAGS)" \
        MBEDTLS_CFLAGS="$(MBEDTLS_CFLAGS)" \
        $(SCRIPTDIR)/cmake-$* $(PWD)/$(PROJECTDIR)

%: %/build/Makefile FORCE
        cd $@/build && $(MAKE) install

that enables actions such as:

# cmake dependencies
wasm-micro-runtime: wasm-micro-runtime/build/Makefile
flatcc: flatcc/build/Makefile
mbedtls: mbedtls/build/Makefile

that will generate a chain of dependencies which will compile correctly a CMake project containing a CMakeList.txt file.

Personal configuration

The build system allows a personal configuration file, useful in same cases where it can be very tedious to pass always all the parameters, and for that reason it tries to include the file config.mk (which can be customized by the user) from the top level directory.

This file can include multiple definitions, for example:

WASI_PREFIX=/opt/wasi-sdk/bin/
CFLAGS=-g -Og
TOOL=clang
KBUILD_DEFCONFIG=configs/unit-test-all-hubs-wasm.config

defining the value of the macros WASI_PREFIX, CFLAGS, TOOL and KBUILD_DEFCONFIG for all the Makefiles (see config target and Toolchains and cross compilation for the meaning of these macros), usable for debug purposes.