在Android Studio 2.2開始旭愧,正式支持cmake編譯颅筋,在與android studio結(jié)合之前,cmake就已經(jīng)作為一個(gè)廣泛使用的構(gòu)建系統(tǒng)输枯,應(yīng)用在許多項(xiàng)目中议泵。通過cmake與ndk,我們可以將c/c++源碼編譯成動(dòng)/靜態(tài)庫桃熄、可執(zhí)行程序等先口,非常的方便。
認(rèn)識(shí)CMake
在使用cmake之前瞳收,我們需要先了解一下cmake碉京,最直接的了解方式是官網(wǎng)。當(dāng)然還有tutorial最好需要看一下螟深,這樣你就能大概理解cmake的一些用法谐宙。如果你還不了解c/c++的編譯過程,請自行百度學(xué)習(xí)界弧,不在本文敘述范圍內(nèi)凡蜻。
CMake的基本操作
在看過官網(wǎng)的資料和tutorial之后搭综,我們需要?jiǎng)邮謱?shí)操一下,如果使用cmake進(jìn)行編譯划栓,這樣我們才能更好的掌握cmake的一些用法兑巾。對于我們后續(xù)cmake與ndk的結(jié)合有莫大的幫助。我們這里通過clion作為ide進(jìn)行demo的學(xué)習(xí)忠荞。
創(chuàng)建可執(zhí)行程序
# 要求最低的cmake版本
cmake_minimum_required(VERSION 3.14)
# 工程名字 工程語言
project(cmakedemo C)
# 設(shè)置cmake c 的標(biāo)準(zhǔn)c99
set(CMAKE_C_STANDARD 99)
# 將main.c文件加入到可執(zhí)行程序cmakedemo中
add_executable(cmakedemo main.c)
add_executable
第一個(gè)參數(shù)是生成可執(zhí)行程序名稱蒋歌,第二個(gè)參數(shù)是源碼文件,如果有多個(gè)源碼文件钻洒,依次加入奋姿,用空格隔開。最后會(huì)生成一個(gè)cmakedemo
可執(zhí)行程序素标。當(dāng)然称诗,我們?nèi)粘J褂眠^程中,更多的是將源碼編譯成動(dòng)/靜態(tài)庫头遭。
目錄結(jié)構(gòu)如下:
多源文件編譯
# 要求最低的cmake版本
cmake_minimum_required(VERSION 3.14)
# 工程名字 工程語言
project(cmakedemo C)
# 設(shè)置cmake c 的標(biāo)準(zhǔn)c99
set(CMAKE_C_STANDARD 99)
# 方法一:將多個(gè)文件加入到可執(zhí)行程序中編譯
# 將main.c MathFunctions.c文件加入到可執(zhí)行程序cmakedemo中
#add_executable(cmakedemo2 main.c MathFunctions.c)
# 方法二:采用目錄形式
# 將當(dāng)前目錄下的文件寓免,都保存在DIR_SRCS變量中
aux_source_directory(. DIR_SRCS)
# 將變量代表的文件路徑加入到可執(zhí)行程序中編譯
add_executable(cmakedemo2 ${DIR_SRCS})
在CMakeLists.txt
文件中,我們有兩種方式將多個(gè)源碼文件加入到編譯计维,方法一將MathFunction.c
文件放在add_executable
最后袜香,并用空格隔開。方法二中鲫惶,我們用了一個(gè)aux_source_directory
蜈首,第一個(gè)參數(shù)表示搜尋的目錄,第二個(gè)參數(shù)DIR_SRCS
表示將目錄下的文件欠母,表示成變量欢策,并在下面應(yīng)用。最后在add_executable
中加入給變量${DIR_SRCS}
赏淌。
目錄結(jié)構(gòu)如下:
多級(jí)目錄
我們源碼的目錄結(jié)構(gòu)踩寇,不會(huì)前兩個(gè)例子中,都在同一級(jí)目錄下六水,經(jīng)常我們的源碼有多級(jí)目錄俺孙。這時(shí)候我們應(yīng)該怎么編譯呢?多級(jí)目錄我們也有兩種編譯方式掷贾。
# 要求最低的cmake版本
cmake_minimum_required(VERSION 3.14)
# 工程名字 工程語言
project(cmakedemo C)
# 設(shè)置cmake c 的標(biāo)準(zhǔn)c99
set(CMAKE_C_STANDARD 99)
## 方法一:
## 將當(dāng)前目錄下的文件睛榄,都保存在DIR_SRCS變量中
#aux_source_directory(. DIR_SRCS)
## 將math目錄下的文件,都保存在DIR_MATH_SRCS變量中
#aux_source_directory(./math DIR_MATH_SRCS)
## 將變量代表的文件路徑加入到可執(zhí)行程序中編譯
#add_executable(cmakedemo3 ${DIR_SRCS} ${DIR_MATH_SRCS})
# 方法二:
# 將當(dāng)前目錄下的文件想帅,都保存在DIR_SRCS變量中
aux_source_directory(. DIR_SRCS)
# 將math目錄加入編譯
add_subdirectory(math)
# 將變量代表的文件路徑加入到可執(zhí)行程序中編譯
add_executable(cmakedemo3 ${DIR_SRCS})
# 添加鏈接庫
target_link_libraries(cmakedemo3 MathFunctions)
## 方法三:
## 將當(dāng)前目錄下的源碼懈费,都保存在DIR_SRCS變量中
#aux_source_directory(. DIR_SRCS)
## 將math目錄下的文件,都保存在DIR_MATH_SRCS變量中
#aux_source_directory(./math DIR_MATH_SRCS)
## 將DIR_MATH_SRCS保存的文件博脑,都編譯進(jìn)入靜態(tài)庫libMathFunctions.a
#add_library(MathFunctions ${DIR_MATH_SRCS})
## 將變量代表的文件路徑加入到可執(zhí)行程序中編譯
#add_executable(cmakedemo3 ${DIR_SRCS})
## 添加鏈接庫
#target_link_libraries(cmakedemo3 MathFunctions)
方法一將來自目錄中的源文件憎乙,保存成DIR_MATH_SRCS
變量,然后在add_executable中應(yīng)用即可叉趣,這樣就把多級(jí)目錄下的源碼都加入到構(gòu)建中泞边。方法二使用add_subdirectory
將math
子目錄加入編譯,這時(shí)候math
中的CMakeLists.txt
文件和源碼也將作為一個(gè)編譯子目錄進(jìn)行處理疗杉。target_link_libraries
指定cmakedemo3
可執(zhí)行程序?qū)㈡溄?code>MathFunctions庫阵谚,MathFunctions
庫將在math子目錄中生成。
在math
子目錄CMakeLists.txt
如下:
aux_source_directory(. DIR_MATH_SRCS)
add_library(MathFunctions ${DIR_MATH_SRCS})
add_library
表示將默認(rèn)生成libMathFunctions.a
靜態(tài)庫烟具。
目錄結(jié)構(gòu)如下:
方法三是將子目錄中的源碼梢什,都編譯成libMathFuntions.a
,最后同樣的將靜態(tài)庫鏈接到目標(biāo)可執(zhí)行程序中,與方法二的區(qū)別是在通過一個(gè)CMakeLists.txt文件朝聋,就可以將靜態(tài)庫的編譯包含在內(nèi)嗡午,無需像方法二一樣在./math
目錄下寫一份CMakeLists.txt文件用于專門編譯靜態(tài)庫。方法二和方法三各自有各自的好處冀痕,方法二更適合單模塊編譯荔睹,可以將某個(gè)目錄下的源文件作為一個(gè)模塊來編譯,適合龐大的目錄結(jié)構(gòu)與模塊層級(jí)編譯言蛇。
自定義編譯選項(xiàng)
# 要求最低的cmake版本
cmake_minimum_required(VERSION 3.14)
# 工程名字 工程語言
project(cmakedemo C)
# 設(shè)置cmake c 的標(biāo)準(zhǔn)c99
set(CMAKE_C_STANDARD 99)
# 加入一個(gè)配置頭文件僻他,用于處理 CMake 對源碼的設(shè)置
configure_file(
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
# 設(shè)置USE_LOCALMATH打開
option(USE_LOCALMATH "TRUE USE LOCAL MATH LIBRARY" OFF)
if (USE_LOCALMATH)
include_directories("${PROJECT_SOURCE_DIR}/math")
add_subdirectory(math)
endif (USE_LOCALMATH)
aux_source_directory(. DIR_SRCS)
# 將變量代表的文件路徑加入到可執(zhí)行程序中編譯
add_executable(cmakedemo4 ${DIR_SRCS})
# 添加鏈接庫
if (USE_LOCALMATH)
target_link_libraries(cmakedemo4 MathFunctions)
endif (USE_LOCALMATH)
在這里我們加入了一個(gè)config.h.in
文件,這個(gè)文件腊尚,主要用來預(yù)定義宏吨拗,通過config.h.in
,在編譯之后可以生成config.h
文件婿斥。config.h.in
文件內(nèi)容如下:
#cmakedefine USE_LOCALMATH
這里我們還使用到了option
劝篷,主要是為了在進(jìn)行編譯時(shí),在CMakeLists.txt同級(jí)目錄下受扳,通過ccmake .
携龟,來進(jìn)行USE_LOCALMATH變量的選擇,是否打開
我們看到勘高,最后有一個(gè)
USE_LOCALMATH
變量峡蟋,可以用過enter鍵來選擇ON或者OFF,如果是ON华望,那么在生成的config.h中蕊蝗,預(yù)定義宏被打開,如下:
#define USE_LOCALMATH
在main.c
中赖舟,我們就能夠使用該宏定義了
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "config.h"
#ifdef USE_LOCALMATH
#include "math/MathFunctions.h"
#endif
int main(int argc, char *argv[]) {
if (argc < 3) {
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
#ifdef USE_LOCALMATH
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#else
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#endif
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
目錄結(jié)構(gòu)如下:
環(huán)境檢查
我們有時(shí)候需要在編譯過程中蓬戚,檢查系統(tǒng)的環(huán)境,是否支持某些函數(shù)宾抓,這個(gè)例子中子漩,我們檢查是否編譯環(huán)境自帶pow函數(shù)豫喧,如果自帶pow函數(shù),則使用pow函數(shù)幢泼,如果沒有則使用自定義的power函數(shù)紧显。
# 要求最低的cmake版本
cmake_minimum_required(VERSION 3.14)
# 工程名字 工程語言
project(cmakedemo C)
# 設(shè)置cmake c 的標(biāo)準(zhǔn)c99
set(CMAKE_C_STANDARD 99)
# 檢查系統(tǒng)是否支持 pow 函數(shù)
include(${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
# 如果pow函數(shù)存在,則定義HAVE_POW宏缕棵,這個(gè)宏可以在下面的if條件中使用孵班,也可以在config.h.in中預(yù)定義cmakedefine HAVE_POW
check_function_exists(pow HAVE_POW)
# 加入一個(gè)配置頭文件,用于處理 CMake 對源碼的設(shè)置
configure_file(
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
# 如果宏未定義招驴,則引入自定義的power函數(shù)
if (!HAVE_POW)
include_directories("${PROJECT_SOURCE_DIR}/math")
add_subdirectory(math)
endif (!HAVE_POW)
aux_source_directory(. DIR_SRCS)
# 將變量代表的文件路徑加入到可執(zhí)行程序中編譯
add_executable(cmakedemo6 ${DIR_SRCS})
# 添加鏈接庫
if (!HAVE_POW)
target_link_libraries(cmakedemo4 MathFunctions)
endif (!HAVE_POW)
首先在頂層 CMakeLists.txt
文件中添加 CheckFunctionExists.cmake
宏篙程,并調(diào)用 check_function_exists
命令測試鏈接器是否能夠在鏈接階段找到 pow 函數(shù)。如果找到pow函數(shù)别厘,則定義HAVE_POW
宏虱饿,當(dāng)然,在config.h.in
中需要預(yù)定義HAVE_POW
宏丹允,如下:
#cmakedefine USE_LOCALMATH
#cmakedefine HAVE_POW
#cmakedefine HAVE_LOCALPOWER
隨后在生成的config.h
中郭厌,就會(huì)定義上該宏,如下:
/* #undef USE_LOCALMATH */
#define HAVE_POW
/* #undef HAVE_LOCALPOWER */
這時(shí)候雕蔽,就可以在源碼中使用宏了折柠。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "config.h"
#ifdef USE_LOCALMATH
#include "math/MathFunctions.h"
#endif
int main(int argc, char *argv[]) {
if (argc < 3) {
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
#ifndef HAVE_POW
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#else
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#endif
printf("%g ^ %d is %g\n", base, exponent, result);
#ifdef HAVE_LOCALPOWER
printf("HAVE_LOCALPOWER . \n");
#elif defined(HAVE_POW)
printf("HAVE_POW . \n");
#endif
return 0;
}
這里有個(gè)注意點(diǎn):check_function_exists
需要在configure_file定義config.h.in
之前調(diào)用,否則對于config.h.in
中預(yù)定義的宏無效批狐。同樣的扇售,在CMakeLists.txt
中也能夠使用HAVE_POW
宏來判斷是否引入math
目錄,是否將math
作為子目錄加入編譯嚣艇,最后是否鏈接MathFunctions
靜態(tài)庫歧斟。
添加版本號(hào)
在應(yīng)用程序中奢方,維護(hù)庫或者可執(zhí)行程序的版本號(hào)是一個(gè)好的習(xí)慣,配合changelog,能夠很直觀的看到庫的更新迭代過程仇矾。在cmake中我們怎樣添加版本號(hào)管理呢织咧?
# 要求最低的cmake版本
cmake_minimum_required(VERSION 3.14)
# 工程名字 工程語言
project(cmakedemo C)
# 設(shè)置cmake c 的標(biāo)準(zhǔn)c99
set(CMAKE_C_STANDARD 99)
set(VERSION_MAJOR 1)
set(VERSION_MINOR 0)
在config.h.in
文件中八秃,添加預(yù)定義
#define VERSION_MAJOR @VERSION_MAJOR@
#define VERSION_MINOR @VERSION_MINOR@
這樣薛闪,在生成的config.h
文件中就有VERSION_MAJOR
與VERSION_MINOR
的定義,在代碼中使用如下:
printf("major version %d , minor version %d \n", VERSION_MAJOR, VERSION_MINOR);
編譯動(dòng)靜態(tài)庫
上面我們都是生成可執(zhí)行程序吱抚,如果我們想要生成動(dòng)態(tài)庫或者靜態(tài)庫應(yīng)該怎么做呢百宇?
動(dòng)態(tài)庫
aux_source_directory(. DIR_MATH_SRCS)
add_library(MathFunctions SHARED ${DIR_MATH_SRCS})
生成動(dòng)態(tài)庫如下:
靜態(tài)庫
aux_source_directory(. DIR_MATH_SRCS)
add_library(MathFunctions STATIC ${DIR_MATH_SRCS})
生成靜態(tài)庫如下:
主要區(qū)別是在
add_library
時(shí)指定STATIC/SHARED
參數(shù)即可。
基本操作總結(jié)
通過以上基本操作秘豹,我們了解了如何生成可執(zhí)行程序携御,生成動(dòng)/靜態(tài)庫,如何添加版本號(hào)、如何進(jìn)行環(huán)境檢查啄刹、如何預(yù)定義宏涮坐、如何對多級(jí)目錄進(jìn)行編譯。對于cmake鸵膏,我們已經(jīng)有了一個(gè)大概的了解膊升,后續(xù)繼續(xù)講一下在android中如何與cmake配合使用,來完成我們的目標(biāo)谭企。
CMake與Android
在android平臺(tái)中,系統(tǒng)已經(jīng)為我們內(nèi)置了很多的原生api供我們鏈接調(diào)用评肆,不同的系統(tǒng)api债查,android為我們提供了不同的庫,具體可以參考Android NDK 原生 API瓜挽。這些預(yù)構(gòu)建的庫盹廷,已經(jīng)存在在android平臺(tái)上了,我們無需將他們打包到apk中久橙,因?yàn)镹DK庫已經(jīng)是cmake搜索路徑的一部分俄占,所以找到提供庫的名字,鏈接到所需庫即可淆衷。那我們要怎么做才能使用這些庫呢缸榄?
find_library用法
添加find_library()
命令到你的cmake構(gòu)建腳本用于定位ndk庫路徑,并且將路徑存儲(chǔ)變量中祝拯。你可以在腳本的其他地方使用這個(gè)變量甚带,下面例子是查找android平臺(tái)的log庫,將路徑存儲(chǔ)在log-lib
變量中佳头。
find_library( # Defines the name of the path variable that stores the
# location of the NDK library.
log-lib
# Specifies the name of the NDK library that
# CMake needs to locate.
log )
接下來我們需要將ndk庫鹰贵,鏈接到我們的目標(biāo)程序或者目標(biāo)庫中:
# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
native-lib
# Links the log library to the target library.
${log-lib} )
這里target_link_libraries
含義是將${log-lib} 路徑的ndk庫,鏈接到native-lib.so中
添加預(yù)構(gòu)建的動(dòng)態(tài)庫
添加一個(gè)預(yù)先構(gòu)建的庫康嘉,類似于為CMake指定另一個(gè)本地構(gòu)建庫碉输。然而因?yàn)閹煲呀?jīng)預(yù)構(gòu)建,你需要使用IMPORTED
告訴cmake亭珍,你需要引入庫到你的構(gòu)建工程中敷钾。
add_library( imported-lib
SHARED
IMPORTED )
這里只是指定了引入一個(gè)動(dòng)態(tài)庫,并且動(dòng)態(tài)庫名稱存儲(chǔ)在本地變量imported-lib
中块蚌。接著需要設(shè)置該動(dòng)態(tài)庫imported-lib
的屬性闰非,首先指定具體庫的位置
set_target_properties( # Specifies the target library.
imported-lib
# Specifies the parameter you want to define.
PROPERTIES IMPORTED_LOCATION
# Provides the path to the library you want to import.
imported-lib/src/${ANDROID_ABI}/libimported-lib.so )
這里set_target_properties()
定義了一個(gè)imported-lib
庫的屬性IMPORTED_LOCATION,指定了該庫的在本地操作系統(tǒng)中的位置峭范,這樣财松,結(jié)合上面add_library
我們就完整的在cmake中引入了一個(gè)動(dòng)態(tài)庫,并且存儲(chǔ)在imported-lib本地變量中,待后續(xù)使用辆毡。
當(dāng)然菜秦,我們引入了動(dòng)態(tài)庫還不夠,編譯時(shí)舶掖,經(jīng)常還需要用到動(dòng)態(tài)庫的頭文件球昨,那么頭文件該怎么引入呢?
include_directories(imported-lib/include/)
include_directories
中是頭文件在操作系統(tǒng)中的相對路徑或者絕對路徑眨攘,相對路徑是對于當(dāng)前CMakeLists.txt的位置而定主慰。
添加預(yù)構(gòu)建的靜態(tài)庫
添加預(yù)構(gòu)建的靜態(tài)庫,與動(dòng)態(tài)庫類似鲫售,只是在add_library
和set_target_properties
中有所不同
add_library(imported-static-lib STATIC IMPORTED)
set_target_properties(imported-static-lib PROPERTIES IMPORTED_LOCATION imported-lib/src/${ANDROID_ABI}/libimported-lib.a)
主要區(qū)別是靜態(tài)庫在add_library
中是STATIC共螺,而動(dòng)態(tài)庫是SHARED,靜態(tài)庫會(huì)編譯進(jìn)目標(biāo)動(dòng)態(tài)庫中情竹,而動(dòng)態(tài)庫藐不,最后編譯完apk后,通過APK Analyzer查看秦效,在apk的lib/${ANDROID_ABI}/目錄下雏蛮,有你所鏈接的動(dòng)態(tài)庫。
編譯過程構(gòu)建靜態(tài)庫
在編譯過程中阱州,可能會(huì)存在整個(gè)c工程會(huì)很龐大挑秉,例如筆者目前工作中的一個(gè)工程源碼就很龐大,有多個(gè)不同的模塊贡耽,組件衷模,多級(jí)目錄。那這種情況下我們可以將某些組件蒲赂,先編譯成靜態(tài)庫阱冶,然后將靜態(tài)庫參與最終目標(biāo)動(dòng)態(tài)庫的編譯。參考CMake基本操作->多級(jí)目錄章節(jié)滥嘴,有三種方法可以參考木蹬。
多工程編譯
多工程編譯類似于CMake基本操作->多級(jí)目錄章節(jié)中的方法二,這里就不重新講若皱。參考示例如下:
# Sets lib_src_DIR to the path of the target CMake project.
set( lib_src_DIR ../gmath )
# Sets lib_build_DIR to the path of the desired output directory.
set( lib_build_DIR ../gmath/outputs )
file(MAKE_DIRECTORY ${lib_build_DIR})
# Adds the CMakeLists.txt file located in the specified directory
# as a build dependency.
add_subdirectory( # Specifies the directory of the CMakeLists.txt file.
${lib_src_DIR}
# Specifies the directory for the build outputs.
${lib_build_DIR} )
# Adds the output of the additional CMake build as a prebuilt static
# library and names it lib_gmath.
add_library( lib_gmath STATIC IMPORTED )
set_target_properties( lib_gmath PROPERTIES IMPORTED_LOCATION
${lib_build_DIR}/${ANDROID_ABI}/lib_gmath.a )
include_directories( ${lib_src_DIR}/include )
# Links the top-level CMake build output against lib_gmath.
target_link_libraries( native-lib ... lib_gmath )
CMake與Android結(jié)合總結(jié)
本章主要講解了cmake與android和結(jié)合镊叁,如何在android中使用cmake,cmake如何使用android平臺(tái)自帶的系統(tǒng)庫走触,構(gòu)建動(dòng)/靜態(tài)庫的過程晦譬,以及多工程編譯,這里已經(jīng)基本滿足我們?nèi)粘DK開發(fā)過程中遇到的大部分情況互广。
CMake與Gradle
未完待續(xù)......