匯入與匯出指南

簡介

在本指南中,我們將介紹 IMPORTED 目標的概念,並示範如何將磁碟上現有的執行檔或函式庫檔案匯入到 CMake 專案中。然後,我們將展示 CMake 如何支援從一個基於 CMake 的專案匯出目標,並將它們匯入到另一個專案中。最後,我們將示範如何封裝一個帶有組態檔的專案,以便輕鬆整合到其他 CMake 專案中。本指南和完整的範例原始碼可以在 CMake 原始碼樹的 Help/guide/importing-exporting 目錄中找到。

匯入目標

IMPORTED 目標用於將 CMake 專案外部的檔案轉換為專案內部的邏輯目標。IMPORTED 目標是使用 add_executable()add_library() 命令的 IMPORTED 選項建立的。不會為 IMPORTED 目標產生建置檔案。一旦匯入,IMPORTED 目標可以像專案中的任何其他目標一樣被引用,並為外部執行檔和函式庫提供方便、彈性的參考。

預設情況下,IMPORTED 目標名稱的作用域在其建立的目錄及其下級目錄中。我們可以使用 GLOBAL 選項來擴展可見性,以便該目標在建置系統中全域可存取。

關於 IMPORTED 目標的詳細資訊,可以通過設定名稱以 IMPORTED_INTERFACE_ 開頭的屬性來指定。例如,IMPORTED_LOCATION 包含磁碟上目標的完整路徑。

匯入執行檔

首先,我們將逐步完成一個簡單的範例,該範例建立一個 IMPORTED 執行檔目標,然後從 add_custom_command() 命令中引用它。

我們需要進行一些設定才能開始。我們想要建立一個執行檔,執行時會在目前目錄中建立一個基本的 main.cc 檔案。這個專案的細節並不重要。導航到 Help/guide/importing-exporting/MyExe,建立一個建置目錄,執行 cmake 並建置和安裝專案。

$ cd Help/guide/importing-exporting/MyExe
$ mkdir build
$ cd build
$ cmake ..
$ cmake --build .
$ cmake --install . --prefix <install location>
$ <install location>/myexe
$ ls
[...] main.cc [...]

現在我們可以將這個執行檔匯入到另一個 CMake 專案中。本節的原始碼可在 Help/guide/importing-exporting/Importing 中找到。在 CMakeLists 檔案中,使用 add_executable() 命令建立一個名為 myexe 的新目標。使用 IMPORTED 選項告訴 CMake 這個目標引用一個位於專案外部的執行檔。不會產生建置它的規則,並且 IMPORTED 目標屬性將設定為 true

add_executable(myexe IMPORTED)

接下來,使用 set_property() 命令設定目標的 IMPORTED_LOCATION 屬性。這將告訴 CMake 目標在磁碟上的位置。位置可能需要調整為上一步中指定的 <安裝位置>

set_property(TARGET myexe PROPERTY
             IMPORTED_LOCATION "../InstallMyExe/bin/myexe")

現在我們可以像引用專案中建置的任何目標一樣引用這個 IMPORTED 目標。在這個例子中,假設我們想在專案中使用產生的原始碼檔案。在 add_custom_command() 命令中使用 IMPORTED 目標

add_custom_command(OUTPUT main.cc COMMAND myexe)

由於 COMMAND 指定了一個執行檔目標名稱,它將自動被上面 IMPORTED_LOCATION 屬性給定的執行檔位置替換。

最後,使用 add_custom_command() 的輸出

add_executable(mynewexe main.cc)

匯入函式庫

以類似的方式,可以通過 IMPORTED 目標存取來自其他專案的函式庫。

注意:本節範例的完整原始碼未提供,留給讀者作為練習。

在 CMakeLists 檔案中,新增一個 IMPORTED 函式庫並指定其在磁碟上的位置

add_library(foo STATIC IMPORTED)
set_property(TARGET foo PROPERTY
             IMPORTED_LOCATION "/path/to/libfoo.a")

然後在我們的專案中使用 IMPORTED 函式庫

