來源:公衆號【很酷的程序員/RealCoolEngineer】
交叉編譯指的在一個平臺上生成另一個平臺上的可執行代碼。很多時候,開發的代碼可能並不只是在開發主機的平臺上執行,比如在Windows下開發的程序,希望能夠在Linux、或者MacOS下執行;或者有時候目標平臺根本就沒有操作系統,沒有對應的編譯器,所以必然需要進行交叉編譯。
交叉編譯筆者在工作中經常會遇到。筆者開發使用的宿主機一般都是Linux(Ubuntu),交叉編譯的目的基本都是爲了將程序代碼編譯成目標嵌入式平臺的庫文件,然後再在目標系統上調用集成,一般都是ARM系列的CPU。而對於不同操作系統(Windows、Linux、MacOS)之間的交叉編譯,則主要是爲了編譯示例程序,方便在不同操作系統上進行展示或者調試。
一 編譯和CMake工具鏈
就像在前面的文章GCC編譯過程概述中說的一樣,目標文件的編譯構建,其實是有很多個階段的,每個階段會使用不同的工具,比如特定語言的編譯器、鏈接器、庫文件操作工具等等,這也是GCC
被稱爲編譯工具套件的原因。
一般情況下,對於GCC
來說,只需要使用命令行工具gcc
或者g++
就可以完成大部分工作,很多步驟都是被封裝好的,套件內的不同工具會被適時地自動調用。
在CMake中,也是類似的概念,這些編譯構建工具統稱爲工具鏈(toolchain),工具鏈基於不同的語言有不同配置,一般情況下,CMake會根據宿主機自動選擇應該使用的工具鏈。
在進行交叉編譯時,需要顯式指定一個工具鏈文件,指明要使用的編譯器、編譯器的配置選項以及其他必要工具的路徑。
1 工具鏈文件
工具鏈文件(toolchain file)習慣上一般以.toolchain.cmake
結尾。
工具鏈文件的編寫並不複雜,一般只需要指定以下這些CMake內置變量:
-
CMAKE_SYSTEM_NAME:表明目標系統名稱,比如
Linux
、QNX
、Android
等;如果目標平臺並沒有系統,則應該指定爲Generic
;默認值爲CMAKE_HOST_SYSTEM_NAME
; -
CMAKE_SYSTEM_PROCESSOR:表明目標平臺架構,比如最常見的
arm
; -
CMAKE_<LANG>_COMPILER:表明特定語言的要採用的編譯器,需要使用完整路徑,一般需要設置
C語言
編譯器變量CMAKE_C_COMPILER
和C++
編譯器變量CMAKE_CXX_COMPILER
; -
CMAKE_<LANG>_FLAGS:設置特定編譯器對應的編譯選項,也可以使用
add_compile_options
爲所有編譯器設置相同的一些編譯選項。
還有一些是可選的參數,根據目標平臺的不同按需設置即可,大多數時候,cmake執行的時候會報錯提示的~
下面是一個使用arm-none-eabi-gcc
編譯器的工具鏈文件示例:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(TOOLS /home/farmer/gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux)
set(CMAKE_C_COMPILER "${TOOLS}/bin/arm-none-eabi-gcc")
set(CMAKE_CXX_COMPILER "${TOOLS}/bin/arm-none-eabi-g++")
set(CMAKE_AR "${TOOLS}/bin/arm-none-eabi-gcc-ar")
add_compile_options(-mthumb -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -g -Os -MMD -MP -Wall)
對於編譯選項,需要結合項目實際開發情況。對於arm
平臺,比較重要的幾個選項包括:
-mcpu
-mfpu
-mfloat-abi
其他更多參數可以在GCC官網或者對應編譯器官方查看。
這裏是假定知道了arm-none-eabi-gcc
所在路徑,對於團隊協作可能並不是很友好,寫死的路徑在不同開發者的機器上可能不一樣。當然,對於編譯都只在服務器進行的情況就沒有關係了。
如果開發者將編譯套件對應可執行文件目錄添加到系統環境變量,那麼也可以直接設置對應的編譯器可執行文件名稱,比如:
set(CMAKE_C_COMPILER "arm-none-eabi-gcc")
set(CMAKE_CXX_COMPILER "arm-none-eabi-g++")
也可以通過find_program
在系統路徑下查找及指定的路徑下查找對應的編譯器可執行文件,再進行設置:
find_program(C_COMPILER "arm-none-eabi-gcc" ${SEARCH_PATHS})
find_program(CXX_COMPILER "arm-none-eabi-g++" ${SEARCH_PATHS})
set(CMAKE_C_COMPILER ${C_COMPILER})
set(CMAKE_CXX_COMPILER ${CXX_COMPILER})
二 CMake交叉編譯
在需要進行交叉編譯的時候,先編寫一個適用於目標平臺的工具鏈文件,然後在執行cmake
命令開始構建時,可以使用參數--toolchain
或者-DCMAKE_TOOLCHAIN_FILE=
指定工具鏈文件的路徑即可:
cmake ... --toolchain <path/to/toolchain-file>
# or
cmake ... -DCMAKE_TOOLCHAIN_FILE=<path/to/toolchain-file>
所以交叉編譯是比較簡單的,一般只需要知道使用什麼編譯套件以及對應的配置。
如果是交叉編譯給Android平臺使用的庫,直接使用Android官方NDK中提供的toolchain就好。
使用Android toolchain路徑一定要通過NDK所在目錄指定:
<NDK>/build/cmake/android.toolchain.cmake
,因爲它會使用相對路徑訪問NDK內部其他的資源,否則會出錯。