安裝檔案

軟體通常會安裝到與原始碼和建置樹不同的目錄中。這允許以乾淨的形式分發軟體,並將使用者與建置過程的細節隔離開來。CMake 提供了 install 命令來指定專案的安裝方式。此命令由 CMakeLists 檔案中的專案調用,並告知 CMake 如何產生安裝腳本。這些腳本會在安裝時執行,以執行檔案的實際安裝。對於 Makefile 產生器(UNIX、NMake、MinGW 等),使用者只需執行 make install(或 nmake install),make 工具就會調用 CMake 的安裝模組。對於基於 GUI 的系統(Visual Studio、Xcode 等),使用者只需建置名為 INSTALL 的目標即可。

每次調用 install 命令都會定義一些安裝規則。在一個 CMakeLists 檔案(原始碼目錄)中,這些規則將按照對應命令被調用的順序進行評估。跨多個目錄的順序在 CMake 3.14 中發生了變化。

install 命令有多種簽名,旨在用於常見的安裝用例。命令的特定調用將簽名指定為第一個參數。簽名為 TARGETSFILESPROGRAMSDIRECTORYSCRIPTCODEEXPORT

install(TARGETS …)

安裝專案內部建置的目標所對應的二進位檔案。

install(FILES …)

通用檔案安裝,通常用於標頭檔、文件和軟體所需的資料檔案。

install(PROGRAMS …)

安裝非專案建置的可執行檔案,例如 shell 腳本。此參數與 install(FILES) 相同,只是安裝檔案的預設權限包含執行位元。

install(DIRECTORY …)

此參數安裝整個目錄樹。它可用於安裝包含資源的目錄,例如圖示和影像。

install(SCRIPT …)

指定在安裝期間執行的使用者提供的 CMake 腳本檔案。這通常用於定義其他規則的預先安裝或後續安裝動作。

install(CODE …)

指定在安裝期間執行的使用者提供的 CMake 程式碼。這與 install (SCRIPT) 相似,但程式碼是在調用中以字串形式內嵌提供的。

install(EXPORT …)

產生並安裝一個 CMake 檔案,其中包含將安裝樹中的目標匯入到另一個專案中的程式碼。

TARGETSFILESPROGRAMSDIRECTORY 簽名都是為了建立檔案的安裝規則。要安裝的目標、檔案或目錄會緊接在簽名名稱參數之後列出。可以使用關鍵字參數後接對應的值來指定其他詳細資訊。大多數簽名提供的關鍵字參數如下所示。

DESTINATION

此參數指定安裝規則將放置檔案的位置,並且後面必須跟一個表示位置的目錄路徑。如果目錄被指定為完整路徑,則會在安裝時將其評估為絕對路徑。如果目錄被指定為相對路徑,則會在安裝時將其評估為相對於安裝前綴的路徑。使用者可以透過快取變數 CMAKE_INSTALL_PREFIX 來設定前綴。CMake 提供了一個平台特定的預設值:在 UNIX 上為 /usr/local,在 Windows 上為「<SystemDrive>/Program Files/<ProjectName>」,其中 SystemDrive 類似於 C:,而 ProjectName 是給予最上層 project 命令的名稱。

PERMISSIONS

此參數指定要在已安裝檔案上設定的檔案權限。只有在要覆蓋特定 install 命令簽名選取的預設權限時,才需要此選項。有效的權限為 OWNER_READOWNER_WRITEOWNER_EXECUTEGROUP_READGROUP_WRITEGROUP_EXECUTEWORLD_READWORLD_WRITEWORLD_EXECUTESETUIDSETGID。某些平台不支援所有這些權限;在這些平台上,這些權限名稱將被忽略。

CONFIGURATIONS

此參數指定套用安裝規則的建置組態清單(Debug、Release 等)。對於 Makefile 產生器,建置組態由 CMAKE_BUILD_TYPE 快取變數指定。對於 Visual Studio 和 Xcode 產生器,組態會在建置 install 目標時選取。只有在目前的安裝組態符合提供給此參數的清單中的一個項目時,才會評估安裝規則。組態名稱比較不區分大小寫。