add_executable(myexe src1.c src2.c)
target_link_libraries(myexe PRIVATE foo)

在 Windows 上,可以將 .dll 及其 .lib 匯入函式庫一起匯入

add_library(bar SHARED IMPORTED)
set_property(TARGET bar PROPERTY
             IMPORTED_LOCATION "c:/path/to/bar.dll")
set_property(TARGET bar PROPERTY
             IMPORTED_IMPLIB "c:/path/to/bar.lib")
add_executable(myexe src1.c src2.c)
target_link_libraries(myexe PRIVATE bar)

具有多個組態的函式庫可以使用單個目標匯入

find_library(math_REL NAMES m)
find_library(math_DBG NAMES md)
add_library(math STATIC IMPORTED GLOBAL)
set_target_properties(math PROPERTIES
  IMPORTED_LOCATION "${math_REL}"
  IMPORTED_LOCATION_DEBUG "${math_DBG}"
  IMPORTED_CONFIGURATIONS "RELEASE;DEBUG"
)
add_executable(myexe src1.c src2.c)
target_link_libraries(myexe PRIVATE math)

產生的建置系統將在發布組態中建置時將 myexe 連結到 m.lib,在偵錯組態中建置時將其連結到 md.lib

匯出目標

雖然 IMPORTED 目標本身很有用,但它們仍然要求匯入它們的專案知道目標檔案在磁碟上的位置。IMPORTED 目標的真正威力在於,提供目標檔案的專案也提供了一個 CMake 檔案來幫助匯入它們。可以設定一個專案來產生必要的資訊,以便其他 CMake 專案可以輕鬆地使用它,無論是從建置目錄、本地安裝還是封裝時。

在剩餘的章節中,我們將逐步完成一組範例專案。第一個專案將建立和安裝一個函式庫以及相應的 CMake 組態和套件檔案。第二個專案將使用產生的套件。

首先,讓我們看看 Help/guide/importing-exporting/MathFunctions 目錄中的 MathFunctions 專案。這裡我們有一個標頭檔 MathFunctions.h,它宣告了一個 sqrt 函數

#pragma once

namespace MathFunctions {
double sqrt(double x);
}

以及一個對應的原始碼檔案 MathFunctions.cxx

#include "MathFunctions.h"

#include <cmath>

namespace MathFunctions {
double sqrt(double x)
{
  return std::sqrt(x);
}
}

不必太擔心 C++ 檔案的細節,它們只是作為一個簡單的範例,將在許多建置系統上編譯和執行。

現在我們可以為 MathFunctions 專案建立一個 CMakeLists.txt 檔案。首先指定 cmake_minimum_required() 版本和 project() 名稱

cmake_minimum_required(VERSION 3.15)
project(MathFunctions)

# make cache variables for install destinations
include(GNUInstallDirs)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

包含 GNUInstallDirs 模組是為了使專案能夠靈活地安裝到不同的平台佈局中,方法是將目錄作為快取變數提供。

使用 add_library() 命令建立一個名為 MathFunctions 的函式庫

add_library(MathFunctions STATIC MathFunctions.cxx)

然後使用 target_include_directories() 命令指定目標的包含目錄

