匯入與匯出指南

簡介

在本指南中,我們將介紹 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 目標在磁碟上的位置。位置可能需要調整為上一步中指定的 <install location>

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 建立匯出資訊時,它將匯出一個特定於目前建置目錄的路徑,並且對於其他專案將無效。我們可以使用 generator expressions 來指定如果我們正在建置函式庫,則包含目前的原始程式目錄。否則,當安裝時,包含 include 目錄。有關詳細資訊,請參閱 建立可重定位的套件 章節。

install(TARGETS)install(EXPORT) 命令一起運作,以安裝目標(在我們的例子中是函式庫)和一個 CMake 檔案,該檔案旨在讓將目標匯入到另一個 CMake 專案變得容易。

首先,在 install(TARGETS) 命令中,我們將指定目標、EXPORT 名稱以及告訴 CMake 將目標安裝到哪裡的目標位置。

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) 命令匯出 MathFunctionsTargets 中的目標,如 install(TARGETS) 命令所定義。

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 巨集有助於傳播套件是 REQUIRED 還是 QUIET 等資訊。find_dependency 巨集也會在找不到依賴項時將 MathFunctions_FOUND 設定為 False,並發出診斷訊息,表示沒有 Stats 套件就無法使用 MathFunctions 套件。

練習: 將一個必要的程式庫加入到 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 時,請記住 include 目錄應指定為相對於 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}>"
  )

參考的變數可能包含程式庫和 include 目錄的絕對路徑,如同在建立套件的機器上找到的一樣。這將會建立一個具有硬編碼依賴項路徑的套件,不適合重定位。

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

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

透過這種方法,套件僅透過 IMPORTED 目標 的名稱參考其外部依賴項。當消費者使用已安裝的套件時,消費者將執行適當的 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
  const double inputValue = std::stod(argv[1]);

  // calculate square root
  const double 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 程式庫

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

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