COMPONENT

此參數指定安裝規則套用的安裝元件。某些專案將其安裝分為多個元件,以便進行單獨的封裝。例如,專案可以定義一個 Runtime 元件,其中包含執行工具所需的檔案;一個 Development 元件,其中包含建置工具擴充功能所需的檔案;以及一個 Documentation 元件,其中包含手冊頁和其他幫助檔案。然後,專案可以透過一次僅安裝一個元件來單獨封裝每個元件以進行分發。預設情況下,會安裝所有元件。元件特定的安裝是為套件維護者使用而設計的高級功能。它需要使用定義 COMPONENT 變數的參數手動調用安裝腳本,以命名所需的元件。請注意,元件名稱不是由 CMake 定義的。每個專案都可以定義自己的一組元件。

OPTIONAL

此參數指定如果不存在要安裝的輸入檔案,則不會出現錯誤。如果輸入檔案存在,則會按要求安裝。如果不存在,則會靜默地不安裝。

安裝目標

專案通常會安裝在其建置過程中建立的一些程式庫和可執行檔案。install 命令提供了 TARGETS 簽名以用於此目的。

TARGETS 關鍵字緊接著一個使用 add_executableadd_library 建立的目標清單,這些目標將被安裝。每個目標會安裝一個或多個對應的檔案。

使用此簽章安裝的檔案可以分為幾種類別,例如 ARCHIVELIBRARYRUNTIME。這些類別旨在依據典型的安裝目的地來分組目標檔案。對應的關鍵字引數是可選的,但如果存在,則指定它們之後的其他引數僅適用於該類型的目標檔案。目標檔案分類如下:

執行檔 - RUNTIME

add_executable 建立 (在 Windows 上為 .exe,在 UNIX 上沒有副檔名)

可載入模組 - LIBRARY

add_library 搭配 MODULE 選項建立 (在 Windows 上為 .dll,在 UNIX 上為 .so)

共享函式庫 - LIBRARY

add_library 在類 UNIX 平台上搭配 SHARED 選項建立 (在大多數 UNIX 上為 .so,在 Mac 上為 .dylib)

動態連結函式庫 - RUNTIME

add_library 在 Windows 平台上搭配 SHARED 選項建立 (.dll)

匯入函式庫 - ARCHIVE

由匯出符號的動態連結函式庫所建立的可連結檔案 (在大多數 Windows 上為 .lib,在 Cygwin 和 MinGW 上為 .dll.a)。

靜態函式庫 - ARCHIVE

add_library 搭配 STATIC 選項建立 (在 Windows 上為 .lib,在 UNIX、Cygwin 和 MinGW 上為 .a)

考慮一個專案定義了一個執行檔 myExecutable,它連結到共享函式庫 mySharedLib。它還提供了一個靜態函式庫 myStaticLib 和一個名為 myPlugin 的執行檔外掛模組,它也連結到共享函式庫。執行檔、靜態函式庫和外掛檔案可以使用以下命令個別安裝:

install(TARGETS myExecutable DESTINATION bin)
install(TARGETS myStaticLib DESTINATION lib/myproject)
install(TARGETS myPlugin DESTINATION lib)

在安裝與之連結的共享函式庫之前,執行檔將無法從安裝位置執行。為了支援所有平台,安裝函式庫需要更加謹慎。它必須安裝在每個平台上動態連結器搜尋的位置。在類 UNIX 平台上,函式庫通常安裝到 lib,而在 Windows 上,它應該放在執行檔旁邊的 bin 中。另一個挑戰是,與 Windows 上的共享函式庫相關聯的匯入函式庫應被視為靜態函式庫,並安裝到 lib/myproject。換句話說,我們有三種不同種類的檔案,使用單個目標名稱建立,必須安裝到三個不同的目的地!幸運的是,這個問題可以使用類別關鍵字引數來解決。可以使用以下命令安裝共享函式庫:

