【实战指南】 CMake搭建编译环境总结

简介: 本文总结了使用CMake搭建编译环境的技巧,涵盖单个及多个源文件的编译、CMakeLists嵌套管理、变量设置、交叉编译配置、常用编译选项及警告处理等内容。通过实例说明了如何高效组织工程结构,并利用CMake灵活控制编译流程,适用于嵌入式开发场景。

CMake搭建编译环境总结


目录



个人简介提供更多文章介绍入口


前言

  交叉编译算是每个嵌入式开发者都会经历的一道坎吧,通俗的描述就是搭建Arm板代码编译环境,让代码能够在Arm板子上跑起来。常用到的编译工具为Makefile和CMake,本篇记录下CMake的常用技巧。

入门案例:单个源文件

代码路径:

https://gitee.com/LinuxTaoist/DesignMode/tree/master/FactoryMode

工程结构

. 
├── CMakeLists.txt
├── abstract_factory.cc
├── factory_method.cc   
└── simple_factory.cc

CMakeList

# 指定最低版本
cmake_minimum_required(VERSION 2.8)
## 指定为C++11 版本
set(CMAKE_CXX_STANDARD 11)
## 指定项目名称
project(FactoryMode)
## 为当前路径以及子目录的源文件加入由-D预编译定义
## add_definitions(-DFOO -DDEBUG ...)
## 编译工具
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")
## 设置C++编译参数(CMAKE_CXX_FLAGS是全局变量)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11 -g3 -fpermissive")
## 生成bin文件 AbFactory
add_executable(AbFactory  abstract_factory.cc)
## 生成bin文件 FacMethod
add_executable(FacMethod  factory_method.cc)
## 生成bin文件 SmpFactory
add_executable(SmpFactory simple_factory.cc)

工程编译

  CMakeList编写完以后,先执行cmake [CMakeList路径],然后make即可。

$ 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

多个源文件

代码路径:

https://gitee.com/LinuxTaoist/DesignMode/tree/master/Proxy

工程结构

  对于工程中存在大量的文件夹和文件时,一个CMakeLst虽然可以将其全部编译,但是维护起来非常麻烦。

  对于庞大的代码架构场景,通常会按模块划分,将一个模块的代码放到一个CMakeList中配置编译,若模块代码还是很多,将此模块再细分成多个小模块用多个CMakeList管理编译。然后将这些CMakeList按照路径层层嵌套。

  如此工程中各个CMakeList树状层层嵌套,最终都会被嵌套至最顶层CMakeList。类似如下结构:

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

  其中Proxy下的CMakeList会包含Client、Ipc、Server中的CMakeList。假设Server子路径还有子文件夹,Server的CMakeList就继续向下包含。

这么做的优点如下:

  • 每个CMakeList职责清晰。只需专注于当前模块或者当前路径的源码编译。
  • 方便模块化编译管理。当不需要编译哪个模块时,只需在顶层CMakeList屏蔽包含指定路径CMakeList即可。
  • 便于维护。每个CMakeList的代码量都比较少,且功能明确,维护者一眼就能看懂。

CMakeList

  • 顶层CMakeList
# 指定最低版本
cmake_minimum_required(VERSION 2.8)
## 指定为C++11 版本
set(CMAKE_CXX_STANDARD 11)
## 指定项目名称
project(ProxyMode)
## 为当前路径以及子目录的源文件加入由-D预编译定义
## add_definitions(-DFOO -DDEBUG ...)
## 编译工具
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")
## 设置C++编译参数(CMAKE_CXX_FLAGS是全局变量)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11 -g3 -fpermissive")
## 包含子路径
add_subdirectory(Server)
add_subdirectory(Client)
add_subdirectory(Ipc)

顶层CMakeList一般需要做如下事项:

① 配置工程相关的属性:使用CMake版本、工程名

② 配置交叉工具:设置编译器、增加编译参数

