使用 CMake 進行交叉編譯

交叉編譯軟體是指在一個系統上建構軟體,但該軟體旨在另一個系統上運行。用於建構軟體的系統稱為「建構主機」,而用於建構軟體的系統則稱為「目標系統」或「目標平台」。目標系統通常運行不同的作業系統(或根本沒有作業系統)和/或在不同的硬體上運行。一個典型的使用案例是在嵌入式設備(如網路交換器、手機或引擎控制單元)的軟體開發中。在這些情況下,目標平台沒有或無法運行所需的軟體開發環境。

CMake 完全支援交叉編譯,範圍從 Linux 到 Windows 的交叉編譯;為超級電腦進行交叉編譯,到為沒有作業系統 (OS) 的小型嵌入式設備進行交叉編譯。

交叉編譯對 CMake 有幾個影響

  • CMake 無法自動偵測目標平台。

  • CMake 無法在預設系統目錄中找到程式庫和標頭。

  • 交叉編譯期間建構的可執行檔無法執行。

交叉編譯支援並不意味著所有基於 CMake 的專案都可以神奇地直接進行交叉編譯(有些可以),而是指 CMake 會區分建構平台和目標平台的資訊,並為使用者提供機制來解決交叉編譯問題,而無需額外要求,例如運行虛擬機器等。

為了支援特定軟體專案的交叉編譯,必須通過工具鏈檔案告知 CMake 關於目標平台的資訊。可能需要調整 CMakeLists.txt。它知道建構平台可能與目標平台具有不同的屬性,並且它必須處理已編譯的可執行檔嘗試在建構主機上執行的情況。

工具鏈檔案

為了使用 CMake 進行交叉編譯,必須建立一個描述目標平台的 CMake 檔案,稱為「工具鏈檔案」。此檔案會告知 CMake 關於目標平台所需的所有資訊。以下是一個在 Linux 下使用適用於 Windows 的 MinGW 交叉編譯器的範例;之後會逐行解釋其內容。

# the name of the target operating system
set(CMAKE_SYSTEM_NAME Windows)

# which compilers to use for C and C++
set(CMAKE_C_COMPILER   i586-mingw32msvc-gcc)
set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++)

# where is the target environment located
set(CMAKE_FIND_ROOT_PATH  /usr/i586-mingw32msvc
    /home/alex/mingw-install)

# adjust the default behavior of the FIND_XXX() commands:
# search programs in the host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)

# search headers and libraries in the target environment
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

假設此檔案以名稱 TC-mingw.cmake 儲存在您的主目錄中,您可以通過設定 CMAKE_TOOLCHAIN_FILE 變數來指示 CMake 使用此檔案

~/src$ cd build
~/src/build$ cmake -DCMAKE_TOOLCHAIN_FILE=~/TC-mingw.cmake ..
...

CMAKE_TOOLCHAIN_FILE 僅需在初始 CMake 執行時指定;之後,結果會從 CMake 快取中重複使用。您不需要為每個要建構的軟體編寫單獨的工具鏈檔案。工具鏈檔案是針對每個目標平台的;也就是說,如果您要為同一個目標平台建構多個軟體套件,您只需要編寫一個工具鏈檔案即可用於所有套件。

工具鏈檔案中的設定是什麼意思?我們將逐一檢視它們。由於 CMake 無法猜測目標作業系統或硬體,因此您必須設定以下 CMake 變數

CMAKE_SYSTEM_NAME

此變數為強制性;它會設定目標系統的名稱,也就是說,設定為與 CMake 在目標系統上執行時的 CMAKE_SYSTEM_NAME 值相同。典型的範例是「Linux」和「Windows」。它用於建構平台檔案的檔案名稱,例如 Linux.cmake 或 Windows-gcc.cmake。如果您的目標是沒有作業系統的嵌入式系統,請將 CMAKE_SYSTEM_NAME 設定為「Generic」。以這種方式預先設定 CMAKE_SYSTEM_NAME 而不是透過偵測,會自動導致 CMake 將建構視為交叉編譯建構,並且 CMake 變數 CMAKE_CROSSCOMPILING 將設定為 TRUE。CMAKE_CROSSCOMPILING 是應該在 CMake 檔案中測試以確定目前建構是否為交叉編譯建構的變數。

CMAKE_SYSTEM_VERSION

設定您的目標系統版本。

CMAKE_SYSTEM_PROCESSOR

此變數為選用;它會設定目標系統的處理器或硬體名稱。它在 CMake 中用於一個目的,即載入 ${CMAKE_SYSTEM_NAME}-COMPILER_ID-${CMAKE_SYSTEM_PROCESSOR}.cmake 檔案。此檔案可用於修改設定,例如目標的編譯器旗標。如果使用每個目標都需要特殊建構設定的交叉編譯器,則您只需要設定此變數。該值可以自由選擇,因此可以是例如 i386、IntelPXA255 或 MyControlBoardRev42。

在 CMake 程式碼中,CMAKE_SYSTEM_XXX 變數始終描述目標平台。對於簡短的 WIN32UNIXAPPLE 變數也是如此。這些變數可用於測試目標的屬性。如果需要測試建構主機系統,則有一組對應的變數:CMAKE_HOST_SYSTEMCMAKE_HOST_SYSTEM_NAMECMAKE_HOST_SYSTEM_VERSIONCMAKE_HOST_SYSTEM_PROCESSOR;以及簡短形式:CMAKE_HOST_WIN32CMAKE_HOST_UNIXCMAKE_HOST_APPLE

