# 10.4 安装超级构建

**NOTE**:*此示例代码可以在* <https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-10/recipe-04> *中找到，其中有一个C++示例。该示例在CMake 3.6版(或更高版本)中是有效的，并且已经在GNU/Linux、macOS和Windows上进行过测试。*

我们的消息库取得了巨大的成功，许多其他程序员都使用它，并且非常满意。也希望在自己的项目中使用它，但是不确定如何正确地管理依赖关系。可以用自己的代码附带消息库的源代码，但是如果该库已经安装在系统上了应该怎么做呢？第8章，展示了超级构建的场景，但是不确定如何安装这样的项目。本示例将带您了解安装超级构建的安装细节。

## 准备工作

此示例将针对消息库，构建一个简单的可执行链接。项目布局如下:

```
├── cmake
│    ├── install_hook.cmake.in
│    └── print_rpath.py
├── CMakeLists.txt
├── external
│    └── upstream
│        ├── CMakeLists.txt
│        └── message
│            └── CMakeLists.txt
└── src
    ├── CMakeLists.txt
    └── use_message.cpp
```

主`CMakeLists.txt`文件配合超级构建，`external`子目录包含处理依赖项的CMake指令。`cmake`子目录包含一个Python脚本和一个模板CMake脚本。这些将用于安装方面的微调，CMake脚本首先进行配置，然后调用Python脚本打印`use_message`可执行文件的`RPATH`:

```python
import shlex
import subprocess
import sys

def main():
  patcher = sys.argv[1]
  elfobj = sys.argv[2]
  tools = {'patchelf': '--print-rpath', 'chrpath': '--list', 'otool': '-L'}
  if patcher not in tools.keys():
  raise RuntimeError('Unknown tool {}'.format(patcher))
  cmd = shlex.split('{:s} {:s} {:s}'.format(patcher, tools[patcher], elfobj))
  rpath = subprocess.run(
      cmd,
      bufsize=1,
      stdout=subprocess.PIPE,
      stderr=subprocess.PIPE,
      universal_newlines=True)
  print(rpath.stdout)

if __name__ == "__main__":
  main()
```

使用平台原生工具可以轻松地打印`RPATH`，稍后我们将在本示例中讨论这些工具。

最后，`src`子目录包含项目的`CMakeLists.txt`和源文件。`use_message.cpp`源文件包含以下内容:

```cpp
#include <cstdlib>
#include <iostream>

#ifdef USING_message
#include <message/Message.hpp>
void messaging()
{
  Message say_hello("Hello, World! From a client of yours!");
  std::cout << say_hello << std::endl;
  Message say_goodbye("Goodbye, World! From a client of yours!");
  std::cout << say_goodbye << std::endl;
}
#else
void messaging()
{
  std::cout << "Hello, World! From a client of yours!" << std::endl;
  std::cout << "Goodbye, World! From a client of yours!" << std::endl;
}
#endif

int main()
{
  messaging();
  return EXIT_SUCCESS;
}
```

## 具体实施

我们将从主`CMakeLists.txt`文件开始，它用来协调超级构建:

1. 与之前的示例相同。首先声明一个C++11项目，设置了默认安装路径、构建类型、目标的输出目录，以及安装树中组件的布局:

   ```
   cmake_minimum_required(VERSION 3.6 FATAL_ERROR)

   project(recipe-04
     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)

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

   message(STATUS "Project will be installed to ${CMAKE_INSTALL_PREFIX}")

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

   # 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")

   # 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()
   ```
2. 设置了`EP_BASE`目录属性，这将为超构建中的子项目设置布局。所有子项目都将在`CMAKE_BINARY_DIR`的子项目文件夹下生成:

   ```
   set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects)
   ```
3. 然后，声明`STAGED_INSTALL_PREFIX`变量。这个变量指向构建目录下的`stage`子目录，项目将在构建期间安装在这里。这是一种沙箱安装过程，让我们有机会检查整个超级构建的布局:

   ```
   set(STAGED_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/stage)
   message(STATUS "${PROJECT_NAME} staged install: ${STAGED_INSTALL_PREFIX}")
   ```
4. 添加`external/upstream`子目录。其中包括使用CMake指令来管理我们的上游依赖关系，在我们的例子中，就是消息库:

   ```
   add_subdirectory(external/upstream)
   ```
