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 here
      set(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 here
      set(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(tfile
            TestAnisotropicDiffusion2D
            TestButterworthLowPass
            TestButterworthHighPass
            TestCityBlockDistance
            TestConvolve
            )
      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 this
      set(${_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 mySharedLib
            RUNTIME DESTINATION bin
            LIBRARY DESTINATION lib
            ARCHIVE DESTINATION lib/myproject)
    
  • 安装普通文件(Files) ,使用install(FILES ...)可安装项目中的任意文件,如头文件、配置文件、文档等。相对路径会相对于当前源码目录进行解析,可以通过 RENAME 参数重命名安装后的文件名。

    install(FILES my-api.h ${CMAKE_CURRENT_BINARY_DIR}/my-config.h
            DESTINATION include)
    install(FILES my-rc DESTINATION /etc
            PERMISSIONS 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/myproject
            FILE_PERMISSIONS
              OWNER_READ OWNER_EXECUTE OWNER_WRITE
              GROUP_READ GROUP_EXECUTE
              WORLD_READ WORLD_EXECUTE
            DIRECTORY_PERMISSIONS
              OWNER_READ OWNER_EXECUTE OWNER_WRITE
              GROUP_READ GROUP_EXECUTE GROUP_WRITE
              WORLD_READ WORLD_EXECUTE
            )
    install(DIRECTORY data/icons DESTINATION share/myproject
            PATTERN ".git" EXCLUDE
            PATTERN "*.txt" EXCLUDE)
    install(DIRECTORY data/icons DESTINATION share/myproject
            REGEX "/.git$" EXCLUDE
            REGEX "/[^/]*.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.mfbz.cn/a/756683.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

双指针算法第一弹(移动零 复写零 快乐数)

目录 前言 1. 移动零 &#xff08;1&#xff09;题目及示例 &#xff08;2&#xff09;一般思路 &#xff08;3&#xff09;双指针解法 2. 复写零 &#xff08;1&#xff09;题目及示例 &#xff08;2&#xff09;一般解法 &#xff08;3&#xff09;双指针解法 3. 快…

计算机基础知识——C基础+C指针+char类型

指针 这里讲的很细 https://blog.csdn.net/weixin_43624626/article/details/130715839 内存地址&#xff1a;内存中每个字节单位都有一个编号&#xff08;一般用十六进制表示&#xff09; 存储类型 数据类型 *指针变量名&#xff1b;int *p; //定义了一个指针变量p,指向的数…

在Redis中使用Lua脚本实现多条命令的原子性操作

Redis作为一个高性能的键值对数据库&#xff0c;被广泛应用于各种场景。然而&#xff0c;在某些情况下&#xff0c;我们需要执行一系列Redis命令&#xff0c;并确保这些命令的原子性。这时&#xff0c;Lua脚本就成为了一个非常实用的解决方案。 问题的提出 假设我们有一个计数…

【深度学习】图形模型基础(2):概率机器学习模型与人工智能

1.引言 1.1.背景 当机器需要从经验中汲取知识时&#xff0c;概率建模成为了一个至关重要的工具。它不仅为理解学习机制提供了理论框架&#xff0c;而且在实际应用中&#xff0c;特别是在设计能够从数据中学习的机器时&#xff0c;概率建模展现出了其独特的价值。概率框架的核…

Power BI可视化表格矩阵如何保持样式导出数据?

故事背景&#xff1a; 有朋友留言询问&#xff1a;自己从Power BI可视化矩阵表格中导出数据时&#xff0c;导出的表格样式会发生改变&#xff0c;需要线下再手动调整&#xff0c;重新进行透视组合成自己想要的格式。 有没有什么办法让表格导出来跟可视化一样&#xff1f; Po…

汽车电子工程师入门系列——CAN 规范系列通读

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

SiteSucker Pro for Mac:一键下载整站,轻松备份与离线浏览!

SiteSucker Pro for Mac是一款专为苹果电脑用户设计的网站下载与备份工具&#x1f578;️。它以其强大的整站下载能力和用户友好的界面&#xff0c;成为了众多Mac用户备份网站、离线浏览的得力助手&#x1f4bb;。 这款软件允许用户一键下载整个网站&#xff0c;包括所有的网页…

Docker(八)-Docker运行mysql8容器实例

1.运行mysql8容器实例并挂载数据卷 -e:配置环境变量 --lower_case_table_names1 设置忽略表名大小写一定要放在镜像之后运行mysql8容器实例之前&#xff0c;先查看是否存在mysql8镜像以及是否存在已运行的mysql实例docker run -d -p 3306:3306 --privilegedtrue -v 【宿主机日…

L03_Redis知识图谱

这些知识点你都掌握了吗?大家可以对着问题看下自己掌握程度如何?对于没掌握的知识点,大家自行网上搜索,都会有对应答案,本文不做知识点详细说明,只做简要文字或图示引导。 Redis 全景图 Redis 知识全景图都包括什么呢?简单来说,就是“两大维度,三大主线”。 Redis …

MySQL连接IDEA(Java Web)保姆级教程

第一步&#xff1a;新建项目(File)->Project 第二步&#xff1a;New Project(JDK最好设置1.8版本与数据库适配&#xff0c;详细适配网请到MySQL官网查询MySQL :: MySQL 8.3 Reference Manual :: Search Results) 第三步&#xff1a;点中MySQLTest(项目名)并连续双击shift键-…

昇思25天学习打卡营第2天|数据集Dataset

学习目标&#xff1a;熟练掌握mindspore.dataset mindspore.dataset中有常用的视觉、文本、音频开源数据集供下载&#xff0c;点赞、关注收藏哦 了解mindspore.dataset mindspore.dataset应用实践 拓展自定义数据集 昇思平台学习时间记录: 一、关于mindspore.dataset minds…

【STM32】在标准库中使用定时器

1.TIM简介 STM32F407系列控制器有2个高级控制定时器、10个通用定时器和2个基本定时器。通常情况下&#xff0c;先看定时器挂在哪个总线上APB1或者APB2&#xff0c;然后定时器时钟需要在此基础上乘以2。 2.标准库实现定时中断 #ifndef __BSP_TIMER_H #define __BSP_TIMER_H#if…

.[emcrypts@tutanota.de].mkp勒索病毒新变种该如何应对?

引言 在数字化时代&#xff0c;随着信息技术的迅猛发展&#xff0c;网络安全问题日益凸显。其中&#xff0c;勒索病毒作为一种极具破坏力的恶意软件&#xff0c;给个人和企业带来了巨大的经济损失和数据安全风险。近期&#xff0c;一种名为“.mkp勒索病毒”的新型威胁开始在网络…

多线程引发的安全问题

前言&#x1f440;~ 上一章我们介绍了线程的一些基础知识点&#xff0c;例如创建线程、查看线程、中断线程、等待线程等知识点&#xff0c;今天我们讲解多线程下引发的安全问题 线程安全&#xff08;最复杂也最重要&#xff09; 产生线程安全问题的原因 锁&#xff08;重要…

在 Python 中创建列表时,应该写 `[]` 还是 `list()`?

在 Python 中&#xff0c;创建列表有两种写法&#xff1a; # 写法一&#xff1a;使用一对方括号 list_1 []# 写法二&#xff1a;调用 list() list_2 list() 那么哪种写法更好呢&#xff1f; 单从写法上来看&#xff0c;[] 要比 list() 简洁&#xff0c;那在性能和功能方面…

江科大笔记—读写内部闪存FLASH读取芯片ID

读写内部闪存FLASH 右下角是OLED&#xff0c;然后左上角在PB1和PB11两个引脚&#xff0c;插上两个按键用于控制。下一个代码读取芯片ID&#xff0c;这个也是接上一个OLED&#xff0c;能显示测试数据就可以了。 STM32-STLINK Utility 本节的代码调试&#xff0c;使用辅助软件…

[机缘参悟-200] - 对自然、人性、人生、人心、人际、企业、社会、宇宙全面系统的感悟 - 全图解

对自然、人性、人生、人心、人际、企业、社会、宇宙进行全面系统的感悟&#xff0c;是一个极其深邃且复杂的主题。以下是对这些领域的简要感悟&#xff1a; 自然&#xff1a; 自然是人类生存的根基&#xff0c;它充满了无尽的奥秘和美丽。自然界的平衡和循环规律&#xff0c;教…

运算符重载之日期类的实现

接上一篇文章&#xff0c;废话不多说&#xff0c;直接上代码 Date.h #pragma once #include<iostream> using namespace std; #include<assert.h>class Date {//友元函数声明friend ostream& operator<<(ostream& out, const Date& d);friend …

学编程容易遇到的误区,请提前规避

随着互联网行业的蓬勃发展和编程技术的普及&#xff0c;越来越多的人开始对编程感兴趣。然而&#xff0c;编程学习并非一蹴而就&#xff0c;新手入门时常常会陷入误区&#xff0c;影响学习状态效率。 今天&#xff0c;我们来一起揭开编程学习常见的五大误区&#xff0c;希望能…

Workbench密码登录登录失败

Workbench密码登录登录失败操作系统禁用了密码登录方式&#xff0c;会导致使用了正确的用户名和密码仍无法登录 sudo vim /etc/ssh/sshd_config 输入O进入编辑 改完后重启 systemctl restart sshd.service 登录报错 有试了几遍登上了 可能是改完还要等一会儿
最新文章