由於 CMake 無法猜測目標系統,因此也無法猜測應該使用哪個編譯器。設定以下變數會定義要用於目標系統的編譯器。

CMAKE_C_COMPILER

這會將 C 編譯器可執行檔指定為完整路徑或僅指定檔案名稱。如果以完整路徑指定,則在搜尋 C++ 編譯器和其他工具(binutils、linker 等)時,會優先使用此路徑。如果編譯器是具有前置名稱的 GNU 交叉編譯器(例如「arm-elf-gcc」),CMake 會偵測到這一點,並自動找到對應的 C++ 編譯器(即「arm-elf-c++」)。也可以通過 CC 環境變數設定編譯器。在工具鏈檔案中直接設定 CMAKE_C_COMPILER 的優點是,關於目標系統的資訊完全包含在此檔案中,並且它不依賴於環境變數。

CMAKE_CXX_COMPILER

這指定了 C++ 編譯器的可執行檔,可以使用完整路徑或僅使用檔名。它的處理方式與 CMAKE_C_COMPILER 相同。如果工具鏈是 GNU 工具鏈,則僅設定 CMAKE_C_COMPILER 就足夠了;CMake 應該會自動找到對應的 C++ 編譯器。至於 CMAKE_C_COMPILER,C++ 編譯器也可以透過 CXX 環境變數設定。

尋找外部函式庫、程式和其他檔案

大多數非簡單的專案都會使用外部函式庫或工具。CMake 提供了 find_programfind_libraryfind_filefind_pathfind_package 命令來達到此目的。它們會在檔案系統中常見的位置搜尋這些檔案並返回結果。find_package 有點不同,它實際上並不會自行搜尋,而是執行 Find<*>.cmake 模組,這些模組反過來會呼叫 find_programfind_libraryfind_filefind_path 命令。