5. 然后，包含`ExternalProject.cmake`标准模块:

   ```
   include(ExternalProject)
   ```
6. 将自己的项目作为外部项目添加，调用`ExternalProject_Add`命令。`SOURCE_DIR`用于指定源位于`src`子目录中。我们会选择适当的CMake参数来配置我们的项目。这里，使用`STAGED_INSTALL_PREFIX`作为子项目的安装目录:

   ```
   ExternalProject_Add(${PROJECT_NAME}_core
     DEPENDS
       message_external
     SOURCE_DIR
       ${CMAKE_CURRENT_SOURCE_DIR}/src
     CMAKE_ARGS
       -DCMAKE_INSTALL_PREFIX=${STAGED_INSTALL_PREFIX}
       -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
       -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
       -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
       -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
       -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}
       -DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}
       -Dmessage_DIR=${message_DIR}
     CMAKE_CACHE_ARGS
       -DCMAKE_PREFIX_PATH:PATH=${CMAKE_PREFIX_PATH}
     BUILD_ALWAYS
       1
     )
   ```
7. 现在，为`use_message`添加一个测试，并由`recipe-04_core`构建。这将运行`use_message`可执行文件的安装，即位于构建树中的安装:

   ```
   enable_testing()

   add_test(
     NAME
         check_use_message
     COMMAND
         ${STAGED_INSTALL_PREFIX}/${INSTALL_BINDIR}/use_message
     )
   ```
8. 最后，可以声明安装规则。因为所需要的东西都已经安装在暂存区域中，我们只要将暂存区域的内容复制到安装目录即可:

   ```
   install(
     DIRECTORY
         ${STAGED_INSTALL_PREFIX}/
     DESTINATION
         .
     USE_SOURCE_PERMISSIONS
     )
   ```
9. 使用`SCRIPT`参数声明一个附加的安装规则。CMake脚本的`install_hook.cmake`将被执行，但只在GNU/Linux和macOS上执行。这个脚本将打印已安装的可执行文件的`RPATH`，并运行它。我们将在下一节详细地讨论这个问题：

   ```
   if(UNIX)
     set(PRINT_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/cmake/print_rpath.py")
     configure_file(cmake/install_hook.cmake.in install_hook.cmake @ONLY)
     install(
       SCRIPT
         ${CMAKE_CURRENT_BINARY_DIR}/install_hook.cmake
       )
   endif()
   ```

`-Dmessage_DIR=${message_DIR}`已作为CMake参数传递给项目，这将正确设置消息库依赖项的位置。`message_DIR`的值在`external/upstream/message`目录下的`CMakeLists.txt`文件中定义。这个文件处理依赖于消息库，让我们看看是如何处理的:

1. 首先，搜索并找到包。用户可能已经在系统的某个地方安装了，并在配置时传递了`message_DIR`:

   ```
   find_package(message 1 CONFIG QUIET)
   ```
2. 如果找到了消息库，我们将向用户报告目标的位置和版本，并添加一个虚拟的`message_external`目标。这里，需要虚拟目标来正确处理超构建的依赖关系:

   ```
   if(message_FOUND)
     get_property(_loc TARGET message::message-shared PROPERTY LOCATION)
     message(STATUS "Found message: ${_loc} (found version ${message_VERSION})")
     add_library(message_external INTERFACE) # dummy
   ```
3. 如果没有找到这个库，我们将把它添加为一个外部项目，从在线Git存储库下载它，然后编译它。安装路径、构建类型和安装目录布局都是由主`CMakeLists.txt`文件设置，C++编译器和标志也是如此。项目将安装到`STAGED_INSTALL_PREFIX`下，然后进行测试:

   ```
   else()
     include(ExternalProject)
     message(STATUS "Suitable message could not be located, Building message instead.")
     ExternalProject_Add(message_external
       GIT_REPOSITORY
         https://github.com/dev-cafe/message.git
       GIT_TAG
         master
       UPDATE_COMMAND
         ""
       CMAKE_ARGS
         -DCMAKE_INSTALL_PREFIX=${STAGED_INSTALL_PREFIX}
         -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
         -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
       CMAKE_CACHE_ARGS
         -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
       TEST_AFTER_INSTALL
         1
       DOWNLOAD_NO_PROGRESS
         1
       LOG_CONFIGURE
         1
       LOG_BUILD
         1
       LOG_INSTALL
         1
     )
   ```
