Summary of Setting Up CMake Build Environment
Table of Contents
-
Introduction
-
Getting Started Example: Single Source File
-
Project Compilation
-
Multiple Source Files
-
Other Usages
-
Setting Local Variables
-
Setting Custom Global Variables
-
Getting Custom Global Variables
-
Specifying Target (bin/library) Output Path
-
Setting Environment Variables
-
Getting Environment Variables
-
Setting Compiler
-
Setting Dependency Library Path
-
Including Header File Paths
-
Adding Compiler Compilation Options
-
Adding Print Statements
-
Nesting CMakeLists Paths
-
Controlling Compilation Process
-
Passing Macros from Shell Scripts to CMakeLists
-
Passing Variables from CMakeLists to Code Projects
-
Compilation Warnings
-
CMake Compilation Warnings and Error Settings
-
Common Warnings
-
Common Modifications Needed for Cross Compilation Environment
-
Setting Default Library and Header File Search Paths
-
Setting Cross Compilation Toolchain
-
Setting Floating Point Handling Method
-
Common Scenarios
-
Compiling Dynamic Libraries
-
Compiling Static Libraries
-
Compiling Executable Files
Introduction
Cross compilation is a hurdle that every embedded developer will encounter. Simply put, it involves setting up a code compilation environment for the Arm board, allowing the code to run on the Arm device. The commonly used compilation tools are Makefile and CMake, and this article documents some common techniques for CMake.
Getting Started Example: Single Source File
Code Path: https://gitee.com/LinuxTaoist/DesignMode/tree/master/FactoryMode
Project Structure
.
├── CMakeLists.txt
├── abstract_factory.cc
├── factory_method.cc
└── simple_factory.cc
CMakeList
# Specify the minimum version
cmake_minimum_required(VERSION 2.8)
## Specify C++11 version
set(CMAKE_CXX_STANDARD 11)
## Specify project name
project(FactoryMode)
## Add pre-compiled definitions for current path and subdirectories
## add_definitions(-DFOO -DDEBUG ...)
## Compilation tools
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")
## Set C++ compilation parameters (CMAKE_CXX_FLAGS is a global variable)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11 -g3 -fpermissive")
## Generate bin file AbFactory
add_executable(AbFactory abstract_factory.cc)
## Generate bin file FacMethod
add_executable(FacMethod factory_method.cc)
## Generate bin file SmpFactory
add_executable(SmpFactory simple_factory.cc)
Project Compilation
After writing the CMakeList, execute cmake [CMakeList path], then make to compile.
$ cmake .
-- The C compiler identification is GNU 7.5.0
-- The CXX compiler identification is GNU 7.5.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /work/src/DesignMode/FactoryMode
$ make
Scanning dependencies of target SmpFactory
[ 16%] Building CXX object CMakeFiles/SmpFactory.dir/simple_factory.cc.o
[ 33%] Linking CXX executable SmpFactory
[ 33%] Built target SmpFactory
Scanning dependencies of target AbFactory
[ 50%] Building CXX object CMakeFiles/AbFactory.dir/abstract_factory.cc.o
[ 66%] Linking CXX executable AbFactory
[ 66%] Built target AbFactory
Scanning dependencies of target FacMethod
[ 83%] Building CXX object CMakeFiles/FacMethod.dir/factory_method.cc.o
[100%] Linking CXX executable FacMethod
[100%] Built target FacMethod
Multiple Source Files
Code Path: https://gitee.com/LinuxTaoist/DesignMode/tree/master/Proxy
Project Structure
When there are many folders and files in a project, a single CMakeList can compile everything, but maintaining it becomes very cumbersome.
For large code structures, it is common to divide the code by module, placing the code of one module in a CMakeList for compilation configuration. If a module still has a lot of code, further subdivide this module into smaller modules managed by multiple CMakeLists. Then nest these CMakeLists layer by layer according to the path.
Thus, each CMakeList in the project is nested in a tree structure, ultimately being nested into the top-level CMakeList. The structure looks like this:
Proxy/
├── CMakeLists.txt
├── Client
│ ├── CMakeLists.txt
│ └── main_client.cc
├── Ipc
│ ├── CMakeLists.txt
│ ├── msg_manager.cc
│ └── msg_manager.h
├── Server
│ ├── Api
│ │ ├── common_type.h
│ │ ├── led_manager_proxy.cc
│ │ └── led_manager_proxy.h
│ ├── CMakeLists.txt
│ ├── Led
│ │ ├── led_manager.cc
│ │ └── led_manager.h
│ └── main_server.cc
└── build
└── build.sh
The CMakeList under Proxy will include the CMakeLists from Client, Ipc, and Server. If the Server sub-path has subfolders, the CMakeList of Server will continue to include them.
The advantages of this approach are as follows:
- Each CMakeList has a clear responsibility. Focus only on the source code compilation for the current module or path.
- Convenient for modular compilation management. When a module does not need to be compiled, simply comment out the inclusion of the specified path CMakeList in the top-level CMakeList.
- Easy to maintain. Each CMakeList has a relatively small amount of code and clear functionality, making it easy for maintainers to understand at a glance.
CMakeList
- Top-level CMakeList
# Specify the minimum version
cmake_minimum_required(VERSION 2.8)
## Specify C++11 version
set(CMAKE_CXX_STANDARD 11)
## Specify project name
project(ProxyMode)
## Add pre-compiled definitions for current path and subdirectories
## add_definitions(-DFOO -DDEBUG ...)
## Compilation tools
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")
## Set C++ compilation parameters (CMAKE_CXX_FLAGS is a global variable)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11 -g3 -fpermissive")
## Include subdirectories
add_subdirectory(Server)
add_subdirectory(Client)
add_subdirectory(Ipc)
The top-level CMakeList generally needs to do the following: 1) Configure project-related properties: CMake version, project name; 2) Configure cross tools: set compiler, add compilation parameters; 3) Include the nested subdirectory CMakeLists.
- Server Path CMakeList
## Specify the minimum version
cmake_minimum_required(VERSION 2.8)
## Macro
set(PROJECT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/..)
cmake_policy(SET CMP0046 NEW)
## Output path
set(OUTPUT_PATH ${PROJECT_PATH}/Out)
set(LIBRARY_OUTPUT_PATH ${OUTPUT_PATH}/lib)
set(EXECUTABLE_OUTPUT_PATH ${OUTPUT_PATH}/bin)
## includes
include_directories(${PROJECT_PATH}/Ipc)
include_directories(${PROJECT_PATH}/Server/Api)
## Library path
link_directories(${OUTPUT_PATH}/lib)
## main_server.exe
set(SRC_BIN_CLIENT main_client.cc)
add_executable (client ${SRC_BIN_CLIENT})
set_target_properties(client PROPERTIES OUTPUT_NAME "mainclient")
target_link_libraries(client c pthread ledapi)
add_dependencies (client libledapi)
The CMakeList in the sub-path needs to focus on compiling files: 1) Include header file paths; 2) Set target generation path; 3) Set compilation targets, bin or so.
Then, based on the expected compilation results, use the relevant variables. In this example, to facilitate execution, a build.sh compilation script has been added. This script replaces the execution of the compilation command while managing the generated cache files in the specified path.
## build.sh
rm -rf ../Out/Cache
rm -rf ../Out/bin/*
rm -rf ../Out/lib/*
mkdir -p ../Out/cache/
cd ../Out/cache/
cmake ../../
make
Other Usages
Setting Local Variables
## Set local variable PROJECT_PATH
set(PROJECT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/..)
## Use local variable PROJECT_PATH
include_directories(${PROJECT_PATH}/Ipc)
Setting Custom Global Variables
## Proxy/CMakeLists.txt
## Set custom global variable PROJECT_DESC
set(PROJECT_DESC "This is project")
set_property(GLOBAL PROPERTY source_list_property "${PROJECT_DESC}")
Getting Custom Global Variables
## Proxy/Ipc/CMakeLists.txt
## Get custom global variable PROJECT_DESC
get_property(PROJECT_DESC GLOBAL PROPERTY source_list_property)
message("PROJECT_DESC=${PROJECT_DESC}")
Specifying Target (bin/library) Output Path
## Set library output path
set(LIBRARY_OUTPUT_PATH xx/Out/lib)
## Set bin file output path
set(EXECUTABLE_OUTPUT_PATH xx/Out/bin)
Setting Environment Variables
set(ENV{} [])
ENV: Environment variable prefix
variable: Variable name
value: Variable value
E.g Set environment CMAKE_FILE
## Set environment variable
set(ENV{CMAKE_FILE} "./IPC")
Getting Environment Variables
# Check if CMAKE_FILE environment variable is defined
if(DEFINED ENV{CMAKE_FILE})
message("CMAKE_FILE: $ENV{CMAKE_FILE}")
else()
message("NOT DEFINED CMAKE_FILE VARIABLES")
endif()
Setting Compiler
## Specify C compiler
set(CMAKE_C_COMPILER "gcc")
## Specify C++ compiler
set(CMAKE_CXX_COMPILER "g++")
When the toolchain path is added to the environment variable, the compiler name can be written directly. When configuring cross-compilation tools, the absolute path of the corresponding cross-compilation toolchain should be written here.
Setting Dependency Library Path
## The parentheses are the absolute path of the dependency library
link_directories(${OUTPUT_PATH}/lib)
Including Header File Paths
## The parentheses are the absolute path of the header files
include_directories (${PROJECT_PATH}/Ipc)
Adding Compiler Compilation Options
## Enable compiler warnings for all compilers (including C, C++ compilers)
add_compile_options("-Wall -Werror")
## Enable compiler warnings for C compiler
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
## Enable compiler warnings for C++ compilation
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
Adding Print Statements
## Print the value of CMAKE_CXX_FLAGS
message("${CMAKE_CXX_FLAGS}")
Nesting CMakeLists Paths
## Add current path Client files
add_subdirectory(Client)
Controlling Compilation Process
Option syntax
## Option compilation process control
option( "" [value])
variable Option name
help_text Description, explanation, remarks
value Option initialization value (all OFF except ON)
E.g TEST_OPTION macro controls compilation process[1]
option(TEST_OPTION "test option" ON)
if (DEFINED TEST_OPTION)
message(STATUS "TEST_OPTION defined: " ${TEST_OPTION})
else ()
message(STATUS "TEST_OPTION un-defined: " ${TEST_OPTION})
endif()
if (TEST_OPTION)
message(STATUS "TEST_OPTION ON.")
add_definitions(-DTEST_OPTION)
else ()
message(STATUS "TEST_OPTION OFF.")
endif()
if (NOT TEST_OPTION)
message(STATUS "NOT-TEST_OPTION ON.")
else ()
message(STATUS "NOT-TEST_OPTION OFF.")
endif()
Shell Scripts Passing Macros to CMakeLists
When executing cmake from the command line, follow with -DXXX to pass macro XXX to CMakeLists. Writing this command line into a script can achieve passing macros from Shell scripts to CMakeLists.
## Add TEST macro
cmake . -DTEST
## Add TEST_OPTION=ON
cmake . -DTEST_OPTION=ON
CMakeLists Passing Variables to Code Projects
## Add TEST macro to code projects
add_definitions(-DTEST)
The code checks whether the TEST macro is defined, implementing macro control.
// *.c / *.cpp
#ifdef TEST
... // code
#endif
#if defined TEST
... // code
#endif
Compilation Warnings
CMake Compilation Warnings and Error Settings
GCC itself sets some compilation warning/error options, classified as follows[2]:
-
-Werror:
-Werror=xxx, indicates that xxx warnings are treated as errors, for example,-Werror=select,-Werror=return-type -
-Wall: Activates all warnings
-
-Wextra: Activates other warnings not included in
-Wall -
-Wpedantic: Issues warnings for all source code that does not conform to ISO C/ISO C++ language standards, equivalent to
-pedantic. The-pedantic-errorsparameter treats these warnings as errors, equivalent to-Werror=pedantic. -
-Wconversion: Issues warnings when implicit conversions may cause value changes. When implicit conversions occur, if the value changes, the result may not be as expected, so it is better to use explicit conversions.
-
-Wshadow: Activates warnings for shadowing (e.g., using variable i as an index in two nested for loops), i.e.,
-Wshadow=global: activates any type of shadowing;-Wshadow=local: activates shadowing of local variables;-Wshadow=compatible-local: activates shadowing of local variables considering variable types.
E.g. Open all compilation warnings and treat warnings as errors, abandon compilation if any warnings occur
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror")
Common Warnings
The GCC compiler supports diagnosing code, issuing warnings for places that are not errors but may contain risks. Common warnings are used to control the types of warnings that need to be issued. Common warnings include[3]:
- -Wall is a very common compilation option used to enable a set of common and easily modified warnings. These options are used for basic checks of the code, such as:
| Option | Effect |
|---|---|
| -Waddress | Checks for suspicious memory address usage |
| -Wformat | Checks if the format used in standard library functions is correct, such as matching the format specifiers in printf’s format string with the corresponding parameters |
| -Wunused-function | Issues warnings for declared but undefined static functions and unused non-inline static functions |
| -Wswitch | When using switch for enumeration types, checks if all enumeration values are included in the branches, otherwise issues warnings |
| -Wunused-variable | Issues warnings for declared but unused variables |
| -Wunused-but-set-variable | Issues warnings for declared and assigned but unused variables |
| -Warray-bounds=1 | Array out-of-bounds check, requires enabling option -ftree-vrp |
For a complete list, refer to Warning-Options[4]
Note: To exclude certain types of warnings, use -Wno-xxx. For example, using -Wall -Wno-unused-variable can exclude -Wunused-variable from -Wall.
- -Wextra alone may not be strict enough; GCC also has
-Wextraas a supplement, including additional warning types not covered by-Wall, such as:
| Option | Effect |
|---|---|
| -Wcast-function-type | Issues warnings when a function is cast to an incompatible function pointer |
| -Wempty-body | Issues warnings when there are empty if, else, or do while statements |
| -Wunused-parameter | Issues warnings when a function has unused parameters, requires -Wall |
| -Wunused-but-set-parameter | Issues warnings for parameters that are set but unused, requires -Wall |
| -Wsign-compare | Issues warnings when comparing signed and unsigned values |
Modifications Commonly Needed for Cross Compilation Environment
Setting Default Library and Header File Search Paths
By default, compilation searches for header files in /usr/include and dependency libraries in /usr/lib. When CMAKE_SYSROOT is set, it will search for header files in xxx/usr/include and dependency libraries in xxx/usr/lib.
## System library path: ${SDKTARGETSYSROOT}/usr/lib
## System header files: ${SDKTARGETSYSROOT}/usr/include
set(CMAKE_SYSROOT "${SDKTARGETSYSROOT}")
Setting Cross Compilation Toolchain
Linux systems running on embedded boards require a cross-compilation toolchain that matches the embedded board.
Similarly, personal code also needs to be compiled with a cross-tool compatible with Linux to run in the Linux environment. Generally, compiling and running on Ubuntu only requires setting it to gcc/g++.
Cross-compilation toolchains are provided by vendors, and users only need to configure them in the compilation script. The way to set the cross-compilation toolchain is as follows:
## Absolute path
set(CMAKE_C_COMPILER "xxx/arm-linux-gcc")
set(CMAKE_CXX_COMPILER "xxx/arm-linux-g++")
Setting Floating Point Handling Method
Some gcc compilers check for soft floating point and hard floating point settings, reporting errors as follows:
armv7at2hf-neon-poky-linux-gnueabi/usr/include/gnu/stubs-32.h:7:11: fatal error: gnu/stubs-soft.h: No such file or directory
7 | # include <gnu/stubs-soft.h>
At first glance, the error log shows that the compiler is missing the file stubs-soft.h. It is speculated that this compiler may not support soft floating point operations?
The solution is to set it to hard floating point operations in the compilation script, as shown below:
## First method
add_definitions("-mfloat-abi=hard -mfpu=neon")
## Second method
add_compile_options(-mfpu=neon -mfloat-abi=hard)
## Third method
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfloat-abi=hard -mfpu=neon")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=hard -mfpu=neon")
Common Scenarios
Compiling Dynamic Libraries
## Generate libtest.so
### Add source path
aux_source_directory (xxx/src SRC_LIB_TEST)
### Generate so library
add_library (libtest SHARED ${SRC_LIB_TEST})
### Specify target name libtest.so
set_target_properties(libtest PROPERTIES OUTPUT_NAME "test")
### Link language C++
set_target_properties(libtest PROPERTIES LINKER_LANGUAGE CXX)
### Link dependency libraries
target_link_libraries(libtest stdc++)
### Add dependencies, will check if the dependency library is generated first
add_dependencies (libtest libstdc++)
Compiling Static Libraries
## Generate libtest.a
aux_source_directory (xxx/src SRC_LIB_TEST)
add_library (libtest STATIC ${SRC_LIB_TEST})
set_target_properties(libtest PROPERTIES OUTPUT_NAME "test")
set_target_properties(libtest PROPERTIES LINKER_LANGUAGE CXX)
target_link_libraries(libtest stdc++)
add_dependencies (libtest libstdc++)
Compiling Executable Files
## Generate test bin
include_directories (${PROJECT_SOURCE_DIR}/inc)
set(SRC_BIN_TEST ${PROJECT_SOURCE_DIR}/src/test.cpp)
add_executable (test ${SRC_BIN_TEST})
set_target_properties(test PROPERTIES OUTPUT_NAME "test")
set_target_properties(test PROPERTIES LINK_FLAGS "-Wl,-rpath-link=${LIBRARY_OUTPUT_PATH}")
target_link_libraries(test stdc++)
add_dependencies (test libstdc++)
References
[1]
Introduction to CMake Option Usage: https://blog.csdn.net/lhl_blog/article/details/123553686
[2]
GCC and CMake Compilation Warning and Error Settings: https://blog.csdn.net/LCHUESTC/article/details/122602001
[3]
Common GCC Compilation Options: https://zhuanlan.zhihu.com/p/393419013
[4]
Warning-Options: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
Conclusion
Put heart into understanding, carefully record, write every article well, and share every piece of valuable information.
More articles include but are not limited to C/C++, Linux, commonly used development tools, etc. You can enter “Article Directory” in the chat interface of the “Open Source 519 WeChat Official Account” or select “Article Directory” in the menu bar. Enter the title of this article in the chat box to view the source code online.

Previous Reviews
Open Source 519 Article Directory
Compilation of High-Frequency Interview Points in Embedded Systems
C++ Concurrency Programming – Synchronization of Concurrent Operations
C++ Concurrency Programming – Thread Management
C++ Design Patterns – Factory Pattern

Likes are a virtue