目录
我们承担ROS,FastDDS等中间件以及C++,cmake等技术的项目开发和专业指导和培训,有10年+工作经验,质量有保证,如有需要请私信联系。
CMake是一个构建系统生成器,将描述构建系统如unix的makefile或者win的vs应当如何操作才能编译代码,然后camke为所选的构建系统生成相应的指令。
在GNU/Linux上,CMake默认使用unix makefile来构建项目,会生成四个文件:
- Makefile:make将运行指令来构建项目
- CMakefile:包含临时文件的目录,CMake用于检测操作系统,编译器等。此外根据所选的生成器还包含特定的其他文件
- cmake_install.cmake:处理安装规则的CMake脚本,在项目安装时使用
- CMakeCache.txt:cmake缓存,cmake在重新运行配置时使用该文件
CMake执行阶段
主要分为三个阶段:
配置阶段
这个阶段会读取CMakeLists.txt,并进行必要的检查如检查系统和编译环境,确认需要的库和工具是否存在。在这个过程中,CMake会生成一个名为CMakeCache.txt的文件,这个文件包含了项目构建过程中需要的各种设置和变量。这些设置和变量可能是由用户通过命令行参数传入的,也可能是通过CMakeLists.txt文件中的指令计算得出的。
执行命令为 cmake .
(或其他功能相同的命令,详细请参考这里),还可以通过-D选项来设置一些变量如,cmake -DCMAKE_BUILD_TYPE=Debug
等
生成阶段
这个阶段与配置阶段是一体的,使用的同样是cmake命令。CMake会生成构建所需构建文件,比如Makefile等
构建阶段
这个阶段,CMake会调用实际的构建工具(如Make或Visual Studio)来编译源代码并生成可执行文件或者库文件。这个阶段的命令是cmake --build
为语言设置标准
CXX_STANDARD
:设置标准
CXX_EXTENSIONS
:告诉CMake,只启用ISO C++标准的编译器标志,而不使用特定编译器的扩展。
CXX_STANDARD_REQUIRED
:指定所选标准的版本,如果这个版本不可用,CMake将停止配置并出现错误。当这个属性被设置为OFF时,CMake将寻找下一个标准的最新版本,直到一个合适的标志
TODO: 更多编译特性和语言标准
set(CMAKE_CXX_STANDARD 17) # C++17
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
通过命令 set_target_properties
- 通过为特定目标提供特定特性:
target_compile_features(<target> <PRIVATE|PUBLIC|INTERFACE> <feature> [...])
编译目标文件
使用add_executable
编译二进制文件,add_library
编译库(静态库或动态库)
构建命令
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[<source>...])
- 参数说明:
name
:库名,在整个project中必须是唯一的;实际生成的库名会根据平台约定创建,如lib<name>.a
(UNIX)或者<name>.lib(Win)
- 关键字参数:
STATIC
:用于创建静态库,即编译文件的打包存档,以便在链接其他目标时使用SHARED
:用于创建动态库,即可以动态链接,并在运行时加载的库OBJECT
:可将给定add_library
的列表中的源码编译到目标文件,不将它们归档到静态库中,也不能将它们链接到共享对象中。如果需要一次性创建静态库和动态库,那么使用对象库尤其有用。——没有用过,待验证
MODULE
:又为DSO组。与SHARED
库不同,它们不链接到项目中的任何目标,不过可以进行动态加载。该参数可以用于构建运行时插件。
source
用来指定要编译的头文件和cpp文件,或者可以不用指定,在后面通过target_link_directories
和target_sources
指定
设置目标文件属性
属性命令
set_target_properties
:设置生成目标的属性,设置的属性可以通过 get_property()
和 get_target_property()
这两个命令获取。cmake中所有的目标属性看这里
set_target_properties(target1 target2 ...
PROPERTIES
prop1 value1 prop2 value2 ...)
为target1,target2,… 设置属性,如:
set_target_properties( <target>
PROPERTIES
CXX_STANDARD 14
CXX_EXTENSIONS OFF
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE 1
)
常用的属性有以下:
POSITION_INDEPENDENT_CODE
:设置生成位置无关代码所需的编译器标志(?)SOVERSION
:版本号,这个只对共享库有效,如果生成的是STATIC
会报错,如值为2.0后生成如下:
OUTPUT_NAME
:用来为生成的可执行文件或库文件设置文件名。这个属性会把add_executable/add_library
的target
名覆盖掉,生成由这个属性指定的名字。DEBUG_POSTFIX
:如果以Debug配置构建项目,则将该属性指定的字符串添加到生成的库(动态或静态)或可执行文件名后,其他如Release不会添加后缀。PUBLIC_HEADER
:这个属性要来设置头文件列表,用该属性指定的头文件在安装时可以通过install
命令的PUBLIC_HEADER
安装到指定位置CXX_VISIBILITY_PRESET
值为hidden将隐藏所有符号 ?当使用GNU编译器时,这将为目标添加-fvisibility=hidden
标志VISIBILITY_INLINES_HIDDEN 1
:这将隐藏内联函数的符号,如果使用GNU编译器,这对应于-fvisibility-inlines-hidden
- rpath相关:
-
INSTALL_RPATH
:rpath,将已安装的可执行目标的RPATH设置为先前的路径,待验证TODO -
INSTALL_RPATH_USE_LINK_PATH
: 值为ON或OFF,告诉CMake将链接器搜索路径附加到可执行文件的RPATH中 -
BUILD_WITH_INSTALL_RPATH
-
SKIP_BUILD_RPATH
:告诉CMake是否生成适当的RPATH,以便能够在构建树中运行可执行文件——还不知道啥作用 - 编译符号相关:
CXX_VISIBILITY_PRESET
:符号是否可见。如果值为hidden,当使用GNU编译器时,这将为目标添加-fvisibility=hidden
标志VISIBILITY_INLINES_HIDDEN 1
:这将隐藏内联函数的符号。如果使用GNU编译器,这对应于-fvisibility-inlines-hidden
get_target_property()
、get_property()
获取属性
指定编译器
CMake将语言的编译器存储在 CMAKE_LANG_COMPILER
变量中,其中<LANG>
是受支持的任何一种语言,对于我们的目的是CXX,C。可以通过以下两种方式设置此变量
- 使用CLI中的-D选项,如:
cmkae -D CMAKE_CXX_COMPILER=clang++
。推荐的方式。 - 设置环境变量,如
env CXX=clang++
;
如果想查看可用的编译器和编译器标志,可以通过 cmake --system-information
查看
构建类型
控制生成构建系统使用的配置变量是 CMAKE_BUILD_TYPE
,该变量默认为空,CMAKE识别的值为:
Debug
Release
RealWithDebInfo
:用于构建较少的优化库或可执行文件,包含调试符号MinSizeRel
:用于不增加目标代码大小的优化方式构建
两种方式设置构建类型:
- 在CMakeLists.txt中设置
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) # 将该变量设置为缓存变量,可以通过缓存进行编辑
endif
- 命令行中-D来切换:
cmake -D CMAKE_BUILD_TYPE=Debug
编译器选项
- CMakeLists.txt中使用命令
target_compile_options
,推荐使用的方式,不仅允许对编译选项进行细粒度控制,而且还可以更好地与CMake的更高级的特性进行集成 - 不修改CMakeLists.txt,命令行通过-D指定
CMAKE_CXX_FLAGS
,如cmake -D CMAKE_CXX_FLAGS="-fno-exceptions -fno-rtti"
- 怎样做到跨平台?
target_compile_options(
<target>
[BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...]
)
为目标设置编译选项,编译选项可以设置三个级别的可见性:
PRIVATE
:编译选项会应用于给定的目标,不会传递给与目标相关的目标。如生成库文件,但不会传递给依赖这个库文件的可执行文件INTERFACE
:给定的编译选项将只应用于指定目标,并传递给与目标相关的目标PUBLIC
:编译选项将应用于指定目标和使用它的目标。
例:
target_compile_options(
my_exe
PRIVATE
"-fPIC"
)
使用与平台无关的文件操作
这个例子中解压缩Eigen打包文件,并相应的为目标设置包含目录
cmake_minimum_required(VERSION 3.20 FATAL_ERROR)
project(recipe-01 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_custom_target(
unpack-eigen
ALL
COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/eigen-3.4.0.tar.gz # 并不会真正解压一个实体目录出来
COMMAND ${CMAKE_COMMAND} -E rename eigen-3.4.0 eigen # 这里会报错:Error renaming from "eigen-3.4.0" to "eigen": Directory not empty
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Unpacking eigen"
)
add_executable(
linear-algebra linear-algebra.cpp
)
add_dependencies(linear-algebra unpack-eigen)
target_include_directories(
linear-algebra
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/eigen # 上面rename 命令所在行删掉后,这里为啥还是能找到eigen?
)
代码中使用到eigen-3.4.0.tar.gz中的源代码文件,但没有实体解压就可以引用到。
构建时运行自定义命令
cmake提供了三个选项来在构建时执行自定义命令
add_custom_command
:编译目标,生成输出文件add_custom_target
这个命令的执行没有输出- 构建目标前后,
add_custom_command
的执行可以没有输出
编译和链接命令
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
project(recipe-06 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(OpenMP)
if(OpenMP_FOUND)
message(STATUS "package found")
set(_scratch_dir ${CMAKE_CURRENT_BINARY_DIR}/omp_try_compile)
else()
message(STATUS "package not found")
endif()
try_compile(
omp_taskloop_test_1
${_scratch_dir}
SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/taskloop.cpp
LINK_LIBRARIES
OpenMP::OpenMP_CXX
)
message(STATUS "result of try_compile: ${omp_taskloop_test_1}")
include(CheckCXXSourceCompiles)
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/taskloop.cpp _snippet)
set(CMAKE_REQUIRED_LIBRARIES OpenMP::OpenMP_CXX)
check_cxx_source_compiles("${_snippet}" omp_taskloop_test_2)
unset(CMAKE_REQUIRED__LIBRARIES)
message(STATUS "Result of check_cxx_source_compiles: ${omp_taskloop_test_2}")
- 要编译的代码片段必须作为CMake变量传入,大多数情况下必须使用
file(READ ...)
来读取,然后代码片段被保存到构建目录的 CMakeFiles/CMakeTmp 子目录中 - 微调编译和链接,必须通过设置以下CMAKE变量进行:
CMAKE_REQUIRED_FLAGS
:设置编译器标志CMAKE_REQUIRED_DEFINITIONS
:设置预编译宏CMAKE_REQUIRED_INCLUDES
:设置包含目录列表CMAKE_REQUIRED_LIBRARIES
:设置可执行目标能够链接的库列表
- 调用
check_<lang>_compiles_function
之后,必须手动取消对这些变量的设置,确保后续使用中不会保留当前的内容
向用户显示选项
需要由用户从外部可以修改的变量值,可以通过在CMakeLists.txt中使用option()
命令,以选项的形式显示逻辑开关,通过CMake的-D的CLI选项,将值传递到cmake中。如: cmake -D my_option=ON ...
-D开关用于为CMake设置任何类型的变量:逻辑变量,路径等等。
optionq签名:option(<option_variable> "<help string>" [initial value])
,参数说明:
<option_variable>
表示该选项的变量的名称。"<help string>"
记录选项的字符串,在CMake的终端或图形用户界面中可见,注意这个参数是必须的,如果缺失会导致默认值不生效。[initial value]
选项的默认值,可以是ON
或OFF
,如果不写默认为OFF
。
可以通过 Modules中的CMakeDependentOption
定义与其他选项之间的依赖关系
编译32位程序
linux 64位操作系统环境下用gcc、g++编译32位程序:
-m32
:将 int , long 和指针设为 32 位,适用于i386
-m64
:将 int 设为 32 位, long 和指针设为 64 位,适用于X86 -64
-mx32
:将 int , long 和指针设为 32 位,适用于X86 -64
编译汇编
编译汇编需要在project
之后使用:
enable_language(ASM)
set(CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS asm)
如果是windows平台下,需要使用 enable_language(ASM_MASM)