4. 最后，将`message_DIR`目录进行设置，为指向新构建的`messageConfig.cmake`文件指明安装路径。注意，这些路径被保存到`CMakeCache`中:

   ```
     if(WIN32 AND NOT CYGWIN)
       set(DEF_message_DIR ${STAGED_INSTALL_PREFIX}/CMake)
     else()
       set(DEF_message_DIR ${STAGED_INSTALL_PREFIX}/share/cmake/message)
     endif()
     file(TO_NATIVE_PATH "${DEF_message_DIR}" DEF_message_DIR)
     set(message_DIR ${DEF_message_DIR}
       CACHE PATH "Path to internally built messageConfig.cmake" FORCE)
   endif()
   ```

我们终于准备好编译我们自己的项目，并成功地将其链接到消息库(无论是系统上已有的消息库，还是新构建的消息库)。由于这是一个超级构建，`src`子目录下的代码是一个完全独立的CMake项目:

1. 声明一个C++11项目：

   ```
   cmake_minimum_required(VERSION 3.6 FATAL_ERROR)

   project(recipe-04_core
     LANGUAGES CXX
     )

   set(CMAKE_CXX_STANDARD 11)
   set(CMAKE_CXX_EXTENSIONS OFF)
   set(CMAKE_CXX_STANDARD_REQUIRED ON)

   include(GNUInstallDirs)

   set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
     ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
   set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
     ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
   set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
     ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
   ```
2. 尝试找到消息库。超级构建中，正确设置`message_DIR`:

   ```
   find_package(message 1 CONFIG REQUIRED)
   get_property(_loc TARGET message::message-shared PROPERTY LOCATION)
   message(STATUS "Found message: ${_loc} (found version ${message_VERSION})")
   ```
3. 添加可执行目标`use_message`，该目标由`use_message.cpp`源文件创建，并连接到`message::message-shared`目标:

   ```
   add_executable(use_message use_message.cpp)

   target_link_libraries(use_message
     PUBLIC
         message::message-shared
     )
   ```
4. 为`use_message`设置目标属性。再次对`RPATH`进行设置:

   ```
   # Prepare RPATH
   file(RELATIVE_PATH _rel ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR} ${CMAKE_INSTALL_PREFIX})
   if(APPLE)
     set(_rpath "@loader_path/${_rel}")
   else()
     set(_rpath "\$ORIGIN/${_rel}")
   endif()
   file(TO_NATIVE_PATH "${_rpath}/${CMAKE_INSTALL_LIBDIR}" use_message_RPATH)

   set_target_properties(use_message
     PROPERTIES
       MACOSX_RPATH ON
       SKIP_BUILD_RPATH OFF
       BUILD_WITH_INSTALL_RPATH OFF
       INSTALL_RPATH "${use_message_RPATH}"
       INSTALL_RPATH_USE_LINK_PATH ON
     )
   ```
5. 最后，为`use_message`目标设置了安装规则:

   ```
   install(
     TARGETS
         use_message
     RUNTIME
       DESTINATION ${CMAKE_INSTALL_BINDIR}
       COMPONENT bin
     )
   ```

现在瞧瞧CMake脚本模板`install_hook.cmake.in`的内容：

1. CMake脚本在我们的主项目范围之外执行，因此没有定义变量或目标的概念。因此，需要设置变量来保存已安装的`use_message`可执行文件的完整路径。注意使用`@INSTALL_BINDIR@`，它将由`configure_file`解析：

   ```
   set(_executable ${CMAKE_INSTALL_PREFIX}/@INSTALL_BINDIR@/use_message)
   ```
2. 需要找到平台本机可执行工具，使用该工具打印已安装的可执行文件的`RPATH`。我们将搜索`chrpath`、`patchelf`和`otool`。当找到已安装的程序时，向用户提供有用的状态信息，并且退出搜索：

   ```
   set(_patcher)
   list(APPEND _patchers chrpath patchelf otool)
   foreach(p IN LISTS _patchers)
     find_program(${p}_FOUND
       NAMES
         ${p}
       )
     if(${p}_FOUND)
       set(_patcher ${p})
       message(STATUS "ELF patching tool ${_patcher} FOUND")
       break()
     endif()
   endforeach()
   ```