install(TARGETS mySharedLib
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib/myproject)

這告訴 CMake,RUNTIME 檔案 (.dll) 應安裝到 binLIBRARY 檔案 (.so) 應安裝到 lib,而 ARCHIVE (.lib) 檔案應安裝到 lib/myproject。在 UNIX 上,將安裝 LIBRARY 檔案;在 Windows 上,將安裝 RUNTIMEARCHIVE 檔案。

如果上述範例專案要封裝到單獨的執行階段和開發元件中,我們必須為每個安裝的目標檔案指定適當的元件。執行檔、共享函式庫和外掛程式是執行應用程式所必需的,因此它們屬於 Runtime 元件。同時,匯入函式庫 (對應於 Windows 上的共享函式庫) 和靜態函式庫僅在開發應用程式的擴充功能時需要,因此屬於 Development 元件。

可以透過將 COMPONENT 引數新增至上述每個命令來指定元件指定。您也可以將所有安裝規則合併到單個命令調用中,這等效於新增了元件的所有上述命令。每個目標產生的檔案都使用其類別的規則安裝。

install(TARGETS myExecutable mySharedLib myStaticLib myPlugin
        RUNTIME DESTINATION bin           COMPONENT Runtime
        LIBRARY DESTINATION lib           COMPONENT Runtime
        ARCHIVE DESTINATION lib/myproject COMPONENT Development)

可以將 NAMELINK_ONLYNAMELINK_SKIP 指定為 LIBRARY 選項。在某些平台上,版本化的共享函式庫具有符號連結,例如

lib<name>.so -> lib<name>.so.1

其中 lib<name>.so.1 是函式庫的 soname,而 lib<name>.so 是「名稱連結」,可協助連結器在給定 -l<name> 時找到函式庫。NAMELINK_ONLY 選項會在安裝函式庫目標時僅安裝名稱連結。NAMELINK_SKIP 選項會導致在安裝函式庫目標時安裝名稱連結以外的函式庫檔案。當未給出任何選項時,將安裝這兩部分。在版本化的共享函式庫沒有名稱連結的平台上,或者在函式庫未版本化的情況下,NAMELINK_SKIP 選項會安裝函式庫,而 NAMELINK_ONLY 選項不安裝任何內容。有關建立版本化的共享函式庫的詳細資訊,請參閱 VERSIONSOVERSION 目標屬性。

安裝檔案

專案可以安裝除了使用 add_executableadd_library 建立的檔案之外的其他檔案,例如標頭檔案或文件。檔案的通用安裝是使用 FILES 簽章指定的。

FILES 關鍵字後面緊接著要安裝的檔案清單。相對路徑是相對於目前原始碼目錄來評估的。檔案將安裝到給定的 DESTINATION 目錄。例如,以下命令

install(FILES my-api.h ${CMAKE_CURRENT_BINARY_DIR}/my-config.h
        DESTINATION include)

將原始碼樹中的檔案 my-api.h 和組建樹中的檔案 my-config.h 安裝到安裝前綴下的 include 目錄中。預設情況下,安裝的檔案會被賦予 OWNER_WRITEOWNER_READGROUP_READWORLD_READ 的權限,但可以透過指定 PERMISSIONS 選項來覆寫此權限。考慮使用者希望在 UNIX 系統上安裝僅限其擁有者 (例如 root) 可讀的全域設定檔的情況。我們可以使用以下命令來完成此操作:

install(FILES my-rc DESTINATION /etc
        PERMISSIONS OWNER_WRITE OWNER_READ)

它會將具有擁有者讀/寫權限的檔案 my-rc 安裝到絕對路徑 /etc 中。

RENAME 引數指定已安裝檔案的名稱,該名稱可能與原始檔案不同。僅當命令安裝單個檔案時才允許重新命名。例如,以下命令

install(FILES version.h DESTINATION include RENAME my-version.h)

會將原始碼目錄中的檔案 version.h 安裝到安裝前綴下的 include/my-version.h 中。

安裝程式

