From d40e483eeea843af6148b7951b332316ff186aa0 Mon Sep 17 00:00:00 2001 From: xiaowei Date: Sat, 14 Sep 2019 13:36:19 +0800 Subject: [PATCH] proofread chapter 10 --- content/chapter10/10.0-chinese.md | 4 +- content/chapter10/10.1-chinese.md | 76 +++++++++++++++---------------- content/chapter10/10.2-chinese.md | 20 ++++---- content/chapter10/10.3-chinese.md | 40 ++++++++-------- content/chapter10/10.4-chinese.md | 66 +++++++++++++-------------- 5 files changed, 103 insertions(+), 103 deletions(-) diff --git a/content/chapter10/10.0-chinese.md b/content/chapter10/10.0-chinese.md index b87cfc9..cdcf5d2 100644 --- a/content/chapter10/10.0-chinese.md +++ b/content/chapter10/10.0-chinese.md @@ -7,8 +7,8 @@ * 输出目标 * 安装超级构建 -前几章中,我们展示了如何使用CMake配置、构建和测试我们的项目。安装项目也是很重要的一部分,本章将演示如何实现这一点。本章的方法涵盖了下图中概述的安装时操作: +前几章中,我们展示了如何使用CMake配置、构建和测试项目。安装项目是很重要的一部分,本章将演示如何实现这一点。本章涵盖了下图中,安装时的所有操作: ![](../../images/preface/2.png) -我们将指导您完成各个步骤,直到完成安装一个简单的C++项目:从项目中构建的文件,并复制到正确的目录,确保其他项目使用CMake时可以找到该工程输出目标。本章中的4个示例将建立在第1章第3节的基础上。之前,我们试图构建一个非常简单的库,并将其链接到一个可执行文件中。我们还展示了如何从相同的源文件构建静态库和动态库。本章中,我们将会讨论安装时发生的事情。 \ No newline at end of file +我们将指导完成各个步骤,直到完成安装一个简单的C++项目:从项目中构建的文件,并复制到正确的目录,确保其他项目使用CMake时可以找到该工程的输出目标。本章中的4个示例将建立在第1章第3节的示例基础上。之前,我们试图构建一个非常简单的库,并将其链接到一个可执行文件中。我们还展示了如何使用相同的源文件构建静态库和动态库。本章中,我们将会讨论安装时所发生的事情。 \ No newline at end of file diff --git a/content/chapter10/10.1-chinese.md b/content/chapter10/10.1-chinese.md index e715ba7..1b7b8fe 100644 --- a/content/chapter10/10.1-chinese.md +++ b/content/chapter10/10.1-chinese.md @@ -2,7 +2,7 @@ **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有效地避免其中的许多错误。 +第一个示例中,将介绍我们的小项目和一些基本概念,这些概念也将在后面的示例中使用。安装文件、库和可执行文件是一项非常基础的任务,但是也可能会带来一些缺陷。我们将带您了解这些问题,并展示如何使用CMake有效地避开这些缺陷。 ## 准备工作 @@ -102,7 +102,7 @@ int main() 我们先来看一下主CMakeLists.txt: -1. 首先声明CMake最低版本,并定义一个C++11项目。请注意,我们已经为我们的项目设置了一个版本,版本关键字的项目命令: +1. 声明CMake最低版本,并定义一个C++11项目。请注意,我们已经为我们的项目设置了一个版本,在`project`中使用`VERSION`进行指定: ```cmake # CMake 3.6 needed for IMPORTED_TARGET option @@ -118,13 +118,13 @@ int main() set(CMAKE_CXX_STANDARD_REQUIRED ON) ``` -2. 用户可以通过`CMAKE_INSTALL_PREFIX`变量定义安装前缀。CMake将这个变量设置一个合理的默认:分别在Windows上的`C:\Program Files`和Unix的`/usr/local`。我们将会打印安装前缀的值: +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`设置此变量改变配置类型,我们将检查是否存在这种情况。如果没有,我们将自己设置为默认的、合理的值: +3. 默认情况下,我们更喜欢以Release的方式配置项目。用户可以通过`CMAKE_BUILD_TYPE`设置此变量,从而改变配置类型,我们将检查是否存在这种情况。如果没有,将设置为默认值: ```cmake if(NOT CMAKE_BUILD_TYPE) @@ -133,7 +133,7 @@ int main() message(STATUS "Build type set to ${CMAKE_BUILD_TYPE}") ``` -4. 接下来,我们告诉CMake在何处构建可执行、静态和动态库目标。便于在用户不打算安装项目的情况下,访问这些构建目标。这里使用标准CMake的`GNUInstallDirs.cmake`模块。这将确保一个合理的和可移植的项目布局: +4. 接下来,告诉CMake在何处构建可执行、静态和动态库目标。便于在用户不打算安装项目的情况下,访问这些构建目标。这里使用标准CMake的`GNUInstallDirs.cmake`模块。这将确保的项目布局的合理性和可移植性: ```cmake include(GNUInstallDirs) @@ -146,7 +146,7 @@ int main() ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) ``` -5. 虽然,前面的命令固定了构建目录中输出的位置,但是需要下面的命令来固定可执行程序、库以及安装前缀中包含的文件的位置。它们大致遵循相同的布局,但是我们定义了新的`INSTALL_LIBDIR`、`INSTALL_BINDIR`、`INSTALL_INCLUDEDIR`和`INSTALL_CMAKEDIR`变量。当然,也可以覆盖这些变量: +5. 虽然,前面的命令配置了构建目录中输出的位置,但是需要下面的命令来配置可执行程序、库以及安装前缀中包含的文件的位置。它们大致遵循相同的布局,但是我们定义了新的`INSTALL_LIBDIR`、`INSTALL_BINDIR`、`INSTALL_INCLUDEDIR`和`INSTALL_CMAKEDIR`变量。当然,也可以覆盖这些变量: ```cmake # Offer the user the choice of overriding the installation directories @@ -172,7 +172,7 @@ int main() endforeach() ``` -7. 根CMakeLists.txt文件中的最后一个指令添加`src`子目录,启用`test`,并添加`tests`子目录: +7. 主CMakeLists.txt文件中的最后一个指令添加`src`子目录,启用测试,并添加`tests`子目录: ```cmake add_subdirectory(src) @@ -180,9 +180,9 @@ int main() add_subdirectory(tests) ``` -现在我们继续分析`src/CMakeLists.txt`这个文件,其定义了要构建的实际目标: +现在我们继续分析`src/CMakeLists.txt`,其定义了构建的实际目标: -1. 我们的项目依赖于UUID库。如第5章第8节中,可以找到以下代码: +1. 我们的项目依赖于UUID库: ```cmake # Search for pkg-config and UUID @@ -196,7 +196,7 @@ int main() endif() ``` -2. 我们希望建立一个动态库,我们将该目标声明为`message-shared `: +2. 我们希望建立一个动态库,将该目标声明为`message-shared `: ```cmake add_library(message-shared SHARED "") @@ -211,7 +211,7 @@ int main() ) ``` -4. 我们为目标声明编译定义和链接库。请注意,所有这些都是`PUBLIC`,以确保所有依赖的目标将正确继承它们: +4. 我们为目标声明编译时定义和链接库。请注意,所有这些都是`PUBLIC`,以确保所有依赖的目标将正确继承它们: ```cmake target_compile_definitions(message-shared @@ -239,7 +239,7 @@ int main() ) ``` -6. 最后,为“Hello, world”程序添加一个可执行目标: +6. 最后,为“Hello, world”程序添加可执行目标: ```cmake add_executable(hello-world_wDSO hello-world.cpp) @@ -269,7 +269,7 @@ int main() file(TO_NATIVE_PATH "${_rpath}/${INSTALL_LIBDIR}" message_RPATH) ``` -2. 现在,我们可以使用这个变量来设置可执行目标`hello-world_wDSO`的`RPATH`(这通过目标属性实现)。我们也可以设置额外的属性,稍后会对此进行更多的讨论: +2. 现在,可以使用这个变量来设置可执行目标`hello-world_wDSO`的`RPATH`(通过目标属性实现)。我们也可以设置额外的属性,稍后会对此进行更多的讨论: ```cmake set_target_properties(hello-world_wDSO @@ -282,7 +282,7 @@ int main() ) ``` -3. 我们终于可以安装库、头文件和可执行文件了!我们使用CMake提供的`install`命令来指定安装位置。注意,路径是相对的,我们将在下文进一步讨论这一点: +3. 终于可以安装库、头文件和可执行文件了!使用CMake提供的`install`命令来指定安装位置。注意,路径是相对的,我们将在后续进一步讨论这一点: ```cmake install( @@ -304,7 +304,7 @@ int main() ) ``` -tests目录中的CMakeLists.txt文件包含简单的指令,以确保“Hello, World”可执行文件能够正确运行: +`tests`目录中的CMakeLists.txt文件包含简单的指令,以确保“Hello, World”可执行文件能够正确运行: ```cmake add_test( @@ -313,7 +313,7 @@ add_test( ) ``` -现在让我们配置、构建和安装项目,并查看结果。当添加了任何安装指令,CMake就会生成一个名为`install`的新目标,该目标将运行安装规则: +现在让我们配置、构建和安装项目,并查看结果。添加安装指令时,CMake就会生成一个名为`install`的新目标,该目标将运行安装规则: ```shell $ mkdir -p build @@ -363,18 +363,18 @@ $HOME/Software/recipe-01/ 这个示例有三个要点我们需要更详细地讨论: * 使用`GNUInstallDirs.cmake`定义目标安装的标准位置 -* 在动态库和可执行目标上设置的属性,特别是RPATH的处理 -* 安装指南 +* 在动态库和可执行目标上设置的属性,特别是`RPATH`的处理 +* 安装指令 ###安装到标准位置 -对于项目的安装来说,什么是好的布局呢?如果只有自己使用该项目,那就无所谓好或坏的布局。然而,一旦向外部发布产品,和他人共用该项目,就应该在安装项目时提供一个合理的布局。幸运的是,我们可以遵循一些标准,CMake可以帮助我们做到这一点。实际上,`GNUInstallDirs.cmake `模块所做的就是定义这样一组变量,这些变量是安装不同类型文件的子目录的名称。在我们的例子中,使用了以下内容: +对于项目的安装来说,什么是好的布局呢?如果只有自己使用该项目,那就无所谓好或坏的布局。然而,一旦向外部发布产品,和他人共用该项目,就应该在安装项目时提供一个合理的布局。幸运的是,我们可以遵循一些标准,CMake可以帮助我们做到这一点。实际上,`GNUInstallDirs.cmake `模块所做的就是定义这样一组变量,这些变量是安装不同类型文件的子目录的名称。在例子中,使用了以下内容: -* CMAKE_INSTALL_BINDIR:这将用于定义用户可执行文件所在的子目录,即所选安装前缀下的`bin`目录。 +* CMAKE_INSTALL_BINDIR:这将用于定义用户可执行文件所在的子目录,即所选安装目录下的`bin`目录。 * CMAKE_INSTALL_LIBDIR:这将扩展到目标代码库(即静态库和动态库)所在的子目录。在64位系统上,它是`lib64`,而在32位系统上,它只是`lib`。 -* CMAKE_INSTALL_INCLUDEDIR:最后,我们使用这个变量为C头文件获取正确的子目录,该变量为include +* CMAKE_INSTALL_INCLUDEDIR:最后,我们使用这个变量为C头文件获取正确的子目录,该变量为`include`。 -然而,用户可能希望覆盖这些选项。我们允许在主CMakeLists.txt文件中使用以下方式: +然而,用户可能希望覆盖这些选项。我们允许在主CMakeLists.txt文件中使用以下方式覆盖选项: ```cmake # Offer the user the choice @@ -393,24 +393,24 @@ PATH "Installation directory for header files") ### 目标属性和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,如果我们在调试配置中构建项目,则要将`_d`后缀添加到生成的动态库中。 +* `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`,这意味着在执行时,将加载动态库。因此,有关库位置的信息需要在某个地方进行编码,以便加载程序能够成功地完成其工作。库的定位有两种方法: +现在讨论一下`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`可以设置可执行文件的运行时搜索路径 + * 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上)变量来查找与可执行文件本身位置相关的路径: +后一种方法更健壮。但是,设置动态对象的`RPATH`时,应该选择哪个路径?我们需要确保可执行文件总是找到正确的动态库,不管它是在构建树中运行还是在安装树中运行。这需要通过设置` hello-world_wDSO`目标的`RPATH`相关属性来实现的,通过`$ORIGIN`(在GNU/Linux上)或`@loader_path`(在macOS上)变量来查找与可执行文件本身位置相关的路径: ```cmake # Prepare RPATH @@ -445,9 +445,9 @@ set_target_properties(hello-world_wDSO **NOTE**:*加载器在Unix系统上如何工作的更多信息,可参见:http://longwei.github.io/rpath_origin/* -### 安装指南 +### 安装指令 -最后,让我们看一下安装指令。我们需要安装一个可执行文件、一个库和一个头文件。可执行文件和库是构建目标,因此我们使用安装命令的`TARGETS`选项。可以同时设置多个目标的安装规则:CMake知道它们是什么类型的目标,无论其是可执行程序库、动态库还是静态库: +最后,看一下安装指令。我们需要安装一个可执行文件、一个库和一个头文件。可执行文件和库是构建目标,因此我们使用安装命令的`TARGETS`选项。可以同时设置多个目标的安装规则:CMake知道它们是什么类型的目标,无论其是可执行程序库、动态库,还是静态库: ```cmake install( @@ -456,7 +456,7 @@ install( hello-world_wDSO ``` -可执行文件将安装在`RUNTIME DESTINATION`,我们将其设置为`${INSTALL_BINDIR}`。动态库安装到`LIBRARY_DESTINATION`,我们将其设置为`${INSTALL_LIBDIR}`。静态库将安装到`ARCHIVE DESTINATION`,我们将其设置为`${INSTALL_LIBDIR}`: +可执行文件将安装在`RUNTIME DESTINATION`,将其设置为`${INSTALL_BINDIR}`。动态库安装到`LIBRARY_DESTINATION`,将其设置为`${INSTALL_LIBDIR}`。静态库将安装到`ARCHIVE DESTINATION`,将其设置为`${INSTALL_LIBDIR}`: ```cmake ARCHIVE @@ -470,13 +470,13 @@ LIBRARY COMPONENT lib ``` -注意,我们不仅指定了`DESTINATION`,还指定了`COMPONENT`。使用` cmake --build . --target install`安装命令,所有组件会按预期安装完毕。然而,有时只安装其中一些可取的。这就是`COMPONENT`关键字帮助我们做的事情。例如,要只安装库,我们可以执行以下步骤: +注意,这里不仅指定了`DESTINATION`,还指定了`COMPONENT`。使用` cmake --build . --target install`安装命令,所有组件会按预期安装完毕。然而,有时只安装其中一些可用的。这就是`COMPONENT`关键字帮助我们做的事情。例如,当只要求安装库,我们可以执行以下步骤: ```shell $ cmake -D COMPONENT=lib -P cmake_install.cmake ``` -自从` Message.hpp `头文件设置为项目的公共头文件,我们可以使用`PUBLIC_HEADER`关键字将其与其他目标安装到选择的目的地:`${INSTALL_INCLUDEDIR}/message`。库的用户现在可以包含头文件:`#include `,这需要在编译时,使用`-I`选项将正确的头文件查找路径位置传递给编译器。 +自从` Message.hpp `头文件设置为项目的公共头文件,我们可以使用`PUBLIC_HEADER`关键字将其与其他目标安装到选择的目的地:`${INSTALL_INCLUDEDIR}/message`。库用户现在可以包含头文件:`#include `,这需要在编译时,使用`-I`选项将正确的头文件查找路径位置传递给编译器。 安装指令中的各种目标地址会被解释为相对路径,除非使用绝对路径。但是相对于哪里呢?根据不同的安装工具而不同,而CMake可以去计算目标地址的绝对路径。当使用`cmake --build . --target install`,路径将相对于`CMAKE_INSTALL_PREFIX`计算。但当使用CPack时,绝对路径将相对于`CPACK_PACKAGING_INSTALL_PREFIX`计算。CPack的用法将在第11章中介绍。 @@ -484,7 +484,7 @@ $ cmake -D COMPONENT=lib -P cmake_install.cmake ## 更多信息 -正确设置`RPATH`可能相当麻烦,但这对于第三方用户来说无法避免。默认情况下,CMake设置可执行程序的`RPATH`,假设它们将从构建树运行。但是,在安装之后`RPATH`被清除,当用户想要运行`hello-world_wDSO`时,就会出现问题。使用Linux上的`ldd`工具,我们可以检查构建树中的`hello-world_wDSO`可执行文件,运行`ldd hello-world_wDSO`将得到以下结果: +正确设置`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) @@ -496,7 +496,7 @@ libmessage.so.1 => /home/user/cmake-cookbook/chapter-10/recipe-01/cxx-example/bu libmessage.so.1 => Not found ``` -这显然是不行的。但是,总是硬编码`RPATH`来指向构建树或安装目录也是错误的:这两个位置中的任何一个都可能被删除,从而导致可执行文件的损坏。这里给出的解决方案为构建树和安装目录中的可执行文件设置了不同的`RPATH`,因此它总是指向“有意义”的地;也就是说,尽可能接近可执行文件。在构建树中运行`ldd`显示相同的输出: +这显然是不行的。但是,总是硬编码`RPATH`来指向构建树或安装目录也是错误的:这两个位置中的任何一个都可能被删除,从而导致可执行文件的损坏。这里给出的解决方案为构建树和安装目录中的可执行文件设置了不同的`RPATH`,因此它总是指向“有意义”的位置;也就是说,尽可能接近可执行文件。在构建树中运行`ldd`显示相同的输出: ```shell libmessage.so.1 => /home/roberto/Workspace/robertodr/cmake- @@ -510,7 +510,7 @@ cookbook/chapter-10/recipe-01/cxx-example/build/lib64/libmessage.so.1 libmessage.so.1 => /home/roberto/Software/ch10r01/bin/../lib64/libmessage.so.1 (0x00007fbd2a725000) ``` -我们使用了带有目标参数的CMake安装命令,因为我们需要安装构建目标。但是,该命令还有另外4个参数: +我们使用了带有目标参数的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 diff --git a/content/chapter10/10.2-chinese.md b/content/chapter10/10.2-chinese.md index 215eb76..8f86dcf 100644 --- a/content/chapter10/10.2-chinese.md +++ b/content/chapter10/10.2-chinese.md @@ -2,12 +2,12 @@ **NOTE**:*此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-10/recipe-02 中找到,其中有一个C++示例。该示例在CMake 3.6版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。* -设想一下,当我们的小型库非常受欢迎时,许多人都在使用它。然而,一些客户希望在安装时使用静态库,而另一些客户也已经注意到所有符号在动态库中都是可见的。最佳方式是规定动态库只公开最小的符号,从而限制代码中定义的对象和函数对外的可见性。我们希望在默认情况下,动态库定义的所有符号都对外隐藏。这将使得项目的贡献者清楚地划分库和外部代码之间的接口,因为他们必须显式地标记所有也意味着要在项目外部使用的符号。因此,我们需要做以下工作: +设想一下,当我们的小型库非常受欢迎时,许多人都在使用它。然而,一些客户希望在安装时使用静态库,而另一些客户也注意到所有符号在动态库中都是可见的。最佳方式是规定动态库只公开最小的符号,从而限制代码中定义的对象和函数对外的可见性。我们希望在默认情况下,动态库定义的所有符号都对外隐藏。这将使得项目的贡献者,能够清楚地划分库和外部代码之间的接口,因为他们必须显式地标记所有要在项目外部使用的符号。因此,我们需要完成以下工作: * 使用同一组源文件构建动态库和静态库 * 确保正确分隔动态库中符号的可见性 -在第1章第3节中,已经展示了CMake提供了与平台无关的方式实现的功能。但是,没有处理符号可见性的问题。我们将用当前的配方重新讨论这两点。 +第1章第3节中,已经展示了CMake提供了与平台无关的方式实现的功能。但是,没有处理符号可见性的问题。我们将用当前的配方重新讨论这两点。 ```c++ #pragma once @@ -35,11 +35,11 @@ private: std::string getUUID(); ``` -Message类的声明中引入了`message_EXPORT`预处理器指令,这个指令将让编译器生成对库的用户可见的符号。 +`Message`类的声明中引入了`message_EXPORT`预处理器指令,这个指令将让编译器生成对库的用户可见的符号。 ## 具体实施 -除了项目的名称外,主CMakeLists.txt文件没有改变。首先看看`src`子目录中的CMakeLists.txt文件,所有工作实际上都在这里进行。我们将重点展示对之前示例的修改之处: +除了项目的名称外,主CMakeLists.txt文件没有改变。首先,看看`src`子目录中的CMakeLists.txt文件,所有工作实际上都在这里进行。我们将重点展示对之前示例的修改之处: 1. 为消息传递库声明`SHARED`库目标及其源。注意,编译定义和链接库没有改变: @@ -94,7 +94,7 @@ Message类的声明中引入了`message_EXPORT`预处理器指令,这个指令 ) ``` -4. 当要更改符号的可见性(从其默认值-隐藏值)时,都应该包含导出头文件。我们已经在`Message.hpp`头文件例这样做了,因为我们想在库中公开一些符号。现在我们将`${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}`目录作为` message-shared`目标的`PUBLIC`包含目录列出: +4. 当要更改符号的可见性(从其默认值-隐藏值)时,都应该包含导出头文件。我们已经在`Message.hpp`头文件例这样做了,因为想在库中公开一些符号。现在将`${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}`目录作为` message-shared`目标的`PUBLIC`包含目录列出: ```cmake target_include_directories(message-shared @@ -190,7 +190,7 @@ Message类的声明中引入了`message_EXPORT`预处理器指令,这个指令 * `CXX_VISIBILITY_PRESET hidden`:这将隐藏所有符号,除非显式地标记了其他符号。当使用GNU编译器时,这将为目标添加`-fvisibility=hidden`标志。 * `VISIBILITY_INLINES_HIDDEN 1`:这将隐藏内联函数的符号。如果使用GNU编译器,这对应于` -fvisibility-inlines-hidden ` -在Windows上,这是默认行为。回想一下,实际上,我们需要在前面的示例中通过设置`WINDOWS_EXPORT_ALL_SYMBOLS`属性为`ON`来覆盖它。 +Windows上,这都是默认行为。实际上,我们需要在前面的示例中通过设置`WINDOWS_EXPORT_ALL_SYMBOLS`属性为`ON`来覆盖它。 如何标记可见的符号?这由预处理器决定,因此需要提供相应的预处理宏,这些宏可以扩展到所选平台上,以便编译器能够理解可见性属性。CMake中有现成的`GenerateExportHeader.cmake `模块。这个模块定义了`generate_export_header`函数,我们调用它的过程如下: @@ -208,7 +208,7 @@ generate_export_header(message-shared ) ``` -该函数生成`messageExport.h`头文件,其中包含预处理器所需的宏。根据`EXPORT_FILE_NAME`选项的请求,在目录`${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}`中生成该文件。如果该选项为空,则头文件将在当前二进制目录中生成。这个函数的第一个参数是现有的目标(在示例中是`message- +该函数生成`messageExport.h`头文件,其中包含预处理器所需的宏。根据`EXPORT_FILE_NAME`选项的请求,在目录`${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}`中生成该文件。如果该选项为空,则头文件将在当前二进制目录中生成。这个函数的第一个参数是现有的目标(示例中是`message- shared`),函数的基本调用只需要传递现有目标的名称即可。可选参数,用于细粒度的控制所有生成宏,也可以传递: * BASE_NAME:设置生成的头文件和宏的名称。 @@ -220,7 +220,7 @@ shared`),函数的基本调用只需要传递现有目标的名称即可。可 * NO_DEPRECATED_MACRO_NAME:设置宏的名称,在编译时将“将要废弃”的代码排除在外。 * DEFINE_NO_DEPRECATED:指示CMake生成预处理器代码,以从编译中排除“将要废弃”的代码。 -在GNU/Linux上,使用GNU编译器,CMake将生成以下`messageExport.h`导出头文件: +GNU/Linux上,使用GNU编译器,CMake将生成以下`messageExport.h`头文件: ```cmake #ifndef message_EXPORT_H @@ -274,8 +274,8 @@ shared`),函数的基本调用只需要传递现有目标的名称即可。可 1. 使用适当的编译器标志。 2. 使用预处理器变量(示例中是`message_EXPORT`)标记要导出的符号。编译时,将解除这些符号(类和函数)的隐藏。 -静态库只是目标文件的归档。因此,可以将源代码编译成目标文件,然后归档器将它们捆绑到归档文件中。这时没有ABI的概念:所有符号在默认情况下都是可见的,编译器的可见标志不影响静态归档。但是,如果要从相同的源文件构建动态和静态库,则需要一种方法来赋予`message_EXPORT`预处理变量意义,这两种情况下都出现在代码中。这里使用` GenerateExportHeader.cmake`模块。它定义一个包含所有逻辑的头文件,用于给出这个预处理变量的正确定义。对于动态库,它将是给定的平台和编译器组合所需要的。注意,根据我们是构建或使用动态库,宏定义的含义也会发生变化。幸运的是,CMake在没有进一步干预的情况下为我们解决了这个问题。对于静态库,它将扩展为一个空字符串,执行我们期望的操作——什么也不做。 +静态库只是目标文件的归档。因此,可以将源代码编译成目标文件,然后归档器将它们捆绑到归档文件中。这时没有ABI的概念:所有符号在默认情况下都是可见的,编译器的可见标志不影响静态归档。但是,如果要从相同的源文件构建动态和静态库,则需要一种方法来赋予`message_EXPORT`预处理变量意义,这两种情况都会出现在代码中。这里使用` GenerateExportHeader.cmake`模块,它定义一个包含所有逻辑的头文件,用于给出这个预处理变量的正确定义。对于动态库,它将给定的平台与编译器相组合。注意,根据构建或使用动态库,宏定义也会发生变化。幸运的是,CMake为我们解决了这个问题。对于静态库,它将扩展为一个空字符串,执行我们期望的操作——什么也不做。 -细心的读者会注意到,构建此处所示的静态和共享库实际上需要编译源代码两次。对于我们的简单示例来说,这不是一个开销很大的操作,但它显然会变得相当繁重,即使对于只比我们的示例稍大一点的项目来说,也是如此。为什么我们选择这种方法,而不是使用第1章第3节的方式呢?对象库负责编译库的第一步:从源文件到对象文件。该步骤中,预处理器将介入并计算`message_EXPORT`。由于对象库的编译只发生一次,`message_EXPORT`被计算为构建动态库库或静态库兼容的值。因此,为了避免歧义,我们选择了更健壮的方法,即编译两次,为的就是让预处理器正确地评估变量的可见性。 +细心的读者会注意到,构建此处所示的静态和共享库实际上需要编译源代码两次。对于我们的简单示例来说,这不是一个很大的开销,但会显得相当麻烦,即使对于只比示例稍大一点的项目来说,也是如此。为什么我们选择这种方法,而不是使用第1章第3节的方式呢?`OBJECT`库负责编译库的第一步:从源文件到对象文件。该步骤中,预处理器将介入并计算`message_EXPORT`。由于对象库的编译只发生一次,`message_EXPORT`被计算为构建动态库库或静态库兼容的值。因此,为了避免歧义,我们选择了更健壮的方法,即编译两次,为的就是让预处理器正确地评估变量的可见性。 **NOTE**:*有关动态共享对象、静态存档和符号可见性的更多细节,建议阅读:http://people.redhat.com/drepper/dsohowto.pdf* \ No newline at end of file diff --git a/content/chapter10/10.3-chinese.md b/content/chapter10/10.3-chinese.md index 2effcf5..251f679 100644 --- a/content/chapter10/10.3-chinese.md +++ b/content/chapter10/10.3-chinese.md @@ -2,7 +2,7 @@ **NOTE**:*此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-10/recipe-03 中找到,其中有一个C++示例。该示例在CMake 3.6版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。* -可以设想,消息库在开源社区已经取得了巨大的成功。人们非常喜欢它,并在自己的项目中使用它将消息打印到屏幕上。用户特别喜欢每个打印的消息都有一个惟一的标识符。但是用户也希望一旦他们编译并安装了库,库就更容易找到。这个示例将展示CMake如何让我们导出目标,以便其他使用CMake的项目可以轻松地获取它们。 +可以假设,消息库在开源社区取得了巨大的成功。人们非常喜欢它,并在自己的项目中使用它将消息打印到屏幕上。用户特别喜欢每个打印的消息都有惟一的标识符。但用户也希望,当他们编译并安装了库,库就能更容易找到。这个示例将展示CMake如何让我们导出目标,以便其他使用CMake的项目可以轻松地获取它们。 ```shell . @@ -21,13 +21,13 @@ └── use_message.cpp ``` -注意,我们在从cmake子目录中,添加了一个`messageConfig.cmake.in`。这个文件将包含导出的目标,还添加了一个测试来检查项目的安装和导出是否按预期工作。 +注意,cmake子目录中添加了一个`messageConfig.cmake.in`。这个文件将包含导出的目标,还添加了一个测试来检查项目的安装和导出是否按预期工作。 ## 具体实施 同样,主CMakeLists.txt文件相对于前一个示例来说没有变化。移动到包含我们的源代码的子目录`src`中: -1. 需要找到UUID库,我们可以重用之前示例中的代码: +1. 需要找到UUID库,可以重用之前示例中的代码: ```cmake # Search for pkg-config and UUID @@ -41,7 +41,7 @@ endif() ``` -2. 接下来,我们设置动态库目标并生成导出头文件,如以下所示: +2. 接下来,设置动态库目标并生成导出头文件: ```cmake add_library(message-shared SHARED "") @@ -63,7 +63,7 @@ ) ``` -3. 我们为目标设置了`PUBLIC`和`INTERFACE`编译定义。注意`$ `生成器表达式在之后的使用: +3. 为目标设置了`PUBLIC`和`INTERFACE`编译定义。注意`$ `生成器表达式的使用: ```cmake target_compile_definitions(message-shared @@ -130,7 +130,7 @@ 现在,来看看安装规则: -1. 因为CMake可以正确地将每个目标放在正确的地方,所以把目标的安装规则都列在一起。这次,我们添加了`EXPORT`关键字,这样CMake将为我们的目标生成一个导出的目标文件: +1. 因为CMake可以正确地将每个目标放在正确的地方,所以把目标的安装规则都列在一起。这次,添加了`EXPORT`关键字,这样CMake将为目标生成一个导出的目标文件: ```cmake install( @@ -197,7 +197,7 @@ ) ``` -6. 最后,我们为这两个自动生成的配置文件设置了安装规则: +6. 最后,为这两个自动生成的配置文件设置了安装规则: ```cmake install( @@ -211,19 +211,19 @@ `cmake/messageConfig.cmake`的内容是什么?该文件的顶部有相关的说明,可以作为用户文档供使用者查看。让我们看看实际的CMake命令: -1. 我们从占位符开始,它将被`configure_package_config_file`命令替换: +1. 占位符将使用`configure_package_config_file`命令进行替换: ```cmake @PACKAGE_INIT@ ``` -2. 我们包括为目标自动生成的导出文件: +2. 包括为目标自动生成的导出文件: ```cmake include("${CMAKE_CURRENT_LIST_DIR}/messageTargets.cmake") ``` -3. 我们检查静态库和动态库,以及两个“Hello, World”可执行文件是否带有CMake提供的`check_required_components`函数: +3. 检查静态库和动态库,以及两个“Hello, World”可执行文件是否带有CMake提供的`check_required_components`函数: ```cmake check_required_components( @@ -234,7 +234,7 @@ ) ``` -4. 我们检查目标`PkgConfig::UUID`是否存在。如果没有,我们再次搜索UUID库(只有在非Windows操作系统下有效): +4. 检查目标`PkgConfig::UUID`是否存在。如果没有,我们再次搜索UUID库(只在非Windows操作系统下有效): ```cmake if(NOT WIN32) @@ -245,7 +245,7 @@ endif() ``` -让我们来测试一下: +测试一下: ```shell $ mkdir -p build @@ -278,7 +278,7 @@ $HOME/Software/recipe-03/ └── messageTargets-release.cmake ``` -出现了一个share子目录,其中包含我们要求CMake自动生成的所有文件。从现在开始,我们消息库的用户可以在他们自己的CMakeLists.txt文件中找到消息库,只要他们设置`message_DIR `的CMake变量,指向安装树中的`share/cmake/message`目录: +出现了一个`share`子目录,其中包含我们要求CMake自动生成的所有文件。现在开始,消息库的用户可以在他们自己的CMakeLists.txt文件中找到消息库,只要他们设置`message_DIR `的CMake变量,指向安装树中的`share/cmake/message`目录: ```cmake find_package(message 1 CONFIG REQUIRED) @@ -290,14 +290,14 @@ find_package(message 1 CONFIG REQUIRED) 这个问题可以通过遵循` message-static `、` message-shared `、`hello-world_wDSO`和`hello-world_wAR`目标概述的模式来解决。我们将单独分析`message-shared`目标的CMake命令,这里只是进行一般性讨论: -1. 生成目标在项目构建中列出其依赖项。对UUID库的链接是 `message-shared `的`PUBLIC`需求,因为它将用于在项目中构建目标和在下游项目中构建目标。编译时宏定义和包含目录需要在`PUBLIC`级或`INTERFACE`级目标上进行设置。它们实际上是在项目中构建目标时所需要的,其他的只与下游项目相关。此外,其中一些只有在项目安装之后才会相关联。这里使用了` $`和`$`生成器表达式。只有消息库外部的下游目标才需要这些,也就是说,只有在安装了目标之后,它们才会变得可见。在我们的例子中,应用如下: +1. 生成目标在项目构建中列出其依赖项。对UUID库的链接是 `message-shared `的`PUBLIC`需求,因为它将用于在项目中构建目标和在下游项目中构建目标。编译时宏定义和包含目录需要在`PUBLIC`级或`INTERFACE`级目标上进行设置。它们实际上是在项目中构建目标时所需要的,其他的只与下游项目相关。此外,其中一些只有在项目安装之后才会相关联。这里使用了` $`和`$`生成器表达式。只有消息库外部的下游目标才需要这些,也就是说,只有在安装了目标之后,它们才会变得可见。我们的例子中,应用如下: * 只有在项目中使用了` message-shared`库,那么`$`才会扩展成`${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR} ` * 只有在` message-shared`库在另一个构建树中,作为一个已导出目标,那么`$ `将会扩展成`${INSTALL_INCLUDEDIR}` 2. 描述目标的安装规则,包括生成文件的名称。 -3. 描述CMake生成的导出文件的安装规则`messageTargets.cmake`文件将安装到`INSTALL_CMAKEDIR`。目标导出文件的安装规则的名称空间选项,将把给定字符串前置到目标的名称中,这有助于避免来自不同项目的目标之间的潜在名称冲突。`INSTALL_CMAKEDIR`变量是在主CMakeLists.txt文件中设置的: +3. 描述CMake生成的导出文件的安装规则`messageTargets.cmake`文件将安装到`INSTALL_CMAKEDIR`。目标导出文件的安装规则的名称空间选项,将把给定字符串前置到目标的名称中,这有助于避免来自不同项目的目标之间的名称冲突。`INSTALL_CMAKEDIR`变量是在主CMakeLists.txt文件中设置的: ```cmake if(WIN32 AND NOT CYGWIN) @@ -310,13 +310,13 @@ find_package(message 1 CONFIG REQUIRED) CMakeLists.txt的最后一部分生成配置文件。包括` CMakePackageConfigHelpers.cmake`模块,分三步完成: -1. 我们调用`write_basic_package_version_file`函数生成一个版本文件包。宏的第一个参数是版本控制文件的路径:` messageConfigVersion.cmake`。版本格式为`Major.Minor.Patch`,并使用`PROJECT_VERSION`指定版本。还可以指定与库的新版本的兼容性。我们的例子中,当库具有相同的主版本时,为了保证兼容性,使用了相同的`SameMajorVersion`参数。 -2. 接下来,我们配置模板文件`messageConfig.cmake.in `,该文件位于cmake子目录中。 -3. 最后,我们为新生成的文件设置安装规则。两者都将安装在`INSTALL_CMAKEDIR`下。 +1. 调用`write_basic_package_version_file`函数生成一个版本文件包。宏的第一个参数是版本控制文件的路径:` messageConfigVersion.cmake`。版本格式为`Major.Minor.Patch`,并使用`PROJECT_VERSION`指定版本,还可以指定与库的新版本的兼容性。例子中,当库具有相同的主版本时,为了保证兼容性,使用了相同的`SameMajorVersion`参数。 +2. 接下来,配置模板文件`messageConfig.cmake.in `,该文件位于`cmake`子目录中。 +3. 最后,为新生成的文件设置安装规则。两者都将安装在`INSTALL_CMAKEDIR`下。 ## 更多信息 -消息库的客户现在非常高兴,因为他们终于可以在自己的系统上安装这个库,对自己的CMakeLists.txt进行简单的修改,就能找到消息库: +消息库的客户现在非常高兴,因为终于可以在自己的系统上安装这个库,对自己的CMakeLists.txt进行简单的修改,就能找到消息库: ```cmake find_package(message VERSION 1 REQUIRED) @@ -328,7 +328,7 @@ find_package(message VERSION 1 REQUIRED) $ cmake -Dmessage_DIR=/path/to/message/share/cmake/message .. ``` -我们示例中包含的测试,显示了如何检查目标的安装是否按照计划进行。看看tests文件夹的结构,我们注意到`use_target`子目录: +我们示例中包含的测试,显示了如何检查目标的安装是否按照计划进行。看看`tests`文件夹的结构,我们注意到`use_target`子目录: ```shell tests/ diff --git a/content/chapter10/10.4-chinese.md b/content/chapter10/10.4-chinese.md index 3226a5c..d2566a6 100644 --- a/content/chapter10/10.4-chinese.md +++ b/content/chapter10/10.4-chinese.md @@ -2,11 +2,11 @@ **NOTE**:*此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-10/recipe-04 中找到,其中有一个C++示例。该示例在CMake 3.6版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。* -我们的消息库取得了巨大的成功,许多其他程序员都使用它,并且非常满意。也希望在自己的项目中使用它,但是不确定如何正确地管理依赖关系。可以用自己的代码附带消息库的源代码,但是如果该库已经安装在系统上了应该怎么做呢?第8章,超级构建模式,展示了超级构建的场景,但是不确定如何安装这样的项目。本示例将带您了解安装超级构建的细节。 +我们的消息库取得了巨大的成功,许多其他程序员都使用它,并且非常满意。也希望在自己的项目中使用它,但是不确定如何正确地管理依赖关系。可以用自己的代码附带消息库的源代码,但是如果该库已经安装在系统上了应该怎么做呢?第8章,展示了超级构建的场景,但是不确定如何安装这样的项目。本示例将带您了解安装超级构建的安装细节。 ## 准备工作 -此示例将针对message库构建一个简单的可执行链接。项目布局如下: +此示例将针对消息库,构建一个简单的可执行链接。项目布局如下: ```shell ├── cmake @@ -23,7 +23,7 @@ └── use_message.cpp ``` -主CMakeLists.txt文件配合超级构建,`external`子目录包含处理依赖项的CMake指令。cmake子目录包含一个Python脚本和一个模板cmake脚本。这些将用于安装方面的微调,CMake脚本首先进行配置,然后调用Python脚本打印`use_message`可执行文件的`RPATH`: +主CMakeLists.txt文件配合超级构建,`external`子目录包含处理依赖项的CMake指令。`cmake`子目录包含一个Python脚本和一个模板CMake脚本。这些将用于安装方面的微调,CMake脚本首先进行配置,然后调用Python脚本打印`use_message`可执行文件的`RPATH`: ```python import shlex @@ -85,7 +85,7 @@ int main() 我们将从主CMakeLists.txt文件开始,它用来协调超级构建: -1. 与之前的示例相同。首先声明一个C++11项目,我们设置了默认安装路径、构建类型、目标的输出目录以及安装树中组件的布局: +1. 与之前的示例相同。首先声明一个C++11项目,设置了默认安装路径、构建类型、目标的输出目录,以及安装树中组件的布局: ```cmake cmake_minimum_required(VERSION 3.6 FATAL_ERROR) @@ -137,20 +137,20 @@ int main() endforeach() ``` -2. 我们设置了`EP_BASE`目录属性,这将为超构建中的子项目设置布局。所有子项目都将在`CMAKE_BINARY_DIR`的子项目文件夹下生成: +2. 设置了`EP_BASE`目录属性,这将为超构建中的子项目设置布局。所有子项目都将在`CMAKE_BINARY_DIR`的子项目文件夹下生成: ```cmake set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects) ``` -3. 然后,声明`STAGED_INSTALL_PREFIX`变量。这个变量指向构建目录下的`stage`子目录。项目将在构建期间安装在这里。这是一种沙箱安装过程,让我们有机会检查整个超级构建的布局: +3. 然后,声明`STAGED_INSTALL_PREFIX`变量。这个变量指向构建目录下的`stage`子目录,项目将在构建期间安装在这里。这是一种沙箱安装过程,让我们有机会检查整个超级构建的布局: ```cmake set(STAGED_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/stage) message(STATUS "${PROJECT_NAME} staged install: ${STAGED_INSTALL_PREFIX}") ``` -4. 我们添加`external/upstream`子目录。其中包括使用CMake指令来管理我们的上游依赖关系,在我们的例子中,就是消息库: +4. 添加`external/upstream`子目录。其中包括使用CMake指令来管理我们的上游依赖关系,在我们的例子中,就是消息库: ```cmake add_subdirectory(external/upstream) @@ -162,7 +162,7 @@ int main() include(ExternalProject) ``` -6. 我们将自己的项目作为外部项目添加,调用`ExternalProject_Add`命令。`SOURCE_DIR`用于指定源位于`src`子目录中。我们会选择适当的CMake参数来配置我们的项目。这里,使用`STAGED_INSTALL_PREFIX`作为子项目的安装目录: +6. 将自己的项目作为外部项目添加,调用`ExternalProject_Add`命令。`SOURCE_DIR`用于指定源位于`src`子目录中。我们会选择适当的CMake参数来配置我们的项目。这里,使用`STAGED_INSTALL_PREFIX`作为子项目的安装目录: ```cmake ExternalProject_Add(${PROJECT_NAME}_core @@ -186,7 +186,7 @@ int main() ) ``` -7. 现在,我们为`use_message`添加一个测试,该测试由`recipe-04_core`构建。这将运行`use_message`可执行文件的安装,即位于构建树中的安装: +7. 现在,为`use_message`添加一个测试,并由`recipe-04_core`构建。这将运行`use_message`可执行文件的安装,即位于构建树中的安装: ```cmake enable_testing() @@ -199,7 +199,7 @@ int main() ) ``` -8. 最后,我们可以声明安装规则。因为所需要的东西都已经安装在暂存区域中,我们只要将暂存区域的内容复制到安装目录即可: +8. 最后,可以声明安装规则。因为所需要的东西都已经安装在暂存区域中,我们只要将暂存区域的内容复制到安装目录即可: ```cmake install( @@ -211,7 +211,7 @@ int main() ) ``` -9. 我们使用`SCRIPT`参数声明一个附加的安装规则。CMake脚本i的` install_hook.cmake `将被执行,但只在GNU/Linux和macOS上执行。这个脚本将打印已安装的可执行文件的`RPATH`,并运行它。我们将在下一节详细地讨论这个问题: +9. 使用`SCRIPT`参数声明一个附加的安装规则。CMake脚本的` install_hook.cmake `将被执行,但只在GNU/Linux和macOS上执行。这个脚本将打印已安装的可执行文件的`RPATH`,并运行它。我们将在下一节详细地讨论这个问题: ```cmake if(UNIX) @@ -226,7 +226,7 @@ int main() `-Dmessage_DIR=${message_DIR}`已作为CMake参数传递给项目,这将正确设置消息库依赖项的位置。`message_DIR`的值在`external/upstream/message`目录下的CMakeLists.txt文件中定义。这个文件处理依赖于消息库,让我们看看是如何处理的: -1. 首先,搜索并找到包。用户可能已经在系统的某个地方安装了它,并在配置时传递了`message_DIR`选项: +1. 首先,搜索并找到包。用户可能已经在系统的某个地方安装了,并在配置时传递了`message_DIR`: ```cmake find_package(message 1 CONFIG QUIET) @@ -273,7 +273,7 @@ int main() ) ``` -4. 最后,我们将`message_DIR`目录进行设置,为指向新构建的` messageConfig.cmake`文件指明安装路径。注意,这些路径被保存到CMakeCache中: +4. 最后,将`message_DIR`目录进行设置,为指向新构建的` messageConfig.cmake`文件指明安装路径。注意,这些路径被保存到CMakeCache中: ```cmake if(WIN32 AND NOT CYGWIN) @@ -312,7 +312,7 @@ int main() ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) ``` -2. 我们尝试找到消息库。在超级构建中,将正确设置`message_DIR`: +2. 尝试找到消息库。超级构建中,正确设置`message_DIR`: ```cmake find_package(message 1 CONFIG REQUIRED) @@ -320,7 +320,7 @@ int main() message(STATUS "Found message: ${_loc} (found version ${message_VERSION})") ``` -3. 我们添加可执行目标`use_message`,该目标由`use_message.cpp`源文件,并连接`message::message-shared`目标: +3. 添加可执行目标`use_message`,该目标由`use_message.cpp`源文件创建,并连接到`message::message-shared`目标: ```cmake add_executable(use_message use_message.cpp) @@ -353,7 +353,7 @@ int main() ) ``` -5. 最后,我们为`use_message`目标设置了安装规则: +5. 最后,为`use_message`目标设置了安装规则: ```cmake install( @@ -365,15 +365,15 @@ int main() ) ``` -现在让我们看看CMake脚本模板,`install_hook.cmake.in`的内容: +现在瞧瞧CMake脚本模板`install_hook.cmake.in`的内容: -1. CMake脚本在我们的主项目范围之外执行,因此没有定义变量或目标的概念。因此,需要设置了一个变量来保存已安装的`use_message`可执行文件的完整路径。注意使用`@INSTALL_BINDIR@`,它将由`configure_file`解析: +1. CMake脚本在我们的主项目范围之外执行,因此没有定义变量或目标的概念。因此,需要设置变量来保存已安装的`use_message`可执行文件的完整路径。注意使用`@INSTALL_BINDIR@`,它将由`configure_file`解析: ```cmake set(_executable ${CMAKE_INSTALL_PREFIX}/@INSTALL_BINDIR@/use_message) ``` -2. 我们需要找到平台本机可执行工具,使用该工具打印已安装的可执行文件的`RPATH`。我们将搜索`chrpath`、`patchelf`和`otool`。当找到已安装的程序时,向用户提供有用的状态信息,并且退出搜索: +2. 需要找到平台本机可执行工具,使用该工具打印已安装的可执行文件的`RPATH`。我们将搜索`chrpath`、`patchelf`和`otool`。当找到已安装的程序时,向用户提供有用的状态信息,并且退出搜索: ```cmake set(_patcher) @@ -391,14 +391,14 @@ int main() endforeach() ``` -3. 检查`_patcher`变量是否为空,这意味着ELF补丁工具是否可用。当为空时,我们要进行的操作将会失败,所以会发出一个致命错误,提醒用户需要安装ELF补丁工具: +3. 检查`_patcher`变量是否为空,这意味着PatchELF工具是否可用。当为空时,我们要进行的操作将会失败,所以会发出一个致命错误,提醒用户需要安装PatchELF工具: ```cmake if(NOT _patcher) message(FATAL_ERROR "ELF patching tool NOT FOUND!\nPlease install one of chrpath, patchelf or otool") ``` -4. 当ELF不费那个工具找到了,则继续。我们调用`print_rpath.py`Python脚本,将`_executable`变量作为参数传递给`execute_process`: +4. 当PatchELF工具找到了,则继续。我们调用Python脚本`print_rpath.py`,将`_executable`变量作为参数传递给`execute_process`: ```cmake find_package(PythonInterp REQUIRED QUIET) @@ -413,7 +413,7 @@ int main() ) ``` -5. 我们检查`_res`变量的返回代码。如果执行成功,我们将打印`_out`变量中捕获的标准输出流。否则,打印退出前捕获的标准输出和错误流: +5. 检查`_res`变量的返回代码。如果执行成功,将打印`_out`变量中捕获的标准输出流。否则,打印退出前捕获的标准输出和错误流: ```cmake if(_res EQUAL 0) @@ -427,7 +427,7 @@ int main() endif() ``` -6. 我们再使用`execute_process`来运行已安装的`use_message`可执行目标: +6. 再使用`execute_process`来运行已安装的`use_message`可执行目标: ```cmake execute_process( @@ -439,7 +439,7 @@ int main() ) ``` -7. 最后,我们向用户报告`execute_process`的结果: +7. 最后,向用户报告`execute_process`的结果: ```cmake if(_res EQUAL 0) @@ -454,11 +454,11 @@ int main() ## 工作原理 -CMake工具箱中,超级构建是非常有用的模式。它通过将复杂的项目划分为更小、更容易管理的子项目来管理它们。此外,我们可以使用CMake作为构建项目的包管理器。CMake可以搜索我们的依赖项,如果在系统上找不到依赖项,则重新构建它们。这里需要三个CMakeLists.txt文件: +CMake工具箱中,超级构建是非常有用的模式。它通过将复杂的项目划分为更小、更容易管理的子项目来管理它们。此外,可以使用CMake作为构建项目的包管理器。CMake可以搜索依赖项,如果在系统上找不到依赖项,则重新构建它们。这里需要三个CMakeLists.txt文件: * 主CMakeLists.txt文件包含项目和依赖项共享的设置,还包括我们自己的项目(作为外部项目)。本例中,我们选择的名称为`${PROJECT_NAME}_core`;也就是`recipe-04_core`,因为项目名称`recipe-04`用于超级构建。 * 外部CMakeLists.txt文件将尝试查找上游依赖项,并在导入目标和构建目标之间进行切换,这取决于是否找到了依赖项。对于每个依赖项,最好有单独的子目录,其中包含一个CMakeLists.txt文件。 -* 最后,我们自己的项目的CMakeLists.txt文件,可以构建一个独立的CMake项目。因为在原则上,我们可以自己配置和构建它,而不需要超级构建提供的依赖关系管理工具。 +* 最后,我们项目的CMakeLists.txt文件,可以构建一个独立的CMake项目。在原则上,我们可以自己配置和构建它,而不需要超级构建提供的依赖关系管理工具。 当对消息库的依赖关系未得到满足时,将首先考虑超级构建: @@ -468,7 +468,7 @@ $ cd build $ cmake -DCMAKE_INSTALL_PREFIX=$HOME/Software/recipe-04 .. ``` -让CMake为我们查找库,这是我们得到的输出: +让CMake查找库,这是我们得到的输出: ```shell -- The CXX compiler identification is GNU 7.3.0 @@ -493,7 +493,7 @@ $ cmake -DCMAKE_INSTALL_PREFIX=$HOME/Software/recipe-04 .. 根据指令,CMake报告如下: -* 安装将分阶段进入构建树。分阶段安装是对实际安装过程进行沙箱化的一种方法。作为开发人员,这对于在运行安装命令之前检查所有库、可执行程序和文件是否安装在正确的位置非常有用。对于用户,在构建目录中给出了相同的结构。这样,即使没有运行正确的安装,我们的项目也可以立即使用。 +* 安装将分阶段进入构建树。分阶段安装是对实际安装过程进行沙箱化的一种方法。作为开发人员,这对于在运行安装命令之前检查所有库、可执行程序和文件是否安装在正确的位置非常有用。对于用户来说,可在构建目录中给出了相同的结构。这样,即使没有运行正确的安装,我们的项目也可以立即使用。 * 系统上没有找到合适的消息库。然后,CMake将运行在构建项目之前构建库所提供的命令,以满足这种依赖性。 如果库已经位于系统的已知位置,我们可以将`-Dmessage_DIR`选项传递给CMake: @@ -502,7 +502,7 @@ $ cmake -DCMAKE_INSTALL_PREFIX=$HOME/Software/recipe-04 .. $ cmake -DCMAKE_INSTALL_PREFIX=$HOME/Software/use_message -Dmessage_DIR=$HOME/Software/message/share/cmake/message .. ``` -事实上,这个库已经找到,并导入了。只对我们自己的项目进行建造操作: +事实上,这个库已经找到并导入。我们对自己的项目进行建造操作: ```shell -- The CXX compiler identification is GNU 7.3.0 @@ -526,7 +526,7 @@ $ cmake -DCMAKE_INSTALL_PREFIX=$HOME/Software/use_message -Dmessage_DIR=$HOME/So -- Build files have been written to: /home/roberto/Workspace/robertodr/cmake-cookbook/chapter-10/recipe-04/cxx-example/build ``` -项目的最终安装规则是,将分段安装文件复制到`CMAKE_INSTALL_PREFIX`: +项目的最终安装规则是,将安装文件复制到`CMAKE_INSTALL_PREFIX`: ```cmake install( @@ -540,7 +540,7 @@ install( 注意使用`.`而不是绝对路径`${CMAKE_INSTALL_PREFIX}`,这样CPack工具就可以正确理解该规则。CPack的用法将在第11章中介绍。 -`recipe-04_core`项目构建一个简单的可执行目标,该目标链接到消息动态库。正如本章前几节所讨论,为了让可执行文件正确运行,需要正确设置`RPATH`。本章的第1节展示了如何在CMake的帮助下实现这一点,同样的模式在CMakeLists.txt中被重用,用于创建`use_message`可执行文件: +`recipe-04_core`项目构建一个简单的可执行目标,该目标链接到消息动态库。正如本章前几节所讨论,为了让可执行文件正确运行,需要正确设置`RPATH`。本章的第1节展示了,如何在CMake的帮助下实现这一点,同样的模式在CMakeLists.txt中被重用,用于创建`use_message`的可执行目标: ```cmake file(RELATIVE_PATH _rel ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR} ${CMAKE_INSTALL_PREFIX}) @@ -574,13 +574,13 @@ if(UNIX) endif() ``` -这个脚本是在安装最后进行执行: +脚本是在安装最后进行执行: ```shell $ cmake --build build --target install ``` -在GNU/Linux系统上,我们将看到以下输出: +GNU/Linux系统上,我们将看到以下输出: ```shell Install the project...