CMake Cookbook笔记(12/23未完待续,游戏服务器观点阅读,编译器及指令集不涉及)

文章目录

    • 一、配置环境(略)
    • 二、从可执行文件到库
      • 1)将单个源码文件编译为可执行文件
      • 2)切换生成器(-G)
      • 3)构建和链接静态库和动态库(还有对象库的使用举例)
      • 4)用条件句控制编译
      • 5)向用户显示选项(在命令行输入参数选择是否开启)
      • 6)指定编译器
      • 7)切换构建类型(release或debug)
      • 8)设置编译器选项(例如 "-fPIC" "-Wall"等)
      • 9)为语言设定标准
      • 10)使用控制流(if-else-endif\foreach\while-endwhile)
    • 三、检测环境
      • 1)检测操作系统
      • 2)处理与平台相关的源代码(根据平台设定宏定义)
      • 3)处理与编译器相关的源代码(略,游戏服务器一般不涉及做编译相关设定修改)
      • 4)检测处理器体系结构(略,大型游戏服务器一般都是固定用32或64位的执行程序)
      • 5)检测处理器指令集(略,不涉及)
      • 6)为Eigen库使能像量化(处理器的向量功能,可以提高代码的性能,如提高线代的矩阵计算)
    • 四、检测外部库和程序
      • 1)检测Python解释器
      • 2)检测Python库
    • 五、创建和运行测试
      • 1)使用平台无关的文件操作
      • 2)配置时运行自定义命令
      • 3)构建时运行自定义命令:Ⅰ. 使用 add_custom_command
      • 4)构建时运行自定义命令:Ⅱ. 使用 add_custom_target
      • 5)构建时为特定目标运行自定义命令
      • 6)探究编译和链接命令
      • 7)探究编译器标志命令
      • 8)探究可执行命令
      • 9)使用生成器表达式微调配置和编译
    • 六、配置时和构建时的操作
    • 七、生成源码
    • 八、构建项目
    • 九、超级构建模式
    • 十、语言混合项目
    • 十一、编写安装程序
    • 十二、打包项目
    • 十三、构建文档
    • 十四、选择生成器和交叉编译
    • 十五、测试面板
    • 十六、使用CMake构建已有项目
    • 十七、备注

一、配置环境(略)

二、从可执行文件到库

  • 本章主要内容
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;
}
  • 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)
  • 通过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
  • 备注
    ①:CMake中,C++是默认的编程语言。不过,我们还是建议使用 LANGUAGES 选项在 project 命令中显式地声明项目的语言
    ②涉及到的文件分类
①Makefile : make 将运行指令来构建项目。
②CMakefile :包含临时文件的目录,CMake用于检测操作系统、编译器等。此外,根据所选的生成器,它还包含特定的文件。
③cmake_install.cmake :处理安装规则的CMake脚本,在项目安装时使用。
④CMakeCache.txt :如文件名所示,CMake缓存。CMake在重新运行配置时使用这个文件。

③命令实现编译

$ cmake -H. -Bbuild

该命令是跨平台的,使用了 -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 .

⑤查看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

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
  • 不同生成器都有自己不同的文件集
①build.ninja 和 rules.ninja :包含Ninja的所有的构建语句和构建规则。
②CMakeCache.txt :CMake会在这个文件中进行缓存,与生成器无关。
③CMakeFiles :包含由CMake在配置期间生成的临时文件。
④cmake_install.cmake :CMake脚本处理安装规则,并在安装时使用。

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)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;
}

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);
};

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;
}
  • 备注
    ①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)
  • 说明
    ①首先, 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. )

注意点:这让库同一个名不具备跨平台性!
我们可以链接到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 命令进行验
证。
  • 备注
    ①如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()
  • cmake解释
    ①用一个选项替换上一个示例的 set(USE_LIBRARY OFF) 命令。该选项将修改 USE_LIBRARY 的值,并设置其默认值为 OFF :
1. option(USE_LIBRARY "Compile sources into a library" OFF)

②现在,可以通过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
  • 备注
    -D 开关用于为CMake设置任何类型的变量:逻辑变量、路径等等
    option 可接受三个参数:
1) 表示该选项的变量的名称。
2)"help string" 记录选项的字符串,在CMake的终端或图形用户界面中可见。
3)[initial value] 选项的默认值,可以是 ON 或 OFF 。

6)指定编译器

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

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

$ env CXX=clang++ cmake ..
  • 跨平台注意点
    我们建议使用 -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 ,不能保证所有编译器或语言都定义了此变量。
  • 使用举例
    我们可以尝试使用不同的编译器,配置下面的示例 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. $ 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. ...

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

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

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

