• CMake Cookbook笔记(11/19未完待续)


    文章目录

    一、配置环境(略)

    二、从可执行文件到库

    • 本章主要内容
    1)将单个源码文件编译为可执行文件
    2)切换生成器
    3)构建和连接静态库与动态库
    4)用条件语句控制编译
    5)向用户显示选项
    6)指定编译器
    7)切换构建类型
    8)设置编译器选项
    9)为语言设定标准
    10)使用控制流进行构造
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1)将单个源码文件编译为可执行文件

    • 代码
    #include 
    #include 
    
    char *say_hello() { return "Hello, CMake world!"; }
    
    int main() {
      printf("%s\n", say_hello());
      return EXIT_SUCCESS;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • CMakeLists.txt

    备注:将该文件与源文件 hello-world.cpp 放在相同的目录中。记住,它只能被命名为 CMakeLists.txt 。

    # set minimum cmake version
    //设置CMake所需的最低版本。如果使用的CMake版本低于该版本,则会发出致命错误:
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    # project name and language
    //声明了项目的名称( recipe-01 )和支持的编程语言(CXX代表C++,C代表C):
    project(recipe-01 LANGUAGES C)
    
    //指示CMake创建一个新目标:可执行文件 hello-world 。
    //这个可执行文件是通过编译和链接源文件 hello-world.cpp 生成的。
    add_executable(hello-world hello-world.c)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 通过build配置目录
    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake ..
    4.
    5. -- The CXX compiler identification is GNU 8.1.0
    6. -- Check for working CXX compiler: /usr/bin/c++
    7. -- Check for working CXX compiler: /usr/bin/c++ -- works
    8. -- Detecting CXX compiler ABI info
    9. -- Detecting CXX compiler ABI info - done
    10. -- Detecting CXX compile features
    11. -- Detecting CXX compile features - done
    12. -- Configuring done
    13. -- Generating done
    14.
    -- Build files have been written to: /home/user/cmake-cookbook/chapter01/recipe-01/cxx-example/build
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 备注
      ①:CMake中,C++是默认的编程语言。不过,我们还是建议使用 LANGUAGES 选项在 project 命令中显式地声明项目的语言
      ②涉及到的文件分类
    ①Makefile : make 将运行指令来构建项目。
    ②CMakefile :包含临时文件的目录,CMake用于检测操作系统、编译器等。此外,根据所选的生成器,它还包含特定的文件。
    ③cmake_install.cmake :处理安装规则的CMake脚本,在项目安装时使用。
    ④CMakeCache.txt :如文件名所示,CMake缓存。CMake在重新运行配置时使用这个文件。
    
    • 1
    • 2
    • 3
    • 4

    ③命令实现编译

    $ cmake -H. -Bbuild
    
    • 1

    该命令是跨平台的,使用了 -H 和 -B 为CLI选项。 -H 表示当前目录中搜索
    根 CMakeLists.txt 文件。 -Bbuild 告诉CMake在一个名为 build 的目录中生成所有的文件。
    ④CMake不强制指定构建目录执行名称或位置,我们完全可以把它放在项目路径之外。这样做同样有效:

    1. $ mkdir -p /tmp/someplace
    2. $ cd /tmp/someplace
    3. $ cmake /path/to/source
    4. $ cmake --build .
    
    • 1
    • 2
    • 3
    • 4

    ⑤查看cmake帮助

    1. $ cmake --build . --target help
    2.
    3. The following are some of the valid targets for this Makefile:
    4. ... all (the default if no target is provided)
    5. ... clean
    6. ... depend
    7. ... rebuild_cache
    8. ... hello-world
    9. ... edit_cache
    10. ... hello-world.o
    11. ... hello-world.i
    12. ... hello-world.s
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2)切换生成器(-G)

    • 定义
      CMake是一个构建系统生成器,可以使用单个CMakeLists.txt为不同平台上的不同工具集配置项目。您可以在CMakeLists.txt中描述构建系统必须运行的操作,以配置并编译代码。基于这些指令,CMake将为所选的构建系统(Unix Makefile、Ninja、Visual Studio等等)生成相应的指令

    • 解释
      我们将重用前一节示例中的 hello-world.cpp 和 CMakeLists.txt 。惟一的区别在使用CMake时,因为现在必须显式地使用命令行方式,用 -G 切换生成器。

    • 举例

    ---------------------配置项目
    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake -G Ninja ..
    4.
    5. -- The CXX compiler identification is GNU 8.1.0
    6. -- Check for working CXX compiler: /usr/bin/c++
    7. -- Check for working CXX compiler: /usr/bin/c++ -- works
    8. -- Detecting CXX compiler ABI info
    9. -- Detecting CXX compiler ABI info - done
    10. -- Detecting CXX compile features
    11. -- Detecting CXX compile features - done
    12. -- Configuring done
    13. -- Generating done
    14.
    -- Build files have been written to: /home/user/cmake-cookbook/chapter01/recipe-02/cxx-exampl
    
    ---------------------构建项目
    1. $ cmake --build .
    2.
    3. [2/2] Linking CXX executable hello-world
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 不同生成器都有自己不同的文件集
    ①build.ninja 和 rules.ninja :包含Ninja的所有的构建语句和构建规则。
    ②CMakeCache.txt :CMake会在这个文件中进行缓存,与生成器无关。
    ③CMakeFiles :包含由CMake在配置期间生成的临时文件。
    ④cmake_install.cmake :CMake脚本处理安装规则,并在安装时使用。
    
    • 1
    • 2
    • 3
    • 4

    3)构建和链接静态库和动态库(还有对象库的使用举例)

    • cmake
    # set minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    # 1、创建项目recipe-03 ,规定源码为C++
    # project name and language
    project(recipe-03 LANGUAGES CXX)
    
    # 2、生成静态库叫message
    # generate a library from sources
    add_library(message
      STATIC
        Message.hpp
        Message.cpp
      )
    
    # 3、生成执行文件叫hello-world
    add_executable(hello-world hello-world.cpp)
    
    # 4、链接库message
    target_link_libraries(hello-world message)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 源文件
      1)Message.cpp
    
    #include "Message.hpp"
    
    #include 
    #include 
    
    std::ostream &Message::printObject(std::ostream &os) {
      os << "This is my very nice message: " << std::endl;
      os << message_;
    
      return os;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2)Message.hpp

    
    #pragma once
    
    #include 
    #include 
    
    class Message {
    public:
      Message(const std::string &m) : message_(m) {}
    
      friend std::ostream &operator<<(std::ostream &os, Message &obj) {
        return obj.printObject(os);
      }
    
    private:
      std::string message_;
      std::ostream &printObject(std::ostream &os);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3)hello-world.cpp

    #include "Message.hpp"
    
    #include 
    #include 
    
    int main() {
      Message say_hello("Hello, CMake World!");
    
      std::cout << say_hello << std::endl;
    
      Message say_goodbye("Goodbye, CMake World");
    
      std::cout << say_goodbye << std::endl;
    
      return EXIT_SUCCESS;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 备注
      ①CMake接受其他值作为 add_library 的第二个参数的有效值,我们来看下本书会用到的值:
    • -STATIC:用于创建静态库,即编译文件的打包存档,以便在链接其他目标时使用,例如:可执行文件。
    • -SHARED:用于创建动态库,即可以动态链接,并在运行时加载的库。可以
      在 CMakeLists.txt 中使用 add_library(message SHARED Message.hpp Message.cpp) 从静态库切换到动态共享对象(DSO)。
    • -OBJECT:可将给定 add_library 的列表中的源码编译到目标文件,不将它们归档到静态库中,也不能将它们链接到共享对象中。如果需要一次性创建静态库和动态库,那么使用对象库尤其有用。我们将在本示例中演示。
    • -MODULE:又为DSO组。与 SHARED 库不同,它们不链接到项目中的任何目标,不过可以进行动态加载。该参数可以用于构建运行时插件

    展示OBJECT库的使用,如下

    1. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    2. project(recipe-03 LANGUAGES CXX)
    3.
    4. add_library(message-objs
    5. OBJECT
    6. Message.hpp
    7. Message.cpp
    8. )
    9.
    10. # this is only needed for older compilers
    11. # but doesn't hurt either to have it
    12. set_target_properties(message-objs
    13. PROPERTIES
    14. POSITION_INDEPENDENT_CODE 1
    15. )
    16.
    17. add_library(message-shared
    18. SHARED
    19. $
    20. )
    21.
    22. add_library(message-static
    23. STATIC
    24. $
    25. )
    26.
    27. add_executable(hello-world hello-world.cpp)
    28.
    29. target_link_libraries(hello-world message-static)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 说明
      ①首先, add_library 改为 add_library(Message-objs OBJECT Message.hpp Message.cpp) 。此外,需要保证编译的目标文件与生成位置无关。可以通过使用 set_target_properties 命令,设置 message-objs 目标的相应属性来实现。
      ②NOTE: 可能在某些平台和/或使用较老的编译器上,需要显式地为目标设置 POSITION_INDEPENDENT_CODE 属性。
      现在,可以使用这个对象库来获取静态库( message-static )和动态库( message-shared )。要
      ③注意引用对象库的生成器表达式语法: $ 。生成器表达式是CMake在生成时(即配置之后)构造,用于生成特定于配置的构建输出。参见传送门:

    ④是否可以让CMake生成同名的两个库?换句话说,它们都可以被称为 message ,而不是 static 和 message-share d吗?我们需要修改这两个目标的属性:

    1. add_library(message-shared
    2. SHARED
    3. $
    4. )
    5.
    6. set_target_properties(message-shared
    7. PROPERTIES
    8. OUTPUT_NAME "message"
    9. )
    10.
    11. add_library(message-static
    12. STATIC
    13. $
    14. )
    15.
    16. set_target_properties(message-static
    17. PROPERTIES
    18. OUTPUT_NAME "message"
    19. )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    注意点:这让库同一个名不具备跨平台性!
    我们可以链接到DSO吗?这取决于操作系统和编译器:

    1. GNU/Linux和macOS上,不管选择什么编译器,它都可以工作。
    2. Windows上,不能与Visual Studio兼容,但可以与MinGW和MSYS2兼容。(生成好的DSO组需要程序员限制符号的可见性。需要在编译器的帮助下实现,但不同的
      操作系统和编译器上,约定不同。)

    4)用条件句控制编译

    • 目的(从与上一个示例的的源代码开始,我们希望能够在不同的两种行为之间进行切换:)
      1) 将 Message.hpp 和 Message.cpp 构建成一个库(静态或动态),然后将生成库链接到 helloworld 可执行文件中。
      2)将 Message.hpp , Message.cpp 和 hello-world.cpp 构建成一个可执行文件,但不生成任何一个库。

    • cmake和解释

    # 1、首先,定义最低CMake版本、项目名称和支持的语言:
    # set minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    # project name and language
    project(recipe-04 LANGUAGES CXX)
    
    # 2、我们引入了一个新变量 USE_LIBRARY ,这是一个逻辑变量,值为 OFF 。我们还打印了它的
    值:
    # introduce a toggle for using a library
    set(USE_LIBRARY OFF)
    message(STATUS "Compile sources into a library? ${USE_LIBRARY}")
    
    # 3、CMake中定义 BUILD_SHARED_LIBS 全局变量,并设置为 OFF 。调用 add_library 并省略
    第二个参数,将构建一个静态库:
    # BUILD_SHARED_LIBS is a global flag offered by CMake
    # to toggle the behavior of add_library
    set(BUILD_SHARED_LIBS OFF)
    
    # 4、然后,引入一个变量 _sources ,包括 Message.hpp 和 Message.cpp :
    # list sources
    list(APPEND _sources Message.hpp Message.cpp)
    
    # 5、引入一个基于 USE_LIBRARY 值的 if-else 语句。如果逻辑为真,
    则 Message.hpp 和 Message.cpp 将打包成一个库:
    if(USE_LIBRARY)
        # add_library will create a static library
        # since BUILD_SHARED_LIBS is OFF
        add_library(message ${_sources})   # 打包库
    
        add_executable(hello-world hello-world.cpp)
        target_link_libraries(hello-world message)
    else()
         add_executable(hello-world hello-world.cpp ${_sources})
    endif()
    
    # 6、补充:我们可以再次使用相同的命令集进行构建。由于 USE_LIBRARY 为 OFF , hello-world 可
    执行文件将使用所有源文件来编译。可以通过在GNU/Linux上,运行 objdump -x 命令进行验
    证。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 备注
      ①如CMake语言文档中描述,逻辑真或假可以用多种方式表示:
      《1》如果将逻辑变量设置为以下任意一种: 1 、 ON 、 YES 、 true 、 Y 或非零数,则逻辑变量为 true 。
      《2》如果将逻辑变量设置为以下任意一种: 0 、 OFF 、 NO 、 false 、 N 、 IGNORE、NOTFOUND 、空字符串,或者以 -NOTFOUND 为后缀,则逻辑变量为 false 。
      BUILD_SHARED_LIBS 是CMake的一个全局标志。因为CMake内部要查询 BUILD_SHARED_LIBS 全局变量,所以 add_library 命令可以在不传递 STATIC/SHARED/OBJECT 参数的情况下调用;如果为 false 或未定义,将生成一个静态库
      if(XXX) + else() + endif() 也可以用 if(USE_LIBRARY)…else(USE_LIBRARY)… endif(USE_LIBIRAY) ,这个格式并不唯一,可以根据个人喜好来决定使用哪种格式。
      _sources 变量是一个局部变量,不应该在当前范围之外使用,可以在名称前加下划线

    5)向用户显示选项(在命令行输入参数选择是否开启)

    • 起因
      看一下前面示例中的静态/动态库示例。与其硬编码 USE_LIBRARY 为 ON 或 OFF ,现在为其设置一个默认值,同时也可以从外部进行更改:
    • cmake
    # set minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    # project name and language
    project(recipe-05 LANGUAGES CXX)
    
    # expose options to the user
    option(USE_LIBRARY "Compile sources into a library" OFF)
    
    message(STATUS "Compile sources into a library? ${USE_LIBRARY}")
    
    include(CMakeDependentOption)
    
    # second option depends on the value of the first
    cmake_dependent_option(
      MAKE_STATIC_LIBRARY "Compile sources into a static library" OFF
      "USE_LIBRARY" ON
      )
    
    # third option depends on the value of the first
    cmake_dependent_option(
      MAKE_SHARED_LIBRARY "Compile sources into a shared library" ON
      "USE_LIBRARY" ON
      )
    
    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
    
    # list sources
    list(APPEND _sources Message.hpp Message.cpp)
    
    if(USE_LIBRARY)
      message(STATUS "Compile sources into a STATIC library? ${MAKE_STATIC_LIBRARY}")
      message(STATUS "Compile sources into a SHARED library? ${MAKE_SHARED_LIBRARY}")
    
      if(MAKE_SHARED_LIBRARY)
        add_library(message SHARED ${_sources})
    
        add_executable(hello-world hello-world.cpp)
    
        target_link_libraries(hello-world message)
      endif()
    
      if(MAKE_STATIC_LIBRARY)
        add_library(message STATIC ${_sources})
    
        add_executable(hello-world hello-world.cpp)
    
        target_link_libraries(hello-world message)
      endif()
    else()
      add_executable(hello-world hello-world.cpp ${_sources})
    endif()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • cmake解释
      ①用一个选项替换上一个示例的 set(USE_LIBRARY OFF) 命令。该选项将修改 USE_LIBRARY 的值,并设置其默认值为 OFF :
    1. option(USE_LIBRARY "Compile sources into a library" OFF)
    
    • 1

    ②现在,可以通过CMake的 -D CLI选项,将信息传递给CMake来切换库的行为:

    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake -D USE_LIBRARY=ON ..
    4.
    5. -- ...
    6. -- Compile sources into a library? ON
    7. -- ...
    8.
    9. $ cmake --build .
    10.
    11. Scanning dependencies of target message
    12. [ 25%] Building CXX object CMakeFiles/message.dir/Message.cpp.o
    13. [ 50%] Linking CXX static library libmessage.a
    14. [ 50%] Built target message
    15. Scanning dependencies of target hello-world
    16. [ 75%] Building CXX object CMakeFiles/hello-world.dir/hello-world.cpp.o
    17. [100%] Linking CXX executable hello-world
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 备注
      -D 开关用于为CMake设置任何类型的变量:逻辑变量、路径等等
      option 可接受三个参数:
    1) 表示该选项的变量的名称。
    2)"help string" 记录选项的字符串,在CMake的终端或图形用户界面中可见。
    3)[initial value] 选项的默认值,可以是 ON 或 OFF 。
    
    • 1
    • 2
    • 3

    6)指定编译器

    • 前提
      CMake将语言的编译器存储在 CMAKE__COMPILER 变量中,其中 是受支持的任何一种语言,对于我们的目的是 CXX 、 C 或 Fortran 。用户可以通过以下两种方式之一设置此变量:
      ①使用CLI中的 -D 选项,例如:
    1. $ cmake -D CMAKE_CXX_COMPILER=clang++ ..
    
    • 1

    ②通过导出环境变量 CXX (C++编译器)、 CC (C编译器)和 FC (Fortran编译器)。例如,使用这个命令使用 clang++ 作为 C++ 编译器:

    $ env CXX=clang++ cmake ..
    
    • 1
    • 跨平台注意点
      我们建议使用 -D CMAKE__COMPILER CLI选项设置编译器,而不是导
      出 CXX 、 CC 和 FC 。这是确保跨平台并与非POSIX兼容的唯一方法。为了避免变量污染环境,这些变量可能会影响与项目一起构建的外部库环境。

    • cmake额外变量和编译器交互:

    ①CMAKE__COMPILER_LOADED :如果为项目启用了语言  ,则将设置为 TRUE 。
    ②CMAKE__COMPILER_ID :编译器标识字符串,编译器供应商所特有。例如, GCC 用于
    GNU编译器集合, AppleClang 用于macOS上的Clang, MSVC 用于Microsoft Visual
    Studio编译器。注意,不能保证为所有编译器或语言定义此变量。
    ③CMAKE_COMPILER_IS_GNU :如果语言  是GNU编译器集合的一部分,则将此
    逻辑变量设置为 TRUE 。注意变量名的  部分遵循GNU约定:C语言为 CC , C++语言
    为 CXX , Fortran语言为 G77 。
    ④CMAKE__COMPILER_VERSION :此变量包含一个字符串,该字符串给定语言的编译器
    版本。版本信息在 major[.minor[.patch[.tweak]]] 中给出。但是,对
    于 CMAKE__COMPILER_ID ,不能保证所有编译器或语言都定义了此变量。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 使用举例
      我们可以尝试使用不同的编译器,配置下面的示例 CMakeLists.txt 。这个例子中,我们将使用CMake变量来探索已使用的编译器(及版本):
    1. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    2. project(recipe-06 LANGUAGES C CXX)
    3.
    
    # 由于这里设置了使用CXX,所以为true,也就是1
    4. message(STATUS "Is the C++ compiler loaded? ${CMAKE_CXX_COMPILER_LOADED}")
    5. if(CMAKE_CXX_COMPILER_LOADED)
    6. message(STATUS "The C++ compiler ID is: ${CMAKE_CXX_COMPILER_ID}")
    7. message(STATUS "Is the C++ from GNU? ${CMAKE_COMPILER_IS_GNUCXX}")
    8.
    message(STATUS "The C++ compiler version is:
    ${CMAKE_CXX_COMPILER_VERSION}")
    9. endif()
    10.
    11. message(STATUS "Is the C compiler loaded? ${CMAKE_C_COMPILER_LOADED}")
    12. if(CMAKE_C_COMPILER_LOADED)
    13. message(STATUS "The C compiler ID is: ${CMAKE_C_COMPILER_ID}")
    14. message(STATUS "Is the C from GNU? ${CMAKE_COMPILER_IS_GNUCC}")
    15. message(STATUS "The C compiler version is: ${CMAKE_C_COMPILER_VERSION}")
    16. endif()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    注意,这个例子不包含任何目标,没有要构建的东西,我们只关注配置步骤:

    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake ..
    4.
    5. ...
    6. -- Is the C++ compiler loaded? 1
    7. -- The C++ compiler ID is: GNU
    8. -- Is the C++ from GNU? 1
    9. -- The C++ compiler version is: 8.1.0
    10. -- Is the C compiler loaded? 1
    11. -- The C compiler ID is: GNU
    12. -- Is the C from GNU? 1
    13. -- The C compiler version is: 8.1.0
    14. ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    7)切换构建类型(release或debug)

    • 背景
      配置时,可以为Debug或Release构建设置相关的选项或属性,例如:编译器和链接器标志

    • 控制变量
      控制生成构建系统使用的配置变量是

    CMAKE_BUILD_TYPE
    
    • 1

    该变量默认为空,CMake识别的值为:

    1. Debug:用于在没有优化的情况下,使用带有调试符号构建库或可执行文件。
    2. Release:用于构建的优化的库或可执行文件,不包含调试符号。
    3. RelWithDebInfo:用于构建较少的优化库或可执行文件,包含调试符号。
    4. MinSizeRel:用于不增加目标代码大小的优化方式,来构建库或可执行文件。
    
    • 1
    • 2
    • 3
    • 4
    • cmake
    
    # set minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    # project name and language
    project(recipe-07 LANGUAGES C CXX)
    
    # we default to Release build type
    if(NOT CMAKE_BUILD_TYPE)
      set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
    endif()
    
    message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
    
    message(STATUS "C flags, Debug configuration: ${CMAKE_C_FLAGS_DEBUG}")
    message(STATUS "C flags, Release configuration: ${CMAKE_C_FLAGS_RELEASE}")
    message(STATUS "C flags, Release configuration with Debug info: ${CMAKE_C_FLAGS_RELWITHDEBINFO}")
    message(STATUS "C flags, minimal Release configuration: ${CMAKE_C_FLAGS_MINSIZEREL}")
    
    message(STATUS "C++ flags, Debug configuration: ${CMAKE_CXX_FLAGS_DEBUG}")
    message(STATUS "C++ flags, Release configuration: ${CMAKE_CXX_FLAGS_RELEASE}")
    message(STATUS "C++ flags, Release configuration with Debug info: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
    message(STATUS "C++ flags, minimal Release configuration: ${CMAKE_CXX_FLAGS_MINSIZEREL}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • cmake解释
      ①设置一个默认的构建类型(本例中是Release),并打印一条消息。要注意的是,该变量被
      设置为缓存变量,可以通过缓存进行编辑(这里没指定的话,默认是Release)
    1. if(NOT CMAKE_BUILD_TYPE)
    2. 	set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
    3. endif()
    4. message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
    
    • 1
    • 2
    • 3
    • 4

    ②打印出CMake设置的相应编译标志:

    1.message(STATUS "C flags, Debug configuration: ${CMAKE_C_FLAGS_DEBUG}")
    2.message(STATUS "C flags, Release configuration: ${CMAKE_C_FLAGS_RELEASE}")
    3.message(STATUS "C flags, Release configuration with Debug info:${CMAKE_C_FLAGS_RELWITHDEBINFO}")
    4.message(STATUS "C flags, minimal Release configuration:${CMAKE_C_FLAGS_MINSIZEREL}")
    5. message(STATUS "C++ flags, Debug configuration: ${CMAKE_CXX_FLAGS_DEBUG}")
    6.
    message(STATUS "C++ flags, Release configuration:
    ${CMAKE_CXX_FLAGS_RELEASE}")
    7.
    message(STATUS "C++ flags, Release configuration with Debug info:
    ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
    8.
    message(STATUS "C++ flags, minimal Release configuration:
    ${CMAKE_CXX_FLAGS_MINSIZEREL}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ③验证配置的输出:(这里显示默认构建release)

    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake ..
    4.
    5. ...
    6. -- Build type: Release
    7. -- C flags, Debug configuration: -g
    8. -- C flags, Release configuration: -O3 -DNDEBUG
    9. -- C flags, Release configuration with Debug info: -O2 -g -DNDEBUG
    10. -- C flags, minimal Release configuration: -Os -DNDEBUG
    11. -- C++ flags, Debug configuration: -g
    12. -- C++ flags, Release configuration: -O3 -DNDEBUG
    13. -- C++ flags, Release configuration with Debug info: -O2 -g -DNDEBUG
    14. -- C++ flags, minimal Release configuration: -Os -DNDEBUG
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ④切换构建类型为debug:(上面指定了release是默认的构建)

    1. $ cmake -D CMAKE_BUILD_TYPE=Debug ..
    2.
    3. -- Build type: Debug
    4. -- C flags, Debug configuration: -g
    5. -- C flags, Release configuration: -O3 -DNDEBUG
    6. -- C flags, Release configuration with Debug info: -O2 -g -DNDEBUG
    7. -- C flags, minimal Release configuration: -Os -DNDEBUG
    8. -- C++ flags, Debug configuration: -g
    9. -- C++ flags, Release configuration: -O3 -DNDEBUG
    10. -- C++ flags, Release configuration with Debug info: -O2 -g -DNDEBUG
    11. -- C++ flags, minimal Release configuration: -Os -DNDEBUG
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    8)设置编译器选项(例如 “-fPIC” "-Wall"等)

    • 示例解释
      原源码链接,main函数在 computer_area.cpp,函数的各种实现分布在不同的文件中,每个几何形状都有一个头文件和源文件。总共有4个头文件和5个源文件要编译:
    1. .
    2. ├─ CMakeLists.txt
    3. ├─ compute-areas.cpp
    4. ├─ geometry_circle.cpp
    5. ├─ geometry_circle.hpp
    6. ├─ geometry_polygon.cpp
    7. ├─ geometry_polygon.hpp
    8. ├─ geometry_rhombus.cpp
    9. ├─ geometry_rhombus.hpp
    10. ├─ geometry_square.cpp
    11. └─ geometry_square.hpp
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • cmake
    # 1、set minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    # 2、project name and language
    project(recipe-08 LANGUAGES CXX)
    
    # 3、打印当前编译器标志。CMake将对所有C++目标使用这些
    message("C++ compiler flags: ${CMAKE_CXX_FLAGS}")
    
    # 4、为目标准备了标志列表,其中一些将无法在Windows上使用
    list(APPEND flags "-fPIC" "-Wall")
    if(NOT WIN32)
      list(APPEND flags "-Wextra" "-Wpedantic")
    endif()
    
    # 5、添加了一个新的目标—— geometry 库,并列出它的源依赖关系:
    add_library(geometry
      STATIC
        geometry_circle.cpp
        geometry_circle.hpp
        geometry_polygon.cpp
        geometry_polygon.hpp
        geometry_rhombus.cpp
        geometry_rhombus.hpp
        geometry_square.cpp
        geometry_square.hpp
      )
    
    # 6、为这个库目标设置了编译选项
    target_compile_options(geometry
      PRIVATE
        ${flags}
      )
    
    # 7、然后,将生成 compute-areas 可执行文件作为一个目标:
    add_executable(compute-areas compute-areas.cpp)
    
    #8、还为可执行目标设置了编译选项:
    target_compile_options(compute-areas
      PRIVATE
        "-fPIC"
      )
    
    # 9、为执行文件链接库
    target_link_libraries(compute-areas geometry)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 备注
      ①编译选项可以添加三个级别的可见性: INTERFACE 、 PUBLIC 和 PRIVATE 。
    • PRIVATE,编译选项会应用于给定的目标,不会传递给与目标相关的目标。我们的示例中, 即使 compute-areas 将链接到 geometry 库, compute-areas 也不会继承 geometry 目标上设置的编译器选项。
    • INTERFACE,给定的编译选项将只应用于指定目标并传递给与目标相关的目标。
    • PUBLIC,编译选项将应用于指定目标和使用它的目标

    ②如何验证,这些标志是否按照我们的意图正确使用呢?或者换句话说,如何确定项目在CMake构建时,实际使用了哪些编译标志?

    • 方法一:使用CMake将额外的参数传递给本地构建工具。本例中会设置环境变量 VERBOSE=1
    输出:
    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake ..
    4. $ cmake --build . -- VERBOSE=1
    5.
    5. ... lots of output ...
    6. 7.
    8. [ 14%] Building CXX object CMakeFiles/geometry.dir/geometry_circle.cpp.o
    9.
    /usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o
    CMakeFiles/geometry.dir/geometry_circle.cpp.o -c /home/bast/tmp/cmakecookbook/chapter-01/recipe-08/cxx-example/geometry_circle.cpp
    10. [ 28%] Building CXX object CMakeFiles/geometry.dir/geometry_polygon.cpp.o
    11.
    /usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o
    CMakeFiles/geometry.dir/geometry_polygon.cpp.o -c /home/bast/tmp/cmakecookbook/chapter-01/recipe-08/cxx-example/geometry_polygon.cpp
    12. [ 42%] Building CXX object CMakeFiles/geometry.dir/geometry_rhombus.cpp.o
    13.
    /usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o
    CMakeFiles/geometry.dir/geometry_rhombus.cpp.o -c /home/bast/tmp/cmakecookbook/chapter-01/recipe-08/cxx-example/geometry_rhombus.cpp
    14. [ 57%] Building CXX object CMakeFiles/geometry.dir/geometry_square.cpp.o
    15.
    /usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o
    CMakeFiles/geometry.dir/geometry_square.cpp.o -c /home/bast/tmp/cmakecookbook/chapter-01/recipe-08/cxx-example/geometry_square.cpp
    16.
    17. ... more output ...
    18.
    19. [ 85%] Building CXX object CMakeFiles/compute-areas.dir/compute-areas.cpp.o
    20.
    /usr/bin/c++ -fPIC -o CMakeFiles/compute-areas.dir/compute-areas.cpp.o -c
    /home/bast/tmp/cmake-cookbook/chapter-01/recipe-08/cxx-example/compute-areas.cpp
    21.
    22. ... more output ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 方法二:不用对 CMakeLists.txt 进行修改。如果想在这个项目中修
      改 geometry 和 compute-areas 目标的编译器选项,可以使用CMake参数进行配置:
    $ cmake -D CMAKE_CXX_FLAGS="-fno-exceptions -fno-rtti" ..
    
    • 1

    这个命令将编译项目,禁用异常和运行时类型标识(RTTI)。
    也可以使用全局标志,可以使用 CMakeLists.txt 运行以下命令:

    1. $ cmake -D CMAKE_CXX_FLAGS="-fno-exceptions -fno-rtti" ..
    
    • 1

    这将使用 -fno-rtti - fpic - wall - Wextra - wpedantic 配置 geometry 目标,同时使用 -
    fno exception -fno-rtti - fpic 配置 compute-areas (因为对compute-areas配置的flags是PRIVATE的)

    9)为语言设定标准

    • 起因(源码传送门)
      3.1版本中,CMake引入了一个独立
      于平台和编译器的机制,用于为 C++ 和 C 设置语言标准:为目标设置 _STANDARD 属性。
    • 举例需求
      对于下面的示例,需要一个符合 C++14 标准或更高版本的 C++ 编译器。此示例代码定义了动物的多态,我们使用 std::unique_ptr 作为结构中的基类:
    1. std::unique_ptr cat = Cat("Simon");
    2. std::unique_ptr dog = Dog("Marlowe);
    
    • 1
    • 2
    • 源码目录
      在这里插入图片描述

    • cmake(本项目引入c++14)

    # 1、声明最低要求的CMake版本,项目名称和语言:
    # set minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    # project name and language
    project(recipe-09 LANGUAGES CXX)
    
    # 2、要求在Windows上导出所有库符号:
    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
    
    # 3、需要为库添加一个目标,这将编译源代码为一个动态库:
    add_library(animals
      SHARED
        Animal.cpp
        Animal.hpp
        Cat.cpp
        Cat.hpp
        Dog.cpp
        Dog.hpp
        Factory.hpp
      )
    
    # 4、现在,为目标设置了 CXX_STANDARD 、 CXX_EXTENSIONS 和 CXX_STANDARD_REQUIRED
    # 属性。还设置了 position_independent ent_code 属性,以避免在使用一些编译器构建DSO
    #时 出现问题:
    set_target_properties(animals
      PROPERTIES
        CXX_STANDARD 14
        CXX_EXTENSIONS OFF
        CXX_STANDARD_REQUIRED ON
        POSITION_INDEPENDENT_CODE 1
      )
    
    # 5、然后,为”动物农场”的可执行文件添加一个新目标,并设置它的属性:
    add_executable(animal-farm animal-farm.cpp)
    set_target_properties(animal-farm
      PROPERTIES
        CXX_STANDARD 14
        CXX_EXTENSIONS OFF
        CXX_STANDARD_REQUIRED ON
      )
    
    #8、将可执行文件链接到库
    target_link_libraries(animal-farm animals)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 编译运行
    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake ..
    4. $ cmake --build .
    5. $ ./animal-farm
    6.
    7. I'm Simon the cat!
    8. I'm Marlowe the dog!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 备注
      ①步骤4和步骤5中,我们为动物和动物农场目标设置了一些属性:
    • CXX_STANDARD会设置我们想要的标准。
    • CXX_EXTENSIONS告诉CMake,只启用 ISO C++ 标准的编译器标志,而不使用特定编译器的扩展。
    • CXX_STANDARD_REQUIRED指定所选标准的版本。如果这个版本不可用,CMake将停止配置并出现错误。当这个属性被设置为 OFF 时,CMake将寻找下一个标准的最新版本,直到一个合适的标志。这意味着,首先查找 C++14 ,然后是 C++11 ,然后是 C++98 。(译者注:目前会从 C++20 或 C++17 开始查找)

    10)使用控制流(if-else-endif\foreach\while-endwhile)

    • 起因
      已经使用过 if-else-endif 。CMake还提供了创建循环的语言工具: foreach endforeach 和 while-endwhile 。两者都可以与 break 结合使用,以便尽早从循环中跳出。本示例将展示如何使用 foreach ,来循环源文件列表。我们将应用这样的循环,在引入新目标的前提下,来为一组源文件进行优化降级。

    • 源码目录
      在这里插入图片描述

    • cmake

    # 1、与示例8中一样,指定了CMake的最低版本、项目名称和语言,并声明了几何库目标:
    # set minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    # project name and language
    project(recipe-10 LANGUAGES CXX)
    
    add_library(geometry
      STATIC
        geometry_circle.cpp
        geometry_circle.hpp
        geometry_polygon.cpp
        geometry_polygon.hpp
        geometry_rhombus.cpp
        geometry_rhombus.hpp
        geometry_square.cpp
        geometry_square.hpp
      )
    
    # 2、使用 -O3 编译器优化级别编译库,对目标设置一个私有编译器选项:
    # we wish to compile the library with the optimization flag: -O3
    target_compile_options(geometry
      PRIVATE
        -O3
      )
    
    # 3、然后,生成一个源文件列表,以较低的优化选项进行编译
    list(
      APPEND sources_with_lower_optimization
        geometry_circle.cpp
        geometry_rhombus.cpp
      )
    
    # 4、循环这些源文件,将它们的优化级别调到 -O2 。使用它们的源文件属性完成:
    # we use the IN LISTS foreach syntax to set source properties
    message(STATUS "Setting source properties using IN LISTS syntax:")
    foreach(_source IN LISTS sources_with_lower_optimization)
      set_source_files_properties(${_source} PROPERTIES COMPILE_FLAGS -O2)
      message(STATUS "Appending -O2 flag for ${_source}")
    endforeach()
    
    # 5、打印这些文件的优化属性
    # we demonstrate the plain foreach syntax to query source properties
    # which requires to expand the contents of the variable
    message(STATUS "Querying sources properties using plain syntax:")
    foreach(_source ${sources_with_lower_optimization})
      get_source_file_property(_flags ${_source} COMPILE_FLAGS)
      message(STATUS "Source ${_source} has the following extra COMPILE_FLAGS: ${_flags}")
    endforeach()
    
    # 6、最后,添加 compute-areas 可执行目标,并将 geometry 库连接上去:
    add_executable(compute-areas compute-areas.cpp)
    target_link_libraries(compute-areas geometry)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 编译确认优化选项
      最后,还使用 VERBOSE=1 检查构建步骤。将看到 -O2 标志添加在 -O3 标志之后,但是最后一个优化级别标志(在本例中是 -O2 )不同
    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake ..
    4.
    4. ...
    5. -- Setting source properties using IN LISTS syntax:
    6. -- Appending -O2 flag for geometry_circle.cpp
    
    
    -------------------------------------
    . $ cmake --build . -- VERBOSE=1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 备注
      foreach-endforeach 语法可用于在变量列表上,表示重复特定任务。本示例中,使用它来操作、设置和获取项目中特定文件的编译器标志。

    (CMake代码片段中引入了另外两个新命令:)
    set_source_files_properties(file PROPERTIES property value) ,它将属性设置为给定
    文件的传递值。与目标非常相似,文件在CMake中也有属性,允许对构建系统进行非常细粒度的控制。源文件的可用属性列表可以在这里找到:
    https://cmake.org/cmake/help/v3.5/manual/cmakeproperties.7.html#source-file-propertie

    get_source_file_property(VAR file property) ,检索给定文件所需属性的值,并将其存储
    在CMake VAR 变量中。
    ④CMake中,列表是用分号分隔的字符串组。列表可以由 list 或 set 命令创建。例
    如, set(var a b c d e) 和 list(APPEND a b c d e) 都创建了列表 a;b;c;d;e 。

    ⑤单独优化的备注
    :为了对一组文件降低优化,将它们收集到一个单独的目标(库)中,并为这个目标显式地设置优化级别,而不是附加一个标志,这样可能会更简洁,不过在本示例中,我们的重点是 foreach & endforeach
    foreach() 的四种使用方式:

    • foreach(loop_var arg1 arg2 ...) : 其中提供循环变量和显式项列表。当
      为 sources_with_lower_optimization 中的项打印编译器标志集时,使用此表单。注意,如果项目列表位于变量中,则必须显式展开它;也就是说, ${sources_with_lower_optimization} 必须作为参数传递。
    • 通过指定一个范围,可以对整数进行循环,例如:foreach(loop_var rangetotal)foreach(loop_var range start stop [step])
    • 对列表值变量的循环,例如: foreach(loop_var IN LISTS [list1[...]]) 。参数解释为列表,其内容就会自动展开。
    • 对变量的循环,例如: foreach(loop_var IN ITEMS [item1 [...]]) 。参数的内容没有展
      开。

    三、检测环境

    1)检测操作系统

    • 示例兼容性
      该示例在CMake 3.5版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。
    • cmake及注释
    # 1、定义CMake最低版本和项目名称。请注意,语言是 NONE :
    # set minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    # project name, in this case no language required
    project(recipe-01 LANGUAGES NONE)
    
    #2、根据检测到的操作系统信息打印消息:
    # print custom message depending on the operating system
    if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
      message(STATUS "Configuring on/for Linux")
    elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
      message(STATUS "Configuring on/for macOS")
    elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
      message(STATUS "Configuring on/for Windows")
    elseif(CMAKE_SYSTEM_NAME STREQUAL "AIX")
      message(STATUS "Configuring on/for IBM AIX")
    else()
      message(STATUS "Configuring on/for ${CMAKE_SYSTEM_NAME}")
    endif()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 备注
      ①CMake为目标操作系统定义了 CMAKE_SYSTEM_NAME ,因此不需要使用定制命令、工具或脚本来查询此信息。然后,可以使用此变量的值实现特定于操作系统的条件和解决方案。在具有 uname 命令的系统上,将此变量设置为 uname -s 的输出。该变量在macOS上设置为“Darwin”。在Linux和Windows上,它分别计算为“Linux”和“Windows”。
      ②:为了最小化从一个平台转移到另一个平台时的成本,应该避免直接使用Shell命令,还应该避免显式的路径分隔符(Linux和macOS上的前斜杠/和Windows上的后斜杠\)。CMake代码中只使用前斜杠作为路径分隔符,CMake将自动将它们转换为所涉及的操作系统环境。

    2)处理与平台相关的源代码(根据平台设定宏定义)

    • 文件目录
      在这里插入图片描述

    • hello-world.cpp

    
    #include 
    #include 
    #include 
    
    std::string say_hello() {
    #ifdef IS_WINDOWS
      return std::string("Hello from Windows!");
    #elif IS_LINUX
      return std::string("Hello from Linux!");
    #elif IS_MACOS
      return std::string("Hello from macOS!");
    #else
      return std::string("Hello from an unknown system!");
    #endif
    }
    
    int main() {
      std::cout << say_hello() << std::endl;
      return EXIT_SUCCESS;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • cmake
    # 1、设置了CMake最低版本、项目名称和支持的语言
    # set minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    # project name and language
    project(recipe-02 LANGUAGES CXX)
    
    # 2、定义可执行文件及其对应的源文件:
    # define executable and its source file
    add_executable(hello-world hello-world.cpp)
    
    # 3、通过定义以下目标编译定义,让预处理器知道系统名称
    # let the preprocessor know about the system name
    if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
      target_compile_definitions(hello-world PUBLIC "IS_LINUX")
    endif()
    if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
      target_compile_definitions(hello-world PUBLIC "IS_MACOS")
    endif()
    if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
      target_compile_definitions(hello-world PUBLIC "IS_WINDOWS")
    endif()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3)处理与编译器相关的源代码(略,游戏服务器一般不涉及做编译相关设定修改)

    4)检测处理器体系结构(略,大型游戏服务器一般都是固定用32或64位的执行程序)

    • 备注
      使用 CMAKE_SIZEOF_VOID_P 是检查当前CPU是否具有32位或64位架构的唯一“真正”可移植的方法。

    5)检测处理器指令集(略,不涉及)

    6)为Eigen库使能像量化(处理器的向量功能,可以提高代码的性能,如提高线代的矩阵计算)

    • 例子作用
      本示例将展示如何使能矢量化,以便使用线性代数的Eigen C++库加速可执行文件。

    • 文件目录
      在这里插入图片描述

    • linear-algebra.cpp

    #include 
    #include 
    
    #include 
    
    EIGEN_DONT_INLINE
    double simple_function(Eigen::VectorXd &va, Eigen::VectorXd &vb) {
      // this simple function computes the dot product of two vectors
      // of course it could be expressed more compactly
      double d = va.dot(vb);
      return d;
    }
    
    int main() {
      int len = 1000000;
      int num_repetitions = 100;
    
      // generate two random vectors
      Eigen::VectorXd va = Eigen::VectorXd::Random(len);
      Eigen::VectorXd vb = Eigen::VectorXd::Random(len);
    
      double result;
      auto start = std::chrono::system_clock::now();
      for (auto i = 0; i < num_repetitions; i++) {
        result = simple_function(va, vb);
      }
      auto end = std::chrono::system_clock::now();
      auto elapsed_seconds = end - start;
    
      std::cout << "result: " << result << std::endl;
      std::cout << "elapsed seconds: " << elapsed_seconds.count() << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    我们期望向量化可以加快 simple_function 中的点积操作。

    • cmake和注释
    # 1、 声明一个 C++11 项目:
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    project(recipe-06 LANGUAGES CXX)
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_EXTENSIONS OFF)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    # 2、使用Eigen库,我们需要在系统上找到它的头文件:
    find_package(Eigen3 3.3 REQUIRED CONFIG)
    
    # 3、CheckCXXCompilerFlag.cmake 标准模块文件:
    include(CheckCXXCompilerFlag)
    
    # 4、检查 -march=native 编译器标志是否工作:
    check_cxx_compiler_flag("-march=native" _march_native_works)
    # 5、另一个选项 -xHost 编译器标志也开启
    check_cxx_compiler_flag("-xHost" _xhost_works)
    
    # 6、设置了一个空变量 _CXX_FLAGS ,来保存刚才检查的两个编译器中找到的编译器标志。如果看
    到 _march_native_works ,我们将 _CXX_FLAGS 设置为 -march=native 。如果看
    到 _xhost_works ,我们将 _CXX_FLAGS 设置为 -xHost 。如果它们都不起作
    用, _CXX_FLAGS 将为空,并禁用矢量化:
    
    set(_CXX_FLAGS)
    if(_march_native_works)
      message(STATUS "Using processor's vector instructions (-march=native compiler flag set)")
      set(_CXX_FLAGS "-march=native")
    elseif(_xhost_works)
      message(STATUS "Using processor's vector instructions (-xHost compiler flag set)")
      set(_CXX_FLAGS "-xHost")
    else()
      message(STATUS "No suitable compiler flag found for vectorization")
    endif()
    
    add_executable(linear-algebra-unoptimized linear-algebra.cpp)
    
    # 7、 为了便于比较,我们还为未优化的版本定义了一个可执行目标,不使用优化标志
    target_link_libraries(linear-algebra-unoptimized
      PRIVATE
        Eigen3::Eigen
      )
    # 8、此外,我们定义了一个优化版本:
    add_executable(linear-algebra linear-algebra.cpp)
    
    target_compile_options(linear-algebra
      PRIVATE
        ${_CXX_FLAGS}
      )
    
    target_link_libraries(linear-algebra
      PRIVATE
        Eigen3::Eigen
      )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 让我们编译可执行文件,并比较运行时间:
    1. $ cmake --build .
    2. $ ./linear-algebra-unoptimized
    3.
    4. result: -261.505
    5. elapsed seconds: 1.97964
    6.
    7. $ ./linear-algebra
    8.
    9. result: -261.505
    10. elapsed seconds: 1.05048
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 备注
      ①指示编译器为我们检查处理器,并为当前体系结构生成本机指令。不同的编译器供应商会使用不同的标志来实现这一点:GNU编译器使用 -march=native 标志来实现这一点,而Intel编译器使用 -xHost 标志。
      ②使用 CheckCXXCompilerFlag.cmake 模块提供的 check_cxx_compiler_flag 函数进行编译器标志的检查:
    check_cxx_compiler_flag("-march=native" _march_native_works)
    
    • 1

    这个函数接受两个参数:

    • 第一个是要检查的编译器标志。
    • 第二个是用来存储检查结果(true或false)的变量。如果检查为真,我们将工作标志添加
      到 _CXX_FLAGS 变量中,该变量将用于为可执行目标设置编译器标志。

    ③本示例可与前一示例相结合,可以使用 cmake_host_system_information 查询处理器功能。

    四、检测外部库和程序

    • 备注
      我们的项目常常会依赖于其他项目和库。本章将演示,如何检测外部库、框架和项目,以及如何链接到这些库。CMake有一组预打包模块,用于检测常用库和程序,例如:Python和Boost。可以使用 cmake --help-module-list 获得现有模块的列表。但是,不是所有的库和程序都包含在其中,有时必须自己编写检测脚本
      find_file:在相应路径下查找命名文件
    find_library:查找一个库文件
    find_package:从外部项目查找和加载设置
    find_path:查找包含指定文件的目录
    find_program:找到一个可执行程序
    
    • 1
    • 2
    • 3
    • 4

    1)检测Python解释器

    • cmake
    #1. 首先,定义CMake最低版本和项目名称。注意,这里不需要任何语言支持
    # set minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    # project name and language
    project(recipe-01 LANGUAGES NONE)
    
    #2. 然后,使用 find_package 命令找到Python解释器:
    # detect python
    find_package(PythonInterp REQUIRED)
    
    #3. 然后,执行Python命令并捕获它的输出和返回值:
    # Execute a tiny Python script
    execute_process(
      COMMAND
        ${PYTHON_EXECUTABLE} "-c" "print('Hello, world!')"
      RESULT_VARIABLE _status
      OUTPUT_VARIABLE _hello_world
      ERROR_QUIET
      OUTPUT_STRIP_TRAILING_WHITESPACE
      )
    
    #4. 最后,打印Python命令的返回值和输出:
    message(STATUS "RESULT_VARIABLE is: ${_status}")
    message(STATUS "OUTPUT_VARIABLE is: ${_hello_world}")
    
    # compare the "manual" messages with the following handy helper
    include(CMakePrintHelpers)
    cmake_print_variables(_status _hello_world)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • cmake执行结果
    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake ..
    4.
    5. -- Found PythonInterp: /usr/bin/python (found version "3.6.5")
    6. -- RESULT_VARIABLE is: 0
    7. -- OUTPUT_VARIABLE is: Hello, world!
    8. -- Configuring done
    9. -- Generating done
    10.
    -- Build files have been written to: /home/user/cmake-cookbook/chapter03/recipe-01/example/build
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 工作原理备注
      find_package 是用于发现和设置包的CMake模块的命令。这些模块包含CMake命令,用于标识系统标准位置中的包。CMake模块文件称为 Find.cmake ,当调用 find_package() 时,模块中的命令将会运行。
      ②除了在系统上实际查找包模块之外,查找模块还会设置了一些有用的变量,反映实际找到了什么,也可以在自己的 CMakeLists.txt 中使用这些变量。对于Python解释器,相关模块
      FindPythonInterp.cmake 附带的设置了一些CMake变量:
    PYTHONINTERP_FOUND:是否找到解释器
    PYTHON_EXECUTABLE:Python解释器到可执行文件的路径
    PYTHON_VERSION_STRING:Python解释器的完整版本信息
    PYTHON_VERSION_MAJOR:Python解释器的主要版本号
    PYTHON_VERSION_MINOR :Python解释器的次要版本号
    PYTHON_VERSION_PATCH:Python解释器的补丁版本号
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ③可以强制CMake,查找特定版本的包。例如,要求Python解释器的版本大于或等于
    2.7:

    find_package(PythonInterp 2.7)
    
    • 1

    ④可以强制满足依赖关系:

     find_package(PythonInterp REQUIRED)
     (如果在查找位置中没有找到适合Python解释器的可执行文件,CMake将中止配置。)
    
    • 1
    • 2

    ⑤软件包没有安装在标准位置时,CMake无法正确定位它们。用户可以使用CLI的 -D 参数传递相应的选项,告诉CMake查看特定的位置。Python解释器可以使用以下配置

    $ cmake -D PYTHON_EXECUTABLE=/custom/location/python ..
    
    • 1

    这将指定非标准 /custom/location/pytho 安装目录中的Python可执行文件。

    2)检测Python库

    五、创建和运行测试

    • 知识概况
      本章会介绍在配置和构建时的自定义行为,我们将学习如何使用这些命令:
      ①execute_process,从CMake中执行任意进程,并检索它们的输出
      ②add_custom_target,创建执行自定义命令的目标
      ③add_custom_command,指定必须执行的命令,以生成文件或在其他目标的特定生成事件中生
      成。

    1)使用平台无关的文件操作

    • 准备工作
      我们将展示如何提取Eigen库文件,并使用提取的源文件编译我们的项目。这个示例中,将重用第3章
      第7节的线性代数例子 linear-algebra.cpp ,用来检测外部库和程序、检测特征库。这里,假设已
      经包含Eigen库文件,已在项目构建前下载。

    • 文件目录
      在这里插入图片描述

    • linear-algebra.cpp

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include 
    
    int main(int argc, char **argv) {
      if (argc != 2) {
        std::cout << "Usage: ./linear-algebra dim" << std::endl;
        return EXIT_FAILURE;
      }
    
      std::chrono::time_point<std::chrono::system_clock> start, end;
      std::chrono::duration<double> elapsed_seconds;
      std::time_t end_time;
    
      std::cout << "Number of threads used by Eigen: " << Eigen::nbThreads()
                << std::endl;
    
      // Allocate matrices and right-hand side vector
      start = std::chrono::system_clock::now();
      int dim = std::atoi(argv[1]);
      Eigen::MatrixXd A = Eigen::MatrixXd::Random(dim, dim);
      Eigen::VectorXd b = Eigen::VectorXd::Random(dim);
      end = std::chrono::system_clock::now();
    
      // Report times
      elapsed_seconds = end - start;
      end_time = std::chrono::system_clock::to_time_t(end);
      std::cout << "matrices allocated and initialized "
                << std::put_time(std::localtime(&end_time), "%a %b %d %Y %r\n")
                << "elapsed time: " << elapsed_seconds.count() << "s\n";
    
      start = std::chrono::system_clock::now();
      // Save matrix and RHS
      Eigen::MatrixXd A1 = A;
      Eigen::VectorXd b1 = b;
      end = std::chrono::system_clock::now();
      end_time = std::chrono::system_clock::to_time_t(end);
      std::cout << "Scaling done, A and b saved "
                << std::put_time(std::localtime(&end_time), "%a %b %d %Y %r\n")
                << "elapsed time: " << elapsed_seconds.count() << "s\n";
    
      start = std::chrono::system_clock::now();
      Eigen::VectorXd x = A.lu().solve(b);
      end = std::chrono::system_clock::now();
    
      // Report times
      elapsed_seconds = end - start;
      end_time = std::chrono::system_clock::to_time_t(end);
    
      double relative_error = (A * x - b).norm() / b.norm();
    
      std::cout << "Linear system solver done "
                << std::put_time(std::localtime(&end_time), "%a %b %d %Y %r\n")
                << "elapsed time: " << elapsed_seconds.count() << "s\n";
      std::cout << "relative error is " << relative_error << std::endl;
    
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • camke
    ## 1、首先,使能C++11项目
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    project(recipe-01 LANGUAGES CXX)
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_EXTENSIONS OFF)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    ## 2、我们将自定义目标添加到构建系统中,自定义目标将提取构建目录中的库文件
    add_custom_target(unpack-eigen   #add_custom_target,创建执行自定义命令的目标
      ALL
      COMMAND
        ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/eigen-eigen-5a0156e40feb.tar.gz
      COMMAND
        ${CMAKE_COMMAND} -E rename eigen-eigen-5a0156e40feb eigen-3.3.4
      WORKING_DIRECTORY
        ${CMAKE_CURRENT_BINARY_DIR}
      COMMENT
        "Unpacking Eigen3 in ${CMAKE_CURRENT_BINARY_DIR}/eigen-3.3.4"
      )
    
    ## 3、 为源文件添加了一个可执行目标:
    add_executable(linear-algebra linear-algebra.cpp)
    
    ## 4、由于源文件的编译依赖于Eigen头文件,需要显式地指定可执行目标对自定义目标的依赖关系:
    add_dependencies(linear-algebra unpack-eigen)
    
    ## 5、最后,指定包含哪些目录:
    target_include_directories(linear-algebra
      PRIVATE
        ${CMAKE_CURRENT_BINARY_DIR}/eigen-3.3.4
      )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 工作原理
      ①细看 add_custom_target 这个命令:
    1. add_custom_target(unpack-eigen
    2. ALL
    3. COMMAND
    4.
    ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/eigen-eigen5a0156e40feb.tar.gz
    4. COMMAND
    5. ${CMAKE_COMMAND} -E rename eigen-eigen-5a0156e40feb eigen-3.3.4
    6. WORKING_DIRECTORY
    7. ${CMAKE_CURRENT_BINARY_DIR}
    8. COMMENT
    9. "Unpacking Eigen3 in ${CMAKE_CURRENT_BINARY_DIR}/eigen-3.3.4"
    10. )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    构建系统中引入了一个名为 unpack-eigen 的目标。因为我们传递了 ALL 参数,目标将始终被执
    行。 构建过程中必须执行一系列没有输出的命令时,可以使用 add_custom_target 命令。正如我们在本示例中所示,可以将自定义目标指定为项目中其他目标的依赖项。此外,自定义目标还可以依赖于其他目标。
    COMMAND 参数指定要执行哪些命令。本例中,我们希望提取存档并将提取的目录重命名
    egan -3.3.4 ,通过以下两个命令实现:

    11. ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/eigen-eigen2. 5a0156e40feb.tar.gz
    12. ${CMAKE_COMMAND} -E rename eigen-eigen-5a0156e40feb eigen-3.3.4
    
    • 1
    • 2

    注意,使用 -E 标志调用CMake命令本身来执行实际的工作。对于许多常见操作,CMake实现了一个
    对所有操作系统都通用的接口,这使得构建系统独立于特定的平台。
    add_custom_target 命令中的下一个参数是工作目录。我们的示例中,它对应于构建目录: CMAKE_CURRENT_BINARY_DIR
    ④最后一个参数 COMMENT ,用于指定CMake在执行自定义目标时输出什么样的消息。
    ⑤使用 -E 标志可以以与操作系统无关的方式,运行许多公共操作。运行 cmake -Ecmake -E help 可以获得特定操作系统的完整列表。例如,这是Linux系统上命令的摘要

    13. Usage: cmake -E <command> [arguments...]
    14. Available commands:
    3.
    capabilities - Report capabilities built into cmake in JSON
    format
    15. chdir dir cmd [args...] - run command in a given directory
    16. compare_files file1 file2 - check if file1 is same as file2
    6.
    copy <file>... destination - copy files to destination (either file or
    directory)
    7.
    copy_directory <dir>... destination - copy content of <dir>... directories
    to 'destination' directory
    17. copy_if_different <file>... destination - copy files if it has changed
    18. echo [<string>...] - displays arguments as text
    19. echo_append [<string>...] - displays arguments as text but no new line
    20. env [--unset=NAME]... [NAME=VALUE]... COMMAND [ARG]...
    21. - run command in a modified environment
    22. environment - display the current environment
    23. make_directory <dir>... - create parent and <dir> directories
    24. md5sum <file>... - create MD5 checksum of files
    25. sha1sum <file>... - create SHA1 checksum of files
    26. sha224sum <file>... - create SHA224 checksum of files
    27. sha256sum <file>... - create SHA256 checksum of files
    28. sha384sum <file>... - create SHA384 checksum of files
    29. 20. sha512sum <file>... - create SHA512 checksum of files
    21. remove [-f] <file>... - remove the file(s), use -f to force it
    22. remove_directory dir - remove a directory and its contents
    23. rename oldname newname - rename a file or directory (on one volume)
    24. server - start cmake in server mode
    25. sleep <number>... - sleep for given number of seconds
    26. tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]
    27. - create or extract a tar or zip archive
    28. time command [args...] - run command and display elapsed time
    29. touch file - touch a file.
    30. touch_nocreate file - touch a file but do not create it.
    31. Available on UNIX only:
    32. create_symlink old new - create a symbolic link new -> old
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    2)配置时运行自定义命令

    3)构建时运行自定义命令:Ⅰ. 使用 add_custom_command

    4)构建时运行自定义命令:Ⅱ. 使用 add_custom_target

    5)构建时为特定目标运行自定义命令

    6)探究编译和链接命令

    7)探究编译器标志命令

    8)探究可执行命令

    9)使用生成器表达式微调配置和编译

    六、配置时和构建时的操作

    七、生成源码

    八、构建项目

    九、超级构建模式

    十、语言混合项目

    十一、编写安装程序

    十二、打包项目

    十三、构建文档

    十四、选择生成器和交叉编译

    十五、测试面板

    十六、使用CMake构建已有项目

    十七、备注

    ①:CMake中,C++是默认的编程语言。不过,我们还是建议使用 LANGUAGES 选项在 project 命令中显式地声明项目的语言

    project(recipe-01 LANGUAGES CXX)
    
    • 1

    ②涉及到的文件分类

    ①Makefile : make 将运行指令来构建项目。
    ②CMakefile :包含临时文件的目录,CMake用于检测操作系统、编译器等。此外,根据所选的生成器,它还包含特定的文件。
    ③cmake_install.cmake :处理安装规则的CMake脚本,在项目安装时使用。
    ④CMakeCache.txt :如文件名所示,CMake缓存。CMake在重新运行配置时使用这个文件。
    
    • 1
    • 2
    • 3
    • 4

    ③命令实现编译

    $ cmake -H. -Bbuild
    
    • 1

    该命令是跨平台的,使用了 -H 和 -B 为CLI选项。 -H 表示当前目录中搜索
    根 CMakeLists.txt 文件。 -Bbuild 告诉CMake在一个名为 build 的目录中生成所有的文件。
    ④CMake不强制指定构建目录执行名称或位置,我们完全可以把它放在项目路径之外。这样做同样有效:

    1. $ mkdir -p /tmp/someplace
    2. $ cd /tmp/someplace
    3. $ cmake /path/to/source
    4. $ cmake --build .
    
    • 1
    • 2
    • 3
    • 4

    ⑤CMake接受其他值作为 add_library 的第二个参数的有效值,我们来看下本书会用到的值:

    • STATIC:用于创建静态库,即编译文件的打包存档,以便在链接其他目标时使用,例如:可执行文件。
    • SHARED:用于创建动态库,即可以动态链接,并在运行时加载的库。可以
      在 CMakeLists.txt 中使用 add_library(message SHARED Message.hpp Message.cpp) 从静态库切换到动态共享对象(DSO)。
    • OBJECT:可将给定 add_library 的列表中的源码编译到目标文件,不将它们归档到静态库中,也不能将它们链接到共享对象中。如果需要一次性创建静态库和动态库,那么使用对象库尤其有用。我们将在本示例中演示。
    • MODULE:又为DSO组。与 SHARED 库不同,它们不链接到项目中的任何目标,不过可以进行动态加载。该参数可以用于构建运行时插件

    展示OBJECT库的使用,如下

    1. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    2. project(recipe-03 LANGUAGES CXX)
    3.
    4. add_library(message-objs
    5. OBJECT
    6. Message.hpp
    7. Message.cpp
    8. )
    9.
    10. # this is only needed for older compilers
    11. # but doesn't hurt either to have it
    12. set_target_properties(message-objs
    13. PROPERTIES
    14. POSITION_INDEPENDENT_CODE 1
    15. )
    16.
    17. add_library(message-shared
    18. SHARED
    19. $
    20. )
    21.
    22. add_library(message-static
    23. STATIC
    24. $
    25. )
    26.
    27. add_executable(hello-world hello-world.cpp)
    28.
    29. target_link_libraries(hello-world message-static)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    7、如CMake语言文档中描述,逻辑真或假可以用多种方式表示:
    《1》如果将逻辑变量设置为以下任意一种: 1 、 ON 、 YES 、 true 、 Y 或非零数,则逻辑变量为 true 。
    《2》如果将逻辑变量设置为以下任意一种: 0 、 OFF 、 NO 、 false 、 N 、 IGNORE、NOTFOUND 、空字符串,或者以 -NOTFOUND 为后缀,则逻辑变量为 false 。
    8、BUILD_SHARED_LIBS 是CMake的一个全局标志。因为CMake内部要查询 BUILD_SHARED_LIBS 全局变量,所以 add_library 命令可以在不传递 STATIC/SHARED/OBJECT 参数的情况下调用;如果为 false 或未定义,将生成一个静态库
    9、if(XXX) + else() + endif() 也可以用 if(USE_LIBRARY)…else(USE_LIBRARY)… endif(USE_LIBIRAY) ,这个格式并不唯一,可以根据个人喜好来决定使用哪种格式。

    10、-D给cmake传递参数
    -D 开关用于为CMake设置任何类型的变量:逻辑变量、路径等等
    option 可接受三个参数:

    1) 表示该选项的变量的名称。
    2)"help string" 记录选项的字符串,在CMake的终端或图形用户界面中可见。
    3)[initial value] 选项的默认值,可以是 ON 或 OFF 。
    
    • 1
    • 2
    • 3

    11、我们为动物和动物农场目标设置了一些属性:

    • CXX_STANDARD会设置我们想要的标准。
    • CXX_EXTENSIONS告诉CMake,只启用 ISO C++ 标准的编译器标志,而不使用特定编译器的扩展。
    • CXX_STANDARD_REQUIRED指定所选标准的版本。如果这个版本不可用,CMake将停止配置并出现错误。当这个属性被设置为 OFF 时,CMake将寻找下一个标准的最新版本,直到一个合适的标志。这意味着,首先查找 C++14 ,然后是 C++11 ,然后是 C++98 。(译者注:目前会从 C++20 或 C++17 开始查找)

    12、foreach-endforeach 语法可用于在变量列表上,表示重复特定任务。本示例中,使用它来操作、设置和获取项目中特定文件的编译器标志。

    (CMake代码片段中引入了另外两个新命令:)
    13、set_source_files_properties(file PROPERTIES property value) ,它将属性设置为给定
    文件的传递值。与目标非常相似,文件在CMake中也有属性,允许对构建系统进行非常细粒度的控制。源文件的可用属性列表可以在这里找到:
    https://cmake.org/cmake/help/v3.5/manual/cmakeproperties.7.html#source-file-propertie

    114、get_source_file_property(VAR file property) ,检索给定文件所需属性的值,并将其存储
    在CMake VAR 变量中。
    15、CMake中,列表是用分号分隔的字符串组。列表可以由 list 或 set 命令创建。例
    如, set(var a b c d e) 和 list(APPEND a b c d e) 都创建了列表 a;b;c;d;e 。

    16、单独优化的备注
    :为了对一组文件降低优化,将它们收集到一个单独的目标(库)中,并为这个目标显式地设置优化级别,而不是附加一个标志,这样可能会更简洁,不过在本示例中,我们的重点是 foreach & endforeach
    17、foreach() 的四种使用方式:

    • foreach(loop_var arg1 arg2 ...) : 其中提供循环变量和显式项列表。当
      为 sources_with_lower_optimization 中的项打印编译器标志集时,使用此表单。注意,如果项目列表位于变量中,则必须显式展开它;也就是说, ${sources_with_lower_optimization} 必须作为参数传递。
    • 通过指定一个范围,可以对整数进行循环,例如:foreach(loop_var rangetotal)foreach(loop_var range start stop [step])
    • 对列表值变量的循环,例如: foreach(loop_var IN LISTS [list1[...]]) 。参数解释为列表,其内容就会自动展开。
    • 对变量的循环,例如: foreach(loop_var IN ITEMS [item1 [...]]) 。参数的内容没有展
      开。

    18、CMake为目标操作系统定义了 CMAKE_SYSTEM_NAME ,因此不需要使用定制命令、工具或脚本来查询此信息。然后,可以使用此变量的值实现特定于操作系统的条件和解决方案。在具有 uname 命令的系统上,将此变量设置为 uname -s 的输出。该变量在macOS上设置为“Darwin”。在Linux和Windows上,它分别计算为“Linux”和“Windows”。

    19、为了最小化从一个平台转移到另一个平台时的成本,应该避免直接使用Shell命令,还应该避免显式的路径分隔符(Linux和macOS上的前斜杠/和Windows上的后斜杠\)。CMake代码中只使用前斜杠作为路径分隔符,CMake将自动将它们转换为所涉及的操作系统环境。

    20、《1》指示编译器为我们检查处理器,并为当前体系结构生成本机指令。不同的编译器供应商会使用不同的标志来实现这一点:GNU编译器使用 -march=native 标志来实现这一点,而Intel编译器使用 -xHost 标志。
    《2》使用 CheckCXXCompilerFlag.cmake 模块提供的 check_cxx_compiler_flag 函数进行编译器标志的检查:

    check_cxx_compiler_flag("-march=native" _march_native_works)
    
    • 1

    这个函数接受两个参数:

    • 第一个是要检查的编译器标志。
    • 第二个是用来存储检查结果(true或false)的变量。如果检查为真,我们将工作标志添加
      到 _CXX_FLAGS 变量中,该变量将用于为可执行目标设置编译器标志。

    21、
    ①细看 add_custom_target 这个命令:

    1. add_custom_target(unpack-eigen
    2. ALL
    3. COMMAND
    4.
    ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/eigen-eigen5a0156e40feb.tar.gz
    4. COMMAND
    5. ${CMAKE_COMMAND} -E rename eigen-eigen-5a0156e40feb eigen-3.3.4
    6. WORKING_DIRECTORY
    7. ${CMAKE_CURRENT_BINARY_DIR}
    8. COMMENT
    9. "Unpacking Eigen3 in ${CMAKE_CURRENT_BINARY_DIR}/eigen-3.3.4"
    10. )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    构建系统中引入了一个名为 unpack-eigen 的目标。因为我们传递了 ALL 参数,目标将始终被执
    行。 构建过程中必须执行一系列没有输出的命令时,可以使用 add_custom_target 命令。正如我们在本示例中所示,可以将自定义目标指定为项目中其他目标的依赖项。此外,自定义目标还可以依赖于其他目标。
    COMMAND 参数指定要执行哪些命令。本例中,我们希望提取存档并将提取的目录重命名
    egan -3.3.4 ,通过以下两个命令实现:

    11. ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/eigen-eigen2. 5a0156e40feb.tar.gz
    12. ${CMAKE_COMMAND} -E rename eigen-eigen-5a0156e40feb eigen-3.3.4
    
    • 1
    • 2

    注意,使用 -E 标志调用CMake命令本身来执行实际的工作。对于许多常见操作,CMake实现了一个
    对所有操作系统都通用的接口,这使得构建系统独立于特定的平台。
    add_custom_target 命令中的下一个参数是工作目录。我们的示例中,它对应于构建目录: CMAKE_CURRENT_BINARY_DIR
    ④最后一个参数 COMMENT ,用于指定CMake在执行自定义目标时输出什么样的消息。
    ⑤使用 -E 标志可以以与操作系统无关的方式,运行许多公共操作。运行 cmake -Ecmake -E help 可以获得特定操作系统的完整列表。例如,这是Linux系统上命令的摘要

    13. Usage: cmake -E <command> [arguments...]
    14. Available commands:
    3.
    capabilities - Report capabilities built into cmake in JSON
    format
    15. chdir dir cmd [args...] - run command in a given directory
    16. compare_files file1 file2 - check if file1 is same as file2
    6.
    copy <file>... destination - copy files to destination (either file or
    directory)
    7.
    copy_directory <dir>... destination - copy content of <dir>... directories
    to 'destination' directory
    17. copy_if_different <file>... destination - copy files if it has changed
    18. echo [<string>...] - displays arguments as text
    19. echo_append [<string>...] - displays arguments as text but no new line
    20. env [--unset=NAME]... [NAME=VALUE]... COMMAND [ARG]...
    21. - run command in a modified environment
    22. environment - display the current environment
    23. make_directory <dir>... - create parent and <dir> directories
    24. md5sum <file>... - create MD5 checksum of files
    25. sha1sum <file>... - create SHA1 checksum of files
    26. sha224sum <file>... - create SHA224 checksum of files
    27. sha256sum <file>... - create SHA256 checksum of files
    28. sha384sum <file>... - create SHA384 checksum of files
    29. 20. sha512sum <file>... - create SHA512 checksum of files
    21. remove [-f] <file>... - remove the file(s), use -f to force it
    22. remove_directory dir - remove a directory and its contents
    23. rename oldname newname - rename a file or directory (on one volume)
    24. server - start cmake in server mode
    25. sleep <number>... - sleep for given number of seconds
    26. tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]
    27. - create or extract a tar or zip archive
    28. time command [args...] - run command and display elapsed time
    29. touch file - touch a file.
    30. touch_nocreate file - touch a file but do not create it.
    31. Available on UNIX only:
    32. create_symlink old new - create a symbolic link new -> old
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
  • 相关阅读:
    app自动化测试之设备交互API详解
    linux内核分析:x86,BIOS到bootloader,内核初始化,syscall, 进程与线程
    【MySQL架构篇】存储引擎
    Acwing 算法基础课 c++模板整理(附python语法基础题)
    Vue源码学习(十九):router基本原理
    算法框架-LLM-1-Prompt设计(一)
    Apollo planning之PathBoundsDecider
    What is Fan-out
    windows安装mysql时卡write configuration file曲线救国 mysql 5.7.39 免安装(ZIP压缩包)版本安装配置
    Streamlit学习笔记
  • 原文地址:https://blog.csdn.net/weixin_43679037/article/details/127914757