專案也可以安裝輔助程式,例如實際上未編譯為目標的 shell 指令碼或 Python 指令碼。可以使用 FILES 簽章,使用 PERMISSIONS 選項新增執行權限來安裝它們。但是,這種情況很常見,足以證明使用更簡單的介面是合理的。CMake 為此目的提供了 PROGRAMS 簽章。

PROGRAMS 關鍵字後面緊接著要安裝的指令碼清單。此命令與 FILES 簽章相同,但預設權限還包括 OWNER_EXECUTEGROUP_EXECUTEWORLD_EXECUTE。例如,我們可以透過以下命令安裝 Python 公用程式指令碼:

install(PROGRAMS my-util.py DESTINATION bin)

它將 my-util.py 安裝到安裝前綴下的 bin 目錄,並賦予其擁有者、群組、世界讀取和執行權限,以及擁有者寫入權限。

安裝目錄

專案也可以提供整個目錄的資源檔案,例如圖示或 html 文件。可以使用 DIRECTORY 簽章來安裝整個目錄。

DIRECTORY 關鍵字後面緊接著要安裝的目錄清單。相對路徑是相對於目前原始碼目錄來評估的。每個指定的目錄都會安裝到目的地目錄。每個輸入目錄名稱的最後一個元件將附加到目的地目錄中,因為該目錄會被複製。例如,以下命令

install(DIRECTORY data/icons DESTINATION share/myproject)

會將原始碼樹中的 data/icons 目錄安裝到安裝前綴下的 share/myproject/icons 中。尾部的斜線會讓最後一個元件為空,並將輸入目錄的內容安裝到目的地。此命令

install(DIRECTORY doc/html/ DESTINATION doc/myproject)

將原始目錄中的 doc/html 內容安裝到安裝前綴下的 doc/myproject 中。如果沒有提供輸入目錄名稱,如

install(DIRECTORY DESTINATION share/myproject/user)

將會建立目標目錄,但不會安裝任何內容到其中。

DIRECTORY 簽章安裝的檔案會被賦予與 FILES 簽章相同的預設權限。由 DIRECTORY 簽章安裝的目錄會被賦予與 PROGRAMS 簽章相同的預設權限。FILE_PERMISSIONSDIRECTORY_PERMISSIONS 選項可用於覆蓋這些預設值。考慮一種情況,其中一個包含範例 shell 腳本的目錄將被安裝到一個可由擁有者和群組寫入的目錄中。我們可以使用以下命令

install(DIRECTORY data/scripts DESTINATION share/myproject
        FILE_PERMISSIONS
          OWNER_READ OWNER_EXECUTE OWNER_WRITE
          GROUP_READ GROUP_EXECUTE
          WORLD_READ WORLD_EXECUTE
        DIRECTORY_PERMISSIONS
          OWNER_READ OWNER_EXECUTE OWNER_WRITE
          GROUP_READ GROUP_EXECUTE GROUP_WRITE
          WORLD_READ WORLD_EXECUTE
        )

data/scripts 目錄安裝到 share/myproject/scripts 中,並設定所需的權限。在某些情況下,專案建立的完整輸入目錄可能已經設定了所需的權限。USE_SOURCE_PERMISSIONS 選項會告知 CMake 在安裝期間使用輸入目錄中的檔案和目錄權限。如果在先前的範例中,輸入目錄已經準備好正確的權限,則可以改用以下命令

install(DIRECTORY data/scripts DESTINATION share/myproject
        USE_SOURCE_PERMISSIONS)

如果要安裝的輸入目錄處於原始碼管理之下,則輸入中可能會有您不希望安裝的額外子目錄。也可能會有不應該安裝或應該以不同權限安裝的特定檔案,而大多數檔案會使用預設值。PATTERNREGEX 選項可用於此目的。PATTERN 選項後面首先會跟著一個 globbing 模式,然後是 EXCLUDEPERMISSIONS 選項。REGEX 選項後面首先會跟著一個正規表示式,然後是 EXCLUDEPERMISSIONSEXCLUDE 選項會略過安裝與前面模式或表示式相符的檔案或目錄,而 PERMISSIONS 選項則會為它們指派特定的權限。

