CMake搭建编译环境总结
目录
- 设置局部变量
- 设置自定义全局变量
- 获取自定义全局变量
- 指定目标(bin/库)输出路径
- 设置环境变量
- 获取环境变量
- 设置编译器
- 设置依赖库路径
- 包含头文件路径
- 添加编译器编译选项
- 添加打印
- CMakeLists路径嵌套
- 控制编译流程
- Shell脚本传递宏至CMakeList
- CMakeLists传递变量至代码工程
个人简介提供更多文章介绍入口
前言
交叉编译算是每个嵌入式开发者都会经历的一道坎吧,通俗的描述就是搭建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