當進行交叉編譯時,這些命令會變得更加複雜。例如,當在 Linux 系統上交叉編譯到 Windows 時,使用 find_package(JPEG) 命令得到 /usr/lib/libjpeg.so` 的結果是沒有用的,因為這會是主機系統而非目標系統的 JPEG 函式庫。在某些情況下,您希望找到適用於目標平台的檔案;在其他情況下,您會希望找到適用於建置主機的檔案。以下變數旨在讓您靈活地變更 CMake 中典型尋找命令的運作方式,以便您可以根據需要找到建置主機和目標檔案。

工具鏈將會帶有其自身適用於目標平台的函式庫和標頭,這些通常會安裝在一個共同的前綴下。最好設定一個目錄,讓所有為目標平台建置的軟體都安裝在此目錄下,這樣軟體套件就不會與工具鏈附帶的函式庫混淆。

find_program 命令通常用於尋找在建置期間執行的程式,因此這應該仍然在主機檔案系統中搜尋,而不是在目標平台的環境中搜尋。find_library 通常用於尋找用於連結目的的函式庫,因此此命令應僅在目標環境中搜尋。對於 find_pathfind_file,情況並不明顯;在許多情況下,它們用於搜尋標頭,因此預設情況下它們應該僅在目標環境中搜尋。可以設定以下 CMake 變數來調整尋找命令在交叉編譯中的行為。

CMAKE_FIND_ROOT_PATH

這是包含目標環境的目錄清單。此處列出的每個目錄都會附加到每個尋找命令的每個搜尋目錄之前。假設您的目標環境安裝在 /opt/eldk/ppc_74xx 下,且該目標平台的安裝會進入 ~/install-eldk-ppc74xx,請將 CMAKE_FIND_ROOT_PATH 設定為這兩個目錄。然後 find_library(JPEG_LIB jpeg) 將會在 /opt/eldk/ppc_74xx/lib/opt/eldk/ppc_74xx/usr/lib~/install-eldk-ppc74xx/lib~/install-eldk-ppc74xx/usr/lib 中搜尋,並應產生 /opt/eldk/ppc_74xx/usr/lib/libjpeg.so

預設情況下,CMAKE_FIND_ROOT_PATH 是空的。如果設定了,則會先搜尋以 CMAKE_FIND_ROOT_PATH 中給定路徑為前綴的目錄,然後再搜尋相同目錄的未加前綴版本。

透過設定此變數,您基本上是為 CMake 中的所有尋找命令新增了一組新的搜尋前綴,但對於某些尋找命令,您可能不希望搜尋目標或主機目錄。您可以透過在呼叫時傳入以下三個選項之一來控制每個尋找命令呼叫的運作方式:NO_CMAKE_FIND_ROOT_PATHONLY_CMAKE_FIND_ROOT_PATHCMAKE_FIND_ROOT_PATH_BOTH。您也可以使用以下三個變數來控制尋找命令的運作方式。

CMAKE_FIND_ROOT_PATH_MODE_PROGRAM

這會設定 find_program 命令的預設行為。它可以設定為 NEVER、ONLY 或 BOTH。當設定為 NEVER 時,CMAKE_FIND_ROOT_PATH 將不會用於 find_program 呼叫,除非明確啟用。如果設定為 ONLY,則只有來自 CMAKE_FIND_ROOT_PATH 的前綴的搜尋目錄才會被 find_program 使用。預設值為 BOTH,這表示會先搜尋加前綴的目錄,然後再搜尋未加前綴的目錄。

在大多數情況下,find_program 用於搜尋可執行檔,然後執行該檔案,例如使用 execute_processadd_custom_command。因此,在大多數情況下,需要來自建置主機的可執行檔,因此通常偏好將 CMAKE_FIND_ROOT_PATH_MODE_PROGRAM 設定為 NEVER。

CMAKE_FIND_ROOT_PATH_MODE_LIBRARY

這與上述相同,但適用於 find_library 命令。在大多數情況下,這用於尋找將用於連結的程式庫,因此需要目標的程式庫。在大多數情況下,應將其設定為 ONLY。

CMAKE_FIND_ROOT_PATH_MODE_INCLUDE

這與上述相同,並用於 find_pathfind_file。在大多數情況下,這用於尋找 include 目錄,因此應搜尋目標環境。在大多數情況下,應將其設定為 ONLY。如果還需要尋找建置主機檔案系統中的檔案(例如,在建置期間處理的一些資料檔案);您可能需要使用 NO_CMAKE_FIND_ROOT_PATHONLY_CMAKE_FIND_ROOT_PATHCMAKE_FIND_ROOT_PATH_BOTH 選項來調整這些 find_pathfind_file 呼叫的行為。

使用如上所述設定的工具鏈檔案,CMake 現在知道如何處理目標平台和交叉編譯器。我們現在應該能夠為目標平台建置軟體。對於複雜的專案,還有更多問題需要處理。

系統檢查

大多數可攜式軟體專案都有一組系統檢查測試,用於判斷(目標)系統的屬性。使用 CMake 檢查系統功能的最簡單方法是測試變數。為此,CMake 提供了變數 UNIXWIN32APPLE。當進行交叉編譯時,這些變數適用於目標平台,對於測試建置主機平台,有對應的變數 CMAKE_HOST_UNIXCMAKE_HOST_WIN32CMAKE_HOST_APPLE

如果這個粒度太粗,則可以測試變數 CMAKE_SYSTEM_NAMECMAKE_SYSTEMCMAKE_SYSTEM_VERSIONCMAKE_SYSTEM_PROCESSOR,以及它們的對應項 CMAKE_HOST_SYSTEM_NAMECMAKE_HOST_SYSTEMCMAKE_HOST_SYSTEM_VERSIONCMAKE_HOST_SYSTEM_PROCESSOR,它們包含相同的資訊,但適用於建置主機而不是目標系統。

if(CMAKE_SYSTEM MATCHES Windows)
   message(STATUS "Target system is Windows")
endif()

if(CMAKE_HOST_SYSTEM MATCHES Linux)
   message(STATUS "Build host runs Linux")
endif()

使用編譯檢查

在 CMake 中,有一些巨集,例如 check_include_filescheck_c_source_runs,用於測試平台的屬性。這些巨集大多數在內部使用 try_compiletry_run 命令。try_compile 命令在交叉編譯時按預期工作;它會嘗試使用交叉編譯工具鏈編譯這段程式碼,這將給出預期的結果。

所有使用 try_run 的測試都將不起作用,因為建立的可執行檔通常無法在建置主機上執行。在某些情況下,這可能是可行的(例如,使用虛擬機器、Wine 等模擬層或實際目標的介面),因為 CMake 不依賴於此類機制。在建置過程中依賴模擬器會引入一組新的潛在問題;它們可能對檔案系統有不同的檢視、使用其他行尾符號、需要特殊的硬體或軟體等。

如果交叉編譯時調用 try_run,它會先嘗試編譯軟體,這會與非交叉編譯時的運作方式相同。如果編譯成功,它會檢查變數 CMAKE_CROSSCOMPILING 以判斷產生的可執行檔是否可以執行。如果無法執行,它會建立兩個快取變數,這些變數必須由使用者或透過 CMake 快取設定。假設命令如下所示

try_run(SHARED_LIBRARY_PATH_TYPE
        SHARED_LIBRARY_PATH_INFO_COMPILED
        ${PROJECT_BINARY_DIR}/CMakeTmp
        ${PROJECT_SOURCE_DIR}/CMake/SharedLibraryPathInfo.cxx
        OUTPUT_VARIABLE OUTPUT
        ARGS "LDPATH"
        )

在這個範例中,原始碼檔案 SharedLibraryPathInfo.cxx 將會被編譯,如果編譯成功,則應執行產生的可執行檔。變數 SHARED_LIBRARY_PATH_INFO_COMPILED 將會被設定為建置的結果,即 TRUE 或 FALSE。CMake 將會建立一個快取變數 SHARED_LIBRARY_PATH_TYPE 並預設為 PLEASE_FILL_OUT-FAILED_TO_RUN。此變數必須設定為可執行檔在目標平台上執行時的退出碼。此外,CMake 將會建立一個快取變數 SHARED_LIBRARY_PATH_TYPE__TRYRUN_OUTPUT 並預設為 PLEASE_FILL_OUT-NOTFOUND。此變數應設定為可執行檔在目標平台上執行時列印到 stdout 和 stderr 的輸出。只有當 try_run 命令與 RUN_OUTPUT_VARIABLEOUTPUT_VARIABLE 參數一起使用時,才會建立此變數。您必須填寫這些變數的適當值。為了幫助您,CMake 會盡力提供有用的資訊。為此,CMake 會建立一個檔案 ${CMAKE_BINARY_DIR}/TryRunResults.cmake,您可以在此處看到一個範例

# SHARED_LIBRARY_PATH_TYPE
#   indicates whether the executable would have been able to run
#   on its target platform. If so, set SHARED_LIBRARY_PATH_TYPE
#   to the exit code (in many cases 0 for success), otherwise
#   enter "FAILED_TO_RUN".
# SHARED_LIBRARY_PATH_TYPE__TRYRUN_OUTPUT
#   contains the text the executable would have printed on
#   stdout and stderr. If the executable would not have been
#   able to run, set SHARED_LIBRARY_PATH_TYPE__TRYRUN_OUTPUT
#   empty. Otherwise check if the output is evaluated by the
#   calling CMake code. If so, check what the source file would
#   have printed when called with the given arguments.
# The SHARED_LIBRARY_PATH_INFO_COMPILED variable holds the build
# result for this TRY_RUN().
#
# Source file: ~/src/SharedLibraryPathInfo.cxx
# Executable : ~/build/cmTryCompileExec-SHARED_LIBRARY_PATH_TYPE
# Run arguments:  LDPATH
#    Called from: [1]   ~/src/CMakeLists.cmake

set(SHARED_LIBRARY_PATH_TYPE
    "0"
    CACHE STRING "Result from TRY_RUN" FORCE)

set(SHARED_LIBRARY_PATH_TYPE__TRYRUN_OUTPUT
    ""
    CACHE STRING "Output from TRY_RUN" FORCE)

您可以找到 CMake 無法確定的所有變數,它們是從哪個 CMake 檔案呼叫的、原始碼檔案、可執行檔的參數以及可執行檔的路徑。CMake 也會將可執行檔複製到建置目錄;它們的名稱為 cmTryCompileExec-<變數名稱>,例如在本例中為 cmTryCompileExec-SHARED_LIBRARY_PATH_TYPE。然後,您可以嘗試在實際目標平台上手動執行此可執行檔並檢查結果。

獲得這些結果後,它們必須放入 CMake 快取中。這可以使用 ccmake 或 cmake-gui 完成,並直接在快取中編輯變數。如果刪除 CMakeCache.txt,則無法在另一個建置目錄中重複使用這些變更。

建議的方法是使用 CMake 建立的 TryRunResults.cmake 檔案。您應該將其複製到安全的位置(即,如果刪除建置目錄,它將不會被刪除的地方),並給它一個有用的名稱,例如 TryRunResults-MyProject-eldk-ppc.cmake。必須編輯此檔案的內容,以便設定命令將所需的變數設定為目標系統的適當值。然後,可以使用 cmake 的 -C 選項預先載入 CMake 快取來使用此檔案

src/build/ $ cmake -C ~/TryRunResults-MyProject-eldk-ppc.cmake .

您不必再次使用其他 CMake 選項,因為它們現在已在快取中。這樣,您可以在多個建置樹狀結構中使用 MyProjectTryRunResults-eldk-ppc.cmake,並且它可以與您的專案一起分發,以便其他使用者更容易交叉編譯它。

執行專案中建置的可執行檔

在某些情況下,在建置過程中必須調用在同一個建置中較早建置的可執行檔;這通常適用於程式碼產生器和類似工具。這在交叉編譯時不起作用,因為可執行檔是為目標平台建置的,並且無法在建置主機上執行(除非使用虛擬機器、相容性層、模擬器等)。使用 CMake,這些程式是使用 add_executable 建立的,並使用 add_custom_commandadd_custom_target 執行。以下三個選項可用於支援 CMake 中的這些可執行檔。舊版本的 CMake 程式碼可能如下所示

add_executable(mygen gen.c)
get_target_property(mygenLocation mygen LOCATION)
add_custom_command(
  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/generated.h"
  COMMAND ${mygenLocation}
       -o "${CMAKE_CURRENT_BINARY_DIR}/generated.h" )

現在,我們將展示如何修改此檔案,使其在交叉編譯時也能運作。基本概念是,僅當為建置主機執行原生建置時才建置可執行檔,然後將其作為可執行檔目標匯出到 CMake 腳本檔案。然後,在交叉編譯時會包含此檔案,並將載入可執行檔 mygen 的可執行檔目標。將會建立與原始目標同名的導入目標。add_custom_command 將目標名稱識別為可執行檔,因此對於 add_custom_command 中的命令,可以直接使用目標名稱;不需要使用 LOCATION 屬性來取得可執行檔的路徑

if(CMAKE_CROSSCOMPILING)
   find_package(MyGen)
else()
   add_executable(mygen gen.c)
   export(TARGETS mygen FILE
          "${CMAKE_BINARY_DIR}/MyGenConfig.cmake")
endif()

add_custom_command(
  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/generated.h"
  COMMAND mygen -o "${CMAKE_CURRENT_BINARY_DIR}/generated.h"
  )

透過這樣修改 CMakeLists.txt,可以交叉編譯專案。首先,必須執行原生建置才能建立必要的 mygen 可執行檔。之後,可以開始交叉編譯建置。必須將原生建置的建置目錄提供給交叉編譯建置,作為 MyGen 套件的位置,以便 find_package(MyGen) 可以找到它

mkdir build-native; cd build-native
cmake ..
make
cd ..
mkdir build-cross; cd build-cross
cmake -DCMAKE_TOOLCHAIN_FILE=MyToolchain.cmake \
      -DMyGen_DIR=~/src/build-native/ ..
make

交叉編譯 Hello World

現在,讓我們實際開始進行交叉編譯。第一步是安裝交叉編譯工具鏈。如果已經安裝了,您可以跳過下一段。

有許多不同的方法和專案處理 Linux 的交叉編譯,範圍從在基於 Linux 的 PDA 上運作的自由軟體專案到商業嵌入式 Linux 供應商。這些專案大多數都有自己建置和使用各自工具鏈的方式。任何這些工具鏈都可以與 CMake 一起使用;唯一的要求是它可以在正常檔案系統中運作,並且不期望像 Scratchbox 專案這樣的「沙箱」環境。

一個易於使用的工具鏈,具有相對完整的目標環境,是嵌入式 Linux 開發工具組 (http://www.denx.de/wiki/DULG/ELDK)。它支援 ARM、PowerPC 和 MIPS 作為目標平台。可以從 ftp://ftp.sunet.se/pub/Linux/distributions/eldk/ 下載 ELDK。最簡單的方法是下載 ISO、掛載它們,然後安裝它們

mkdir mount-iso/
sudo mount -tiso9660 mips-2007-01-21.iso mount-iso/ -o loop
cd mount-iso/
./install -d /home/alex/eldk-mips/
...
Preparing...           ########################################### [100%]
   1:appWeb-mips_4KCle ########################################### [100%]
Done
ls /opt/eldk-mips/
bin  eldk_init  etc  mips_4KC  mips_4KCle  usr  var  version

ELDK(和其他工具鏈)可以安裝在任何地方,無論是在主目錄中還是系統範圍內,如果有更多使用者使用它們。在此範例中,工具鏈現在將位於 /home/alex/eldk-mips/usr/bin/ 中,而目標環境位於 /home/alex/eldk-mips/mips_4KC/ 中。

現在,已經安裝了交叉編譯工具鏈,必須設定 CMake 以使用它。如前所述,這是透過為 CMake 建立工具鏈檔案來完成的。在此範例中,工具鏈檔案如下所示

# the name of the target operating system
set(CMAKE_SYSTEM_NAME Linux)

# which C and C++ compiler to use
set(CMAKE_C_COMPILER /home/alex/eldk-mips/usr/bin/mips_4KC-gcc)
set(CMAKE_CXX_COMPILER
    /home/alex/eldk-mips/usr/bin/mips_4KC-g++)

# location of the target environment
set(CMAKE_FIND_ROOT_PATH /home/alex/eldk-mips/mips_4KC
                          /home/alex/eldk-mips-extra-install )

# adjust the default behavior of the FIND_XXX() commands:
# search for headers and libraries in the target environment,
# search for programs in the host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

工具鏈檔案可以位於任何位置,但最好將它們放置在中央位置,以便可以在多個專案中重複使用它們。我們將此檔案儲存為 ~/Toolchains/Toolchain-eldk-mips4K.cmake。上述變數在這裡設定:CMAKE_SYSTEM_NAME、C/C++ 編譯器和 CMAKE_FIND_ROOT_PATH,以指定目標環境的程式庫和標頭檔所在的位置。還設定了尋找模式,以便僅在目標環境中搜尋程式庫和標頭檔,而僅在主機環境中搜尋程式。現在,我們將交叉編譯第 2 章中的 hello world 專案

project(Hello)
add_executable(Hello Hello.c)

執行 CMake,這次告訴它使用上面的工具鏈檔案

mkdir Hello-eldk-mips
cd Hello-eldk-mips
cmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchains/Toolchain-eldk-mips4K.cmake ..
make VERBOSE=1

這應該會為您提供一個可以在目標平台上執行的可執行檔。由於 VERBOSE=1 選項,您應該會看到使用了交叉編譯器。現在,我們將透過新增系統檢查和安裝規則來使範例更加複雜。我們將建置並安裝一個名為 Tools 的共享程式庫,然後建置連結到 Tools 程式庫的 Hello 應用程式。

include(CheckIncludeFiles)
check_include_files(stdio.h HAVE_STDIO_H)

set(VERSION_MAJOR 2)
set(VERSION_MINOR 6)
set(VERSION_PATCH 0)

configure_file(config.h.in ${CMAKE_BINARY_DIR}/config.h)

add_library(Tools SHARED tools.cxx)
set_target_properties(Tools PROPERTIES
    VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}
    SOVERSION ${VERSION_MAJOR})

install(FILES tools.h DESTINATION include)
install(TARGETS Tools DESTINATION lib)

一般的 CMakeLists.txt 沒有任何差異;交叉編譯不需要任何特殊的前提條件。CMakeLists.txt 會檢查標頭檔 stdio.h 是否可用,並設定 Tools 函式庫的版本號。這些設定會寫入 config.h,然後在 tools.cxx 中使用。版本號也會用來設定 Tools 函式庫的版本號。函式庫和標頭檔會分別安裝到 ${CMAKE_INSTALL_PREFIX}/lib 和 ${CMAKE_INSTALL_PREFIX}/include。執行 CMake 會得到以下結果

mkdir build-eldk-mips
cd build-eldk-mips
cmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchains/Toolchain-eldk-mips4K.cmake \
      -DCMAKE_INSTALL_PREFIX=~/eldk-mips-extra-install ..
-- The C compiler identification is GNU
-- The CXX compiler identification is GNU
-- Check for working C compiler: /home/alex/eldk-mips/usr/bin/mips_4KC-gcc
-- Check for working C compiler:
   /home/alex/eldk-mips/usr/bin/mips_4KC-gcc -- works
-- Check size of void*
-- Check size of void* - done
-- Check for working CXX compiler: /home/alex/eldk-mips/usr/bin/mips_4KC-g++
-- Check for working CXX compiler:
   /home/alex/eldk-mips/usr/bin/mips_4KC-g++ -- works
-- Looking for include files HAVE_STDIO_H
-- Looking for include files HAVE_STDIO_H - found
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alex/src/tests/Tools/build-mips
make install
Scanning dependencies of target Tools
[100%] Building CXX object CMakeFiles/Tools.dir/tools.o
Linking CXX shared library libTools.so
[100%] Built target Tools
Install the project...
-- Install configuration: ""
-- Installing /home/alex/eldk-mips-extra-install/include/tools.h
-- Installing /home/alex/eldk-mips-extra-install/lib/libTools.so

如以上輸出所示,CMake 偵測到正確的編譯器,找到目標平台的 stdio.h 標頭檔,並成功產生 Makefiles。接著呼叫 make 指令,成功建置並將函式庫安裝到指定的安裝目錄。現在我們可以建置一個使用 Tools 函式庫並進行一些系統檢查的可執行檔。

project(HelloTools)

find_package(ZLIB REQUIRED)

find_library(TOOLS_LIBRARY Tools)
find_path(TOOLS_INCLUDE_DIR tools.h)

if(NOT TOOLS_LIBRARY OR NOT TOOLS_INCLUDE_DIR)
  message FATAL_ERROR "Tools library not found")
endif()

set(CMAKE_INCLUDE_CURRENT_DIR TRUE)
set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE TRUE)
include_directories("${TOOLS_INCLUDE_DIR}"
                    "${ZLIB_INCLUDE_DIR}")

add_executable(HelloTools main.cpp)
target_link_libraries(HelloTools ${TOOLS_LIBRARY}
                      ${ZLIB_LIBRARIES})
set_target_properties(HelloTools PROPERTIES
                      INSTALL_RPATH_USE_LINK_PATH TRUE)

install(TARGETS HelloTools DESTINATION bin)

建置方式與函式庫相同;必須使用工具鏈檔案,然後應該就可以正常運作。

cmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchains/Toolchain-eldk-mips4K.cmake \
      -DCMAKE_INSTALL_PREFIX=~/eldk-mips-extra-install ..
-- The C compiler identification is GNU
-- The CXX compiler identification is GNU
-- Check for working C compiler: /home/alex/denx-mips/usr/bin/mips_4KC-gcc
-- Check for working C compiler:
   /home/alex/denx-mips/usr/bin/mips_4KC-gcc -- works
-- Check size of void*
-- Check size of void* - done
-- Check for working CXX compiler: /home/alex/denx-mips/usr/bin/mips_4KC-g++
-- Check for working CXX compiler:
   /home/alex/denx-mips/usr/bin/mips_4KC-g++ -- works
-- Found ZLIB: /home/alex/denx-mips/mips_4KC/usr/lib/libz.so
-- Found Tools library: /home/alex/denx-mips-extra-install/lib/libTools.so
-- Configuring done
-- Generating done
-- Build files have been written to:
   /home/alex/src/tests/HelloTools/build-eldk-mips
make
[100%] Building CXX object CMakeFiles/HelloTools.dir/main.o
Linking CXX executable HelloTools
[100%] Built target HelloTools

很明顯地,CMake 找到了正確的 zlib 和 libTools.so,而 libTools.so 是在前一個步驟中安裝的。

為微控制器進行交叉編譯

CMake 的用途不只於將程式交叉編譯到具有作業系統的目標,它也可以用於開發具有小型微控制器且完全沒有作業系統的深度嵌入式裝置。舉例來說,我們將使用 Small Devices C Compiler (http://sdcc.sourceforge.net),它可以在 Windows、Linux 和 Mac OS X 上執行,並支援 8 位元和 16 位元的微控制器。為了驅動建置,我們將在 Windows 下使用 MS NMake。如同之前,第一步是編寫工具鏈檔案,讓 CMake 知道目標平台。對於 sdcc,它應該如下所示:

set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER "c:/Program Files/SDCC/bin/sdcc.exe")

對於沒有作業系統的目標,應使用「Generic」作為系統名稱,即 CMAKE_SYSTEM_NAME。「Generic」的 CMake 平台檔案不會設定任何特定功能。它假設目標平台不支援共享函式庫,因此所有屬性都將取決於編譯器和 CMAKE_SYSTEM_PROCESSOR。上面的工具鏈檔案沒有設定與 FIND 相關的變數。只要 CMake 指令中沒有使用任何 find 指令,這就沒問題。在許多小型微控制器的專案中,情況將會如此。CMakeLists.txt 應該如下所示:

project(Blink C)

add_library(blink blink.c)

add_executable(hello main.c)
target_link_libraries(hello blink)

其他 CMakeLists.txt 檔案沒有重大差異。一個重點是,使用 project 指令明確啟用「C」語言。如果沒有這樣做,CMake 也會嘗試啟用 C++ 的支援,但會失敗,因為 sdcc 只支援 C。執行 CMake 並建置專案應該會像往常一樣運作。

cmake -G"NMake Makefiles" \
      -DCMAKE_TOOLCHAIN_FILE=c:/Toolchains/Toolchain-sdcc.cmake ..
-- The C compiler identification is SDCC
-- Check for working C compiler: c:/program files/sdcc/bin/sdcc.exe
-- Check for working C compiler: c:/program files/sdcc/bin/sdcc.exe -- works
-- Check size of void*
-- Check size of void* - done
-- Configuring done
-- Generating done
-- Build files have been written to: C:/src/tests/blink/build

nmake
Microsoft (R) Program Maintenance Utility Version 7.10.3077
Copyright (C) Microsoft Corporation.  All rights reserved.

Scanning dependencies of target blink
[ 50%] Building C object CMakeFiles/blink.dir/blink.rel
Linking C static library blink.lib
[ 50%] Built target blink
Scanning dependencies of target hello
[100%] Building C object CMakeFiles/hello.dir/main.rel
Linking C executable hello.ihx
[100%] Built target hello

這是一個使用 NMake 和 sdcc 以及 sdcc 預設設定的簡單範例。當然,可以有更複雜的專案佈局。對於這類專案,最好設定一個安裝目錄,可以在其中安裝可重複使用的函式庫,這樣就可以更容易地在多個專案中使用它們。通常需要為 sdcc 選擇正確的目標平台;並非所有人都使用 i8051,這是 sdcc 的預設值。建議的方法是透過設定 CMAKE_SYSTEM_PROCESSOR 來完成。

這將導致 CMake 搜尋並載入平台檔案 Platform/Generic-SDCC-C-${CMAKE_SYSTEM_PROCESSOR}.cmake。由於這是在載入 Platform/Generic-SDCC-C.cmake 之前發生的,因此可以用它來設定特定目標硬體和專案的編譯器和連結器旗標。因此,需要一個稍微複雜的工具鏈檔案:

get_filename_component(_ownDir
                       "${CMAKE_CURRENT_LIST_FILE}" PATH)
set(CMAKE_MODULE_PATH "${_ownDir}/Modules" ${CMAKE_MODULE_PATH})

set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER "c:/Program Files/SDCC/bin/sdcc.exe")
set(CMAKE_SYSTEM_PROCESSOR "Test_DS80C400_Rev_1")

# here is the target environment located
set(CMAKE_FIND_ROOT_PATH  "c:/Program Files/SDCC"
                          "c:/ds80c400-install" )

# adjust the default behavior of the FIND_XXX() commands:
# search for headers and libraries in the target environment
# search for programs in the host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

此工具鏈檔案包含一些新的設定;它也應該是您需要的最複雜的工具鏈檔案。CMAKE_SYSTEM_PROCESSOR 設定為 Test_DS80C400_Rev_1,這是特定目標硬體的識別碼。這會導致 CMake 嘗試載入 Platform/Generic-SDCC-C-Test_DS80C400_Rev_1.cmake。由於此檔案不存在於 CMake 系統模組目錄中,因此必須調整 CMake 變數 CMAKE_MODULE_PATH,以便可以找到此檔案。如果此工具鏈檔案儲存到 c:/Toolchains/sdcc-ds400.cmake,則硬體特定的檔案應儲存到 c:/Toolchains/Modules/Platform/。以下顯示一個範例

set(CMAKE_C_FLAGS_INIT "-mds390 --use-accelerator")
set(CMAKE_EXE_LINKER_FLAGS_INIT "")

這將選擇 DS80C390 作為目標平台,並將 –use-accelerator 引數新增至預設編譯旗標。在此範例中,使用了「NMake Makefiles」產生器。同樣地,例如,「MinGW Makefiles」產生器可用於來自 MinGW 的 GNU make,或者可以使用其他 Windows 版本的 GNU make。至少需要 3.78 版本,或 UNIX 下的「Unix Makefiles」產生器。此外,也可以使用任何基於 Makefile 的 IDE 專案產生器;例如 Eclipse、CodeBlocks 或 KDevelop3 產生器。

交叉編譯複雜專案 - VTK

建置複雜專案是一個多步驟的過程。此處的複雜是指專案使用執行可執行檔的測試,並且建置可執行檔,這些可執行檔稍後會在建置中用於產生程式碼(或類似的東西)。其中一個這樣的專案是 VTK,它使用多個 try_run 測試並建立數個程式碼產生器。在專案上執行 CMake 時,每個 try_run 指令都會產生錯誤訊息;最後在建置目錄中會有一個 TryRunResults.cmake 檔案。您需要瀏覽此檔案的所有項目,並填入適當的值。如果您不確定正確的結果,您也可以嘗試在真實的目標平台上執行測試二進位檔,這些檔案會儲存在二進位目錄中。

VTK 包含數個程式碼產生器,其中一個是 ProcessShader。這些程式碼產生器是使用 add_executable 新增的;get_target_property(LOCATION) 用於取得產生的二進位檔的位置,然後在 add_custom_commandadd_custom_target 指令中使用。由於在建置期間無法執行交叉編譯的可執行檔,因此 add_executable 呼叫會被 if (NOT CMAKE_CROSSCOMPILING) 指令包圍,並且使用帶有 IMPORTED 選項的 add_executable 指令將可執行檔目標匯入專案。這些匯入陳述式位於 VTKCompileToolsConfig.cmake 檔案中,該檔案不需要手動建立,而是由 VTK 的原生建置建立。

為了交叉編譯 VTK,您需要

  • 安裝工具鏈並為 CMake 建立工具鏈檔案。

  • 為建置主機原生建置 VTK。

  • 針對目標平台執行 CMake。

  • 完成 TryRunResults.cmake

  • 使用來自原生建置的 VTKCompileToolsConfig.cmake 檔案。

  • 建置。

因此,首先使用標準程序為建置主機建置一個原生的 VTK。

cd VTK
mkdir build-native; cd build-native
ccmake ..
make

使用 ccmake 確保已啟用所有必要選項;例如,如果目標平台需要 Python 包裝,則必須在 build-native/ 中啟用 Python 包裝。完成此建置後,build-native/ 中將會有一個 VTKCompileToolsConfig.cmake 檔案。如果此步驟成功,我們可以繼續交叉編譯專案,在此範例中是針對 IBM BlueGene 超級電腦。

cd VTK
mkdir build-bgl-gcc
cd build-bgl-gcc
cmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchains/Toolchain-BlueGeneL-gcc.cmake \
      -DVTKCompileTools_DIR=~/VTK/build-native/ ..

這將會針對每個 try_run 產生一個錯誤訊息,以及一個 TryRunResults.cmake 檔案,您必須如上述說明完成該檔案。您應該將此檔案儲存到安全的位置,否則在下次 CMake 執行時會被覆寫。

cp TryRunResults.cmake ../TryRunResults-VTK-BlueGeneL-gcc.cmake
ccmake -C ../TryRunResults-VTK-BlueGeneL-gcc.cmake .
...
make

在第二次執行 ccmake 時,所有其他參數都可以跳過,因為它們現在都已在快取中。可以將 CMake 指向包含 CMakeCache.txt 的建置目錄,這樣 CMake 就會判斷出這是建置目錄。

一些提示與技巧

處理 try_run 測試

為了讓您的專案更容易進行交叉編譯,請盡量避免 try_run 測試,並改用其他方法來測試。如果您無法避免 try_run 測試,請盡量只使用執行的結束代碼,而不要使用程序的輸出。這樣,在交叉編譯時,就不需要為 try_run 測試設定結束代碼以及 stdout 和 stderr 變數。這允許省略 try_runOUTPUT_VARIABLERUN_OUTPUT_VARIABLE 選項。

如果您已經完成這些操作,為目標平台建立並完成正確的 TryRunResults.cmake 檔案,您可以考慮將此檔案新增至專案的原始碼中,以便其他人可以重複使用。這些檔案是針對每個目標平台和每個工具鏈的。

目標平台和工具鏈問題

如果您的工具鏈無法在沒有特殊參數的情況下建置簡單的程式,例如連結器腳本檔案或記憶體佈局檔案,CMake 最初執行的測試將會失敗。為了使其正常運作,CMake 模組 CMakeForceCompiler 提供了以下巨集

CMAKE_FORCE_SYSTEM(name version processor),
CMAKE_FORCE_C_COMPILER(compiler compiler_id sizeof_void_p)
CMAKE_FORCE_CXX_COMPILER(compiler compiler_id).

這些巨集可以在工具鏈檔案中使用,以便預先設定所需的變數並避免 CMake 測試。

UNIX 下的 RPATH 處理

對於原生建置,CMake 預設會使用 RPATH 來建置可執行檔和函式庫。在建置樹中,設定 RPATH 的目的是讓可執行檔可以從建置樹中執行;也就是說,RPATH 指向建置樹。在安裝專案時,CMake 會再次連結可執行檔,這次使用的是安裝樹的 RPATH,預設情況下為空。

在進行交叉編譯時,您可能希望以不同的方式設定 RPATH 處理。由於可執行檔無法在建置主機上執行,因此從一開始就使用安裝 RPATH 來建置它更有意義。有幾個 CMake 變數和目標屬性可以用於調整 RPATH 處理。

set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_INSTALL_RPATH "<whatever you need>")

透過這兩個設定,目標將會使用安裝 RPATH 而不是建置 RPATH 進行建置,這避免了在安裝時再次連結它們的需求。如果您的專案中不需要 RPATH 支援,則不需要設定 CMAKE_INSTALL_RPATH;預設情況下為空。

CMAKE_INSTALL_RPATH_USE_LINK_PATH 設定為 TRUE 對於原生建置很有用,因為它會自動從目標連結的所有函式庫中收集 RPATH。對於交叉編譯,它應該保留在預設的 FALSE 設定,因為在目標平台上,自動產生的 RPATH 在大多數情況下會是錯誤的;它可能會有與建置主機不同的檔案系統佈局。