target_include_directories(MathFunctions
                           PUBLIC
                           "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
                           "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)

我們需要告訴 CMake,我們想要使用不同的包含目錄,具體取決於我們是正在建置函式庫還是從已安裝的位置使用它。如果我們不這樣做,當 CMake 建立匯出資訊時,它將匯出一個特定於當前建置目錄的路徑,並且對於其他專案將無效。我們可以使用 產生器 表達式 來指定,如果我們正在建置函式庫,則包含當前原始碼目錄。否則,當安裝時,包含 include 目錄。有關更多詳細資訊,請參閱 建立可重定位套件 章節。

install(TARGETS)install(EXPORT) 命令協同工作,以安裝目標(在本例中為函式庫)和一個 CMake 檔案,該檔案旨在使其易於將目標匯入到另一個 CMake 專案中。

首先,在 install(TARGETS) 命令中,我們將指定目標、EXPORT 名稱和目標安裝位置的目的地。

install(TARGETS MathFunctions
        EXPORT MathFunctionsTargets
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
        INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

在這裡,EXPORT 選項告訴 CMake 建立一個名為 MathFunctionsTargets 的匯出。IMPORTED 目標已設定適當的屬性,以定義其 使用需求,例如 INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONS 和其他相關的內建 INTERFACE_ 屬性。在 COMPATIBLE_INTERFACE_STRING 和其他 相容介面屬性 中列出的使用者定義屬性的 INTERFACE 變體也會傳播到產生的 IMPORTED 目標。例如,在本例中,IMPORTED 目標的 INTERFACE_INCLUDE_DIRECTORIES 屬性將填充 INCLUDES DESTINATION 屬性指定的目錄。由於給出了相對路徑,因此將其視為相對於 CMAKE_INSTALL_PREFIX

請注意,我們尚未要求 CMake 安裝匯出。

我們不想忘記使用 install(FILES) 命令安裝 MathFunctions.h 標頭檔。標頭檔應安裝到 target_include_directories() 命令在上面指定的 include 目錄中。

install(FILES MathFunctions.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

現在 MathFunctions 函式庫和標頭檔已安裝,我們還需要明確安裝 MathFunctionsTargets 匯出詳細資訊。使用 install(EXPORT) 命令匯出 install(TARGETS) 命令中定義的 MathFunctionsTargets 中的目標。

install(EXPORT MathFunctionsTargets
        FILE MathFunctionsTargets.cmake
        NAMESPACE MathFunctions::
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
)

此命令產生 MathFunctionsTargets.cmake 檔案,並安排將其安裝到 ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions。該檔案包含適合下游使用的程式碼,用於從安裝樹匯入安裝命令中列出的所有目標。

NAMESPACE 選項將 MathFunctions:: 前置於寫入匯出檔案的目標名稱。雙冒號的約定給了 CMake 一個提示,表明該名稱是 IMPORTED 目標,當下游專案使用它時。這樣,如果未找到提供它的套件,CMake 可以發出診斷訊息。

產生的匯出檔案包含建立 IMPORTED 函式庫的程式碼。

# Create imported target MathFunctions::MathFunctions
add_library(MathFunctions::MathFunctions STATIC IMPORTED)

set_target_properties(MathFunctions::MathFunctions PROPERTIES
  INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
)

此程式碼與我們在 匯入函式庫 章節中手動建立的範例非常相似。請注意,${_IMPORT_PREFIX} 是相對於檔案位置計算的。

外部專案可以使用 include() 命令載入此檔案,並從安裝樹引用 MathFunctions 函式庫,就好像它是在自己的樹中建置的一樣。例如

1 include(GNUInstallDirs)
2 include(${INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions/MathFunctionTargets.cmake)
3 add_executable(myexe src1.c src2.c)
4 target_link_libraries(myexe PRIVATE MathFunctions::MathFunctions)

第 2 行載入目標 CMake 檔案。雖然我們只匯出了一個目標,但此檔案可以匯入任意數量的目標。它們的位置是相對於檔案位置計算的,以便可以輕鬆移動安裝樹。第 4 行引用匯入的 MathFunctions 函式庫。產生的建置系統將連結到安裝位置的函式庫。

執行檔也可以使用相同的過程匯出和匯入。

任意數量的目標安裝可以與相同的匯出名稱關聯。匯出名稱被認為是全域的,因此任何目錄都可以貢獻目標安裝。install(EXPORT) 命令只需要調用一次即可安裝引用所有目標的檔案。下面是一個範例,說明如何將多個匯出合併到一個匯出檔案中,即使它們位於專案的不同子目錄中。

# A/CMakeLists.txt
add_executable(myexe src1.c)
install(TARGETS myexe DESTINATION lib/myproj
        EXPORT myproj-targets)

# B/CMakeLists.txt
add_library(foo STATIC foo1.c)
install(TARGETS foo DESTINATION lib EXPORTS myproj-targets)

# Top CMakeLists.txt
add_subdirectory (A)
add_subdirectory (B)
install(EXPORT myproj-targets DESTINATION lib/myproj)

建立套件

至此,MathFunctions 專案正在匯出其他專案使用所需的目標資訊。我們可以透過產生組態檔,使 CMake find_package() 命令可以找到我們的專案,從而使這個專案更容易被其他專案使用。

首先,我們需要在 CMakeLists.txt 檔案中進行一些新增。首先,包含 CMakePackageConfigHelpers 模組,以存取一些用於建立組態檔的輔助函數。

include(CMakePackageConfigHelpers)

然後,我們將建立一個套件組態檔和一個套件版本檔。

建立套件組態檔

使用 configure_package_config_file() 命令(由 CMakePackageConfigHelpers 提供)來產生套件組態檔。請注意,應使用此命令而不是普通的 configure_file() 命令。它有助於通過避免在已安裝的組態檔中硬編碼路徑來確保產生的套件是可重定位的。給定 INSTALL_DESTINATION 的路徑必須是將安裝 MathFunctionsConfig.cmake 檔案的目的地。我們將在下一節中檢查套件組態檔的內容。

configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
  INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
)

使用 install(FILES) 命令安裝產生的組態檔。MathFunctionsConfigVersion.cmakeMathFunctionsConfig.cmake 都安裝到相同的位置,完成套件的建立。

install(FILES
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
)

現在我們需要建立套件組態檔本身。在本例中,Config.cmake.in 檔案非常簡單,但足以讓下游使用 IMPORTED 目標。

@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake")

check_required_components(MathFunctions)

檔案的第一行僅包含字串 @PACKAGE_INIT@。這會在檔案配置時展開,並允許使用以 PACKAGE_ 為前綴的可重定位路徑。它還提供了 set_and_check()check_required_components() 巨集。

check_required_components 輔助巨集通過檢查所有必需組件的 <Package>_<Component>_FOUND 變數來確保已找到所有請求的非可選組件。即使套件沒有任何組件,也應在套件組態檔的末尾調用此巨集。這樣,CMake 可以確保下游專案沒有指定任何不存在的組件。如果 check_required_components 失敗,則 <Package>_FOUND 變數將設定為 FALSE,並且該套件被視為未找到。

set_and_check() 巨集應在組態檔中使用,而不是普通的 set() 命令來設定目錄和檔案位置。如果引用的檔案或目錄不存在,則巨集將失敗。

如果應由 MathFunctions 套件提供任何巨集,則它們應位於單獨的檔案中,該檔案安裝到與 MathFunctionsConfig.cmake 檔案相同的位置,並從那裡包含。

套件的所有必需依賴項也必須在套件組態檔中找到。 假設我們的專案中需要 Stats 函式庫。在 CMakeLists 檔案中,我們將新增

find_package(Stats 2.6.4 REQUIRED)
target_link_libraries(MathFunctions PUBLIC Stats::Types)

由於 Stats::Types 目標是 MathFunctionsPUBLIC 依賴項,因此下游也必須找到 Stats 套件並連結到 Stats::Types 函式庫。應在組態檔中找到 Stats 套件以確保這一點。

include(CMakeFindDependencyMacro)
find_dependency(Stats 2.6.4)

來自 CMakeFindDependencyMacro 模組的 find_dependency 巨集通過傳播套件是否為 REQUIREDQUIET 等來提供幫助。find_dependency 巨集還將 MathFunctions_FOUND 設定為 False,如果未找到依賴項,則會顯示診斷訊息,指出 MathFunctions 套件在沒有 Stats 套件的情況下無法使用。

練習: 將一個必需的函式庫新增到 MathFunctions 專案。

建立套件版本檔

CMakePackageConfigHelpers 模組提供了 write_basic_package_version_file() 命令,用於建立簡單的套件版本檔。當調用 find_package() 時,CMake 會讀取此檔案以確定與請求版本的相容性,並設定一些特定於版本的變數,例如 <PackageName>_VERSION<PackageName>_VERSION_MAJOR<PackageName>_VERSION_MINOR 等。有關更多詳細資訊,請參閱 cmake-packages 文件。

set(version 3.4.1)

set_property(TARGET MathFunctions PROPERTY VERSION ${version})
set_property(TARGET MathFunctions PROPERTY SOVERSION 3)
set_property(TARGET MathFunctions PROPERTY
  INTERFACE_MathFunctions_MAJOR_VERSION 3)
set_property(TARGET MathFunctions APPEND PROPERTY
  COMPATIBLE_INTERFACE_STRING MathFunctions_MAJOR_VERSION
)

# generate the version file for the config file
write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
  VERSION "${version}"
  COMPATIBILITY AnyNewerVersion
)

在我們的範例中,MathFunctions_MAJOR_VERSION 被定義為 COMPATIBLE_INTERFACE_STRING,這表示它在任何依賴項的相依性之間必須相容。透過在這個版本和下一個版本的 MathFunctions 中設定這個自訂定義的使用者屬性,如果嘗試將版本 3 與版本 4 一起使用,cmake 將發出診斷訊息。如果套件的不同主要版本設計為不相容,套件可以選擇採用這種模式。

從建置樹匯出目標

通常,專案在被外部專案使用之前會先建置和安裝。然而,在某些情況下,希望直接從建置樹匯出目標。然後,外部專案可以使用這些目標,該專案參考建置樹,而無需進行安裝。export() 命令用於產生一個檔案,該檔案從專案建置樹匯出目標。

如果我們希望我們的範例專案也可以從建置目錄中使用,我們只需要在 CMakeLists.txt 中加入以下內容

export(EXPORT MathFunctionsTargets
       FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/MathFunctionsTargets.cmake"
       NAMESPACE MathFunctions::
)

在這裡,我們使用 export() 命令來為建置樹產生匯出目標。在這種情況下,我們將在建置目錄的 cmake 子目錄中建立一個名為 MathFunctionsTargets.cmake 的檔案。產生的檔案包含匯入目標所需的程式碼,並且可以由知道專案建置樹的外部專案載入。這個檔案是建置樹特定的,並且不可重新定位

可以建立一個合適的套件組態檔案和套件版本檔案,為建置樹定義一個套件,該套件可以在不安裝的情況下使用。建置樹的使用者可以簡單地確保 CMAKE_PREFIX_PATH 包含建置目錄,或者在快取中將 MathFunctions_DIR 設定為 <build_dir>/MathFunctions

此功能的一個範例應用是在跨編譯時在主機平台上建置可執行檔。包含可執行檔的專案可以在主機平台上建置,然後為另一個平台進行跨編譯的專案可以載入它。

建置和安裝套件

此時,我們已經為我們的專案產生了一個可重新定位的 CMake 組態,該組態可以在專案安裝後使用。讓我們嘗試建置 MathFunctions 專案

mkdir MathFunctions_build
cd MathFunctions_build
cmake ../MathFunctions
cmake --build .

在建置目錄中,請注意檔案 MathFunctionsTargets.cmake 已在 cmake 子目錄中建立。

現在安裝專案

$ cmake --install . --prefix "/home/myuser/installdir"

建立可重新定位的套件

install(EXPORT) 建立的套件設計為可重新定位,使用相對於套件本身位置的路徑。它們不得參考套件建置所在機器上的絕對路徑,這些路徑在套件可能安裝的機器上將不存在。

在為 EXPORT 定義目標的介面時,請記住,包含目錄應指定為相對於 CMAKE_INSTALL_PREFIX 的相對路徑,但不應明確包含 CMAKE_INSTALL_PREFIX

target_include_directories(tgt INTERFACE
  # Wrong, not relocatable:
  $<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include/TgtName>
)

target_include_directories(tgt INTERFACE
  # Ok, relocatable:
  $<INSTALL_INTERFACE:include/TgtName>
)

$<INSTALL_PREFIX> 產生器運算式可以用作安裝前綴的佔位符,而不會導致不可重新定位的套件。如果使用複雜的產生器運算式,這是必要的

target_include_directories(tgt INTERFACE
  # Ok, relocatable:
  $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include/TgtName>
)

這也適用於參考外部相依性的路徑。不建議使用可能包含路徑的任何屬性填充,例如 INTERFACE_INCLUDE_DIRECTORIESINTERFACE_LINK_LIBRARIES,這些路徑與相依性相關。例如,以下程式碼可能不適用於可重新定位的套件

target_link_libraries(MathFunctions INTERFACE
  ${Foo_LIBRARIES} ${Bar_LIBRARIES}
  )
target_include_directories(MathFunctions INTERFACE
  "$<INSTALL_INTERFACE:${Foo_INCLUDE_DIRS};${Bar_INCLUDE_DIRS}>"
  )

被參考的變數可能包含程式庫和包含目錄的絕對路徑,如同在建立套件的機器上找到的那樣。這將建立一個套件,其中包含硬編碼的相依性路徑,不適合重新定位。

理想情況下,應該透過它們自己的 匯入目標 來使用這些相依性,這些目標具有自己的 IMPORTED_LOCATION 和使用需求屬性,例如 INTERFACE_INCLUDE_DIRECTORIES 已適當填充。然後,這些匯入的目標可以與 target_link_libraries() 命令一起用於 MathFunctions

target_link_libraries(MathFunctions INTERFACE Foo::Foo Bar::Bar)

透過這種方法,套件僅透過 匯入目標 的名稱來參考其外部相依性。當消費者使用已安裝的套件時,消費者將執行適當的 find_package() 命令(透過上面描述的 find_dependency 巨集)來尋找相依性,並在他們自己的機器上使用適當的路徑填充匯入的目標。

使用套件組態檔案

現在我們準備好建立一個專案來使用已安裝的 MathFunctions 程式庫。在本節中,我們將使用來自 Help\guide\importing-exporting\Downstream 的原始碼。在這個目錄中,有一個名為 main.cc 的原始檔,它使用 MathFunctions 程式庫來計算給定數字的平方根,然後印出結果

// A simple program that outputs the square root of a number
#include <iostream>
#include <string>

#include "MathFunctions.h"

int main(int argc, char* argv[])
{
  if (argc < 2) {
    std::cout << "Usage: " << argv[0] << " number" << std::endl;
    return 1;
  }

  // convert input to double
  double const inputValue = std::stod(argv[1]);

  // calculate square root
  double const sqrt = MathFunctions::sqrt(inputValue);
  std::cout << "The square root of " << inputValue << " is " << sqrt
            << std::endl;

  return 0;
}

和之前一樣,我們將從 CMakeLists.txt 檔案中的 cmake_minimum_required()project() 命令開始。對於這個專案,我們還將指定 C++ 標準。

cmake_minimum_required(VERSION 3.15)
project(Downstream)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

我們可以使用 find_package() 命令

find_package(MathFunctions 3.4.1 EXACT)

建立可執行檔

add_executable(myexe main.cc)

並連結到 MathFunctions 程式庫

target_link_libraries(myexe PRIVATE MathFunctions::MathFunctions)

就這樣!現在讓我們嘗試建置 Downstream 專案。

mkdir Downstream_build
cd Downstream_build
cmake ../Downstream
cmake --build .

在 CMake 組態期間可能出現警告

CMake Warning at CMakeLists.txt:4 (find_package):
  By not providing "FindMathFunctions.cmake" in CMAKE_MODULE_PATH this
  project has asked CMake to find a package configuration file provided by
  "MathFunctions", but CMake did not find one.

  Could not find a package configuration file provided by "MathFunctions"
  with any of the following names:

    MathFunctionsConfig.cmake
    mathfunctions-config.cmake

  Add the installation prefix of "MathFunctions" to CMAKE_PREFIX_PATH or set
  "MathFunctions_DIR" to a directory containing one of the above files.  If
  "MathFunctions" provides a separate development package or SDK, be sure it
  has been installed.

CMAKE_PREFIX_PATH 設定為先前安裝 MathFunctions 的位置,然後重試。確保新建立的可執行檔如預期般執行。

新增組件

讓我們編輯 MathFunctions 專案以使用組件。本節的原始碼可以在 Help\guide\importing-exporting\MathFunctionsComponents 中找到。這個專案的 CMakeLists 檔案新增了兩個子目錄:AdditionSquareRoot

cmake_minimum_required(VERSION 3.15)
project(MathFunctionsComponents)

# make cache variables for install destinations
include(GNUInstallDirs)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

add_subdirectory(Addition)
add_subdirectory(SquareRoot)

產生並安裝套件組態和套件版本檔案

include(CMakePackageConfigHelpers)

# set version
set(version 3.4.1)

# generate the version file for the config file
write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
  VERSION "${version}"
  COMPATIBILITY AnyNewerVersion
)

# create config file
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
  INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
  NO_CHECK_REQUIRED_COMPONENTS_MACRO
)

