CMake搭建编译环境总结
目录
- 前言
- 入门案例:单个源文件
- 工程编译
- 多个源文件
- 其他用法
- 设置局部变量
- 设置自定义全局变量
- 获取自定义全局变量
- 指定目标(bin/库)输出路径
- 设置环境变量
- 获取环境变量
- 设置编译器
- 设置依赖库路径
- 包含头文件路径
- 添加编译器编译选项
- 添加打印
- CMakeLists路径嵌套
- 控制编译流程
- Shell脚本传递宏至CMakeList
- CMakeLists传递变量至代码工程
- 编译警告
- CMake编译警告和报错设置
- 常用警告
- 配置交叉编译环境常需要的修改
- 设置默认库和头文件搜索路径
- 设置交叉编译工具链
- 设置浮点运算处理方式
- 常见场景
- 编译动态库
- 编译静态库
- 编译可执行文件
前言
交叉编译算是每个嵌入式开发者都会经历的一道坎吧,通俗的描述就是搭建Arm板代码编译环境,让代码能够在Arm板子上跑起来。常用到的编译工具为Makefile和CMake,本篇记录下CMake的常用技巧。
入门案例:单个源文件
代码路径:
https://gitee.com/LinuxTaoist/DesignMode/tree/master/FactoryMode
工程结构
代码语言:javascript复制.
├── CMakeLists.txt
├── abstract_factory.cc
├── factory_method.cc
└── simple_factory.cc
CMakeList
代码语言:javascript复制# 指定最低版本
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。类似如下结构:
代码语言:javascript复制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编译脚本。这个脚本代替执行编译命令,同时将编译生成的缓存文件放到指定路径管理。
代码语言:javascript复制## buid.sh
rm -rf ../Out/Cache
rm -rf ../Out/bin/*
rm -rf ../Out/lib/*
mkdir -p ../Out/cache/
cd ../Out/cache/
cmake ../../
make
其他用法
设置局部变量
代码语言:javascript复制## 设置局部变量PROJECT_PATH
set(PROJECT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/..)
## 使用局部变量PROJECT_PATH
include_directories(${PROJECT_PATH}/Ipc)
设置自定义全局变量
代码语言:javascript复制## Proxy/CMakelists.txt
## 设置自定义全局变量 PROJECT_DESC
set(PROJECT_DESC "This is project")
set_property(GLOBAL PROPERTY source_list_property "${PROJECT_DESC}")
获取自定义全局变量
代码语言:javascript复制## Proxy/Ipc/CMakeLists.txt
## 获取自定义全局变量 PROJECT_DESC
get_property(PROJECT_DESC GLOBAL PROPERTY source_list_property)
message("PROJECT_DESC=${PROJECT_DESC}")
指定目标(bin/库)输出路径
代码语言:javascript复制## 设置库输出路径
set(LIBRARY_OUTPUT_PATH xx/Out/lib)
## 设置bin文件输出路径
set(EXECUTABLE_OUTPUT_PATH xx/Out/bin)
设置环境变量
代码语言:javascript复制set(ENV{<variable>} [<value>])
ENV:环境变量标志性前缀
variable:变量名称
value:变量值
E.g 设置环境 CMAKE_FILE
代码语言:javascript复制## 设置环境变量
set(ENV{CMAKE_FILE} "./IPC")
获取环境变量
代码语言:javascript复制# 判断CMAKE_FILE环境变量是否定义
if(DEFINED ENV{CMAKE_FILE})
message("CMAKE_FILE: $ENV{CMAKE_FILE}")
else()
message("NOT DEFINED CMAKE_FILE VARIABLES")
endif()
设置编译器
代码语言:javascript复制## 指定C编译工具
set(CMAKE_C_COMPILER "gcc")
## 指定C 编译工具
set(CMAKE_CXX_COMPILER "g ")
当编译工具链路径被加到环境变量中,可以直接写编译工具的名称。在配交叉编译工具时,此处应写对应交叉编译工具链的绝对路径。
设置依赖库路径
代码语言:javascript复制## 括号为依赖库的绝对路径
link_directories(${OUTPUT_PATH}/lib)
包含头文件路径
代码语言:javascript复制## 括号为包含头文件的绝对路径
include_directories (${PROJECT_PATH}/Ipc)
添加编译器编译选项
代码语言:javascript复制## 针对所有编译器,开启编译警告 (包括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")
添加打印
代码语言:javascript复制## 打印CMAKE_CXX_FLAGS的值
message("${CMAKE_CXX_FLAGS}")
CMakeLists路径嵌套
代码语言:javascript复制## 添加当前路径Client文件
add_subdirectory(Client)
控制编译流程
option 语法
代码语言:javascript复制 ## option编译流程控制
option(<variable> "<help_text>" [value])
variable 选项名
help_text 描述、解释、备注
value 选项初始化值(除ON而外全为OFF)
E.g TEST_OPTION宏控制编译流程[1]
代码语言:javascript复制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。
代码语言:javascript复制## 增加TEST宏
cmake . -DTEST
## 增加TEST_OPTION=ON
cmake . -DTEST_OPTION=ON
CMakeLists传递变量至代码工程
代码语言:javascript复制## 向代码工程添加TEST宏
add_definitions(-DTEST)
代码判断宏TEST是否有定义,实现宏控
代码语言:javascript复制// *.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 打开所有编译告警,并视警告为错误,出现任何警告放弃编译
代码语言:javascript复制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 即可。
编译工具链都是由厂商提供,用户只需要在编译脚本配置即可。设置交叉编译工具链方式如下:
代码语言:javascript复制## 绝对路径
set(CMAKE_C_COMPILER "xxx/arm-linux-gcc")
set(CMAKE_CXX_COMPILER "xxx/arm-linux-g ")
设置浮点运算处理方式
在某些gcc编译器会检查软浮点和硬浮点设置,报错log如下:
代码语言:javascript复制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
。猜测可能此编译器不支持软浮点运算?
解决方法是在编译脚本将其设置为硬浮点运算,如下方式:
代码语言:javascript复制## 第一种
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")
常见场景
编译动态库
代码语言:javascript复制## 生成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 )
编译静态库
代码语言:javascript复制## 生成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 )
编译可执行文件
代码语言:javascript复制## 生成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
最后
用心感悟,认真记录,写好每一篇文章,分享每一框干货。
更多文章内容包括但不限于C/C 、Linux、开发常用神器等,可进入“开源519公众号”聊天界面输入“文章目录” 或者 菜单栏选择“文章目录”查看。公众号后台聊天框输入本文标题,在线查看源码。