③ 包含需要嵌套的子路径CMakeList

  • Server路径 CMakeList
## 指定最低版本
## 指定最低版本
cmake_minimum_required(VERSION 2.8)
## 宏
set(PROJECT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/..)
cmake_policy(SET CMP0046 NEW)
## 输出路径
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)
## 库路径
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)

子路径下的CMakeList需要关心编译文件:

① 包含头文件路径

② 设置目标生成路径

③ 设置编译目标,bin或so

然后就是根据预期编译的结果,使用相关的变量即可。例子中,为了方便执行,增加了build.sh编译脚本。这个脚本代替执行编译命令,同时将编译生成的缓存文件放到指定路径管理。

## buid.sh
rm -rf ../Out/Cache
rm -rf ../Out/bin/*
rm -rf ../Out/lib/*
mkdir -p ../Out/cache/
cd ../Out/cache/
cmake ../../
make

其他用法

设置局部变量

## 设置局部变量PROJECT_PATH
set(PROJECT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/..)
## 使用局部变量PROJECT_PATH
include_directories(${PROJECT_PATH}/Ipc)

设置自定义全局变量

## Proxy/CMakelists.txt
## 设置自定义全局变量 PROJECT_DESC
set(PROJECT_DESC "This is project")
set_property(GLOBAL PROPERTY source_list_property "${PROJECT_DESC}")

获取自定义全局变量

## Proxy/Ipc/CMakeLists.txt
## 获取自定义全局变量 PROJECT_DESC
get_property(PROJECT_DESC GLOBAL PROPERTY source_list_property)
message("PROJECT_DESC=${PROJECT_DESC}")

指定目标(bin/库)输出路径

## 设置库输出路径
set(LIBRARY_OUTPUT_PATH     xx/Out/lib)
## 设置bin文件输出路径
set(EXECUTABLE_OUTPUT_PATH  xx/Out/bin)

设置环境变量

set(ENV{<variable>} [<value>])   
ENV:环境变量标志性前缀
variable:变量名称
value:变量值

E.g 设置环境 CMAKE_FILE

## 设置环境变量
set(ENV{CMAKE_FILE} "./IPC")

获取环境变量

# 判断CMAKE_FILE环境变量是否定义
if(DEFINED ENV{CMAKE_FILE})
    message("CMAKE_FILE: $ENV{CMAKE_FILE}")
else()
    message("NOT DEFINED CMAKE_FILE VARIABLES")
endif()

设置编译器

## 指定C编译工具
set(CMAKE_C_COMPILER "gcc")
## 指定C++编译工具
set(CMAKE_CXX_COMPILER "g++")

当编译工具链路径被加到环境变量中,可以直接写编译工具的名称。在配交叉编译工具时,此处应写对应交叉编译工具链的绝对路径。

设置依赖库路径

## 括号为依赖库的绝对路径
link_directories(${OUTPUT_PATH}/lib)

包含头文件路径

## 括号为包含头文件的绝对路径
include_directories (${PROJECT_PATH}/Ipc)

添加编译器编译选项

## 针对所有编译器,开启编译警告 (包括C、C++编译器)
add_compile_options("-Wall -Werror")
## 针对C编译器,开启编译警告
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
## 针对C++编译,开启编译警告
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")

添加打印

## 打印CMAKE_CXX_FLAGS的值
message("${CMAKE_CXX_FLAGS}")

CMakeLists路径嵌套

## 添加当前路径Client文件
add_subdirectory(Client)

控制编译流程

option 语法

## option编译流程控制
 option(<variable> "<help_text>" [value])
 variable 选项名
 help_text 描述、解释、备注
 value 选项初始化值(除ON而外全为OFF)

E.g TEST_OPTION宏控制编译流程[1]

option(TEST_OPTION "test opiton" 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脚本传递宏至CMakeList

命令行执行cmake时,跟随-DXXX,即可从命令行传递宏XXX至CMakeList。将此命令行写入脚本,便能实现Shell脚本传递宏至CMakeList。

## 增加TEST宏
cmake . -DTEST
## 增加TEST_OPTION=ON
cmake . -DTEST_OPTION=ON

CMakeLists传递变量至代码工程

## 向代码工程添加TEST宏
add_definitions(-DTEST)

代码判断宏TEST是否有定义,实现宏控

// *.c / *.cpp
#ifdef TEST
...  // code
#endif
#if defined TEST
...  // code
#endif

编译警告

CMake编译警告和报错设置

gcc本身设置了一些编译告警/报错选项,归类如下[2]

  • -Werror:-Werror=xxx,表示将xxx的warning变为error,例如-Werror=select, -Werror=return-type
  • -Wall:激活所有的warnings
  • -Wextra:激活不在-Wall所在的warning的其它warnings
  • -Wpedantic: 对于所有不符合 ISO C/ISO C++ 语言标准的源代码发出警告,等价于-pedantic
    -pedantic-errors参数将这些警告视为错误,等同于-Werror=pedantic
  • -Wconversion: 在隐式转换可能导致值变化的时候发出警告。在隐式转换的时候,如果值发生变化,那么结果可能就不是预料中的,所以最好使用显式转换。
  • -Wshadow:激活遮蔽(如两个嵌套的for循环都用变量i做index)类型的warning,即:
    -Wshadow=global:激活任意类型的遮蔽;
    -Wshadow=local:激活local变量的遮蔽(如两个嵌套的for循环都用变量i做index);
    -Wshadow=compatible-local:激活local变量的遮蔽,考虑变量类型(如上例中的i在内外两层的for循环中是不同的类型);

E.g 打开所有编译告警,并视警告为错误,出现任何警告放弃编译

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror")

常用警告

GCC编译器支持对代码进行诊断,针对代码本身不是错误但是疑似错误或者可能存在风险的地方发出警告,而警告编译选项就是用于控制需要告警的警告类型的。常见告警如下[3]

  • -Wall
    这是一个非常常用的编译选项,用于启用一批比较常见且易于修改的警告,这些选项都是对代码进行基本的检查,比如下面这些:
选项 作用
-Waddress 检查是否存在可疑的内存地址使用
-Wformat 检查标准库函数的使用格式是否正确,比如printf的格式化字符串中的格式符和对应的参数是否匹配
-Wunused-function 对已声明但是未定义的静态函数和未被使用的非内联静态函数发出警告
-Wswitch 当用switch用于枚举类型时,判断分支是否包含所有枚举值,否则发出警告
-Wunused-variable 对声明但未被使用的变量发出警告
-Wunused-but-set-variable 对声明且被赋值但未被使用的变量发出警告
-Warray-bounds=1 数组越界检查,需启用选项-ftree-vrp

完整列表参考 Warning-Options[4]

:当需要排除某些类型的警告,使用-Wno-xxx。 比如使用-Wall -Wno-unused-variable可以从-Wall中排除-Wunused-variable

  • -Wextra
    单单只有-Wall可能还不够严格,GCC还有-Wextra作为补充,包括另外一些没有被-Wall包含的警告类型,譬如:
选项 作用
-Wcast-function-type 当函数被强转为不兼容的函数指针时发出警告
-Wempty-body 当存在空的if、else或者do while语句时发出警告
-Wunused-parameter 当函数有未被使用的参数时发出警告,需配合-Wall
-Wunused-but-set-parameter 当存在被设置但是未被使用的参数发出警告,需配合-Wall
-Wsign-compare 当比较有符号和无符号值时发出警告

配置交叉编译环境常需要的修改

设置默认库和头文件搜索路径

编译默认会从/usr/include目录中搜索头文件、从/usr/lib中搜索依赖库。当设置了CMAKE_SYSROOT后,则会从xxx/usr/include搜索头文件、从xxx/usr/lib中搜索依赖库。

## 系统库路径:${SDKTARGETSYSROOT}/usr/lib
## 系统头文件:${SDKTARGETSYSROOT}/usr/include
set(CMAKE_SYSROOT "${SDKTARGETSYSROOT}")

设置交叉编译工具链

Linux系统在嵌入式板子上运行,需要与嵌入式板配套的交叉编译工具链编译。

同样的,个人代码也需要与编译Linux配套的交叉工具编译,才能在Linux环境运行。一般在Ubuntu上编译运行,只需要设置为gcc/g++即可。

编译工具链都是由厂商提供,用户只需要在编译脚本配置即可。设置交叉编译工具链方式如下:

## 绝对路径
set(CMAKE_C_COMPILER "xxx/arm-linux-gcc")
set(CMAKE_CXX_COMPILER "xxx/arm-linux-g++")

设置浮点运算处理方式

在某些gcc编译器会检查软浮点和硬浮点设置,报错log如下:

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>

初步看报错log,是因为编译器没有文件stubs-soft.h。猜测可能此编译器不支持软浮点运算?

解决方法是在编译脚本将其设置为硬浮点运算,如下方式:

## 第一种
add_definitions("-mfloat-abi=hard -mfpu=neon")
## 第二种
add_compile_options(-mfpu=neon -mfloat-abi=hard)
## 第三种
set(CMAKE_C_FLAGS   "${CMAKE_C_FLAGS} -mfloat-abi=hard -mfpu=neon")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=hard -mfpu=neon")

常见场景

编译动态库

## 生成libtest.so
### 添加源码路径
aux_source_directory (xxx/src            SRC_LIB_TEST)
### 生成so库
add_library          (libtest SHARED     ${SRC_LIB_TEST})
### 指定生成目标名libtest.so
set_target_properties(libtest PROPERTIES OUTPUT_NAME "test")
### 链接语言C++
set_target_properties(libtest PROPERTIES LINKER_LANGUAGE CXX)
### 链接依赖库
target_link_libraries(libtest stdc++)
### 添加依赖库,会先检查依赖库是否生成
add_dependencies     (libtest libstdc++)

编译静态库

## 生成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++)

编译可执行文件

## 生成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++)

[1]

CMake之Option使用简介: https://blog.csdn.net/lhl_blog/article/details/123553686

[2]

gcc和cmake的编译警告和报错设置: https://blog.csdn.net/LCHUESTC/article/details/122602001

[3]

GCC常用编译选项: https://zhuanlan.zhihu.com/p/393419013

[4]

Warning-Options: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html

相关文章
|
7天前
|
人工智能 数据可视化 安全
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
本文详解如何用阿里云Lighthouse一键部署OpenClaw,结合飞书CLI等工具,让AI真正“动手”——自动群发、生成科研日报、整理知识库。核心理念:未来软件应为AI而生,CLI即AI的“手脚”,实现高效、安全、可控的智能自动化。
34477 17
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
|
19天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
45307 142
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
8天前
|
人工智能 JSON 监控
Claude Code 源码泄露:一份价值亿元的 AI 工程公开课
我以为顶级 AI 产品的护城河是模型。读完这 51.2 万行泄露的源码,我发现自己错了。
4872 21
|
1天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
1971 6
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
7天前
|
人工智能 API 开发者
阿里云百炼 Coding Plan 售罄、Lite 停售、Pro 抢不到?最新解决方案
阿里云百炼Coding Plan Lite已停售,Pro版每日9:30限量抢购难度大。本文解析原因,并提供两大方案:①掌握技巧抢购Pro版;②直接使用百炼平台按量付费——新用户赠100万Tokens,支持Qwen3.5-Max等满血模型,灵活低成本。
1813 5
阿里云百炼 Coding Plan 售罄、Lite 停售、Pro 抢不到?最新解决方案