# install config files
install(FILES
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
)

如果在下游使用 find_package() 時指定了 COMPONENTS,它們會列在 <PackageName>_FIND_COMPONENTS 變數中。我們可以使用這個變數來驗證所有必要的組件目標都包含在 Config.cmake.in 中。同時,這個函數將充當自訂的 check_required_components 巨集,以確保下游只嘗試使用支援的組件。

@PACKAGE_INIT@

set(_MathFunctions_supported_components Addition SquareRoot)

foreach(_comp ${MathFunctions_FIND_COMPONENTS})
  if (NOT _comp IN_LIST _MathFunctions_supported_components)
    set(MathFunctions_FOUND False)
    set(MathFunctions_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}")
  endif()
  include("${CMAKE_CURRENT_LIST_DIR}/MathFunctions${_comp}Targets.cmake")
endforeach()

在這裡,MathFunctions_NOT_FOUND_MESSAGE 被設定為一個診斷訊息,指出由於指定了無效的組件而找不到套件。可以為 _FOUND 變數設定為 False 的任何情況設定這個訊息變數,並且它將顯示給使用者。

AdditionSquareRoot 目錄是相似的。讓我們看看其中一個 CMakeLists 檔案

# create library
add_library(SquareRoot STATIC SquareRoot.cxx)