3. 检查`_patcher`变量是否为空，这意味着PatchELF工具是否可用。当为空时，我们要进行的操作将会失败，所以会发出一个致命错误，提醒用户需要安装PatchELF工具:

   ```
   if(NOT _patcher)
       message(FATAL_ERROR "ELF patching tool NOT FOUND!\nPlease install one of chrpath, patchelf or otool")
   ```
4. 当PatchELF工具找到了，则继续。我们调用Python脚本`print_rpath.py`，将`_executable`变量作为参数传递给`execute_process`：

   ```
     find_package(PythonInterp REQUIRED QUIET)
     execute_process(
       COMMAND
         ${PYTHON_EXECUTABLE} @PRINT_SCRIPT@ "${_patcher}"
       "${_executable}"
       RESULT_VARIABLE _res
       OUTPUT_VARIABLE _out
       ERROR_VARIABLE _err
       OUTPUT_STRIP_TRAILING_WHITESPACE
       )
   ```
5. 检查`_res`变量的返回代码。如果执行成功，将打印`_out`变量中捕获的标准输出流。否则，打印退出前捕获的标准输出和错误流:

   ```
     if(_res EQUAL 0)
       message(STATUS "RPATH for ${_executable} is ${_out}")
     else()
       message(STATUS "Something went wrong!")
       message(STATUS "Standard output from print_rpath.py: ${_out}")
       message(STATUS "Standard error from print_rpath.py: ${_err}")
       message(FATAL_ERROR "${_patcher} could NOT obtain RPATH for ${_executable}")
     endif()
   endif()
   ```
6. 再使用`execute_process`来运行已安装的`use_message`可执行目标:

   ```
   execute_process(
     COMMAND ${_executable}
     RESULT_VARIABLE _res
     OUTPUT_VARIABLE _out
     ERROR_VARIABLE _err
     OUTPUT_STRIP_TRAILING_WHITESPACE
     )
   ```
7. 最后，向用户报告`execute_process`的结果:

   ```
   if(_res EQUAL 0)
     message(STATUS "Running ${_executable}:\n ${_out}")
   else()
     message(STATUS "Something went wrong!")
     message(STATUS "Standard output from running ${_executable}:\n ${_out}")
     message(STATUS "Standard error from running ${_executable}:\n ${_err}")
     message(FATAL_ERROR "Something went wrong with ${_executable}")
   endif()
   ```

## 工作原理

CMake工具箱中，超级构建是非常有用的模式。它通过将复杂的项目划分为更小、更容易管理的子项目来管理它们。此外，可以使用CMake作为构建项目的包管理器。CMake可以搜索依赖项，如果在系统上找不到依赖项，则重新构建它们。这里需要三个`CMakeLists.txt`文件：

* 主`CMakeLists.txt`文件包含项目和依赖项共享的设置，还包括我们自己的项目(作为外部项目)。本例中，我们选择的名称为`${PROJECT_NAME}_core`；也就是`recipe-04_core`，因为项目名称`recipe-04`用于超级构建。
* 外部`CMakeLists.txt`文件将尝试查找上游依赖项，并在导入目标和构建目标之间进行切换，这取决于是否找到了依赖项。对于每个依赖项，最好有单独的子目录，其中包含一个`CMakeLists.txt`文件。
* 最后，我们项目的`CMakeLists.txt`文件，可以构建一个独立的CMake项目。在原则上，我们可以自己配置和构建它，而不需要超级构建提供的依赖关系管理工具。

当对消息库的依赖关系未得到满足时，将首先考虑超级构建:

```
$ mkdir -p build
$ cd build
$ cmake -DCMAKE_INSTALL_PREFIX=$HOME/Software/recipe-04 ..
```

让CMake查找库，这是我们得到的输出:

```
-- The CXX compiler identification is GNU 7.3.0
-- Check for working CXX compiler: /nix/store/gqg2vrcq7krqi9rrl6pphvsg81sb8pjw-gcc-wrapper-7.3.0/bin/g++
-- Check for working CXX compiler: /nix/store/gqg2vrcq7krqi9rrl6pphvsg81sb8pjw-gcc-wrapper-7.3.0/bin/g++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Project will be installed to /home/roberto/Software/recipe-04
-- Build type set to Release
-- Installing LIB components to /home/roberto/Software/recipe-04/lib64
-- Installing BIN components to /home/roberto/Software/recipe-04/bin
-- Installing INCLUDE components to /home/roberto/Software/recipe-04/include
-- Installing CMAKE components to /home/roberto/Software/recipe-04/share/cmake/recipe-04
-- recipe-04 staged install: /home/roberto/Workspace/robertodr/cmake-cookbook/chapter-10/recipe-04/cxx-example/build/stage
-- Suitable message could not be located, Building message instead.
-- Configuring done
-- Generating done
-- Build files have been written to: /home/roberto/Workspace/robertodr/cmake-cookbook/chapter-10/recipe-04/cxx-example/build
```

根据指令，CMake报告如下:

* 安装将分阶段进入构建树。分阶段安装是对实际安装过程进行沙箱化的一种方法。作为开发人员，这对于在运行安装命令之前检查所有库、可执行程序和文件是否安装在正确的位置非常有用。对于用户来说，可在构建目录中给出了相同的结构。这样，即使没有运行正确的安装，我们的项目也可以立即使用。
* 系统上没有找到合适的消息库。然后，CMake将运行在构建项目之前构建库所提供的命令，以满足这种依赖性。

如果库已经位于系统的已知位置，我们可以将`-Dmessage_DIR`选项传递给CMake:

```
$ cmake -DCMAKE_INSTALL_PREFIX=$HOME/Software/use_message -Dmessage_DIR=$HOME/Software/message/share/cmake/message ..
```

事实上，这个库已经找到并导入。我们对自己的项目进行建造操作:

```
-- The CXX compiler identification is GNU 7.3.0
-- Check for working CXX compiler: /nix/store/gqg2vrcq7krqi9rrl6pphvsg81sb8pjw-gcc-wrapper-7.3.0/bin/g++
-- Check for working CXX compiler: /nix/store/gqg2vrcq7krqi9rrl6pphvsg81sb8pjw-gcc-wrapper-7.3.0/bin/g++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Project will be installed to /home/roberto/Software/recipe-04
-- Build type set to Release
-- Installing LIB components to /home/roberto/Software/recipe-04/lib64
-- Installing BIN components to /home/roberto/Software/recipe-04/bin
-- Installing INCLUDE components to /home/roberto/Software/recipe-04/include
-- Installing CMAKE components to /home/roberto/Software/recipe-04/share/cmake/recipe-04
-- recipe-04 staged install: /home/roberto/Workspace/robertodr/cmake-cookbook/chapter-10/recipe-04/cxx-example/build/stage
-- Checking for one of the modules 'uuid'
-- Found message: /home/roberto/Software/message/lib64/libmessage.so.1 (found version 1.0.0)
-- Configuring done
-- Generating done
-- Build files have been written to: /home/roberto/Workspace/robertodr/cmake-cookbook/chapter-10/recipe-04/cxx-example/build
```

项目的最终安装规则是，将安装文件复制到`CMAKE_INSTALL_PREFIX`:

```
install(
  DIRECTORY
      ${STAGED_INSTALL_PREFIX}/
  DESTINATION
      .
  USE_SOURCE_PERMISSIONS
  )
```

注意使用`.`而不是绝对路径`${CMAKE_INSTALL_PREFIX}`，这样CPack工具就可以正确理解该规则。CPack的用法将在第11章中介绍。

`recipe-04_core`项目构建一个简单的可执行目标，该目标链接到消息动态库。正如本章前几节所讨论，为了让可执行文件正确运行，需要正确设置`RPATH`。本章的第1节展示了，如何在CMake的帮助下实现这一点，同样的模式在`CMakeLists.txt`中被重用，用于创建`use_message`的可执行目标:

```
file(RELATIVE_PATH _rel ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR} ${CMAKE_INSTALL_PREFIX})
if(APPLE)
  set(_rpath "@loader_path/${_rel}")
else()
  set(_rpath "\$ORIGIN/${_rel}")
endif()
file(TO_NATIVE_PATH "${_rpath}/${CMAKE_INSTALL_LIBDIR}" use_message_RPATH)

set_target_properties(use_message
  PROPERTIES
    MACOSX_RPATH ON
    SKIP_BUILD_RPATH OFF
    BUILD_WITH_INSTALL_RPATH OFF
    INSTALL_RPATH "${use_message_RPATH}"
    INSTALL_RPATH_USE_LINK_PATH ON
  )
```