每個輸入檔案和目錄都會以包含正斜線的完整路徑測試模式或正規表示式。模式只會比對完整路徑結尾的完整檔案或目錄名稱,而正規表示式可以比對任何部分。例如,模式 foo* 會比對 .../foo.txt,但不會比對 .../myfoo.txt.../foo/bar.txt;,但是,正規表示式 foo 會比對所有這些。

回到上面安裝圖示目錄的範例,考慮輸入目錄由 git 管理且還包含一些我們不想安裝的額外文字檔案的情況。此命令

install(DIRECTORY data/icons DESTINATION share/myproject
        PATTERN ".git" EXCLUDE
        PATTERN "*.txt" EXCLUDE)

會安裝圖示目錄,同時忽略任何包含的 .git 目錄或文字檔案。使用 REGEX 選項的等效命令為

install(DIRECTORY data/icons DESTINATION share/myproject
        REGEX "/.git$" EXCLUDE
        REGEX "/[^/]*.txt$" EXCLUDE)

其使用 '/' 和 '$' 來限制比對,方式與模式相同。考慮一個類似的情況,其中輸入目錄包含 shell 腳本和我們希望以不同權限安裝的文字檔案,而不是其他檔案。此命令

install(DIRECTORY data/other/ DESTINATION share/myproject
        PATTERN ".git" EXCLUDE
        PATTERN "*.txt"
          PERMISSIONS OWNER_READ OWNER_WRITE
        PATTERN "*.sh"
          PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)

會將原始目錄中的 data/other 內容安裝到 share/myproject 中,同時忽略 .git 目錄,並為 .txt.sh 檔案提供特定權限。

安裝腳本

專案安裝可能需要執行除了將檔案放置在安裝樹之外的其他工作。第三方套件可能會提供它們自己的機制來註冊必須在專案安裝期間調用的新外掛程式。SCRIPT 簽章即為此目的而提供。

SCRIPT 關鍵字後面緊接著一個 CMake 腳本的名稱。CMake 將在安裝期間執行此腳本。如果給定的檔案名稱是相對路徑,則將根據目前的原始碼目錄進行評估。一個簡單的用例是在安裝期間列印訊息。我們先撰寫一個包含以下程式碼的 message.cmake 檔案

message("Installing My Project")

然後使用以下命令參考此腳本

install(SCRIPT message.cmake)

自訂安裝腳本不會在主要的 CMakeLists 檔案處理期間執行;它們會在安裝過程本身期間執行。包含 install (SCRIPT) 呼叫的程式碼中定義的變數和巨集將無法從腳本存取。但是,在腳本執行期間定義了一些變數,可用於取得有關安裝的資訊。變數 CMAKE_INSTALL_PREFIX 設定為實際的安裝前綴。這可能與對應的快取變數值不同,因為安裝腳本可能會由使用不同前綴的封裝工具執行。使用者或封裝工具可能會設定環境變數 ENV{DESTDIR}。它的值會附加到安裝前綴和絕對安裝路徑,以決定安裝檔案的位置。為了在磁碟上參考安裝位置,自訂腳本可以使用 $ENV{DESTDIR}${CMAKE_INSTALL_PREFIX} 作為路徑的頂部。變數 CMAKE_INSTALL_CONFIG_NAME 設定為目前正在安裝的建置組態名稱 (Debug、Release 等)。在組件特定的安裝期間,變數 CMAKE_INSTALL_COMPONENT 設定為目前組件的名稱。

安裝程式碼

自訂安裝腳本(像上面的訊息一樣簡單)更容易使用放置在 install 命令呼叫中的內嵌腳本程式碼來建立。CODE 簽章即為此目的而提供。

CODE 關鍵字後面緊接著一個包含要放置在安裝腳本中的程式碼的字串。可以使用以下命令建立安裝時間訊息

