# 10.1 安装项目
**NOTE**:*此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-10/recipe-01 中找到,其中有一个C++示例和一个Fortran示例。该示例在CMake 3.6版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。*
第一个示例中,将介绍我们的小项目和一些基本概念,这些概念也将在后面的示例中使用。安装文件、库和可执行文件是一项非常基础的任务,但是也可能会带来一些缺陷。我们将带您了解这些问题,并展示如何使用CMake有效地避开这些缺陷。
## 准备工作
第1章第3节的示例,几乎复用:只添加对UUID库的依赖。这个依赖是有条件的,如果没有找到UUID库,我们将通过预处理程序排除使用UUID库的代码。项目布局如下:
```shell
.
├── CMakeLists.txt
├── src
│ ├── CMakeLists.txt
│ ├── hello-world.cpp
│ ├── Message.cpp
│ └── Message.hpp
└── tests
└── CMakeLists.txt
```
我们已经看到,有三个`CMakeLists.txt`,一个是主`CMakeLists.txt`,另一个是位于`src`目录下的,还有一个是位于`test`目录下的。
` Message.hpp`头文件包含以下内容:
```c++
#pragma once
#include <iosfwd>
#include <string>
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);
};
std::string getUUID();
```
`Message.cpp`中有相应的实现:
```c++
#include "Message.hpp"
#include <iostream>
#include <string>
#ifdef HAVE_UUID
#include <uuid/uuid.h>
#endif
std::ostream &Message::printObject(std::ostream &os)
{
os << "This is my very nice message: " << std::endl;
os << message_ << std::endl;
os << "...and here is its UUID: " << getUUID();
return os;
}
#ifdef HAVE_UUID
std::string getUUID()
{
uuid_t uuid;
uuid_generate(uuid);
char uuid_str[37];
uuid_unparse_lower(uuid, uuid_str);
uuid_clear(uuid);
std::string uuid_cxx(uuid_str);
return uuid_cxx;
}
#else
std::string getUUID()
{
return "Ooooops, no UUID for you!";
}
#endif
```
最后,示例`hello-world.cpp`内容如下:
```c++
#include <cstdlib>
#include <iostream>
#include "Message.hpp"
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;
}
```
## 具体实施
我们先来看一下主`CMakeLists.txt`:
1. 声明CMake最低版本,并定义一个C++11项目。请注意,我们已经为我们的项目设置了一个版本,在`project`中使用`VERSION`进行指定:
```cmake
# CMake 3.6 needed for IMPORTED_TARGET option
# to pkg_search_module
cmake_minimum_required(VERSION 3.6 FATAL_ERROR)
project(recipe-01
LANGUAGES CXX
VERSION 1.0.0
)
# <<< General set up >>>
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
```
2. 用户可以通过`CMAKE_INSTALL_PREFIX`变量定义安装目录。CMake会给这个变量设置一个默认值:Windows上的`C:\Program Files`和Unix上的`/usr/local`。我们将会打印安装目录的信息:
```cmake
message(STATUS "Project will be installed to ${CMAKE_INSTALL_PREFIX}")
```
3. 默认情况下,我们更喜欢以Release的方式配置项目。用户可以通过`CMAKE_BUILD_TYPE`设置此变量,从而改变配置类型,我们将检查是否存在这种情况。如果没有,将设置为默认值:
```cmake
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
message(STATUS "Build type set to ${CMAKE_BUILD_TYPE}")
```
4. 接下来,告诉CMake在何处构建可执行、静态和动态库目标。便于在用户不打算安装项目的情况下,访问这些构建目标。这里使用标准CMake的`GNUInstallDirs.cmake`模块。这将确保的项目布局的合理性和可移植性:
```cmake
include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
```
5. 虽然,前面的命令配置了构建目录中输出的位置,但是需要下面的命令来配置可执行程序、库以及安装前缀中包含的文件的位置。它们大致遵循相同的布局,但是我们定义了新的`INSTALL_LIBDIR`、`INSTALL_BINDIR`、`INSTALL_INCLUDEDIR`和`INSTALL_CMAKEDIR`变量。当然,也可以覆盖这些变量:
```cmake
# Offer the user the choice of overriding the installation directories
set(INSTALL_LIBDIR ${CMAKE_INSTALL_LIBDIR} CACHE PATH "Installation directory for libraries")
set(INSTALL_BINDIR ${CMAKE_INSTALL_BINDIR} CACHE PATH "Installation directory for executables")
set(INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE PATH "Installation directory for header files")
if(WIN32 AND NOT CYGWIN)
set(DEF_INSTALL_CMAKEDIR CMake)
else()
set(DEF_INSTALL_CMAKEDIR share/cmake/${PROJECT_NAME})
endif()
set(INSTALL_CMAKEDIR ${DEF_INSTALL_CMAKEDIR} CACHE PATH "Installation directory for CMake files")
```
6. 报告组件安装的路径:
```cmake
# Report to user
foreach(p LIB BIN INCLUDE CMAKE)
file(TO_NATIVE_PATH ${CMAKE_INSTALL_PREFIX}/${INSTALL_${p}DIR} _path )
message(STATUS "Installing ${p} components to ${_path}")
unset(_path)
endforeach()
```
7. 主`CMakeLists.txt`文件中的最后一个指令添加`src`子目录,启用测试,并添加`tests`子目录:
```cmake
add_subdirectory(src)
enable_testing()
add_subdirectory(tests)
```
现在我们继续分析`src/CMakeLists.txt`,其定义了构建的实际目标:
1. 我们的项目依赖于UUID库:
```cmake
# Search for pkg-config and UUID
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_search_module(UUID uuid IMPORTED_TARGET)
if(TARGET PkgConfig::UUID)
message(STATUS "Found libuuid")
set(UUID_FOUND TRUE)
endif()
endif()
```
2. 我们希望建立一个动态库,将该目标声明为`message-shared `:
```cmake
add_library(message-shared SHARED "")
```
3. 这个目标由`target_sources`命令指定:
```cmake
target_sources(message-shared
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/Message.cpp
)
```
4. 我们为目标声明编译时定义和链接库。请注意,所有这些都是`PUBLIC`,以确保所有依赖的目标将正确继承它们:
```cmake
target_compile_definitions(message-shared
PUBLIC
$<$<BOOL:${UUID_FOUND}>:HAVE_UUID>
)
target_link_libraries(message-shared
PUBLIC
$<$<BOOL:${UUID_FOUND}>:PkgConfig::UUID>
)
```
5. 然后设置目标的附加属性:
```cmake
set_target_properties(message-shared
PROPERTIES
POSITION_INDEPENDENT_CODE 1
SOVERSION ${PROJECT_VERSION_MAJOR}
OUTPUT_NAME "message"
DEBUG_POSTFIX "_d"
PUBLIC_HEADER "Message.hpp"
MACOSX_RPATH ON
WINDOWS_EXPORT_ALL_SYMBOLS ON
)
```
6. 最后,为“Hello, world”程序添加可执行目标:
```cmake
add_executable(hello-world_wDSO hello-world.cpp)
```
7. `hello-world_wDSO`可执行目标,会链接到动态库:
```cmake
target_link_libraries(hello-world_wDSO
PUBLIC
message-shared
)
```
`src/CMakeLists.txt`文件中,还包含安装指令。考虑这些之前,我们需要设置可执行文件的`RPATH`:
1. 使用CMake路径操作,我们可以设置`message_RPATH`变量。这将为GNU/Linux和macOS设置适当的`RPATH`:
```cmake
RPATH
file(RELATIVE_PATH _rel ${CMAKE_INSTALL_PREFIX}/${INSTALL_BINDIR} ${CMAKE_INSTALL_PREFIX})
if(APPLE)
set(_rpath "@loader_path/${_rel}")
else()
set(_rpath "\$ORIGIN/${_rel}")
endif()
file(TO_NATIVE_PATH "${_rpath}/${INSTALL_LIBDIR}" message_RPATH)
```
2. 现在,可以使用这个变量来设置可执行目标`hello-world_wDSO`的`RPATH`(通过目标属性实现)。我们也可以设置额外的属性,稍后会对此进行更多的讨论:
```cmake
set_target_properties(hello-world_wDSO
PROPERTIES
MACOSX_RPATH ON
SKIP_BUILD_RPATH OFF
BUILD_WITH_INSTALL_RPATH OFF
INSTALL_RPATH "${message_RPATH}"
INSTALL_RPATH_USE_LINK_PATH ON
)
```
3. 终于可以安装库、头文件和可执行文件了!使用CMake提供的`install`命令来指定安装位置。注意,路径是相对的,我们将在后续进一步讨论这一点:
```cmake
install(
TARGETS
message-shared
hello-world_wDSO
ARCHIVE
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
RUNTIME
DESTINATION ${INSTALL_BINDIR}
COMPONENT bin
LIBRARY
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
PUBLIC_HEADER
DESTINATION ${INSTALL_INCLUDEDIR}/message
COMPONENT dev
)
```
`tests`目录中的`CMakeLists.txt`文件包含简单的指令,以确保“Hello, World”可执行文件能够正确运行:
```cmake
add_test(
NAME test_shared
COMMAND $<TARGET_FILE:hello-world_wDSO>
)
```
现在让我们配置、构建和安装项目,并查看结果。添加安装指令时,CMake就会生成一个名为`install`的新目标,该目标将运行安装规则:
```shell
$ mkdir -p build
$ cd build
$ cmake -G"Unix Makefiles" -DCMAKE_INSTALL_PREFIX=$HOME/Software/recipe-01
$ cmake --build . --target install
```
GNU/Linux构建目录的内容如下:
```shell
build
├── bin
│ └── hello-world_wDSO
├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
├── CTestTestfile.cmake
├── install_manifest.txt
├── lib64
│ ├── libmessage.so -> libmessage.so.1
│ └── libmessage.so.1
├── Makefile
├── src
├── Testing
└── tests
```
另一方面,在安装位置,可以找到如下的目录结构:
```shell
$HOME/Software/recipe-01/
├── bin
│ └── hello-world_wDSO
├── include
│ └── message
│ └── Message.hpp
└── lib64
├── libmessage.so -> libmessage.so.1
└── libmessage.so.1
```
这意味着安装指令中给出的位置,是相对于用户给定的`CMAKE_INSTALL_PREFIX`路径。
## 工作原理
这个示例有三个要点我们需要更详细地讨论:
* 使用`GNUInstallDirs.cmake`定义目标安装的标准位置
* 在动态库和可执行目标上设置的属性,特别是`RPATH`的处理
* 安装指令
###安装到标准位置
对于项目的安装来说,什么是好的布局呢?如果只有自己使用该项目,那就无所谓好或坏的布局。然而,一旦向外部发布产品,和他人共用该项目,就应该在安装项目时提供一个合理的布局。幸运的是,我们可以遵循一些标准,CMake可以帮助我们做到这一点。实际上,`GNUInstallDirs.cmake `模块所做的就是定义这样一组变量,这些变量是安装不同类型文件的子目录的名称。在例子中,使用了以下内容:
* ***CMAKE_INSTALL_BINDIR**:这将用于定义用户可执行文件所在的子目录,即所选安装目录下的`bin`目录。
* **CMAKE_INSTALL_LIBDIR**:这将扩展到目标代码库(即静态库和动态库)所在的子目录。在64位系统上,它是`lib64`,而在32位系统上,它只是`lib`。
* **CMAKE_INSTALL_INCLUDEDIR**:最后,我们使用这个变量为C头文件获取正确的子目录,该变量为`include`。
然而,用户可能希望覆盖这些选项。我们允许在主`CMakeLists.txt`文件中使用以下方式覆盖选项:
```cmake
# Offer the user the choice
of overriding the installation directories
set(INSTALL_LIBDIR ${CMAKE_INSTALL_LIBDIR} CACHE PATH
"Installation directory for libraries")
set(INSTALL_BINDIR ${CMAKE_INSTALL_BINDIR} CACHE PATH
"Installation directory for executables")
set(INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE
PATH "Installation directory for header files")
```
这重新定义了在项目中使用的`INSTALL_BINDIR`、`INSTALL_LIBDIR`和`INSTALL_INCLUDEDIR`变量。我们还定义了`INSTALL_CMAKEDIR`变量,但它所扮演的角色将在接下来的几个示例中详细讨论。
**TIPS**:*`GNUInstallDirs.cmake`模块定义了额外的变量,这些变量将有助于,将已安装的文件放置到所选安装前缀的子目录中。请参考CMake在线文档:https://cmake.org/cmake/help/v3.6/module/GNUInstallDirs.html*
### 目标属性和RPATH处理
让我们更仔细地看看在动态库目标上设置的属性,需要设置以下内容:
* `POSITION_INDEPENDENT_CODE 1`:设置生成位置无关代码所需的编译器标志。有关更多信息,请参考https://en.wikipedia.org/wiki/position-independentent_code
* `SOVERSION ${PROJECT_VERSION_MAJOR}` : 这是动态库提供的应用程序编程接口(API)版本。在设置语义版本之后,将其设置为与项目的主版本一致。CMake目标也有一个版本属性,可以用来指定目标的构建版本。注意,`SOVERSION`和`VERSION`有所不同:随着时间的推移,提供相同API的多个构建版本。本例中,我们不关心这种的粒度控制:仅使用`SOVERSION`属性设置API版本就足够了,CMake将为我们将`VERSION`设置为相同的值。相关详细信息,请参考官方文档:https://cmake.org/cmake/help/latest/prop_tgt/SOVERSION.html
* `OUTPUT_NAME "message"`:这告诉CMake库的名称`message`,而不是目标` message-shared `的名称,` libmessage.so.1 `将在构建时生成。从前面给出的构建目录和安装目录的也可以看出,` libmessage.so`的符号链接也将生成。
* `DEBUG_POSTFIX "_d" `:这告诉CMake,如果我们以Debug配置构建项目,则将`_d`后缀添加到生成的动态库。
* `PUBLIC_HEADER "Message.hpp"`:我们使用这个属性来设置头文件列表(本例中只有一个头文件),声明提供的API函数。这主要用于macOS上的动态库目标,也可以用于其他操作系统和目标。有关详细信息,请参见官方文档:https://cmake.org/cmake/help/v3.6/prop_tgt/PUBLIC_HEADER.html
* `MACOSX_RPATH ON`:这将动态库的`install_name`部分(目录)设置为macOS上的`@rpath`。
* `WINDOWS_EXPORT_ALL_SYMBOLS ON`:这将强制在Windows上编译以导出所有符号。注意,这通常不是一个好的方式,我们将在第2节中展示如何生成导出头文件,以及如何在不同的平台上保证符号的可见性。
现在讨论一下`RPATH`。我们将` hello-world_wDSO`可执行文件链接到`libmessage.so.1`,这意味着在执行时,将加载动态库。因此,有关库位置的信息需要在某个地方进行编码,以便加载程序能够成功地完成其工作。库的定位有两种方法:
* 通过设置环境变量通知链接器:
* GNU/Linux上,这需要将路径附加到`LD_LIBRARY_PATH`环境变量中。注意,这很可能会污染系统中所有应用程序的链接器路径,并可能导致符号冲突( https://gms.tf/ld_library_path-considered-harmful.htm )。
* macOS上,可以设置`DYLD_LIBRARY_PATH`变量。这与GNU/Linux上的`LD_LIBRARY_PATH`有相同的问题,可以通过使用`DYLD_FALLBACK_LIBRARY_PATH`变量来(部分的)改善这种情况。请看下面的链接,获取相关例子: https://stackoverflow.com/a/3172515/2528668
* 可被编码到可执行文件中,使用`RPATH`可以设置可执行文件的运行时搜索路径
后一种方法更健壮。但是,设置动态对象的`RPATH`时,应该选择哪个路径?我们需要确保可执行文件总是找到正确的动态库,不管它是在构建树中运行还是在安装树中运行。这需要通过设置` hello-world_wDSO`目标的`RPATH`相关属性来实现的,通过`$ORIGIN`(在GNU/Linux上)或`@loader_path`(在macOS上)变量来查找与可执行文件本身位置相关的路径:
```cmake
# Prepare RPATH
file(RELATIVE_PATH _rel ${CMAKE_INSTALL_PREFIX}/${INSTALL_BINDIR} ${CMAKE_INSTALL_PREFIX})
if(APPLE)
set(_rpath "@loader_path/${_rel}")
else()
set(_rpath "\$ORIGIN/${_rel}")
endif()
file(TO_NATIVE_PATH "${_rpath}/${INSTALL_LIBDIR}" message_RPATH)
```
当设置了`message_RPATH`变量,目标属性将完成剩下的工作:
```cmake
set_target_properties(hello-world_wDSO
PROPERTIES
MACOSX_RPATH ON
SKIP_BUILD_RPATH OFF
BUILD_WITH_INSTALL_RPATH OFF
INSTALL_RPATH "${message_RPATH}"
INSTALL_RPATH_USE_LINK_PATH ON
)
```
让我们详细研究一下这个命令:
* `SKIP_BUILD_RPATH OFF `:告诉CMake生成适当的`RPATH`,以便能够在构建树中运行可执行文件。
* `UILD_WITH_INSTALL_RPATH OFF`:关闭生成可执行目标,使其`RPATH`调整为与安装树的`RPATH`相同。在构建树中不运行可执行文件。
* `INSTALL_RPATH "${message_RPATH}" `:将已安装的可执行目标的`RPATH`设置为先前的路径。
* `INSTALL_RPATH_USE_LINK_PATH ON`:告诉CMake将链接器搜索路径附加到可执行文件的`RPATH`中。
**NOTE**:*加载器在Unix系统上如何工作的更多信息,可参见:http://longwei.github.io/rpath_origin/*
### 安装指令
最后,看一下安装指令。我们需要安装一个可执行文件、一个库和一个头文件。可执行文件和库是构建目标,因此我们使用安装命令的`TARGETS`选项。可以同时设置多个目标的安装规则:CMake知道它们是什么类型的目标,无论其是可执行程序库、动态库,还是静态库:
```cmake
install(
TARGETS
message-shared
hello-world_wDSO
```
可执行文件将安装在`RUNTIME DESTINATION`,将其设置为`${INSTALL_BINDIR}`。动态库安装到`LIBRARY_DESTINATION`,将其设置为`${INSTALL_LIBDIR}`。静态库将安装到`ARCHIVE DESTINATION`,将其设置为`${INSTALL_LIBDIR}`:
```cmake
ARCHIVE
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
RUNTIME
DESTINATION ${INSTALL_BINDIR}
COMPONENT bin
LIBRARY
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
```
注意,这里不仅指定了`DESTINATION`,还指定了`COMPONENT`。使用` cmake --build . --target install`安装命令,所有组件会按预期安装完毕。然而,有时只安装其中一些可用的。这就是`COMPONENT`关键字帮助我们做的事情。例如,当只要求安装库,我们可以执行以下步骤:
```shell
$ cmake -D COMPONENT=lib -P cmake_install.cmake
```
自从` Message.hpp `头文件设置为项目的公共头文件,我们可以使用`PUBLIC_HEADER`关键字将其与其他目标安装到选择的目的地:`${INSTALL_INCLUDEDIR}/message`。库用户现在可以包含头文件:`#include <message/Message.hpp>`,这需要在编译时,使用`-I`选项将正确的头文件查找路径位置传递给编译器。
安装指令中的各种目标地址会被解释为相对路径,除非使用绝对路径。但是相对于哪里呢?根据不同的安装工具而不同,而CMake可以去计算目标地址的绝对路径。当使用`cmake --build . --target install`,路径将相对于`CMAKE_INSTALL_PREFIX`计算。但当使用CPack时,绝对路径将相对于`CPACK_PACKAGING_INSTALL_PREFIX`计算。CPack的用法将在第11章中介绍。
**NOTE**:*Unix Makefile和Ninja生成器还提供了另一种机制:`DESTDIR`。可以在`DESTDIR`指定的目录下重新定位整个安装树。也就是说,`env DESTDIR=/tmp/stage cmake --build . --target install`将安装相对于`CMAKE_INSTALL_PREFIX`和`/tmp/stage`目录。可以在这里阅读更多信息:https://www.gnu.org/prep/standards/html_node/DESTDIR.html*
## 更多信息
正确设置`RPATH`可能相当麻烦,但这对于用户来说无法避免。默认情况下,CMake设置可执行程序的`RPATH`,假设它们将从构建树运行。但是,安装之后`RPATH`被清除,当用户想要运行`hello-world_wDSO`时,就会出现问题。使用Linux上的`ldd`工具,我们可以检查构建树中的`hello-world_wDSO`可执行文件,运行`ldd hello-world_wDSO`将得到以下结果:
```shell
libmessage.so.1 => /home/user/cmake-cookbook/chapter-10/recipe-01/cxx-example/build/lib64/libmessage.so.1(0x00007f7a92e44000)
```
在安装目录中运行`ldd hello-world_wDSO`将得到以下结果:
```shell
libmessage.so.1 => Not found
```
这显然是不行的。但是,总是硬编码`RPATH`来指向构建树或安装目录也是错误的:这两个位置中的任何一个都可能被删除,从而导致可执行文件的损坏。这里给出的解决方案为构建树和安装目录中的可执行文件设置了不同的`RPATH`,因此它总是指向“有意义”的位置;也就是说,尽可能接近可执行文件。在构建树中运行`ldd`显示相同的输出:
```shell
libmessage.so.1 => /home/roberto/Workspace/robertodr/cmake-
cookbook/chapter-10/recipe-01/cxx-example/build/lib64/libmessage.so.1
(0x00007f7a92e44000)
```
另外,在安装目录下,我们得到:
```shell
libmessage.so.1 => /home/roberto/Software/ch10r01/bin/../lib64/libmessage.so.1 (0x00007fbd2a725000)
```
我们使用了带有目标参数的CMake安装命令,因为我们需要安装构建目标。而该命令还有另外4个参数:
* **FILES**和**PROGRAMS**,分别用于安装文件或程序。安装后,并设置安装文件适当的权限。对于文件,对所有者具有读和写权限,对组以及其他用户和组具有读权限。对于程序,将授予执行权限。注意,`PROGRAMS`要与非构建目标的可执行程序一起使用。参见: https://cmake.org/cmake/help/v3.6/command/install.html#installing-files
* **DIRECTORY**,用于安装目录。当只给出一个目录名时,它通常被理解为相对于当前源目录。可以对目录的安装粒度进行控制。请参考在线文档: https://cmake.org/cmake/help/v3.6/command/install.html#installing-directories
* **SCRIPT**,可以使用它在CMake脚本中定义自定义安装规则。参见: https://cmake.org/cmake/help/v3.6/command/install.html#custom-installation-logic
* **EXPORT**,我们将此参数的讨论推迟到第3节,该参数用于导出目标。
- Introduction
- 前言
- 第0章 配置环境
- 0.1 获取代码
- 0.2 Docker镜像
- 0.3 安装必要的软件
- 0.4 测试环境
- 0.5 上报问题并提出改进建议
- 第1章 从可执行文件到库
- 1.1 将单个源文件编译为可执行文件
- 1.2 切换生成器
- 1.3 构建和链接静态库和动态库
- 1.4 用条件句控制编译
- 1.5 向用户显示选项
- 1.6 指定编译器
- 1.7 切换构建类型
- 1.8 设置编译器选项
- 1.9 为语言设定标准
- 1.10 使用控制流
- 第2章 检测环境
- 2.1 检测操作系统
- 2.2 处理与平台相关的源代码
- 2.3 处理与编译器相关的源代码
- 2.4 检测处理器体系结构
- 2.5 检测处理器指令集
- 2.6 为Eigen库使能向量化
- 第3章 检测外部库和程序
- 3.1 检测Python解释器
- 3.2 检测Python库
- 3.3 检测Python模块和包
- 3.4 检测BLAS和LAPACK数学库
- 3.5 检测OpenMP的并行环境
- 3.6 检测MPI的并行环境
- 3.7 检测Eigen库
- 3.8 检测Boost库
- 3.9 检测外部库:Ⅰ. 使用pkg-config
- 3.10 检测外部库:Ⅱ. 自定义find模块
- 第4章 创建和运行测试
- 4.1 创建一个简单的单元测试
- 4.2 使用Catch2库进行单元测试
- 4.3 使用Google Test库进行单元测试
- 4.4 使用Boost Test进行单元测试
- 4.5 使用动态分析来检测内存缺陷
- 4.6 预期测试失败
- 4.7 使用超时测试运行时间过长的测试
- 4.8 并行测试
- 4.9 运行测试子集
- 4.10 使用测试固件
- 第5章 配置时和构建时的操作
- 5.1 使用平台无关的文件操作
- 5.2 配置时运行自定义命令
- 5.3 构建时运行自定义命令:Ⅰ. 使用add_custom_command
- 5.4 构建时运行自定义命令:Ⅱ. 使用add_custom_target
- 5.5 构建时为特定目标运行自定义命令
- 5.6 探究编译和链接命令
- 5.7 探究编译器标志命令
- 5.8 探究可执行命令
- 5.9 使用生成器表达式微调配置和编译
- 第6章 生成源码
- 6.1 配置时生成源码
- 6.2 使用Python在配置时生成源码
- 6.3 构建时使用Python生成源码
- 6.4 记录项目版本信息以便报告
- 6.5 从文件中记录项目版本
- 6.6 配置时记录Git Hash值
- 6.7 构建时记录Git Hash值
- 第7章 构建项目
- 7.1 使用函数和宏重用代码
- 7.2 将CMake源代码分成模块
- 7.3 编写函数来测试和设置编译器标志
- 7.4 用指定参数定义函数或宏
- 7.5 重新定义函数和宏
- 7.6 使用废弃函数、宏和变量
- 7.7 add_subdirectory的限定范围
- 7.8 使用target_sources避免全局变量
- 7.9 组织Fortran项目
- 第8章 超级构建模式
- 8.1 使用超级构建模式
- 8.2 使用超级构建管理依赖项:Ⅰ.Boost库
- 8.3 使用超级构建管理依赖项:Ⅱ.FFTW库
- 8.4 使用超级构建管理依赖项:Ⅲ.Google Test框架
- 8.5 使用超级构建支持项目
- 第9章 语言混合项目
- 9.1 使用C/C++库构建Fortran项目
- 9.2 使用Fortran库构建C/C++项目
- 9.3 使用Cython构建C++和Python项目
- 9.4 使用Boost.Python构建C++和Python项目
- 9.5 使用pybind11构建C++和Python项目
- 9.6 使用Python CFFI混合C,C++,Fortran和Python
- 第10章 编写安装程序
- 10.1 安装项目
- 10.2 生成输出头文件
- 10.3 输出目标
- 10.4 安装超级构建
- 第11章 打包项目
- 11.1 生成源代码和二进制包
- 11.2 通过PyPI发布使用CMake/pybind11构建的C++/Python项目
- 11.3 通过PyPI发布使用CMake/CFFI构建C/Fortran/Python项目
- 11.4 以Conda包的形式发布一个简单的项目
- 11.5 将Conda包作为依赖项发布给项目
- 第12章 构建文档
- 12.1 使用Doxygen构建文档
- 12.2 使用Sphinx构建文档
- 12.3 结合Doxygen和Sphinx
- 第13章 选择生成器和交叉编译
- 13.1 使用CMake构建Visual Studio 2017项目
- 13.2 交叉编译hello world示例
- 13.3 使用OpenMP并行化交叉编译Windows二进制文件
- 第14章 测试面板
- 14.1 将测试部署到CDash
- 14.2 CDash显示测试覆盖率
- 14.3 使用AddressSanifier向CDash报告内存缺陷
- 14.4 使用ThreadSaniiser向CDash报告数据争用
- 第15章 使用CMake构建已有项目
- 15.1 如何开始迁移项目
- 15.2 生成文件并编写平台检查
- 15.3 检测所需的链接和依赖关系
- 15.4 复制编译标志
- 15.5 移植测试
- 15.6 移植安装目标
- 15.7 进一步迁移的措施
- 15.8 项目转换为CMake的常见问题
- 第16章 可能感兴趣的书
- 16.1 留下评论——让其他读者知道你的想法