为了检查这是否合适，可以使用本机工具打印已安装的可执行文件的`RPATH`。我们将对该工具的调用，封装到Python脚本中，并将其进一步封装到CMake脚本中。最后，使用`SCRIPT`关键字将CMake脚本作为安装规则调用:

```
if(UNIX)
  set(PRINT_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/cmake/print_rpath.py")
  configure_file(cmake/install_hook.cmake.in install_hook.cmake @ONLY)
  install(
    SCRIPT
      ${CMAKE_CURRENT_BINARY_DIR}/install_hook.cmake
    )
endif()
```

脚本是在安装最后进行执行:

```
$ cmake --build build --target install
```

GNU/Linux系统上，我们将看到以下输出:

```
Install the project...
-- Install configuration: "Release"
-- Installing: /home/roberto/Software/recipe-04/.
-- Installing: /home/roberto/Software/recipe-04/./lib64
-- Installing: /home/roberto/Software/recipe-04/./lib64/libmessage.so
-- Installing: /home/roberto/Software/recipe-04/./lib64/libmessage_s.a
-- Installing: /home/roberto/Software/recipe-04/./lib64/libmessage.so.1
-- Installing: /home/roberto/Software/recipe-04/./include
-- Installing: /home/roberto/Software/recipe-04/./include/message
-- Installing: /home/roberto/Software/recipe-04/./include/message/Message.hpp
-- Installing: /home/roberto/Software/recipe-04/./include/message/messageExport.h
-- Installing: /home/roberto/Software/recipe-04/./share
-- Installing: /home/roberto/Software/recipe-04/./share/cmake
-- Installing: /home/roberto/Software/recipe-04/./share/cmake/message
-- Installing: /home/roberto/Software/recipe-04/./share/cmake/message/messageTargets-release.cmake
-- Installing: /home/roberto/Software/recipe-04/./share/cmake/message/messageConfigVersion.cmake
-- Installing: /home/roberto/Software/recipe-04/./share/cmake/message/messageConfig.cmake
-- Installing: /home/roberto/Software/recipe-04/./share/cmake/message/messageTargets.cmake
-- Installing: /home/roberto/Software/recipe-04/./bin
-- Installing: /home/roberto/Software/recipe-04/./bin/hello-world_wAR
-- Installing: /home/roberto/Software/recipe-04/./bin/use_message
-- Installing: /home/roberto/Software/recipe-04/./bin/hello-world_wDSO
-- ELF patching tool chrpath FOUND
-- RPATH for /home/roberto/Software/recipe-04/bin/use_message is /home/roberto/Software/recipe-04/bin/use_message: RUNPATH=$ORIGIN/../lib64:/home/roberto/Workspace/robertodr/cmake-cookbook/chapter-10/recipe-04/cxx-example/build/stage/lib64:/nix/store/di389pfcw2krnmh8nmkn55d1rnzmba37-CMake-Cookbook/lib64:/nix/store/di389pfcw2krnmh8nmkn55d1rnzmba37-CMake-Cookbook/lib:/nix/store/mjs2b8mmid86lvbzibzdlz8w5yrjgcnf-util-linux-2.31.1/lib:/nix/store/2kcrj1ksd2a14bm5sky182fv2xwfhfap-glibc-2.26-131/lib:/nix/store/4zd34747fz0ggzzasy4icgn3lmy89pra-gcc-7.3.0-lib/lib
-- Running /home/roberto/Software/recipe-04/bin/use_message:
This is my very nice message:
Hello, World! From a client of yours!
...and here is its UUID: a8014bf7-5dfa-45e2-8408-12e9a5941825
This is my very nice message:
Goodbye, World! From a client of yours!
...and here is its UUID: ac971ef4-7606-460f-9144-1ad96f713647
```

**NOTE**:*我们建议使用的工具是PatchELF (*<https://nixos.org/patchelf.html> *)、chrpath (*<https://linux.die.net/man/1/chrpath> *)和otool (*<http://www.manpagez.com/man/1/otool/> *)。第一种方法适用于GNU/Linux和macOS，而chrpath和otool分别适用于GNU/Linux和macOS。*


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chenxiaowei.gitbook.io/cmake-cookbook/10.0-chinese/10.4-chinese.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
