CMake的使用,以及如何將一個項目移植到Android。
CMake的用法
先讓我們簡單學習回顧一下cmake的基本知識:
基本流程
以linux平臺爲例,使用 CMake 生成 Makefile 並編譯的流程如下:
- 編寫 CMake 配置文件 CMakeLists.txt 。
- 執行命令 cmake Path-to-Cmakelist/CMakeLists.txt 生成 Makefile。
- 使用 make 命令進行編譯。
語法
CMakeLists文件可以包含comments,commands,以及 空行。
註釋以#開頭
command:包含命令名字,括號,用空格分開的參數 comand(arg1 arg2 …)
所有的空白行都會被忽略。
基本的命令
- project
project:用來聲明項目的名字,也可以指定項目的開發語言
project(projectname [cxx] [c] [java] [none])
如果沒有指定語言,那麼CMake默認指定爲c和c++
在每一個project出現的CMakeLists.txt,CMake都會創建一個top level的IDE project文件。這個project會包括出現在該CMakeLists.txt中的所有的targets,以及它所有的subdirecotry, 用add_subdirectory命令來指定。
-
set
set命令用來定義或者修改變量或者lists
和set對應的remove命令 -
add_executable / add_library
用來定義what executable/libs to build, 並且指定source 文件。
另外一些命令可以從這裏找到:
https://cmake.org/cmake/help/v3.0/manual/cmake-variables.7.html
https://cmake.org/Wiki/CMake_Useful_Variables
關於cmake在普通項目中的用法,請參考這篇文章:cmake入門實踐
交叉編譯
現在移動開發越老越火,我們免不了會將一些項目porting到Android/iOS平臺,這個時候就要用到交叉編譯:即在你host宿主機上(例如你用的Ubuntu電腦)要生成target目標機(例如Android手機)的程序。在編譯的過程中會涉及到相關頭文件的切換和編譯器的選擇以及環境變量的改變等,今天就來看看CMake是如何來做交叉編譯的。
首先需要明確以下幾點:
- CMake不能自動判斷出目標機系統,需要我們指定。
- 一般情況下Build出來的可執行文件是不能直接運行在宿主機上。
- 編譯過程中不能用宿主機上的原聲頭文件和庫,而是需要用到一套不同的頭文件和庫。
CMAKE_TOOLCHIAIN_FILE
這個應該是CMake交叉編譯中最重要的概念。正如前面提到過的,CMake不知道你的目標平臺是什麼、用什麼編譯起、如何編譯等等,所以你需要提供預設一些變量到CMake,其中最爲方便的一個方法就是將相關的變量設置都放進一個文件(cmake腳本)中去,然後將該文件通過CMAKE_TOOLCHIAIN_FILE傳遞給CMake, 例如:
cmake -D CMAKE_TOOLCHIAIN_FILE="/path/to/my-cmake-toolchain-file" ..
此處我們假設要執行的CMakeLists file在上一級目錄中。
下面將要在這個文件中需要設置的幾個重要的變量分兩大類依次介紹一下。
設置目標系統以及Toolchain
- CMAKE_SYSTEM_NAME:
在toolchain腳本中必須要設置的變量,只有當CMAKE_SYSTEM_NAME這個變量被設置了,CMake才認爲此時正在交叉編譯,它會額外設置一個變量CMAKE_CROSSCOMPILING爲TRUE。
CMAKE_SYSTEM_NAME即目標機target所在的操作系統名稱,比如ARM或者Linux你就需要寫”Linux”,如果Android平臺你就寫”Android”,如果你的嵌入式平臺沒有相關OS你即需要寫成”Generic”.
- CMAKE_SYSTEM_PROCESSOR:
這個是可選項,但是在移動開發中很重要,代表目標系統的硬件或者CPU型號,例如ARM,X86 etc。
- CMAKE_C_COMPILER:
即C語言編譯器,這裏可以將變量設置成完整路徑或者文件名
- CMAKE_CXX_COMPILER:
C++編譯器。
搜索查找外部依賴
稍微大一點的項目都會用到一些外部依賴庫或者tool,CMake提供了FIND_PROGRAM(), FIND_LIBRARY(), FIND_FILE(), FIND_PATH() and FIND_PACKAGE() 等命令來進行外部依賴的搜索查找。
但是有個問題,假如我們在給一個ARM處理器的移動設備做交叉編譯,其中需要尋找libjpeg.so,假如FIND_PACKAGE(JPEG) 返回的是/usr/lib/libjpeg.so,那麼這就會有問題,因爲找到的這個so庫只是給你的宿主機系統(例如一個x86的Ubuntu主機)服務的,不能用於Arm系統。所以你需要告訴CMake去其它地方去查找,這個時候你就咬配置以下的變量了:
- CMAKE_FIND_ROOT_PATH:
代表了一系列的相關文件夾路徑的根路徑的變更,比如你設置了/opt/arm/,所有的Find_xxx.cmake都會優先根據這個路徑下的/usr/lib,/lib等進行查找,然後纔會去你自己的/usr/lib和/lib進行查找.
- CMAKE_FIND_ROOT_PATH_MODE_PROGRAM:
對FIND_PROGRAM()起作用,有三種取值,NEVER,ONLY,BOTH,第一個表示不在你CMAKE_FIND_ROOT_PATH設置的目錄下進行查找,第二個表示只在這個路徑下查找,第三個表示先查找這個路徑,再查找全局路徑
- CMAKE_FIND_ROOT_PATH_MODE_LIBRARY
對FIND_LIBRARY()起作用,表示在鏈接的時候的庫的相關選項,因此這裏需要設置成ONLY來保證我們的庫是在交叉環境中找的.
一個小例子
附上一個CMake官方文檔中的toolchian file的小例子,這樣我們就會對如何寫toolchain文件有了感性認識:
# this one is important
SET(CMAKE_SYSTEM_NAME Linux)
#this one not so much
SET(CMAKE_SYSTEM_VERSION 1)
# specify the cross compiler
SET(CMAKE_C_COMPILER /opt/eldk-2007-01-19/usr/bin/ppc_74xx-gcc)
SET(CMAKE_CXX_COMPILER /opt/eldk-2007-01-19/usr/bin/ppc_74xx-g++)
# where is the target environment
SET(CMAKE_FIND_ROOT_PATH /opt/eldk-2007-01-19/ppc_74xx /home/alex/eldk-ppc74xx-inst)
# search for programs in the build host directories
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# for libraries and headers in the target directories
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
執行起來也很簡單,如下:
~/src$ cd build
~/src/build$ cmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchain-eldk-ppc74xx.cmake ..
實戰:CMake項目的Android porting
接下來我們談談Androi平臺上的交叉編譯,google爲我們提供了NDK用來做Native(C/C++)的build, 這其中包括編譯器,debugger等。
所以我們如果想用CMake來交叉編譯Android的native庫或者應用的時候,需要做以下的事情:
-
指明target平臺爲Android:
set( CMAKE_SYSTEM_NAME Android )
- 從ndk安裝目錄搜尋依賴的庫和頭文件。
- 用ndk提供的編譯器。
- 指明ndk level version
- 指明目標硬件是ARM還是X86.
- …
可以看到,一大堆的東西需要去設置,幸好,我們有Android-cmake項目可以幫忙,它幫我們設置了大部分的事情,我們需要做的就是指定cmake的toolchain file爲這個項目裏面的android.toolchain.cmake文件,然後設置好NDK路徑即可:
# Usage Linux:
# $ export ANDROID_NDK=/absolute/path/to/the/android-ndk
# $ mkdir build && cd build
# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake ..
# $ make -j8
如果有時間的話,我會寫一個實際項目中的例子來看看如何將一個項目通過cmake遷移到Android。