install(CODE "MESSAGE(\"Installing My Project\")")

其效果與 message.cmake 腳本相同,但包含內嵌程式碼。

安裝先決條件共用程式庫

可執行檔通常使用共用程式庫作為建構模組來建置。當您安裝這類可執行檔時,您也必須安裝其先決條件共用程式庫,稱為「先決條件」,因為可執行檔需要它們的存在才能正確載入和執行。共用程式庫的三個主要來源是作業系統本身、您自己的專案的建置產品和屬於外部專案的第三方程式庫。作業系統的那些程式庫可以在無需安裝任何東西的情況下依賴它們的存在:它們位於可執行檔執行的基礎平台上。您自己的專案中的建置產品可能在 CMakeLists 檔案中具有 add_library 建置規則,因此為它們建立 CMake 安裝規則應該很簡單。當有超過少數的第三方程式庫,或者當它們的集合在第三方專案的版本之間波動時,第三方程式庫通常會成為一個高維護項目。程式庫可能會被新增、程式碼可能會被重組,而且第三方共用程式庫本身實際上可能還有額外的先決條件,這些先決條件乍看之下並不明顯。

CMake 提供了一個模組 BundleUtilities,以使其更容易處理所需的共用程式庫。此模組提供了 fixup_bundle 函式,使用相對於可執行檔的明確定義位置來複製和修正先決條件共用程式庫。對於 Mac 套件應用程式,它會將程式庫嵌入到套件中,並使用 install_name_tool 來修正它們,以建立一個自包含的單元。在 Windows 上,它會將程式庫複製到與可執行檔相同的目錄中,因為可執行檔會在它們自己的目錄中搜尋它們所需的 DLL。

fixup_bundle 函式可協助您建立可重新定位的安裝樹。Mac 使用者很欣賞自包含的套件應用程式:您可以將它們拖曳到任何地方、按兩下它們,它們仍然可以運作。它們不依賴在作業系統本身以外的特定位置安裝任何東西。同樣地,沒有管理員權限的 Windows 使用者很欣賞可重新定位的安裝樹,其中可執行檔和所有所需的 DLL 都安裝在同一個目錄中,以便它無論您將其安裝在何處都可以運作。您甚至可以在安裝後移動它們,它們仍然可以運作。

要使用 fixup_bundle,首先安裝您的其中一個可執行目標。然後,設定一個可以在安裝時呼叫的 CMake 腳本。在設定好的 CMake 腳本中,只需 include BundleUtilities 並使用適當的參數呼叫 fixup_bundle 函數。

在 CMakeLists.txt 中

install(TARGETS myExecutable DESTINATION bin)

# To install, for example, MSVC runtime libraries:
include(InstallRequiredSystemLibraries)

# To install other/non-system 3rd party required libraries:
configure_file(
  ${CMAKE_CURRENT_SOURCE_DIR}/FixBundle.cmake.in
  ${CMAKE_CURRENT_BINARY_DIR}/FixBundle.cmake
  @ONLY
  )

install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/FixBundle.cmake)

在 FixBundle.cmake.in 中

include(BundleUtilities)

# Set bundle to the full path name of the executable already
# existing in the install tree:
set(bundle
   "${CMAKE_INSTALL_PREFIX}/myExecutable@CMAKE_EXECUTABLE_SUFFIX@")

# Set other_libs to a list of full path names to additional
# libraries that cannot be reached by dependency analysis.
# (Dynamically loaded PlugIns, for example.)
set(other_libs "")

# Set dirs to a list of directories where prerequisite libraries
# may be found:
set(dirs
   "@CMAKE_RUNTIME_OUTPUT_DIRECTORY@"
   "@CMAKE_LIBRARY_OUTPUT_DIRECTORY@"
   )

fixup_bundle("${bundle}" "${other_libs}" "${dirs}")

您有責任驗證您是否有權限複製和散佈您的可執行檔所需的共享函式庫。某些函式庫可能具有限制性的軟體許可證,禁止像 fixup_bundle 這樣進行複製。