CMake In-Depth Analysis and Maintenance Manual
[ English | 简体中文 ]
This document is intended to serve as an in-depth technical reference for build system maintainers and advanced developers of openvela. It covers the internal architecture of the CMake build system, advanced development practices, and techniques for solving complex problems.
Before reading this document, it is recommended that you are familiar with the basic operations described in the CMake Quick Start Guide.
I. CMake Build System Architecture
This section provides a deep dive into the internal workflow and core design of the openvela CMake build system.
1. Overall Build Flow
The openvela CMake build process follows the standard NuttX framework, with nuttx/CMakeLists.txt as its entry point. The entire flow generates build rules during the cmake configuration phase and executes the compilation during the --build phase. It can be summarized into the following key stages:
- Configuration and Environment Check: Performs basic CMake configuration, resolves core working directories like
NuttXDirandAppDir, and executes fundamental sanity checks. - Kconfig and Configuration Import: Parses the
defconfigfile, identifies and links theboard,chip, andappsdirectories, and generates a complete Kconfig tree. Subsequently, it generates the.configfile and imports all macro definitions from it into the CMake environment as cache variables. - Toolchain Loading: Loads the corresponding CMake toolchain file based on configurations like
CONFIG_ARCH, setting up the cross-compilation environment. - Build Context Generation: Executes modules like
mkconfig.cmakeandgen_header.cmaketo generate necessary headers and configurations, such asconfig.handversion.h, ensuring the build environment is ready. - Target Library Generation: CMake iteratively traverses module directories such as
arch,drivers,mm,sched, andapps, compiling the source files of each module and packaging them into corresponding static libraries (.afiles). - Final Artifact Linking: Links all generated static libraries (
${nuttx_libs}) to produce thenuttxELF executable file. - Artifact Post-Processing: Executes user-defined
POST_BUILDactions, such as generating.binfiles viaobjcopyor packaging firmware.
The build steps are illustrated in the figure below:

2. Build Dependency Relationships
All modules are ultimately organized into different categories of library collections and linked to form the final product. The dependency relationship is shown in the figure below:
II. Advanced Development Practices Guide
1. Adapting Custom Boards and Chips
To add CMake support for your custom hardware, you need to create CMakeLists.txt files in the corresponding board and chip directories.
Example directory structure:
# Location: vendor/vendor_name
├── boards
│ ├── <chip_name>
│ │ └── <board_name>
│ │ ├── Kconfig
│ │ ├── CMakeList.txt <-- Top-level board CMake file
│ │ └── src
│ │ ├── Make.defs
│ │ └── CMakeList.txt <-- Board source CMake file
├── chips
│ └── chip_name
│ ├── Kconfig
│ ├── Make.defs
│ └── CMakeList.txt <-- Chip-level CMake file
Example CMakeLists.txt content:
-
chips/<chip_name>/CMakeLists.txt:# Add chip-related source files (e.g., startup files) set(SRCS chip_startup.S) # Add the source files to the 'arch' target, which will be archived into libarch.a target_sources(arch PRIVATE ${SRCS}) -
boards/<chip_name>/<board_name>/src/CMakeLists.txt:# Add board-level drivers and initialization source files set(SRCS board_source.c) # Add the source files to the 'board' target, which will be archived into libboard.a target_sources(board PRIVATE ${SRCS}) -
boards/<chip_name>/<board_name>/CMakeLists.txt:# Include the src directory in the build add_subdirectory(src) # Set the path for the Linker Script set_property(GLOBAL PROPERTY LD_SCRIPT "${NUTTX_BOARD_ABS_DIR}/scripts/app.ld" ) # Define a post_build target for automatic artifact processing after the build add_custom_target( nuttx_post_build ALL POST_BUILD COMMAND ${NUTTX_DIR}/../vendor/xxx/post_build.sh ${CMAKE_BINARY_DIR} )
2. Porting a Third-Party Library
Methodology: When to Reuse, When to Rewrite?
If a third-party library already provides a CMakeLists.txt, you can evaluate whether to reuse it.
Decision Principle: Follow this principle when choosing a porting method. You should not reuse a third-party library's CMakeLists.txt and should instead write a new adaptation script if any of the following conditions apply:
- The third-party library does not support cross-compilation or has strong dependencies on the host platform.
- The third-party library needs to be registered as a built-in application in openvela.
- The third-party library's
CMakeLists.txtcontains compile options that cannot be controlled externally. - The third-party library's build script defines too many redundant targets or targets that conflict with the system.
If the third-party library is a pure static library without the issues above, you may consider reusing its build script.
Method 1 (Recommended): Write a New CMakeLists.txt
This method offers the greatest flexibility and control by completely rewriting the build logic. For details, refer to the Basic Porting Method section in the CMake Quick Start guide.
Method 2: Reuse via add_subdirectory
-
Description: This method uses
add_subdirectory()to include the third-party library as a subproject and integrates its library target into openvela usingnuttx_add_external_library. -
Characteristics: Allows reuse of external scripts, reducing the amount of porting code. However, it may introduce redundant or conflicting targets, requiring a careful evaluation of its
CMakeLists.txt. -
Example (
libpng):# Control its build behavior by setting its CACHE variables externally set(PNG_SHARED OFF CACHE BOOL "Disable libpng shared library" FORCE) set(PNG_EXECUTABLES OFF CACHE BOOL "Disable libpng executable" FORCE) set(PNG_TESTS OFF CACHE BOOL "Disable libpng tests program" FORCE) # Add the third-party library as a subdirectory add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/libpng ${CMAKE_CURRENT_BINARY_DIR}/libpng EXCLUDE_FROM_ALL) # Integrate the target defined by the third-party library into the openvela build environment nuttx_add_external_library(png_static)
Method 3: Independent Build via ExternalProject_Add
-
Description: This method initiates a completely separate sub-build process to compile the third-party library and then brings its build artifacts into the main project as an
IMPORTEDlibrary. -
Characteristics: Provides good isolation but is complex, requiring manual handling of toolchain passing and artifact importing.
-
Example (
libpng):# Manually pass openvela's cross-compilation toolchain info to the sub-build process set(FLAGS_ARGS "$<JOIN:$<TARGET_PROPERTY:nuttx,COMPILE_OPTIONS>, >") set(EXTERN_C_FLAGS "${CMAKE_C_FLAGS} ${FLAGS_ARGS}") ExternalProject_Add( libpng_external SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/libpng/ BINARY_DIR ${CMAKE_BINARY_DIR}/external/libpng_external CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_C_FLAGS=${EXTERN_C_FLAGS} -DPNG_SHARED=OFF -DPNG_EXECUTABLES=OFF -DPNG_TEST=OFF TEST_COMMAND "" INSTALL_COMMAND "" ) # Bring the sub-build's artifacts into the main build process as an IMPORTED library add_library(libpng STATIC IMPORTED GLOBAL) set_target_properties(libpng PROPERTIES IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/external/libpng_external/libpng.a ) add_dependencies(libpng libpng_external) set_property(GLOBAL APPEND PROPERTY NUTTX_SYSTEM_LIBRARIES libpng)
III. FAQ
1. How can I make a module's header files visible to all other modules?
Use the nuttx_export_header() function.
-
Background: The
PUBLICkeyword oftarget_include_directories()is only effective for targets with an explicit dependency relationship (viatarget_link_libraries). In NuttX's flat module structure, there are typically no direct link dependencies between modules, soPUBLICcannot propagate include paths. -
Solution:
nuttx_export_header()adds the exported include directory path to a global property, making it visible to all subsequent targets.# nuttx/cmake/nuttx_export_header.cmake # Usage: # nuttx_export_header(TARGET <string> INCLUDE_DIRECTORIES <list>) # Example (in the CMakeLists.txt for libtommath): nuttx_export_header(TARGET libtommath INCLUDE_DIRECTORIES ${LIBTOMMATH_DIR}) -
Usage Recommendation: This method is suitable for common base libraries. For dependencies between specific libraries, it is still recommended to manage them explicitly using
nuttx_add_dependencies().
2. How can I remove or override global compile options?
Problem: The toolchain's default compile options (e.g., -march, -mabi) conflict with the requirements of a specific chip (e.g., -mcpu). How can this be resolved?
Solution: Use the nuttx_remove_compile_options() function.
-
Background: In Makefiles, the toolchain's default settings can be overridden by redefining
ARCHCPUFLAGSinMake.defs. CMake'sadd_compile_options()has no direct inverse operation, so openvela provides this enhanced function. -
Example:
# For example, with a custom toolchain for a RISC-V architecture where # -march and -mabi conflict with -mcpu # CMake does not provide an inverse operation for add_compile_options(), # so we can use the enhanced function nuttx_remove_compile_options(ARGS) here. # For this custom RISC-V architecture, remove the conflicting options. nuttx_remove_compile_options(-march -mabi) # CFLAGS before removal: -O2 -g -march=rv32if -mabi=ilp32f -mcpu=e907fp # CFLAGS after removal: -O2 -g -mcpu=e907fp
Appendix
Core CMake Modules
NuttX's CMake modules are defined in the nuttx/cmake/ directory. They are the core extensions of the build system, providing encapsulated, specialized functions, analogous to the tools and .mk scripts in the Makefile system.
| Module File | Function Description |
|---|---|
menuconfig.cmake |
Defines configuration targets like menuconfig based on Kconfig-frontend. |
nuttx_add_application.cmake |
A wrapper function for adding NuttX built-in applications. |
nuttx_add_dependencies.cmake |
An enhanced add_dependencies that can automatically pass the include directories of dependent targets. |
nuttx_add_library.cmake |
An enhanced wrapper function for CMake's add_library. |
nuttx_add_module.cmake |
A wrapper function for generating independent kernel modules (.ko). |
nuttx_add_romfs.cmake |
A wrapper function for adding and generating a ROMFS image. |
nuttx_add_subdirectory.cmake |
An enhanced wrapper function for CMake's add_subdirectory. |
nuttx_add_symtab.cmake |
Generates a symbol table for undefined symbols. |
nuttx_create_symlink.cmake |
A method for creating symbolic links (symlinks). |
nuttx_export_header.cmake |
Exports the include directories of a specified target to a global scope, making them visible to all modules. |
nuttx_generate_headers.cmake |
Defines the nuttx_context target, used for generating necessary header files. |
nuttx_generate_outputs.cmake |
Defines commands like objcopy for generating final artifacts such as .bin files. |
nuttx_kconfig.cmake |
The core logic for Kconfig parsing and .config generation. |
nuttx_mkconfig.cmake |
Generates include/nuttx/config.h based on the .config file. |
nuttx_mkversion.cmake |
Generates include/nuttx/version.h based on Git information. |
nuttx_parse_function_args.cmake |
An enhancement for CMake's built-in cmake_parse_arguments module. |
nuttx_redefine_symbols.cmake |
Used for the sim simulation platform to rename potentially conflicting symbols. |
nuttx_remove_compile_options.cmake |
An inverse operation to add_compile_options, used for removing global compile options. |
symtab.c.in |
A code generation template used in conjunction with nuttx_add_symtab.cmake. |