cmake相关
·
1.交叉编译参考
- 参考Cross Compiling for Linux
- 凡有不清楚的地方,可通过cmake的document里搜索相关知识
- 编译工具链必须设置在project函数之前
- 建议把开发库也放到编译工具链SYSROOT里
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm) //类似confiugre 一步里的--host,一般与编译工具链的target目录有密切关联;可设置为aarch64
set(CMAKE_SYSROOT /home/devel/rasp-pi-rootfs) //设置的ROOTFS,find_xxx函数会从中寻找库/头文件;是目标平台的环境
set(CMAKE_STAGING_PREFIX /home/devel/stage) //用于ROOTFS无法写入时,指定安装路径
set(tools /home/devel/gcc-4.7-linaro-rpi-gnueabihf) //指定编译工具链:tarball解压后的路径
set(CMAKE_C_COMPILER ${tools}/bin/arm-linux-gnueabihf-gcc) //交叉编译的cc compiler
set(CMAKE_CXX_COMPILER ${tools}/bin/arm-linux-gnueabihf-g++)
// 说明可执行程序不在ROOTFS寻找,库/头文件等应该只在ROOTFS寻找
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
2.find_library设置额外查询路径
- 使用
find_library函数查找需要的库- 在默认路径下,利用PATHS/HINTS指定额外路径即可
- 不设置NO_DEFAULT_PATH:设置会阻止去额外路径查询
- 类似makefile里的-Lpath -lxxx,当然要完成这个效果需要联动target_link_libraries
find_library(MOSQUITTO_LIBRARY NAMES mosquitto PATH ${DEPS_DIR})
- 当然cmake也可以直接设置上述的"-Lpath -lxxx"
set(DEV_LINKER_FLAGS "-Lpath -lxxx“ )
3.链接动态库
- 找到相关的库后,执行程序需要链接库
- 使用
target_link_libraries函数 - 类似makefile里的-lxxx
target_link_libraries(${PROJECT_NAME} ${YOUR_LIBRARY})
4.设定rpath
- 若生产环境,相关的库没有部署在系统默认路径
- 此时,需要指明这些库的位置
- 可使用
set_target_properties函数设置- 其中,使用$ORIGIN,表示程序的运行路径
- 从而达成去相对$ORIGIN的路径寻找相关库
- 类似makefile里的-Wl,-rpath=xxx
set_target_properties(${PROJECT_NAME} PROPERTIES
INSTALL_RPATH "$ORIGIN"
BUILD_WITH_INSTALL_RPATH TRUE
)
5.设置编译的源文件
- 使用
file函数设置目录文件 - 类似makefile里的vpath关键字
set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
file(GLOB SOURCES "${SOURCE_DIR}/*.c")
6.设置编译的输出路径
- 使用
set_target_properties函数输出目录 - 类似makefile里mv指令
set(BUILD_BIN_DIR "${CMAKE_CURRENT_BINARY_DIR}/bin")
set(BUILD_LIB_DIR "${CMAKE_CURRENT_BINARY_DIR}/lib")
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${BUILD_BIN_DIR})
7.设置编译的目标文件
- 使用
add_executable函数构建可执行程序 - 使用
add_library函数构建库文件 - 类似gcc -o $@ $^
add_executable(${PROJECT_NAME} ${SOURCES})
8.设置自定义的编译选项
- 使用
target_compile_definitions函数设置自定义的编译选项- 比如-DSELF_DEFINITION
- 比如-O0
- 这个函数需要在有编译目标文件后,才可以设置
- 在
add_executable、add_library函数后面
- 在
9.设置子目录的CMakeLists.txt调用
- 使用
add_subdirectory函数指定要调用的子目录CMakeLists.txt- 传递子目录路径
- 路径下有子目录的CMakeLists.txt
- 构建后,顶层make会自动先构建子目录,再构建顶层目录
- 传递子目录路径
9.1 子目录的CMakeLists.txt
# 子目录subdir下的CMakeLists.txt
# 目录结构
# subdir
# - src
# - include
# - lib
cmake_minimum_required(VERSION 3.10)
# 将源文件添加到静态库
# mylib,构建时会自动补全为libmylib.a/libmylib.so
add_library(mylib STATIC src/file1.c src/file2.c)
# set_target_properties 设置库的输出路径
# 设置库的安装路径:头文件、库;make install
install(DIRECTORY src/include/ DESTINATION include)
install(TARGETS lib DESTINATION lib)
9.2 顶层目录调用
add_executable(myapp main.cpp)
add_subdirectory(subdir)
target_link_libraries(myapp mylib) #不必写全路径,只要指明mylib即可;自动去subdir里找libmylib.a
# 必须有target_link_libraries,否则不会自动构建该库
10.cmake推荐范例
cmake 用于快速创建构建系统即Makefile,很多的cmake内置宏,可以在生产的Makefile里看到值
cmake 生成Makefile后,执行make即可构建。
该系统,比传统的编写Makefile多了一层更容易理解的cmake封装。
# 设置cmake版本要求
# 设置编译工具:*.cmake
# 包括基础头文件、库的寻找路径
# 设置cmake项目
# 设置该项目的源码文件路径
# 设置该项目的构建空间里的输出目录
# 设置该项目的编译目标文件:可执行文件/库
# 设置编译目标文件的属性
# 自定义的编译宏
# 依赖的动态库、静态库并设置链接: *.cmake
# 构建本地的代码库:动态库、静态库 && 设置链接 : CMakeLists.txt && 一般链接仅需要xxx库名即可
# 运行库路径
# 打印cmake编译信息
# 什么项目、源码目录、输出目录等,需要的信息
# 之后调用cmake -Sxxx -Byyy生成Makefile
# 执行make/make clean等方便完成项目的构建与清理
11.CMake宏
CMAKE_BINARY_DIR顶层CMakeLists.txt对应的构建目录- 全局变量
- 设置后不会改变
- 默认CMake -Sxxx -Byyy设置
各层CMakeLists.txt共用一个相同值
CMAKE_SOURCE_DIR顶层CMakeLists.txt所在的源码目录- 全局变量
- 设置后不会改变
- 默认CMake -Sxxx -Byyy设置
各层CMakeLists.txt共用一个相同值
CMAKE_CURRENT_BINARY_DIR各层CMakeLists.txt对应的构建目录- 一般默认为各层所在的目录
- 可指定,在所在目录创建子目录
各CMakeLists.txt独享
CMAKE_CURRENT_SOURCE_DIR各层CMakeLists.txt对应的源码目录- 一般默认为各层所在的目录
- 可指定,在所在目录创建子目录
各CMakeLists.txt独享
12.cmake根据编译工具判定目标平台的机器长度
- 交叉编译里,往往根据目标平台选择适配的交叉编译工具链
- 这样就可以基于交叉编译工具链,判定目标平台的机器长度
- 利用cmake自带的宏 CMAKE_SIZEOF_VOID_P
- 设置目标平台的机器长度相关预编译宏
- 就可以编写针对不同机器长度的程序代码
# 设置项目名称和最低版本要求
cmake_minimum_required(VERSION 3.10)
project(myapp)
# 添加可执行文件
add_executable(myapp main.cpp)
# 根据目标平台的指针大小设置预编译宏
if (CMAKE_SIZEOF_VOID_P EQUAL 4)
target_compile_definitions(myapp PUBLIC MACHINE_32_BIT)
message(STATUS "Target is 32-bit.")
else()
target_compile_definitions(myapp PUBLIC MACHINE_64_BIT)
message(STATUS "Target is 64-bit.")
endif()
13.设置编译器的兼容
虽然参考第8点,可设置自定义的编译选项;但如果需要兼容不同版本的编译器尚有不足。
低版本gcc 4.9.4 libc库没有local_time_r需要增加–gnu99/c99兼容
高版本gcc 13.1.0 libc库则可以正常使用local_time_r
兼容的关键在于激活-gnu99/c99的选项,cmakel里可以这样实现:
set(CMAKE_C_FLAGS_INIT "-std=gnu99 ${CMAKE_C_FLAGS_INIT}")
14.设置编译器针对机器的微调
arm版本的机器与对应的编译器:
- 机器,为armv8的32位兼容模式,即CPU arch=7、CPU part=0xd03
- 编译器,支持-march=armv7-a也支持-march=armv8-a
- 如果让编译器采用其默认配置,将构建针对armv8-a的一些指令
- 最终导致在32位兼容模式的机器上运行,出现非法指令即"illegal instruction"
解决方式,就是明确编译器-march=armv7-a -mtune=cortex-a8,这样向下兼容。默认可能-march=armv8-a -mtune=cortex-a53
cmake的设置方式如下:
target_compile_options(${PROJECT_NAME} PRIVATE -march=armv7-a -mtune=cortex-a8)
15. 动态生成version.h
为了更好地定位故障,在项目每次构建时将仓库的版本git describe写入version.h文件,从而获取实时版本信息
- CMakeLists.txt 的工作:
- 执行
git describe --tags --always --dirty命令获取版本信息 - 将信息写入VERSION
- 每次构建执行目标generate_version_h:会先删除version.h,再生成version.h
- 添加version.h进入构建系统
- 执行
- 创建version.h.in模板
- 注意"@VERSION@" 应该与CMake里的VERSION值一致
# Generate version.h from version.h.in
execute_process(
COMMAND git describe --tags --always --dirty
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Custom command to delete existing version.h and generate a new one
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/version.h
COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_BINARY_DIR}/version.h
COMMAND ${CMAKE_COMMAND} -E echo "#define VERSION \"${VERSION}\"" > ${CMAKE_BINARY_DIR}/version.h
DEPENDS ${CMAKE_SOURCE_DIR}/version.h.in
COMMENT "Generating version.h from version.h.in"
VERBATIM
)
# Add a custom target to ensure version.h is generated before the build
add_custom_target(generate_version_h
DEPENDS ${CMAKE_BINARY_DIR}/version.h
)
# Add a pre-build step to ensure version.h is generated before building the executable
add_dependencies(mosquitto_simulator generate_version_h)
# Include the generated version.h in the build
target_include_directories(mosquitto_simulator PRIVATE ${CMAKE_BINARY_DIR})
16.构建项目的子模块(本地代码库)
可以把项目的全部源文件,一次性构建出目标程序或库
- 简单快速
- 有时不利于定位构建过程里的问题
可以将构建拆分为子模块(库)/目标程序 - 可定位构建过程里的问题
- 构建更为清晰
子模块(本地代码库)构建的例子
# 定义项目名称
# 设置项目源文件
# 设置项目执行文件(欲构建的目标程序)
# 设置项目build-type
# 设置项目的系统/第三方依赖库
# 构建项目子模块(本地代码库)
# 构建本地依赖库
set(SUBMOD_SOURCE_DIR "${SOURCE_DIR}/abc")
add_subdirectory(${SUBMOD_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} abc)
# 设置构建的输出目录
...
17.设置Release/Debug版本的不同编译检查
- Release版本使用CMake宏,CMAKE_C_FLAGS_RELEASE
- Debug版本使用CMake宏CMAKE_C_FLAGS_DEBUG
- 主流c编译器gcc,新出的流行编译器clang,对编译选项的设置有所差异,可以分别设置
if(CMAKE_BUILD_TYPE STREQUAL "Release")
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
# GCC专用选项
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wmaybe-uninitialized -Werror=uninitialized")
elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang")
# Clang等效选项
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wsometimes-uninitialized -Werror=uninitialized")
endif()
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wunused-variable")
endif()
18.链接阶段报错的排查
- cmake将make的细节隐藏起来,让开发人员能专注于项目的构建逻辑而非细节
- 可一旦构建失败,就需要进入被隐藏的地方进行排查
- cmake隐藏的细节,藏在Cmakefiles目录下面
- 针对链接阶段的报错,我们查看其中的link.txt文件即可
- link.txt 记录了具体的链接指令
- 这些指令可手动执行
- 通过手动执行就可以进一步排查问题
- 比如链接阶段报没有main入口的问题
- 常见的原因是源码文件没有设置main函数
- 不常见的原因是link.txt里的命令被错误拆为多行:设置-Lpath -lxxx是错误的没有使用\完成折叠
更多推荐


所有评论(0)