CMake之构建配置


CMake是一个构建系统生成器,将描述构建系统如unix的makefile或者win的vs应当如何操作才能编译代码,然后camke为所选的构建系统生成相应的指令。
在GNU/Linux上,CMake默认使用unix makefile来构建项目,会生成四个文件:

  • Makefile:make将运行指令来构建项目
  • CMakefile:包含临时文件的目录,CMake用于检测操作系统,编译器等。此外根据所选的生成器还包含特定的其他文件
  • cmake_install.cmake:处理安装规则的CMake脚本,在项目安装时使用
  • CMakeCache.txt:cmake缓存,cmake在重新运行配置时使用该文件

为语言设置标准

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

add_library(<name> [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [<source>...])

  • 参数说明:
    • name:库名,在整个project中必须是唯一的;实际生成的库名会根据平台约定创建,如lib.a(UNIX)或者.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 变量中,其中是受支持的任何一种语言,对于我们的目的是CXX,C。可以通过以下两种方式设置此变量

  1. 使用CLI中的-D选项,如: cmkae -D CMAKE_CXX_COMPILER=clang++。推荐的方式。
  2. 设置环境变量,如 env CXX=clang++;

如果想查看可用的编译器和编译器标志,可以通过 cmake --system-information 查看

构建类型

控制生成构建系统使用的配置变量是 CMAKE_BUILD_TYPE,该变量默认为空,CMAKE识别的值为:

  1. Debug
  2. Release
  3. RealWithDebInfo:用于构建较少的优化库或可执行文件,包含调试符号
  4. MinSizeRel:用于不增加目标代码大小的优化方式构建

两种方式设置构建类型:

  1. 在CMakeLists.txt中设置
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) # 将该变量设置为缓存变量,可以通过缓存进行编辑
endif
  1. 命令行中-D来切换:cmake -D CMAKE_BUILD_TYPE=Debug

编译器选项

  1. CMakeLists.txt中使用命令target_compile_options,推荐使用的方式,不仅允许对编译选项进行细粒度控制,而且还可以更好地与CMake的更高级的特性进行集成
  2. 不修改CMakeLists.txt,命令行通过-D指定 CMAKE_CXX_FLAGS,如 cmake -D CMAKE_CXX_FLAGS=“-fno-exceptions -fno-rtti”
  3. 怎样做到跨平台?
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提供了三个选项来在构建时执行自定义命令

  1. add_custom_command:编译目标,生成输出文件
  2. add_custom_target 这个命令的执行没有输出
  3. 构建目标前后,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。
    可以通过 Modules中的 CMakeDependentOption定义与其他选项之间的依赖关系

猜你喜欢

转载自blog.csdn.net/u010378559/article/details/130651073