add_library(MathFunctions::SquareRoot ALIAS SquareRoot)

# add include directories
target_include_directories(SquareRoot
                           PUBLIC
                           "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
                           "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)

# install the target and create export-set
install(TARGETS SquareRoot
        EXPORT SquareRootTargets
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
        INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

# install header file
install(FILES SquareRoot.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

# generate and install export file
install(EXPORT SquareRootTargets
        FILE MathFunctionsSquareRootTargets.cmake
        NAMESPACE MathFunctions::
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
)

現在我們可以按照前面章節中描述的方式建置專案。為了測試使用這個套件,我們可以使用 Help\guide\importing-exporting\DownstreamComponents 中的專案。與之前的 Downstream 專案相比,有兩個不同之處。首先,我們需要尋找套件組件。將 find_package 行從

find_package(MathFunctions 3.4.1 EXACT)

改為

find_package(MathFunctions 3.4 COMPONENTS Addition SquareRoot)

以及 target_link_libraries 行從

target_link_libraries(myexe PRIVATE MathFunctions::MathFunctions)

改為

target_link_libraries(myexe PRIVATE MathFunctions::Addition MathFunctions::SquareRoot)

main.cc 中,將 #include MathFunctions.h 替換為


#include "Addition.h"
#include "SquareRoot.h"

最後,使用 Addition 程式庫

  double const sum = MathFunctions::add(inputValue, inputValue);
  std::cout << inputValue << " + " << inputValue << " = " << sum << std::endl;

建置 Downstream 專案並確認它可以找到並使用套件組件。