前言
? 交叉編譯算是每個(gè)嵌入式開發(fā)者都會(huì)經(jīng)歷的一道坎吧,通俗的描述就是搭建Arm板代碼編譯環(huán)境,讓代碼能夠在Arm板子上跑起來。常用到的編譯工具為Makefile和CMake,本篇記錄下CMake的常用技巧。
入門案例:單個(gè)源文件
代碼路徑:
https://gitee.com/LinuxTaoist/DesignMode/tree/master/FactoryMode
工程結(jié)構(gòu)
.
├── CMakeLists.txt
├── abstract_factory.cc
├── factory_method.cc
└── simple_factory.cc
CMakeList
# 指定最低版本
cmake_minimum_required(VERSION 2.8)
## 指定為C++11 版本
set(CMAKE_CXX_STANDARD 11)
## 指定項(xiàng)目名稱
project(FactoryMode)
## 為當(dāng)前路徑以及子目錄的源文件加入由-D預(yù)編譯定義
## add_definitions(-DFOO -DDEBUG ...)
## 編譯工具
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")
## 設(shè)置C++編譯參數(shù)(CMAKE_CXX_FLAGS是全局變量)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11 -g3 -fpermissive")
## 生成bin文件 AbFactory
add_executable(AbFactory abstract_factory.cc)
## 生成bin文件 FacMethod
add_executable(FacMethod factory_method.cc)
## 生成bin文件 SmpFactory
add_executable(SmpFactory simple_factory.cc)
工程編譯
? CMakeList編寫完以后,先執(zhí)行cmake [CMakeList路徑],然后make即可。
$ cmake .
-- The C compiler identification is GNU 7.5.0
-- The CXX compiler identification is GNU 7.5.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /work/src/DesignMode/FactoryMode
$ make
Scanning dependencies of target SmpFactory
[ 16%] Building CXX object CMakeFiles/SmpFactory.dir/simple_factory.cc.o
[ 33%] Linking CXX executable SmpFactory
[ 33%] Built target SmpFactory
Scanning dependencies of target AbFactory
[ 50%] Building CXX object CMakeFiles/AbFactory.dir/abstract_factory.cc.o
[ 66%] Linking CXX executable AbFactory
[ 66%] Built target AbFactory
Scanning dependencies of target FacMethod
[ 83%] Building CXX object CMakeFiles/FacMethod.dir/factory_method.cc.o
[100%] Linking CXX executable FacMethod
[100%] Built target FacMethod
多個(gè)源文件
代碼路徑:
https://gitee.com/LinuxTaoist/DesignMode/tree/master/Proxy
工程結(jié)構(gòu)
? 對于工程中存在大量的文件夾和文件時(shí),一個(gè)CMakeLst雖然可以將其全部編譯,但是維護(hù)起來非常麻煩。
? 對于龐大的代碼架構(gòu)場景,通常會(huì)按模塊劃分,將一個(gè)模塊的代碼放到一個(gè)CMakeList中配置編譯,若模塊代碼還是很多,將此模塊再細(xì)分成多個(gè)小模塊用多個(gè)CMakeList管理編譯。然后將這些CMakeList按照路徑層層嵌套。
? 如此工程中各個(gè)CMakeList樹狀層層嵌套,最終都會(huì)被嵌套至最頂層CMakeList。類似如下結(jié)構(gòu):
Proxy/
├── CMakeLists.txt
├── Client
│ ├── CMakeLists.txt
│ └── main_client.cc
├── Ipc
│ ├── CMakeLists.txt
│ ├── msg_manager.cc
│ └── msg_manager.h
├── Server
│ ├── Api
│ │ ├── common_type.h
│ │ ├── led_manager_proxy.cc
│ │ └── led_manager_proxy.h
│ ├── CMakeLists.txt
│ ├── Led
│ │ ├── led_manager.cc
│ │ └── led_manager.h
│ └── main_server.cc
└── build
└── build.sh
? 其中Proxy下的CMakeList會(huì)包含Client、Ipc、Server中的CMakeList。假設(shè)Server子路徑還有子文件夾,Server的CMakeList就繼續(xù)向下包含。
這么做的優(yōu)點(diǎn)如下:
- 每個(gè)CMakeList職責(zé)清晰。只需專注于當(dāng)前模塊或者當(dāng)前路徑的源碼編譯。
- 方便模塊化編譯管理。當(dāng)不需要編譯哪個(gè)模塊時(shí),只需在頂層CMakeList屏蔽包含指定路徑CMakeList即可。
- 便于維護(hù)。每個(gè)CMakeList的代碼量都比較少,且功能明確,維護(hù)者一眼就能看懂。
CMakeList
- 頂層CMakeList
# 指定最低版本
cmake_minimum_required(VERSION 2.8)
## 指定為C++11 版本
set(CMAKE_CXX_STANDARD 11)
## 指定項(xiàng)目名稱
project(ProxyMode)
## 為當(dāng)前路徑以及子目錄的源文件加入由-D預(yù)編譯定義
## add_definitions(-DFOO -DDEBUG ...)
## 編譯工具
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")
## 設(shè)置C++編譯參數(shù)(CMAKE_CXX_FLAGS是全局變量)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11 -g3 -fpermissive")
## 包含子路徑
add_subdirectory(Server)
add_subdirectory(Client)
add_subdirectory(Ipc)
頂層CMakeList一般需要做如下事項(xiàng):
① 配置工程相關(guān)的屬性:使用CMake版本、工程名
② 配置交叉工具:設(shè)置編譯器、增加編譯參數(shù)
③ 包含需要嵌套的子路徑CMakeList
- Server路徑 CMakeList
## 指定最低版本
## 指定最低版本
cmake_minimum_required(VERSION 2.8)
## 宏
set(PROJECT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/..)
cmake_policy(SET CMP0046 NEW)
## 輸出路徑
set(OUTPUT_PATH ${PROJECT_PATH}/Out)
set(LIBRARY_OUTPUT_PATH ${OUTPUT_PATH}/lib)
set(EXECUTABLE_OUTPUT_PATH ${OUTPUT_PATH}/bin)
## includes
include_directories(${PROJECT_PATH}/Ipc)
include_directories(${PROJECT_PATH}/Server/Api)
## 庫路徑
link_directories(${OUTPUT_PATH}/lib)
## main_server.exe
set(SRC_BIN_CLIENT main_client.cc)
add_executable (client ${SRC_BIN_CLIENT})
set_target_properties(client PROPERTIES OUTPUT_NAME "mainclient")
target_link_libraries(client c pthread ledapi)
add_dependencies (client libledapi)
子路徑下的CMakeList需要關(guān)心編譯文件:
① 包含頭文件路徑
② 設(shè)置目標(biāo)生成路徑
③ 設(shè)置編譯目標(biāo),bin或so
然后就是根據(jù)預(yù)期編譯的結(jié)果,使用相關(guān)的變量即可。例子中,為了方便執(zhí)行,增加了build.sh編譯腳本。這個(gè)腳本代替執(zhí)行編譯命令,同時(shí)將編譯生成的緩存文件放到指定路徑管理。
## buid.sh
rm -rf ../Out/Cache
rm -rf ../Out/bin/*
rm -rf ../Out/lib/*
mkdir -p ../Out/cache/
cd ../Out/cache/
cmake ../../
make
其他用法
設(shè)置局部變量
## 設(shè)置局部變量PROJECT_PATH
set(PROJECT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/..)
## 使用局部變量PROJECT_PATH
include_directories(${PROJECT_PATH}/Ipc)
設(shè)置自定義全局變量
## Proxy/CMakelists.txt
## 設(shè)置自定義全局變量 PROJECT_DESC
set(PROJECT_DESC "This is project")
set_property(GLOBAL PROPERTY source_list_property "${PROJECT_DESC}")
獲取自定義全局變量
## Proxy/Ipc/CMakeLists.txt
## 獲取自定義全局變量 PROJECT_DESC
get_property(PROJECT_DESC GLOBAL PROPERTY source_list_property)
message("PROJECT_DESC=${PROJECT_DESC}")
指定目標(biāo)(bin/庫)輸出路徑
## 設(shè)置庫輸出路徑
set(LIBRARY_OUTPUT_PATH xx/Out/lib)
## 設(shè)置bin文件輸出路徑
set(EXECUTABLE_OUTPUT_PATH xx/Out/bin)
設(shè)置環(huán)境變量
set(ENV{<variable>} [<value>])
ENV:環(huán)境變量標(biāo)志性前綴
variable:變量名稱
value:變量值
E.g 設(shè)置環(huán)境 CMAKE_FILE
## 設(shè)置環(huán)境變量
set(ENV{CMAKE_FILE} "./IPC")
獲取環(huán)境變量
# 判斷CMAKE_FILE環(huán)境變量是否定義
if(DEFINED ENV{CMAKE_FILE})
message("CMAKE_FILE: $ENV{CMAKE_FILE}")
else()
message("NOT DEFINED CMAKE_FILE VARIABLES")
endif()
設(shè)置編譯器
## 指定C編譯工具
set(CMAKE_C_COMPILER "gcc")
## 指定C++編譯工具
set(CMAKE_CXX_COMPILER "g++")
當(dāng)編譯工具鏈路徑被加到環(huán)境變量中,可以直接寫編譯工具的名稱。在配交叉編譯工具時(shí),此處應(yīng)寫對應(yīng)交叉編譯工具鏈的絕對路徑。
設(shè)置依賴庫路徑
## 括號為依賴庫的絕對路徑
link_directories(${OUTPUT_PATH}/lib)
包含頭文件路徑
## 括號為包含頭文件的絕對路徑
include_directories (${PROJECT_PATH}/Ipc)
添加編譯器編譯選項(xiàng)
## 針對所有編譯器,開啟編譯警告 (包括C、C++編譯器)
add_compile_options("-Wall -Werror")
## 針對C編譯器,開啟編譯警告
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
## 針對C++編譯,開啟編譯警告
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
添加打印
## 打印CMAKE_CXX_FLAGS的值
message("${CMAKE_CXX_FLAGS}")
CMakeLists路徑嵌套
## 添加當(dāng)前路徑Client文件
add_subdirectory(Client)
控制編譯流程
option 語法
## option編譯流程控制
option(<variable> "<help_text>" [value])
variable 選項(xiàng)名
help_text 描述、解釋、備注
value 選項(xiàng)初始化值(除ON而外全為OFF)
option(TEST_OPTION "test opiton" ON)
if (DEFINED TEST_OPTION)
message(STATUS "TEST_OPTION defined: " ${TEST_OPTION})
else ()
message(STATUS "TEST_OPTION un-defined: " ${TEST_OPTION})
endif()
if (TEST_OPTION)
message(STATUS "TEST_OPTION ON.")
add_definitions(-DTEST_OPTION)
else ()
message(STATUS "TEST_OPTION OFF.")
endif()
if (NOT TEST_OPTION)
message(STATUS "NOT-TEST_OPTION ON.")
else ()
message(STATUS "NOT-TEST_OPTION OFF.")
endif()
Shell腳本傳遞宏至CMakeList
命令行執(zhí)行cmake時(shí),跟隨-DXXX,即可從命令行傳遞宏XXX至CMakeList。將此命令行寫入腳本,便能實(shí)現(xiàn)Shell腳本傳遞宏至CMakeList。
## 增加TEST宏
cmake . -DTEST
## 增加TEST_OPTION=ON
cmake . -DTEST_OPTION=ON
CMakeLists傳遞變量至代碼工程
## 向代碼工程添加TEST宏
add_definitions(-DTEST)
代碼判斷宏TEST是否有定義,實(shí)現(xiàn)宏控
// *.c / *.cpp
#ifdef TEST
... // code
#endif
#if defined TEST
... // code
#endif
編譯警告
CMake編譯警告和報(bào)錯(cuò)設(shè)置
gcc本身設(shè)置了一些編譯告警/報(bào)錯(cuò)選項(xiàng),歸類如下:
-Werror:
-Werror=xxx,表示將xxx的warning變?yōu)閑rror,例如-Werror=select,-Werror=return-type-Wall:激活所有的warnings
-Wextra:激活不在
-Wall所在的warning的其它warnings-Wpedantic: 對于所有不符合 ISO C/ISO C++ 語言標(biāo)準(zhǔn)的源代碼發(fā)出警告,等價(jià)于
-pedantic。
-pedantic-errors參數(shù)將這些警告視為錯(cuò)誤,等同于-Werror=pedantic。-Wconversion: 在隱式轉(zhuǎn)換可能導(dǎo)致值變化的時(shí)候發(fā)出警告。在隱式轉(zhuǎn)換的時(shí)候,如果值發(fā)生變化,那么結(jié)果可能就不是預(yù)料中的,所以最好使用顯式轉(zhuǎn)換。
-Wshadow:激活遮蔽(如兩個(gè)嵌套的for循環(huán)都用變量i做index)類型的warning,即:
-Wshadow=global:激活任意類型的遮蔽;
-Wshadow=local:激活local變量的遮蔽(如兩個(gè)嵌套的for循環(huán)都用變量i做index);
-Wshadow=compatible-local:激活local變量的遮蔽,考慮變量類型(如上例中的i在內(nèi)外兩層的for循環(huán)中是不同的類型);
E.g 打開所有編譯告警,并視警告為錯(cuò)誤,出現(xiàn)任何警告放棄編譯
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror")
常用警告
GCC編譯器支持對代碼進(jìn)行診斷,針對代碼本身不是錯(cuò)誤但是疑似錯(cuò)誤或者可能存在風(fēng)險(xiǎn)的地方發(fā)出警告,而警告編譯選項(xiàng)就是用于控制需要告警的警告類型的。常見告警如下:
- -Wall
這是一個(gè)非常常用的編譯選項(xiàng),用于啟用一批比較常見且易于修改的警告,這些選項(xiàng)都是對代碼進(jìn)行基本的檢查,比如下面這些:
| 選項(xiàng) | 作用 |
|---|---|
| -Waddress | 檢查是否存在可疑的內(nèi)存地址使用 |
| -Wformat | 檢查標(biāo)準(zhǔn)庫函數(shù)的使用格式是否正確,比如printf的格式化字符串中的格式符和對應(yīng)的參數(shù)是否匹配 |
| -Wunused-function | 對已聲明但是未定義的靜態(tài)函數(shù)和未被使用的非內(nèi)聯(lián)靜態(tài)函數(shù)發(fā)出警告 |
| -Wswitch | 當(dāng)用switch用于枚舉類型時(shí),判斷分支是否包含所有枚舉值,否則發(fā)出警告 |
| -Wunused-variable | 對聲明但未被使用的變量發(fā)出警告 |
| -Wunused-but-set-variable | 對聲明且被賦值但未被使用的變量發(fā)出警告 |
| -Warray-bounds=1 | 數(shù)組越界檢查,需啟用選項(xiàng)-ftree-vrp |
完整列表參考 Warning-Options
注:當(dāng)需要排除某些類型的警告,使用-Wno-xxx。 比如使用-Wall -Wno-unused-variable可以從-Wall中排除-Wunused-variable。
- -Wextra
單單只有-Wall可能還不夠嚴(yán)格,GCC還有-Wextra作為補(bǔ)充,包括另外一些沒有被-Wall包含的警告類型,譬如:
| 選項(xiàng) | 作用 |
|---|---|
| -Wcast-function-type | 當(dāng)函數(shù)被強(qiáng)轉(zhuǎn)為不兼容的函數(shù)指針時(shí)發(fā)出警告 |
| -Wempty-body | 當(dāng)存在空的if、else或者do while語句時(shí)發(fā)出警告 |
| -Wunused-parameter | 當(dāng)函數(shù)有未被使用的參數(shù)時(shí)發(fā)出警告,需配合-Wall |
| -Wunused-but-set-parameter | 當(dāng)存在被設(shè)置但是未被使用的參數(shù)發(fā)出警告,需配合-Wall |
| -Wsign-compare | 當(dāng)比較有符號和無符號值時(shí)發(fā)出警告 |
配置交叉編譯環(huán)境常需要的修改
設(shè)置默認(rèn)庫和頭文件搜索路徑
編譯默認(rèn)會(huì)從/usr/include目錄中搜索頭文件、從/usr/lib中搜索依賴庫。當(dāng)設(shè)置了CMAKE_SYSROOT后,則會(huì)從xxx/usr/include搜索頭文件、從xxx/usr/lib中搜索依賴庫。
## 系統(tǒng)庫路徑:${SDKTARGETSYSROOT}/usr/lib
## 系統(tǒng)頭文件:${SDKTARGETSYSROOT}/usr/include
set(CMAKE_SYSROOT "${SDKTARGETSYSROOT}")
設(shè)置交叉編譯工具鏈
Linux系統(tǒng)在嵌入式板子上運(yùn)行,需要與嵌入式板配套的交叉編譯工具鏈編譯。
同樣的,個(gè)人代碼也需要與編譯Linux配套的交叉工具編譯,才能在Linux環(huán)境運(yùn)行。一般在Ubuntu上編譯運(yùn)行,只需要設(shè)置為gcc/g++即可。
編譯工具鏈都是由廠商提供,用戶只需要在編譯腳本配置即可。設(shè)置交叉編譯工具鏈方式如下:
## 絕對路徑
set(CMAKE_C_COMPILER "xxx/arm-linux-gcc")
set(CMAKE_CXX_COMPILER "xxx/arm-linux-g++")
設(shè)置浮點(diǎn)運(yùn)算處理方式
在某些gcc編譯器會(huì)檢查軟浮點(diǎn)和硬浮點(diǎn)設(shè)置,報(bào)錯(cuò)log如下:
armv7at2hf-neon-poky-linux-gnueabi/usr/include/gnu/stubs-32.h:7:11: fatal error: gnu/stubs-soft.h: No such file or directory
7 | # include <gnu/stubs-soft.h>
初步看報(bào)錯(cuò)log,是因?yàn)榫幾g器沒有文件stubs-soft.h。猜測可能此編譯器不支持軟浮點(diǎn)運(yùn)算?
解決方法是在編譯腳本將其設(shè)置為硬浮點(diǎn)運(yùn)算,如下方式:
## 第一種
add_definitions("-mfloat-abi=hard -mfpu=neon")
## 第二種
add_compile_options(-mfpu=neon -mfloat-abi=hard)
## 第三種
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfloat-abi=hard -mfpu=neon")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=hard -mfpu=neon")
常見場景
編譯動(dòng)態(tài)庫
## 生成libtest.so
### 添加源碼路徑
aux_source_directory (xxx/src SRC_LIB_TEST)
### 生成so庫
add_library (libtest SHARED ${SRC_LIB_TEST})
### 指定生成目標(biāo)名libtest.so
set_target_properties(libtest PROPERTIES OUTPUT_NAME "test")
### 鏈接語言C++
set_target_properties(libtest PROPERTIES LINKER_LANGUAGE CXX)
### 鏈接依賴庫
target_link_libraries(libtest stdc++)
### 添加依賴庫,會(huì)先檢查依賴庫是否生成
add_dependencies (libtest libstdc++)
編譯靜態(tài)庫
## 生成libtest.a
aux_source_directory (xxx/src SRC_LIB_TEST)
add_library (libtest STATIC ${SRC_LIB_TEST})
set_target_properties(libtest PROPERTIES OUTPUT_NAME "test")
set_target_properties(libtest PROPERTIES LINKER_LANGUAGE CXX)
target_link_libraries(libtest stdc++)
add_dependencies (libtest libstdc++)
編譯可執(zhí)行文件
## 生成test bin
include_directories (${PROJECT_SOURCE_DIR}/inc)
set(SRC_BIN_TEST ${PROJECT_SOURCE_DIR}/src/test.cpp)
add_executable (test ${SRC_BIN_TEST})
set_target_properties(test PROPERTIES OUTPUT_NAME "test")
set_target_properties(test PROPERTIES LINK_FLAGS "-Wl,-rpath-link=${LIBRARY_OUTPUT_PATH}")
target_link_libraries(test stdc++)
add_dependencies (test libstdc++)