Build Implementation

[English]

Design Philosophy

The ARMINO build system is mainly designed with object-oriented thinking. Key points to understand:

  • CMakeLists.txt is scanned twice, allowing special handling during dependency expansion

  • Dependencies are expanded before building to simplify dependency specification

  • projects/components are derived from cmake’s projects/target, override defaults by setting specific variables before function calls, and set specific properties after function calls

  • Allow projects/components to add hooks before, during, and after core script execution

  • Provide encapsulated APIs to handle over 90% of common scenarios; only rare special cases require complex cmake scripts

Build Scripts

The ARMINO build system list files are located in /tools/build_tools/cmake. The modules implementing core build system functionality are as follows:

  • build.cmake - Build-related functions: build initialization, retrieving/setting build properties, build processing.

  • component.cmake - Component-related functions: adding components, retrieving/setting component properties, registering components.

  • kconfig.cmake - Generate configuration files (sdkconfig, sdkconfig.h, sdkconfig.cmake, etc.) from Kconfig files.

  • target.cmake - Set build target and toolchain file.

  • utilities.cmake - Other helper commands.

In addition to these files, there are two important CMake scripts in /tools/build_tools/cmake:

  • armino.cmake - Sets build parameters and imports the core modules listed above.

  • project.cmake - Imports armino.cmake and provides a custom project() command that handles all the heavy lifting when building executables. Included in the top-level CMakeLists.txt of standard ARMINO projects.

Other files in /tools/build_tools/cmake are supporting files or third-party scripts for the build process.

Build Process

The build process can be roughly divided into four phases: initialization, component list generation, component processing, and finalization.

Initialization

This phase sets up necessary parameters for the build.

  • After importing armino.cmake into project.cmake, the following steps are executed:
    • Set ARMINO_PATH from environment variable or infer relative path from the project.cmake path included in the top-level CMakeLists.txt.

    • Add /tools/build_tools/cmake to CMAKE_MODULE_PATH and import core modules and various helper/third-party scripts.

    • Set build tools/executables, such as the default Python interpreter.

    • Set global build parameters: compile options, compile definitions, include directories for all components.

    • Add components from components and middleware to the build.

  • The initial part of the custom project() command executes the following steps:
    • Set ARMINO_TARGET in environment variable or CMake cache, and set the corresponding CMAKE_TOOLCHAIN_FILE to use.

    • Add components from EXTRA_COMPONENTS_DIRS to the build

    • Prepare arguments for calling armino_build_process() from variables like COMPONENTS/EXCLUDE_COMPONENTS, SDKCONFIG, SDKCONFIG_DEFAULTS, etc.

Calling the armino_build_process() command marks the end of this phase.

Component List Generation

This phase builds a list of components to be processed during the build, occurring in the first half of armino_build_process().

  • Find the public and private dependencies of each component. Create a subprocess that executes each component’s CMakeLists.txt in script mode. The values of armino_component_register REQUIRES and PRIV_REQUIRES parameters are returned to the parent process. This is component dependency expansion (or early expansion). The variable CMAKE_BUILD_EARLY_EXPANSION is defined during this step.

  • Recursively import components based on public and private dependencies.

Note

Each component’s CMakeLists.txt is executed twice. The first time occurs during the _<Component List Generation> phase to allow armino_component_register() to expand component dependencies. At this time, Kconfig has not been loaded yet, so you cannot use CONFIG_XXX values from Kconfig to decide whether a component should be loaded. If you need to add component dependencies based on CPU during the component list generation phase, you can refer to the following approach:

armino_build_get_property(target ARMINO_SOC)
if ("${target}" STREQUAL "bk7239")
    list(APPEND depenency_component)
endif()

Component Processing

This phase processes the components in the build, the second half of armino_build_process().

  • Load project configuration from the sdkconfig file and generate sdkconfig.cmake and sdkconfig.h header files. These two files define configuration variables/macros that can be accessed from build scripts and C/C++ source/header files respectively.

  • Import each component’s project_include.cmake.

  • Add each component as a subdirectory and process its CMakeLists.txt. The component CMakeLists.txt calls the registration command armino_component_register to add source files, import directories, create component libraries, link dependencies, etc.

Finalization

This phase is the remaining steps of armino_build_process().

  • Create the executable and link it to the component libraries.

  • Generate project metadata files such as project_description.json and display information about the built project.

  • The execution steps of the compilation process are all saved in the build.ninja file in the build directory under the corresponding project soc. If you need to view the detailed compilation tree, you can check the armino folder in the same directory, which stores .c.obj and .a files from the compilation process.

Please refer to /tools/build_tools/cmake/project.cmake for more information.