# 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章,展示了超级构建的场景,但是不确定如何安装这样的项目。本示例将带您了解安装超级构建的安装细节。
## 准备工作
此示例将针对消息库,构建一个简单的可执行链接。项目布局如下:
```shell
├── 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`源文件包含以下内容:
```c++
#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
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`的子项目文件夹下生成:
```cmake
set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects)
```
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指令来管理我们的上游依赖关系,在我们的例子中,就是消息库:
```cmake
add_subdirectory(external/upstream)
```
5. 然后,包含` ExternalProject.cmake`标准模块:
```cmake
include(ExternalProject)
```
6. 将自己的项目作为外部项目添加,调用`ExternalProject_Add`命令。`SOURCE_DIR`用于指定源位于`src`子目录中。我们会选择适当的CMake参数来配置我们的项目。这里,使用`STAGED_INSTALL_PREFIX`作为子项目的安装目录:
```cmake
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`可执行文件的安装,即位于构建树中的安装:
```cmake
enable_testing()
add_test(
NAME
check_use_message
COMMAND
${STAGED_INSTALL_PREFIX}/${INSTALL_BINDIR}/use_message
)
```
8. 最后,可以声明安装规则。因为所需要的东西都已经安装在暂存区域中,我们只要将暂存区域的内容复制到安装目录即可:
```cmake
install(
DIRECTORY
${STAGED_INSTALL_PREFIX}/
DESTINATION
.
USE_SOURCE_PERMISSIONS
)
```
9. 使用`SCRIPT`参数声明一个附加的安装规则。CMake脚本的` install_hook.cmake `将被执行,但只在GNU/Linux和macOS上执行。这个脚本将打印已安装的可执行文件的`RPATH`,并运行它。我们将在下一节详细地讨论这个问题:
```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()
```
`-Dmessage_DIR=${message_DIR}`已作为CMake参数传递给项目,这将正确设置消息库依赖项的位置。`message_DIR`的值在`external/upstream/message`目录下的`CMakeLists.txt`文件中定义。这个文件处理依赖于消息库,让我们看看是如何处理的:
1. 首先,搜索并找到包。用户可能已经在系统的某个地方安装了,并在配置时传递了`message_DIR`:
```cmake
find_package(message 1 CONFIG QUIET)
```
2. 如果找到了消息库,我们将向用户报告目标的位置和版本,并添加一个虚拟的`message_external`目标。这里,需要虚拟目标来正确处理超构建的依赖关系:
```cmake
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`下,然后进行测试:
```cmake
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`中:
```cmake
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
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`:
```cmake
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`目标:
```cmake
add_executable(use_message use_message.cpp)
target_link_libraries(use_message
PUBLIC
message::message-shared
)
```
4. 为`use_message`设置目标属性。再次对`RPATH`进行设置:
```cmake
# 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`目标设置了安装规则:
```cmake
install(
TARGETS
use_message
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT bin
)
```
现在瞧瞧CMake脚本模板`install_hook.cmake.in`的内容:
1. CMake脚本在我们的主项目范围之外执行,因此没有定义变量或目标的概念。因此,需要设置变量来保存已安装的`use_message`可执行文件的完整路径。注意使用`@INSTALL_BINDIR@`,它将由`configure_file`解析:
```cmake
set(_executable ${CMAKE_INSTALL_PREFIX}/@INSTALL_BINDIR@/use_message)
```
2. 需要找到平台本机可执行工具,使用该工具打印已安装的可执行文件的`RPATH`。我们将搜索`chrpath`、`patchelf`和`otool`。当找到已安装的程序时,向用户提供有用的状态信息,并且退出搜索:
```cmake
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工具:
```cmake
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`:
```cmake
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`变量中捕获的标准输出流。否则,打印退出前捕获的标准输出和错误流:
```cmake
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`可执行目标:
```cmake
execute_process(
COMMAND ${_executable}
RESULT_VARIABLE _res
OUTPUT_VARIABLE _out
ERROR_VARIABLE _err
OUTPUT_STRIP_TRAILING_WHITESPACE
)
```
7. 最后,向用户报告`execute_process`的结果:
```cmake
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项目。在原则上,我们可以自己配置和构建它,而不需要超级构建提供的依赖关系管理工具。
当对消息库的依赖关系未得到满足时,将首先考虑超级构建:
```shell
$ mkdir -p build
$ cd build
$ cmake -DCMAKE_INSTALL_PREFIX=$HOME/Software/recipe-04 ..
```
让CMake查找库,这是我们得到的输出:
```shell
-- 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:
```shell
$ 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
-- 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`:
```cmake
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`的可执行目标:
```cmake
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脚本作为安装规则调用:
```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()
```
脚本是在安装最后进行执行:
```shell
$ cmake --build build --target install
```
GNU/Linux系统上,我们将看到以下输出:
```shell
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。*
- 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 留下评论——让其他读者知道你的想法