ATE Build System is a non-recursive set of makefile definitions and templates that can be used to simplify the definition of targets. It is based on Make and allows to build the following types of targets:
- Native binaries
- Shared libraries
- Static libraries
Its design was inspired by the AOSP build system, although it is much lighter and simple.
In addition to GNU make
, you will need also Python 3
.
sudo apt install make python3
A top level makefile system should include the top.mk
file inside this repository. Targets can be defined in build.mk
files. There are a few macros that can be used when writing target scripts. In particular, the following user-defined functions are very commonly used in custom build files:
all-makefiles-under
: Takes a single directory input and finds allbuild.mk
files inside it. Paired with an include directory is used to include subdirectories to the build system.current-dir
: Obtains the path of the current file. It needs to be called before including any other file, otherwise an erroneous result will be returned.
BUILD_SYSTEM_DIR := tools/build_system
include $(BUILD_SYSTEM_DIR)/top.mk
include $(call all-makefiles-under, .)
In order to define targets local variables are used. To ensure that they are empty when defining a new target, we use the following include:
include $(CLEAR_VARS)
This will clear all local variables. Then we can define our local variables for the current target. Once we are done, we need to include one of the following three types of targets:
include $(BUILD_BINARY)
include $(BUILD_SHARED_LIBRARY)
include $(BUILD_STATIC_LIBRARY)
The following is an example of how to define a binary target and link it against a static library:
LOCAL_DIR := $(call current-dir)
include $(CLEAR_VARS)
LOCAL_NAME := test
LOCAL_CFLAGS := \
-Os \
-g3 \
-I$(LOCAL_DIR)/Inc \
-Wall \
-Werror
LOCAL_LDFLAGS := -lc
LOCAL_SRC := \
$(LOCAL_DIR)/Src/main.c
LOCAL_STATIC_LIBS := \
libtest_static
include $(BUILD_BINARY)
By defining the LOCAL_STATIC_LIBS
, all exported header paths are automatically included when building the binary. To define the static library we could use the following:
LOCAL_DIR := $(call current-dir)
include $(CLEAR_VARS)
LOCAL_NAME := test_static
LOCAL_CFLAGS := \
-Os \
-g3 \
-I$(LOCAL_DIR)/Inc \
-Wall \
-Werror
LOCAL_ARFLAGS := -rcs
LOCAL_SRC := \
$(LOCAL_DIR)/Src/testlib.c
LOCAL_EXPORTED_DIRS := \
$(LOCAL_DIR)/Inc
include $(BUILD_STATIC_LIB)
LOCAL_EXPORTED_DIRS
is used to define the exported headers for the library. These are the ones that will get included when building binaries that link against this library. More than one path can be specified. All exported directories are included in the targets that link against this library.
ATE Build System supports custom compiler profiles. These are complex predefined compiler configurations that can be applied for each target. Currently it supports:
arm_clang
: Usesclang
with thearm-none-eabi
triplet. In addition, it uses the sysroot from thearm-none-eabi-gcc
toolchain, which is automatically detected from thearm-none-eabi-gcc
binary present in your PATH environment variable. Links againstlibc_nano
andlibstdc++_nano
. Uses the-nostdlib
linker flag.
When you define binary or library target the following make
autogenerated targets are created:
$(LOCAL_NAME)
: builds the target binary or library.clean_$(LOCAL_NAME)
: cleans the target binary or library, leaving other targets untouched.run_$(LOCAL_NAME)
: runs the target binary. As you may expect, this is only available for binaries.
A compilation database is automatically generated. If you don't want to symlink the compilation database to the root of the top level makefile, then define the following variable after including the top.mk
file:
include buildsystem/top.mk
SYMLINK_COMP_DB :=
You can also specify another path for the symbolic link:
include buildsystem/top.mk
SYMLINK_COMP_DB := my_root_dir
The output tree has the following layout:
build/
├── compile_commands.json
├── intermediates
│ ├── target_name
│ │ └── target_dir
│ │ └── src
│ │ ├── main.d
│ │ ├── main.db
│ │ └── main.o
├── lib
│ ├── exports
│ │ ├── target_name
│ │ │ └── target_dir
│ │ │ └── exported
│ │ │ ├── header.d
│ │ │ └── header.h
│ ├── shared_lib_target_name.so
│ └── static_lib_target_name.a
└── targets
├── binary_target_name
└── binary_target_name.map
.db
files identify partial compiler database entries. They are built into a singlecompile_commands.json
file in the build directory..d
files represent dependencies for specific files. Header files are not explicitly handled as dependencies. Instead, when the compiler compiles a translation unit into an object file it generates each.d
files with the dependencies of the translation unit (all included header files).- Exported header files are copied into the
build/lib/exports/target_name
directory, as they are needed when distributing a shared or static library, so they should be in the output tree. - All build outputs for each target are isolated in their own directory, ensuring that there are no name collisions, as target names need to be unique.
It is possible to specify a different name for the output build directory. This can be done by setting the BUILD_DIR
variable to a suitable relative or absolute path. This can be done either in the top level makefile before including the build system top.mk
file or by dynamically setting it when invoking make.
BUILD_DIR := my_ouput_dir
include buildsystem/top.mk
$ make BUILD_DIR=my_output_dir
When debugging and verifying that your build rules work as expected it is particularly useful to print all command invocations. This can be done by turning on verbose output dynamically by invoking main like:
make SILENT=
Overriding the SILENT
variable makes the build system display all commands with verbose output.