CMAKE_BUILD_TYPE

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

1. Debug:用于在没有优化的情况下,使用带有调试符号构建库或可执行文件。
2. Release:用于构建的优化的库或可执行文件,不包含调试符号。
3. RelWithDebInfo:用于构建较少的优化库或可执行文件,包含调试符号。
4. MinSizeRel:用于不增加目标代码大小的优化方式,来构建库或可执行文件。
  • 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}")
  • 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}")

②打印出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}")

③验证配置的输出:(这里显示默认构建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

④切换构建类型为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

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
  • 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)
  • 备注
    ①编译选项可以添加三个级别的可见性: 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 ...
  • 方法二:不用对 CMakeLists.txt 进行修改。如果想在这个项目中修
    改 geometry 和 compute-areas 目标的编译器选项,可以使用CMake参数进行配置:
$ cmake -D CMAKE_CXX_FLAGS="-fno-exceptions -fno-rtti" ..

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

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

这将使用 -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);
  • 源码目录
    CMake Cookbook笔记(12/23未完待续,游戏服务器观点阅读,编译器及指令集不涉及)_第1张图片

  • 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. $ 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!
  • 备注
    ①步骤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 Cookbook笔记(12/23未完待续,游戏服务器观点阅读,编译器及指令集不涉及)_第2张图片

  • 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)
  • 编译确认优化选项
    最后,还使用 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

  • 备注
    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()
  • 备注
    ①CMake为目标操作系统定义了 CMAKE_SYSTEM_NAME ,因此不需要使用定制命令、工具或脚本来查询此信息。然后,可以使用此变量的值实现特定于操作系统的条件和解决方案。在具有 uname 命令的系统上,将此变量设置为 uname -s 的输出。该变量在macOS上设置为“Darwin”。在Linux和Windows上,它分别计算为“Linux”和“Windows”。
    ②:为了最小化从一个平台转移到另一个平台时的成本,应该避免直接使用Shell命令,还应该避免显式的路径分隔符(Linux和macOS上的前斜杠/和Windows上的后斜杠\)。CMake代码中只使用前斜杠作为路径分隔符,CMake将自动将它们转换为所涉及的操作系统环境。

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

  • 文件目录
    CMake Cookbook笔记(12/23未完待续,游戏服务器观点阅读,编译器及指令集不涉及)_第3张图片

  • 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;
}
  • 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()

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

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

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

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

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

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

  • 文件目录
    CMake Cookbook笔记(12/23未完待续,游戏服务器观点阅读,编译器及指令集不涉及)_第4张图片

  • 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;
}

我们期望向量化可以加快 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. $ 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
  • 备注
    ①指示编译器为我们检查处理器,并为当前体系结构生成本机指令。不同的编译器供应商会使用不同的标志来实现这一点:GNU编译器使用 -march=native 标志来实现这一点,而Intel编译器使用 -xHost 标志。
    ②使用 CheckCXXCompilerFlag.cmake 模块提供的 check_cxx_compiler_flag 函数进行编译器标志的检查:
check_cxx_compiler_flag("-march=native" _march_native_works)

这个函数接受两个参数:

  • 第一个是要检查的编译器标志。
  • 第二个是用来存储检查结果(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)检测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)
  • 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
  • 工作原理备注
    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解释器的补丁版本号

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

find_package(PythonInterp 2.7)

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

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

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

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

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

2)检测Python库

五、创建和运行测试

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

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

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

  • 文件目录
    CMake Cookbook笔记(12/23未完待续,游戏服务器观点阅读,编译器及指令集不涉及)_第5张图片

  • 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;
}
  • 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
  )
  • 工作原理
    ①细看 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. )

构建系统中引入了一个名为 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

注意,使用 -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

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

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

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

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

6)探究编译和链接命令

7)探究编译器标志命令

8)探究可执行命令

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

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

七、生成源码

八、构建项目

九、超级构建模式

十、语言混合项目

十一、编写安装程序

十二、打包项目

十三、构建文档

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

十五、测试面板

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

十七、备注

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

project(recipe-01 LANGUAGES CXX)

②涉及到的文件分类

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

③命令实现编译

$ cmake -H. -Bbuild

该命令是跨平台的,使用了 -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 .

⑤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)

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 。

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)

这个函数接受两个参数:

  • 第一个是要检查的编译器标志。
  • 第二个是用来存储检查结果(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. )

构建系统中引入了一个名为 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

注意,使用 -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

你可能感兴趣的:(服务器开发专栏,c++,c++,服务器,linux)