# 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 留下評論——讓其他讀者知道你的想法