CMake(1)基础使用

CMake之(1)基础使用


Author: Once Day Date: 2024年6月29日

一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…

漫漫长路,有人对你微笑过嘛…

全系列文章可参考专栏: Linux实践记录_Once-Day的博客-CSDN博客

参考文章:

  • CMake - Upgrade Your Software Build System
  • CMake Tutorial — CMake 3.30.0-rc4 Documentation
  • Mastering CMake — Mastering CMake
  • Packaging With CPack — Mastering CMake
  • CMake Documentation and Community
  • cmake-language(7) — CMake 3.30.0-rc4 Documentation
  • cmake-variables(7) — CMake 3.30.0-rc4 Documentation
  • cmake-commands(7) — CMake 3.30.0-rc4 Documentation
  • cmake-properties(7) — CMake 3.30.0-rc4 Documentation
  • cmake-modules(7) — CMake 3.30.0-rc4 Documentation

文章目录

  • CMake之(1)基础使用
        • 1. 概述
          • 1.1 CMake介绍
          • 1.2 CMake简单使用示例
          • 1.3 CMake辅助工具
          • 1.4 CMake与Google Test
          • 1.5 CMake特性
        • 2. CMake使用
          • 2.1 搭建CMake环境
          • 2.2 常见cmake参数介绍
          • 2.3 常见编译器参数
          • 2.4 CMakeLists文件编写
          • 2.5 CMake cache配置缓存
          • 2.6 CMake关键概念
          • 2.7 CMake策略
          • 2.8 CMake模块
          • 2.9 CMake安装文件(install)

1. 概述
1.1 CMake介绍

CMake是一个开源的跨平台自动化构建工具,它主要用于管理大型软件项目的构建、测试和打包过程。CMake通过编写简单的配置文件来描述项目的构建过程,然后根据不同平台的特性生成对应的本地化构建文件,如Unix下的Makefile或Windows下的Visual Studio项目文件。

使用CMake可以让开发者从复杂的构建细节中解放出来,专注于代码的编写和维护。它的主要优点包括:

  • 跨平台性:CMake支持多种操作系统和编译器,使得项目可以轻松地在不同平台上构建。

  • 简单易用:CMake使用简洁的语法编写配置文件,学习曲线相对平缓。

  • 模块化设计:CMake提供了大量的内置模块和函数,可以方便地引入第三方库和管理复杂的依赖关系。

  • 灵活性高:CMake支持各种自定义的构建选项和条件编译,可以根据需要调整构建过程。

CMake适用于各种规模的C/C++项目,尤其是那些需要跨平台构建、集成第三方库或进行自定义构建的项目。

但是,CMake也有一些不足之处:

  • 语法和概念需要额外学习,对于简单的项目可能显得有些重。

  • 生成的构建文件可读性较差,出错时调试困难。

  • 缺乏对其他语言(如Java、Python等)的原生支持。

与CMake类似的构建工具还有GNU Make、Autotools、SCons、Meson等。其中,GNU Make是Unix下的传统构建工具,使用Makefile来描述构建过程,但缺乏跨平台支持。Autotools在跨平台方面做了改进,但配置过程较为复杂。SCons和Meson都使用Python作为配置语言,提供了更现代化的构建方式,但生态圈不如CMake成熟。

总的来说,CMake凭借其良好的跨平台性、易用性和灵活性,已经成为C/C++项目构建的首选工具之一。

1.2 CMake简单使用示例

下面是一个简单的CMake使用示例。

(1) 假设我们有一个名为hello的项目,其目录结构如下

hello/ # 项目根目录
├── CMakeLists.txt # CMake配置清单文件
├── include/ # 项目头文件目录
│   └── hello.h
└── src/ # 项目源代码目录├── hello.cpp└── main.cpp

其中,CMakeLists.txt是CMake的配置文件,include/目录下是头文件,src/目录下是源文件。

(2) CMakeLists.txt的内容填写如下

# 最小CMake版本号限制
cmake_minimum_required(VERSION 3.10)
# 项目名称
project(hello)
# C++的版本号
set(CMAKE_CXX_STANDARD 11)
# 头文件搜索目录
include_directories(include)
# 可执行文件和其源代码文件
add_executable(hello src/main.cpp src/hello.cpp)

这个配置文件的作用是:

  1. 指定CMake的最小版本要求为3.10。
  2. 指定项目名称为hello
  3. 设置C++标准为C++11。
  4. include/目录添加到头文件搜索路径中。
  5. 添加一个名为hello的可执行目标,其源文件为src/main.cppsrc/hello.cpp

(3) 接下来,我们在项目根目录下创建一个build/目录,并进入该目录

# 在单独的子目录下编译, 避免编译中间文件干扰输出
mkdir build
cd build

(4) 然后,执行以下命令生成构建文件:

cmake ..

这个命令会根据上一级目录中的CMakeLists.txt文件生成相应的构建文件(如Makefile)。

(5) 最后,执行构建命令:

cmake --build .

这个命令会调用相应的构建工具(如Make)来编译项目,生成可执行文件hello

(6) 执行可执行文件:

./hello

这样,我们就完成了一个简单的CMake项目的构建过程。对于更复杂的项目,我们可以在CMakeLists.txt中添加更多的配置选项,如条件编译、子目录管理、第三方库链接等,以满足项目的特定需求

1.3 CMake辅助工具

除了基本的构建和测试功能外,CMake还提供了一些辅助工具和功能,以进一步简化和自动化项目管理过程。下面介绍几个常见的CMake辅助工具和功能:

(1) CMake Cache,一个文本文件,用于存储CMake配置过程中的变量值和选项。

  • 当首次运行CMake时,它会在构建目录下生成一个名为CMakeCache.txt的文件,记录了所有的缓存变量及其值。
  • 缓存变量可以通过set()命令设置,并使用CACHE关键字指定变量类型和描述信息。
  • 使用缓存变量可以避免每次运行CMake时重复设置相同的变量值,提高配置效率。
  • 可以使用cmake -LAH命令列出所有的缓存变量及其当前值。

(2) CPack,CMake内置的打包工具,用于生成各种格式的安装包,如ZIP、TGZ、RPM、DEB等。

  • 通过在CMakeLists.txt中设置一些变量和指令,可以控制安装包的内容、版本、依赖等信息。
  • 常用的CPack变量包括CPACK_PACKAGE_NAMECPACK_PACKAGE_VERSIONCPACK_PACKAGE_DESCRIPTION_SUMMARY等。
  • 可以使用include(CPack)命令启用CPack,并在构建完成后使用cpack命令生成安装包。

(3) CTest,CMake内置的测试管理工具,用于自动化运行和管理项目的测试用例。

  • 通过在CMakeLists.txt中使用add_test()命令添加测试用例,并使用enable_testing()命令启用测试功能。
  • 可以使用ctest命令运行所有的测试用例,并生成测试报告。
  • CTest支持多种测试属性和配置,如测试超时、资源锁定、依赖关系等。
  • 可以与CDash等测试结果管理平台集成,实现测试结果的可视化和分析。

(4) ccmake和cmake-gui,CMake的交互式配置工具,提供了基于文本或图形界面的配置方式。

  • 使用ccmake或cmake-gui可以方便地查看和修改CMake缓存变量,而无需直接编辑CMakeCache.txt文件。
  • ccmake基于ncurses库,提供了基于文本的交互式界面。
  • cmake-gui基于Qt库,提供了基于图形的交互式界面,支持多平台。

除此之外,还有一些其他的小特性:

  • CMake支持条件编译和平台检测,可以根据不同的操作系统、编译器、架构等条件生成相应的构建文件。
  • CMake提供了大量的模块和函数,用于查找和链接各种第三方库,如Boost、OpenCV、Qt等。
  • CMake支持代码生成和自定义命令,可以在构建过程中自动生成代码文件或执行自定义的脚本。
  • CMake支持多语言混合编译,可以在同一个项目中使用C、C++、Fortran、CUDA等不同的编程语言。
1.4 CMake与Google Test

下面以Google Test为例,演示如何在CMake中集成测试,在1.2节示例的基础上,为hello项目添加单元测试

(1) 首先,我们需要下载Google Test库,并将其解压到项目根目录下的googletest/目录中,在项目根目录下的CMakeLists.txt文件中添加以下内容

cmake_minimum_required(VERSION 3.10)project(hello)set(CMAKE_CXX_STANDARD 11)include_directories(include)add_library(hello_lib src/hello.cpp)add_executable(hello src/main.cpp)
target_link_libraries(hello hello_lib)enable_testing()add_subdirectory(googletest)add_executable(hello_test test/hello_test.cpp)
target_link_libraries(hello_test hello_lib gtest_main)include(GoogleTest)
gtest_discover_tests(hello_test)

这个配置文件的主要变化有:

  • hello.cpp编译为一个名为hello_lib的库,而不是直接链接到可执行文件中。
  • 启用测试功能,并添加googletest/子目录。
  • 添加一个名为hello_test的测试可执行文件,其源文件为test/hello_test.cpp,并链接hello_lib库和gtest_main库。
  • 使用GoogleTest模块自动发现并运行测试。

(2) 接下来,在test/目录下创建hello_test.cpp文件,编写测试用例

#include "gtest/gtest.h"
#include "hello.h"TEST(HelloTest, BasicAssertions) {EXPECT_EQ(hello(42), 42);
}

(3) 然后,按照与上一个示例相同的步骤,在build/目录下执行以下命令:

# 生成cmake实例信息
cmake ..
# 开始以当前目录的cmake实例信息进行编译
cmake --build .
# 执行单元测试函数
ctest

ctest命令会自动运行项目中的所有测试,并输出测试结果。

1.5 CMake特性

CMake是一个跨平台的自动化构建工具,它为多开发者协作和多目标平台的项目提供了许多优势,包括:

  • 自动搜索软件构建所需的程序、库和头文件,包括考虑环境变量和Windows注册表设置。

  • 支持在源码树外的目录树中构建,这是UNIX平台上常见的有用特性,CMake在Windows上也提供了这一特性。这允许开发者在不担心删除源文件的情况下删除整个构建目录。

  • 能够为自动生成的文件(如Qt的moc()或SWIG包装器生成器)创建复杂的自定义命令。这些命令用于在构建过程中生成新的源文件,然后将其编译到软件中。

  • 在配置时选择可选组件的能力。例如,VTK的几个库是可选的,CMake为用户提供了一种简单的方式来选择要构建的库。

  • 能够从简单的文本文件自动生成工作区和项目。对于具有许多程序或测试用例的系统,这可能非常方便,每个程序或测试用例都需要单独的项目文件,通常使用IDE手动创建项目文件是一个繁琐的过程。

  • 能够在静态和共享构建之间轻松切换。CMake知道如何在所有支持的平台上创建共享库和模块。处理复杂的特定于平台的链接器标志,并且在许多UNIX系统上支持共享库的内置运行时搜索路径等高级功能。

  • 自动生成文件依赖关系并支持大多数平台上的并行构建。

在开发跨平台软件时,CMake还提供了许多额外的特性:

  • 能够测试机器字节序和其他特定于硬件的特性。

  • 一组适用于所有平台的构建配置文件。这避免了开发人员必须在项目内以多种不同格式维护相同信息的问题。

  • 支持在所有支持它的平台上构建共享库。

  • 能够使用系统相关信息(如数据文件的位置和其他信息)配置文件。CMake可以创建包含以#define宏形式存在的数据文件路径和其他信息的头文件。系统特定的标志也可以放在配置的头文件中。与编译器的命令行-D选项相比,这有优势,因为它允许其他构建系统使用CMake构建的库,而无需指定构建期间使用的完全相同的命令行选项。

2. CMake使用
2.1 搭建CMake环境

在Windows和Linux上搭建CMake环境的步骤有一些差异,但总体流程相似。

Windows:

  1. 下载CMake安装包,访问CMake官方网站(https://cmake.org/download/),选择适合的Windows版本的安装包(如cmake-3.20.0-windows-x86_64.msi)。

  2. 安装CMake,选择要添加到PATH环境变量的组件,建议选择"Add CMake to the system PATH for all users"。

  3. 验证安装,打开命令提示符(cmd.exe),输入cmake --version,如果显示CMake的版本信息,则说明安装成功。

  4. 配置编译器,CMake本身不编译代码,需要配合编译器使用,常用的编译器有Visual Studio、MinGW等,确保编译器已经安装,并且可执行文件所在目录已添加到PATH环境变量中。

Linux(以Ubuntu为例):

  1. 更新软件包列表,打开终端,输入sudo apt update,更新软件包列表。
  2. 安装CMake,在终端中输入sudo apt install cmake,安装CMake,等待安装完成。
  3. 验证安装,在终端中输入cmake --version,如果显示CMake的版本信息,则说明安装成功。
  4. Linux上常用的编译器有GCC和Clang,大多数Linux发行版默认安装了GCC,可以通过gcc --version命令验证。如果没有安装,可以使用sudo apt install build-essential命令安装GCC。

需要注意的是,CMake生成的构建文件取决于所使用的编译器和平台。在Windows上,如果安装了Visual Studio,CMake默认生成Visual Studio项目文件。如果使用MinGW,则生成Makefile。在Linux上,CMake默认生成Makefile。

2.2 常见cmake参数介绍
参数作用示例
-S \<path\>指定源代码目录的路径cmake -S . -B build
-B \<path\>指定构建目录的路径cmake -S . -B build
-G \<generator\>指定生成器cmake -G "Unix Makefiles"
-D \<var\>=\<value\>设置CMake缓存变量的值cmake -DCMAKE_BUILD_TYPE=Release
-U \<globbing_expr\>取消设置匹配表达式的CMake缓存变量cmake -U CMAKE_BUILD_TYPE
-C \<initial-cache\>预加载脚本以填充CMake缓存cmake -C config.cmake
-P \<script\>在脚本模式下运行CMake,不生成构建系统cmake -P script.cmake
-T \<toolset\>指定构建时使用的工具集cmake -T clang
-A \<platform\>指定构建时使用的平台名称cmake -A x64
--graphviz=\<file\>生成graphviz dot文件cmake --graphviz=project.dot
--system-information \<file\>将系统信息转储到文件中cmake --system-information info.txt
--debug-output启用调试输出cmake --debug-output
--trace将每个执行的cmake命令的调用跟踪到stderrcmake --trace
--trace-expand类似–trace,但也展开变量引用cmake --trace-expand
--trace-source=\<file\>将每个执行的cmake命令的调用跟踪到指定文件cmake --trace-source=trace.txt
--warn-uninitialized在引用未初始化的变量时发出警告cmake --warn-uninitialized
--no-warn-unused-cli不警告未使用的命令行参数cmake --no-warn-unused-cli
--check-system-vars在引用未定义的环境变量时发出警告cmake --check-system-vars
--help显示帮助信息cmake --help
--version显示CMake版本信息cmake --version
2.3 常见编译器参数

CMake配置项目时提供了三种方法来指定编译器:通过生成器、环境变量或缓存条目。

  • 通过生成器指定编译器,某些生成器与特定的编译器绑定,例如Visual Studio 19生成器总是使用Microsoft Visual Studio 19编译器。对于基于Makefile的生成器,CMake将尝试一系列常用的编译器,直到找到一个可用的编译器。

  • 通过环境变量指定编译器,可以在运行CMake之前设置环境变量来指定编译器,CC环境变量指定C编译器,CXX环境变量指定C++编译器。

    export CC=/usr/bin/clang
    export CXX=/usr/bin/clang++
    
  • 通过缓存条目指定编译器,可以在CMake命令行中使用-D选项直接指定编译器

    cmake -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ ..
    

    注意,如果在运行CMake后需要更改编译器,应该重新创建一个空的构建目录并重新运行CMake

通过环境变量设置编译器和链接器标志:

  • 可以通过设置环境变量来初始化编译器和链接器的标志。
  • CFLAGS环境变量初始化CMAKE_C_FLAGS缓存值,用于设置C编译器标志。
  • CXXFLAGS环境变量初始化CMAKE_CXX_FLAGS缓存值,用于设置C++编译器标志。
  • LDFLAGS环境变量初始化链接器标志的缓存值。
export CFLAGS="-Wall -Werror"
export CXXFLAGS="-std=c++11 -O2"
export LDFLAGS="-L/path/to/lib -lmylib"

通过环境变量或缓存条目指定编译器和标志的方法适用于所有生成器,但在跨平台开发时,使用环境变量或缓存条目指定编译器和标志可以提高可移植性。

2.4 CMakeLists文件编写

CMake语言由注释、命令和变量组成:

  • 注释以#开头,直到行尾,如下所示:

    set(Foo a)       # 1 unquoted arg -> value is "a"
    
  • 变量采用${VAR}语法引用,变量名区分大小写,可包含字母、数字和下划线,多个值用分号隔开存储为字符串。变量作用域为当前目录及其子目录。使用set()命令定义变量,unset()命令取消变量定义。此外,还可以直接访问环境变量$ENV{VAR}和Windows注册表项。

    set(Foo "")      # 1 quoted arg -> value is ""
    set(Foo a)       # 1 unquoted arg -> value is "a"
    set(Foo "a b c") # 1 quoted arg -> value is "a b c"
    set(Foo a b c)   # 3 unquoted args -> value is "a;b;c"
    

    变量引用时,如果未定义,则返回空字符串。如果引用时存在双引号,则当作字符串展开,否则作为值展开(即展开为多个值)。

    set(Foo a b c)    # 3 unquoted args -> value is "a;b;c"
    command(${Foo})   # unquoted arg replaced by a;b;c# and expands to three arguments
    command("${Foo}") # quoted arg value is "a;b;c"
    set(Foo "")       # 1 quoted arg -> value is empty string
    command(${Foo})   # unquoted arg replaced by empty string# and expands to zero arguments
    command("${Foo}") # quoted arg value is empty string
    

    变量在函数或者子目录修改时,一个新的变量会产生并等于改变后的值,这意味着不会影响到外部原变量的值,如下:

    function(foo)message(${test}) # test is 1 hereset(test 2)message(${test}) # test is 2 here, but only in this scope
    endfunction()set(test 1)
    foo()
    message(${test}) # test will still be 1 here
    

    如果想在子目录或者函数里改变外部值(或者称返回值),可以如下操作:

    function(foo)message(${test}) # test is 1 hereset(test 2 PARENT_SCOPE)message(${test}) # test still 1 in this scope
    endfunction()set(test 1)
    foo()
    message(${test}) # test will now be 2 here
    
  • 命令由命令名、左括号、参数列表和右括号组成,参数以空格分隔,可用引号括起,命令名字不区分大小写

    除了分割变量之外,其余空白字符全部被忽略掉,对于存在双引号的变量,总是被当成一个变量处理。

    command("")          # 1 quoted argument
    command("a b c")     # 1 quoted argument
    command("a;b;c")     # 1 quoted argument
    command("a" "b" "c") # 3 quoted arguments
    command(a b c)       # 3 unquoted arguments
    command(a;b;c)       # 1 unquoted argument expands to 3
    

流程控制命令分为三类:

  • 条件语句,例如if()、elseif()、else()和endif(),根据条件执行不同的命令。

    if(MSVC80)# do something here
    elseif(MSVC90)# do something else
    elseif(APPLE)# do something else
    endif()
    
  • 循环语句,例如foreach()和while(),用于重复执行一组命令,break()用于提前跳出循环。

    foreach(tfileTestAnisotropicDiffusion2DTestButterworthLowPassTestButterworthHighPassTestCityBlockDistanceTestConvolve)add_test(${tfile}-image ${VTK_EXECUTABLE}${VTK_SOURCE_DIR}/Tests/rtImageTest.tcl${VTK_SOURCE_DIR}/Tests/${tfile}.tcl-D ${VTK_DATA_ROOT}-V Baseline/Imaging/${tfile}.png-A ${VTK_SOURCE_DIR}/Wrapping/Tcl)
    endforeach()
    

    foreach第一个值tfile是循环变量,后续的Test*值是循环集合,逐一赋值给tfile来遍历执行

    #####################################################
    # run paraview and ctest test dashboards for 6 hours
    #
    while(${CTEST_ELAPSED_TIME} LESS 36000)set(START_TIME ${CTEST_ELAPSED_TIME})ctest_run_script("dash1_ParaView_vs71continuous.cmake")ctest_run_script("dash1_cmake_vs71continuous.cmake")
    endwhile()
    
  • 过程定义语句,function()定义函数,可以接受参数并设置PARENT_SCOPE变量将结果返回给调用方。macro()定义宏,类似函数但不引入新的变量作用域,参数也不会被当作变量对待。return()从函数、目录或文件中返回。

    function(DetermineTime _time)# pass the result up to whatever invoked thisset(${_time} "1:23:45" PARENT_SCOPE)
    endfunction()# now use the function we just defined
    DetermineTime(current_time)if(DEFINED current_time)message(STATUS "The time is now: ${current_time}")
    endif()# define a simple macro
    macro(assert TEST COMMENT)if(NOT ${TEST})message("Assertion failed: ${COMMENT}")endif()
    endmacro()# use the macro
    find_library(FOO_LIB foo /usr/local/lib)
    assert(${FOO_LIB} "Unable to find library foo")
    

此外,一些命令支持使用正则表达式作为参数,CMake使用Texas Instruments的开源正则表达式类进行解析。

2.5 CMake cache配置缓存

CMake Cache是CMake构建系统的一个重要概念,可以看作是一个配置文件。

  • 存储用户的选择和设置,避免每次运行CMake时重复输入信息。第一次在一个项目上运行CMake时,会在构建树的顶层目录下生成CMakeCache.txt文件,后续运行时会读取这个文件。

    例如,option()命令可以创建一个布尔型缓存变量:

    option(USE_JPEG "Do you want to use the jpeg library")
    

    这会在CMakeCache.txt中创建一个USE_JPEG变量,用户可以在CMake GUI中设置它的值,后续运行时会保留用户的设置。

  • 存储CMake自己确定的一些系统相关的变量值,在多次运行之间复用,提高效率。这些变量通常需要编译和运行一些程序来确定,得到后就可以存入缓存,避免重复计算。这些变量对用户不可见,如果系统环境发生了重大变化(如更换操作系统或编译器),需要删除缓存文件。

  • 处理复杂项目中选项之间的依赖关系。有的项目中,设置某个选项后,下次运行CMake时会出现新的相关选项。CMake通过缓存机制,让用户逐步设置所有相关选项,直到不再出现新选项为止。

可以通过set()命令将变量存入缓存:

set(USE_JPEG ON CACHE BOOL "include jpeg support?") 

CACHE选项指定将变量存入缓存,还可以指定变量类型和说明字符串。类型会影响CMake GUI如何显示和设置变量,但在缓存文件中总是以字符串形式存储。

一般不建议直接编辑CMakeCache.txt,因为其语法可能发生变化,而且包含了很多绝对路径,不适合在不同构建目录间移动

一旦变量进入缓存,就不能在CMakeLists中直接修改其"缓存"值,否则会覆盖用户的设置。如果一定要修改,需要使用set()的FORCE选项,没有进入缓存的变量仍然可以在当前作用域内被修改。

可以用mark_as_advanced()将缓存条目标记为高级,默认情况下在CMake GUI中不显示,避免干扰普通用户。

有时需要在没有GUI的情况下初始化缓存,如在夜间构建或批量生成构建目录时,可使用下述方法:

# 用-D选项传递键值对
cmake -DBUILD_TESTING:BOOL=ON ...
# 用`-C`选项指定一个初始化文件,其中可以包含set()命令来设置缓存变量
set(VTK_USE_HYBRID ON CACHE BOOL "doc string")
# 用FORCE强制设置
set(VTK_USE_HYBRID ON CACHE BOOL "doc" FORCE)
# 用INTERNAL类型在设置的同时隐藏选项
set(VTK_USE_HYBRID ON CACHE INTERNAL "doc")
2.6 CMake关键概念

参考文档: [Key Concepts — Mastering CMake](https://cmake.org/cmake/help/book/mastering-cmake/chapter/Key Concepts.html)

CMake具有一些关键概念,包括target、property、source file、directory等。

Target代表了CMake构建的可执行文件、库和工具

  • add_libraryadd_executable等命令都会创建target。

  • Target有name和type等属性。

  • 库类型可以是STATIC、SHARED或MODULE。

    add_library(foo STATIC foo1.c foo2.c)
    
  • 可执行文件入口点可以是main或WinMain。

  • 可以用set_target_propertiesget_target_property命令设置和读取target的属性,如LINK_FLAGS等。

  • target_link_libraries命令可以指定target依赖的库,CMake会自动传递这些库的使用要求(如包含目录等)。

    add_library(foo foo.cxx)
    target_link_libraries(foo bar)add_executable(foobar foobar.cxx)
    target_link_libraries(foobar foo)
    

    尽管只有foo被显示指定链接到foobar,但是bar同样也会被链接,因为foo和bar之间的关联可以传递到foobar和foo之间。

  • 在Windows上,debug库要和debug库链接,release要和release链接,target_link_libraries支持debug和optimized关键字来实现这一点。

    add_executable(foo foo.c)
    target_link_libraries(foo debug libdebug optimized libopt)
    
  • Object Library是一种特殊的库,包含编译后的目标文件,但不会被链接成库文件。

    add_library(A OBJECT a.cpp)
    add_library(B OBJECT b.cpp)
    add_library(Combined $<TARGET_OBJECTS:A> $<TARGET_OBJECTS:B>)
    
  • 其他target可以用$<TARGET_OBJECTS:name>引用这些目标文件,主要用于对复杂工程的源代码进行分组管理。

Source File代表源文件,和target类似也有一些属性可以设置和读取,可以用set_source_files_properties命令进行操作。

此外还有Directory、Test等对象,也可以通过set/get_xxx_properties命令来操作其属性。

2.7 CMake策略

每当CMake引入了一个新特性或改变,而这些变更不能完全向后兼容旧版本时,就可能会出现问题。比如当有人尝试用新版CMake构建使用旧版CMakeLists文件的项目时,Policies机制可以帮助最终用户和开发者应对这些问题。

CMake中所有的policy都以CMPNNNN的形式命名,其中NNNN是一个整数值。Policy通常会同时支持旧行为(与早期CMake版本兼容)和新行为(被认为是正确的,推荐新项目使用)。每个policy都有文档详细说明变更的动机,以及新旧行为。

项目可以通过设置每个policy来请求使用旧行为或新行为。当CMake遇到可能受特定policy影响的用户代码时,它会检查项目是否设置了该policy。如果设置了(为OLD或NEW),则CMake遵循指定的行为。如果未设置,则使用旧行为,但会发出警告,告知项目作者设置该policy。

设置policy行为有几种方式:

  • 最快的方式是将所有policy设置为与编写项目时使用的CMake发布版本相对应的版本。这可以通过cmake_policy命令的VERSION签名来实现,cmake_minimum_required命令会要求最低版本的CMake并调用cmake_policy。项目应该总是以下面的行开始:

    cmake_minimum_required(VERSION 3.20) 
    project(MyProject)# ...使用 CMake 3.20 policy 的代码
    
  • 单独设置每个 policy(增量式)。这有时对于想要逐步将项目转换为使用新行为,或抑制有关依赖旧行为的警告的项目作者很有帮助。可以使用cmake_policy命令的SET选项来明确请求特定policy的旧行为或新行为。

Policy设置使用堆栈进行范围控制,进入项目的新子目录(使用add_subdirectory)时会压入堆栈的新级别,离开时会弹出。因此,在项目的一个目录中设置policy不会影响父目录或同级目录,但会影响子目录

当 CMake 新版本引入新的policy时,可能会为某些现有项目生成警告,这些警告表明可能需要更改项目以处理新的 policy。虽然项目的旧版本可以继续在警告下构建,但项目开发树应该更新以考虑新的policy。

2.8 CMake模块

CMake提供了一种代码复用的机制,称为modules(模块)。通过 modules,CMakeLists 文件可以复用其他地方的代码片段。这使得整个社区能够共享可复用的代码。在CMake中,这些代码片段被称为cmake-modules,通常可以在CMake安装目录的Modules子目录下找到。

模块的位置可以使用模块文件的完整路径指定,也可以让CMake自己去查找模块。CMake 会在CMAKE_MODULE_PATH指定的目录中查找模块;如果找不到,它会在Modules子目录中查找。这样项目就可以覆盖CMake提供的模块,并根据自己的需要对其进行定制。

CMake的模块主要可以分为两类:

  • Find Modules (查找模块),这些模块支持find_package命令,用于确定属于给定包的软件元素(如头文件或库)的位置。不要直接包含它们,而是使用 find_package 命令。每个模块都附有文档,描述它查找的包以及它提供结果的变量。

  • Utility Modules (工具模块),工具模块只是将一些 CMake 命令放到一个文件中,然后可以使用include命令将其包含到其他CMakeLists文件中。例如,以下命令将包含CMake的CheckTypeSize模块,然后使用它定义的宏:

    include(CheckTypeSize)
    check_type_size(long SIZEOF_LONG)
    

    这些模块测试系统以提供有关目标平台或编译器的信息,例如 float 的大小或对 ANSI C++ 流的支持。

    许多这样的模块都有以Test或Check为前缀的名称,例如 TestBigEndian 和 CheckTypeSize。

其中一些模块会尝试编译代码以确定正确的结果。在这些情况下,源代码通常与模块同名,但带有 .c 或 .cxx 扩展名。工具模块还提供了使用CMake语言实现的有用的宏和函数,旨在用于特定的、常见的用例。

2.9 CMake安装文件(install)

CMake提供了功能强大的安装机制,通过在CMakeLists.txt中使用install命令,可以方便地指定如何安装项目生成的文件。使用 install命令主要涉及以下几个方面:

  • 安装目标文件(Targets),使用install(TARGETS ...)可以安装项目构建生成的二进制文件,包括可执行文件和库文件。

    (1) 可执行文件(RUNTIME),通过add_executable生成,在Windows上后缀为.exe,Unix上没有后缀。

    (2) 动态库(LIBRARY),Unix通过add_library并使用SHARED选项生成,后缀一般为.so,Windows通过add_library并使用MODULE选项生成,后缀为.dll。

    (3) 静态库(ARCHIVE),通过add_library并使用STATIC选项生成,Windows 上为.lib,Unix上为.a。

    (4) 导入库(ARCHIVE),在Windows上动态库同时会生成一个.lib的导入库文件。

    install(TARGETS myExecutable DESTINATION bin)
    install(TARGETS myStaticLib DESTINATION lib/myproject)
    install(TARGETS myPlugin DESTINATION lib)
    # 根据平台的区别,不同的库安装目录不一样
    install(TARGETS mySharedLibRUNTIME DESTINATION binLIBRARY DESTINATION libARCHIVE DESTINATION lib/myproject)
    
  • 安装普通文件(Files) ,使用install(FILES ...)可安装项目中的任意文件,如头文件、配置文件、文档等。相对路径会相对于当前源码目录进行解析,可以通过 RENAME 参数重命名安装后的文件名。

    install(FILES my-api.h ${CMAKE_CURRENT_BINARY_DIR}/my-config.hDESTINATION include)
    install(FILES my-rc DESTINATION /etcPERMISSIONS OWNER_WRITE OWNER_READ)
    install(FILES version.h DESTINATION include RENAME my-version.h)
    
  • 安装程序(Programs),使用install(PROGRAMS ...)可安装非目标的可执行程序,如脚本文件。与**install(FILES)**类似,不同之处是默认会添加可执行权限。

    install(PROGRAMS my-util.py DESTINATION bin)
    
  • 安装目录(Directory),使用install(DIRECTORY ...)可以安装整个目录,会递归安装目录中的内容。如果目录路径以/结尾,则只安装目录内容,不包含目录本身。可以使用PATTERN和REGEX参数过滤要安装的文件,USE_SOURCE_PERMISSIONS参数会沿用源目录的权限设置。

    install(DIRECTORY data/icons DESTINATION share/myproject)
    install(DIRECTORY doc/html/ DESTINATION doc/myproject)
    install(DIRECTORY DESTINATION share/myproject/user)
    install(DIRECTORY data/scripts DESTINATION share/myprojectFILE_PERMISSIONSOWNER_READ OWNER_EXECUTE OWNER_WRITEGROUP_READ GROUP_EXECUTEWORLD_READ WORLD_EXECUTEDIRECTORY_PERMISSIONSOWNER_READ OWNER_EXECUTE OWNER_WRITEGROUP_READ GROUP_EXECUTE GROUP_WRITEWORLD_READ WORLD_EXECUTE)
    install(DIRECTORY data/icons DESTINATION share/myprojectPATTERN ".git" EXCLUDEPATTERN "*.txt" EXCLUDE)
    install(DIRECTORY data/icons DESTINATION share/myprojectREGEX "/.git$" EXCLUDEREGEX "/[^/]*.txt$" EXCLUDE)
    
  • 安装时脚本(Script),使用install(SCRIPT ...)指定一个脚本,会在安装时执行。脚本不是在CMake处理CMakeLists.txt时执行,而是在安装时执行。脚本中可以使用一些预定义变量,如CMAKE_INSTALL_PREFIXCMAKE_INSTALL_CONFIG_NAME等。

    install(SCRIPT message.cmake)
    
  • 安装时代码(Code),使用install(CODE ...)可以直接在CMakeLists.txt中指定安装时要执行的CMake代码。

    install(CODE "MESSAGE(\"Installing My Project\")")
    
  • 安装第三方依赖库(Fixup Bundle),项目经常会依赖一些第三方动态库,在安装可执行文件时,需要将这些依赖库拷贝并修复路径后一并安装。CMake 提供了BundleUtilities模块的fixup_bundle函数来自动处理这个过程。在 Mac 上,会将动态库拷贝到bundle内部并修复install_name。在 Windows 上,会将动态库拷贝到可执行文件所在目录。

在 CMake 的install命令中,可以使用一些参数来控制安装的细节,下面介绍几个常用的参数。

(1) DESTINATION用于指定文件安装的目标路径

  • 如果指定的是绝对路径,安装时会直接使用该路径。
  • 如果指定的是相对路径,安装时会相对于CMAKE_INSTALL_PREFIX 进行解析。CMAKE_INSTALL_PREFIX变量可以由用户设置,CMake 也提供了默认值,Unix 上默认为/usr/local,Windows 上默认为c:/Program Files/${PROJECT_NAME}

(2) PERMISSIONS,用于指定安装文件的权限

  • 默认情况下,不同类型(如TARGETS/FILES/PROGRAMS/DIRECTORY)的文件,CMake 会设置一些默认权限,通过该参数可以覆盖默认权限设置。

  • 可供设置的权限有:OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, WORLD_READ, WORLD_WRITE, WORLD_EXECUTE, SETUID, SETGID。

(3) CONFIGURATIONS,用于指定安装规则适用的构建配置,如 Debug, Release 等。CMake 支持多配置构建,对于不同的配置,安装规则可能会有所不同。

  • 对于Makefile生成器,配置名通过CMAKE_BUILD_TYPE变量设置。
  • 对于VS, Xcode等IDE工程生成器,配置名在生成安装目标时选择。
  • 只有当前安装配置名与此处设置的名字列表匹配,对应的安装规则才会生效。配置名的大小写不敏感。

(4) COMPONENT,用于指定安装规则适用的安装组件。项目可以将安装内容划分成若干个组件,从而可以选择性地进行安装。例如划分成 Runtime, Development, Documentation 等组件。

(5) OPTIONAL,用于指定安装文件缺失时不报错。默认情况下,如果 install 命令指定的文件不存在,会报错。设置该参数后,如果文件存在就安装,不存在也不会报错。







Alt

Once Day

也信美人终作土,不堪幽梦太匆匆......

如果这篇文章为您带来了帮助或启发,不妨点个赞👍和关注,再加上一个小小的收藏⭐!

(。◕‿◕。)感谢您的阅读与支持~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/37584.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++中的三大池:线程池,内存池,数据库连接池

C中有三大池&#xff0c;即我们常说的&#xff1a;线程池&#xff0c;内存池&#xff0c;数据库连接池。 一.线程池 多线程同时访问共享资源造成数据混乱的原因就是因为CPU的上下文切换导致&#xff0c;线程池就是为了解决此问题而生。 多线程常用的有&#xff1a;std::threa…

【工具推荐】Nuclei

文章目录 NucleiLinux安装方式Kali安装Windows安装 Nuclei Nuclei 是一款注重于可配置性、可扩展性和易用性的基于模板的快速漏洞验证工具。它使用 Go 语言开发&#xff0c;具有强大的可配置性、可扩展性&#xff0c;并且易于使用。Nuclei 的核心是利用模板&#xff08;表示为简…

使用Jetpack Compose实现具有多选功能的图片网格

使用Jetpack Compose实现具有多选功能的图片网格 在现代应用中,多选功能是一项常见且重要的需求。例如,Google Photos允许用户轻松选择多个照片进行分享、添加到相册或删除。在本文中,我们将展示如何使用Jetpack Compose实现类似的多选行为,最终效果如下: 主要步骤 实现…

查看Windows启动时长

&#xff08;附图片&#xff09;电脑自带检测开机时长---查看方式_电脑开机时长命令-CSDN博客 eventvwr - Windows日志 - 系统 - 查找 - 6013.jpg

如何利用ChatGPT改善日常生活:一个普通人的指南

当你打开 ChatGPT&#xff0c;显现的是一个简洁的聊天界面。 许多人利用 ChatGPT 进行日常对话。 然而&#xff0c;ChatGPT 的功能远不止于此。 对话只是其众多能力中的一种&#xff0c;如果仅将其视为高级版的聊天机器人&#xff0c;那未免低估了它。 AI 在信息处理方面的…

VMware ESXi 8.0U3 macOS Unlocker OEM BIOS 集成驱动版,新增 12 款 I219 网卡驱动

VMware ESXi 8.0U3 macOS Unlocker & OEM BIOS 集成驱动版&#xff0c;新增 12 款 I219 网卡驱动 VMware ESXi 8.0U3 macOS Unlocker & OEM BIOS 集成网卡驱动和 NVMe 驱动 (集成驱动版) 发布 ESXi 8.0U3 集成驱动版&#xff0c;在个人电脑上运行企业级工作负载 请访…

ETAS工具导入DEXT生成Dcm及Dem模块(二)

文章目录 前言DcmDcmDsdDcmDslDcmDspDcmPageBufferCfgDem报错解决总结前言 之前一篇文章介绍了导入DEXT之后在cfggen之前的更改,cfggen完成之后,就可以生成dcm,dem的配置了,但生成完配置后,如果直接生成BSW代码,会报错。本文介绍在cfggen完成后,生成BSW代码前的修改 Dc…

哈尔滨高校大学智能制造实验室数字孪生可视化系统平台项目的验收

哈尔滨高校大学智能制造实验室数字孪生可视化系统平台项目的验收&#xff0c;标志着这一技术在教育领域的应用取得了新的突破。项目旨在开发一个数字孪生可视化系统平台&#xff0c;用于哈尔滨高校大学智能制造实验室的设备模拟、监测与数据分析。项目的主要目标包括&#xff1…

【算法专题--链表】两数相加 -- 高频面试题(图文详解,小白一看就懂!!)

目录 一、前言 二、题目描述 三、解题方法 ⭐双指针 -- 模拟进位 (使用哨兵位头节点) &#x1f95d; 什么是哨兵位头节点&#xff1f; &#x1f347;思路解析 &#x1f34d;案例图解 四、总结与提炼 五、共勉 一、前言 两数相加 这道题&#xff0c;可以说是--…

SpringCloud Alibaba Seata2.0分布式事务AT模式实践总结

这里我们划分订单、库存与支付三个module来实践Seata的分布式事务。 依赖版本(jdk17)&#xff1a; <spring.boot.version>3.1.7</spring.boot.version> <spring.cloud.version>2022.0.4</spring.cloud.version> <spring.cloud.alibaba.version>…

计算机监控软件有哪些?10款常年霸榜的计算机监控软件

计算机监控软件是企业管理和保护信息安全的重要工具&#xff0c;它们帮助企业管理者监督员工的计算机使用行为&#xff0c;确保工作效率、数据安全以及合规性。在众多监控软件中&#xff0c;有些产品因其卓越的功能、易用性、安全性以及持续获得的良好市场反馈而常年占据行业领…

C++——探索智能指针的设计原理

前言: RAII是资源获得即初始化&#xff0c; 是一种利用对象生命周期来控制程序资源地手段。 智能指针是在对象构造时获取资源&#xff0c; 并且在对象的声明周期内控制资源&#xff0c; 最后在对象析构的时候释放资源。注意&#xff0c; 本篇文章参考——C 智能指针 - 全部用法…

IBCS 虚拟专线——让企业用于独立IP

在当今竞争激烈的商业世界中&#xff0c;企业的数字化运营对网络和服务器的性能有着极高的要求。作为一家企业的 IT 主管&#xff0c;我深刻体会到了在网络和服务器配置方面所面临的种种挑战&#xff0c;以及 IBCS 虚拟专线带来的革命性改变。 我们企业在业务扩张的过程中&…

msvcr120.dll文件下载的高级教程,修复msvcr120.dll 详细步骤分享

当电脑系统或特定应用程序无法找到或访问到msvcr120.dll文件时&#xff0c;便会导致错误消息的出现&#xff0c;例如“找不到 msvcr120.dll”、“msvcr120.dll丢失”等。这篇文章将大家讨论关于msvcr120.dll文件的内容、msvcr120.dll丢失问题的解决方法&#xff0c;其中最常见的…

web使用cordova打包Andriod

一.安装Gradel 1.下载地址 Gradle Distributions 2.配置环境 3.测试是否安装成功 在cmd gradle -v 二.创建vite项目 npm init vitelatest npm install vite build 三.创建cordova项目 1.全局安装cordova npm install -g cordova 2. 创建项目 cordova create cordova-app c…

VuePress日常使用

本篇来讲解下更多关于 VuePress 的基本用法 ‍ 配置首页 现在的页面太简单了&#xff0c;我们可以对项目首页进行配置&#xff0c;修改 docs/README.md &#xff08;这些配置是什么后面会说&#xff09;&#xff1a; --- home: true heroImage: https://s3.bmp.ovh/imgs/20…

Mac可以读取NTFS吗 Mac NTFS软件哪个好 mac ntfs读写工具免费

在跨操作系统环境下使用外部存储设备时&#xff0c;特别是当Windows系统的U盘被连接到Mac电脑时&#xff0c;常常会遇到文件系统兼容性的问题。由于Mac OS原生并不完全支持对NTFS格式磁盘的读写操作&#xff0c;导致用户无法直接在Mac上向NTFS格式的U盘或硬盘写入数据。下面我们…

揭示隐藏的模式:秩和检验和单因素方差分析的实战指南【考题】

1.研究一种新方法对于某实验结果准确性提高的效果&#xff0c;并将其与原有方法进行比较&#xff0c;结果见下表&#xff0c;请评价两者是否有不同? (行无序&#xff0c;列有序)-->单方向有序-->两独立样本的秩和检验) 如下图所示&#xff0c;先将相关数据导入spss。 图…

什么是指令微调(LLM)

经过大规模数据预训练后的语言模型已经具备较强的模型能力&#xff0c;能够编码丰富的世界知识&#xff0c;但是由于预训练任务形式所限&#xff0c;这些模型更擅长于文本补全&#xff0c;并不适合直接解决具体的任务。 指令微调是相对“预训练”来讲的&#xff0c;预训练的时…

SpringBoot3基础用法

技术和工具「!喜新厌旧」 一、背景 最近在一个轻量级的服务中&#xff0c;尝试了最新的技术和工具选型&#xff1b; 即SpringBoot3&#xff0c;JDK17&#xff0c;IDEA2023&#xff0c;Navicat16&#xff0c;虽然新的技术和工具都更加强大和高效&#xff0c;但是适应采坑的过程…