CMake

CMake

什么是CMake

  • 在Android Studio2.2和以上版本,构建原生库的默认工具是CMake
  • CMake是一个跨平台的构建工具,可以使用简洁的语句来描述所有平台的安装(编译过程)。能够输出各种各样的makefile或者project文件。CMake并不直接构建出最终的软件,而是产生其他工具脚本比如makefile,然后在依据这个工具的构建方式使用。
  • CMake是一个比make更高级的编译配置工具,它可以根据不同的平台,不同的编译器,生成相应的makefile或者vcproj项目,从而达到跨平台的目的。Android Studio利用CMake生成的是ninja。ninja是一个小型的关注速度的构建系统。我们一般不需要关心ninja的脚本,只需要知道怎么配置CMake就行了
  • CMake是一个跨平台的支持产出各种不同的构建建脚本的一个工具。

CMake源文件

  • CMake源文件包含命令、注释、空格和换行
  • 已CMakeLists.txt命名或者以.cmake为扩展名
  • 可以通过add_subdirectory()命令吧子目录的CMake源文件添加进来
  • CMake源文件中所有有效的语句都是命令,可以是内置命令或者自定义的函数或宏命令。

CMake的注释

单行注释使用 # ,多行注释使用#[[]]

1
2
3
4
#单行注释
#[[
多行注释多行注释
]]

CMake变量

CMake中所有变量都是string类型。可以使用set()和unset()命令来生命或者移除一个变量

变量的引用使用${变量名}

变量名是大小写敏感的

想要看到message命令打印的信息,build工程然后在路径app\externalNativeBuild\cmake\debug\armeabi-v7a\cmake_build_output.txt.txt中就能看到

1
2
3
4
#声明变量
set(name 123)
#引用变量 message是打印命令
message("name = ${name}")

CMake列表

  • 列表也是字符串,可以把列表看成一个特殊的变量,它有多个值
  • 语法格式:set(列表名 值1 值2…)
  • 引用:${列表名}
    1
    2
    3
    4
    5
    6
    #声明列表
    set(list_var 1 2 3 4 5 )
    #或者
    set(list_1 "1;2;3;4;5")
    #打印
    message("list_var=${list_var}")

Cmake操作符

类型 名称
一元 EXIST,COMMAND,DEFINED
二元 EQUAL,LESS,LESS_EQUAL,GREATER,GREATER_EQUAL,STREQUAL,
STRLESS,STRLESS_EQUAL,STRGREATER,STRGREATER_EQUAL,
VERSION_EQUAL,VERSION_GREATER,VERSION_GREATER_EQUAL,MATCHES
逻辑 NOT,AND,OR

这些操作符都是大小写敏感的。优先级带括号的>一元>二元>逻辑

布尔常量值

类型
true 1,ON,YES,TRUE,Y,非0的值
false 0,OFF,FALSE,N,IGNORE,NOTFOUUND,空字符串,以-NOTFOUND结尾的字符串

条件命令

if(表达式) elseif

1
2
3
4
5
6
set(if_tap OFF)
set(elseif_tap ON)
if(${if_tap})
message("if")
elseif(${elseif_tap})
message

循环命令

while(表达式) endwhile(表达式) break()可以跳出整个循环。continue()可以跳出当前循环。

1
2
3
4
5
set(a "")
while(NOT a STREQUAL "XXX")
set(a "${a}x")
message("a = ${a}")
endwhile()

循环遍历

第一种:foreach(循环变量 参数1 参数2…)
endforeach(循环变量)。break()可以跳出整个循环。continue()可以跳出当前循环

1
2
3
foreach(item 1 2 3)
message("item = ${item}")
endforeach(item)

第二种:foreach(循环变量 RANGE total) endforeach(循环变量)。循环范围从0到total

1
2
3
foreach(item RANGE 3)
message("item = ${item}")
endforeach(item)

第三种:foreach(循环变量 RANGE start stop step) endforeach(循环变量)。循环变量从start到stop,每次增量为step

1
2
3
4
5
foreach(item RANGE 0 7 3)
message("item = ${item}")
endforeach(item)
--------
打印为0 3 6

第四种:foreach(循环变量 IN LISTS 列表)
endforeach(循环变量)

1
2
3
4
set(list_var 1 2 3)
foreach(item IN LISTS list_var)
message("item = ${item}")
endforeach(item)

自定义函数命令

自定义函数命令格式:function([arg1[arg2[arg3]]]) endfunction()

函数命令调用:name(实参列表)

1
2
3
4
5
6
7
8
9
10
function(func x y z)
message("call function func")
message("x = ${x}")
message("y = ${y}")
message("z = ${z}")
message("ARGC = ${ARGC}")
message("agr1 = ${ARGV0} arg2 = ${ARGV1} arg3 = ${ARGV2}")
endfunction(func)

func(1 2 3)

ARGC:表示传入产出的个数

ARGV: 表示所有参数列表

ARGV0: 第一个参数 ARGV1:第二个参数 ARGV2 第二个参数

自定义宏命令

自定义宏命令格式:macro([arg1[arg2[arg3]]]) endfunction()

宏命令用:name(实参列表)

计算机科学里的宏(Macro),是一种批量批处理的称谓。一般说来,宏是一种规则或模式,或称语法替换

1
2
3
4
5
6
7
8
macro(ma x y z)
message("call macro ma")
message("x = ${x}")
message("y = ${y}")
message("z = ${z}")
endmacro

ma(1 2 3)

CMake作用域

  • 全局层:cache变量 在整个项目范围可见,一般在set定义变量时,指定CACHE参数就能定义为cache变量
  • 目录层:在当前目录CMakeLists.txt中定义,以及在该文件包含的其他cmake源文件中定义的变量
  • 函数层:在命令哈数中定义的变量,属于函数作用域内的变量

CMakeLists.txt中命令解析

使用AndoridStudio创建一个C++工程后,系统会默认给我们创建一个CMakeLists.txt文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#指定cmake最低支持的版本
cmake_minimum_required(VERSION 3.4.1)
#
add_library(
native-lib

SHARED

native-lib.cpp)
find_library(
log-lib

log)
target_link_libraries(
native-lib

${log-lib})

cmake_minimum_required(VERSION 3.4.1) 是指定cmake最低支持的版本

aux_source_directory(. DIR_SRCS) 第一个参数传入一个目录的路径,.代表查找当前目录所有源文件,并将云文件名称列表保存到第二个参数DIR_SRC变量,不能查找子目录。

add_library 添加一个库或者导入预编译的库

添加一个库:

  • 添加一个库文件,名称为
  • 指定STATIC,SHSRED,MODULE参数来指定库的类型,STATIC:静态库;SHARED:动态库;MODULE:在使用dyld的系统中有效,如果不支持dyld等同于SHARED
  • EXCLUDE_FORM_ALL:表示该库不会被默认构建
  • sorece1 sorece2….sourceN用来指定库的源文件
1
2
add_library(<name> [STATIC|SHARED|MODULE]
[EXCLUDE_FROM_ALL] source1 source2...sourceN)

导入预编译的库

  • 添加一个已存在的预编译的库名为
  • 一般配合set_target_properties使用
1
2
3
4
5
6
7
8
add_library(<name> <SHARED|STATIC|MODULE|UNKNOWN> IMPORTED)
#比如
add_library(test SHARED IMPORTED)
set_target_projecties(
test#指明目标库的名称
#指明要设置的参数库路径
#/${ANDROID_ABI}/libtest.so#导入库的路径
PROPRETIES IMPORTED_LOCATION)

set 设置CMake变量

1
2
3
4
5
6
7
8
9
10
11
#设置可执行文件的输出路径(EXCUTABLE_OUTPUT_PATH是全局变量)
set(EXECUTABLE_OUT_PATH [out_path])

#设置库文件的输出路径(LIBRARY_OUT_PATH是全局变量)
set(LIBRARY_OUT_PATH [output_path])

#设置C++编译参数(CMAKE_CXX_FLAGS是全局变量)
set(CMAKE_CXX_FLAGS "-Wall std=c++ll")

#设置源文件集合(SOURCE_FILES是本地变量即自定义变量)
set(SOURCE_FILES main.cpp test.cpp...)

include_directories 设置头文件目录,相当于g++选项中的-l参数

1
2
#可以用相对路径或者绝对路径,也可以使用自定义的变量值
include_directories(./include ${MY_INCLUDE})

add_executable 添加可执行文件

1
2
#第一个参数文件名 第二个路径
add_executable(<name> #{SRC_LIST})

target_link_libraries 将若干个文件链接到目标库文件,链接的顺序应该符合gcc链接顺序规则。

1
2
3
4
5
6
7
8
9
target_link_libraries(<name> lib1 lib2 lib3)

#如果出现互相依赖的静态库,CMake会允许依赖图中包含循环依赖比如:
add_library(A STATIC a.c)
add_library(B STATIC b.c)
target_link_libraries(A B)
target_link_libraries(B A)
add_executable(main main.c)
target_link_libraries(main A)

被链接的库放在依赖它的库的后面,即如果上面的命令中lib1依赖于lib2,lib2又依赖于lib3,则在前面的命令中,必须严格按照lib1 lib2 lib3的顺序排列否则会报错

add_definitions 添加编译参数。为当前路径以及子目录文件假如有D引入的define falg

1
add_definitions(-DFOO -DDEBUG ...)

add_subdirectory 如果当前目录下还有子目录时,可以使用add_subdirectory,子目录中也需要包含有CMakeLists.txt

1
2
3
#dub_dir:指定包含CMakeLists.txt和源码文件的子目录的位置
#binary_dir:是输出路径,一般可以不指定
add_definitions(sub_dir [binary_dir])

file 文件操作命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#将message写入filename文件中,会覆盖文件原有内容
file(WRITE filename "message")
#将message写入filename文件中,会追加在文件末尾
file(APPEND filename "message")
#从filename文件中读取内容并存储到var变量中,如果指定了numBytes和offset
#则从offset处开始最多度numBytes个字节
#另外如果指定了HEX参数,则内容会以十六进制形式存储在var变量中
file(READ filename var [LIMIT numBytes] [OFFSET offset] [HEX])
#重命名文件
file(RENME <oldname> <newname>)
#删除文件 等于rm命令
file(REMOVE [file1...])
#根据指定url下载文件
#timeout超市事件;下载状态会保存到status中;
#下载日志会被保存到log;
#sum指定多下载文件预期的MD5值
#如果指定hi自定进行比对,
#如果不一致则返回一个错误:SHOW_PROGRESS
#进度信息会以状态信息的形式被打印出来
file(DOWNLOAD url file [TIMEOUT timeout] [STATUS status] [LOG log] [EXPECTED_MD5 sum]
[SHOW_PROGRESS])
#创建目录
FILE(MAKE_DIRECTORY [dir1 dir2 ...])
#会把path转换为以unix的/开头的cmake风格路径保存在result总
file(TO_CMAKE_PATH pathresult)
#会把cmake风格的路径转换为本地路径风格window下用“\” unix下用“/”
file(TO_NATIVE_PATH path result)
#将会为所有匹配查询表达式的文件生成一个文件list
#并将该list存储进变量variable里面
#如果一个表达式指定了RELATIVE,返回的结果
#将会是相对于给定路径的相对路径
#查询表达式例子:*.cxx,*.vt?
#NOTE:按照官方文档说法,不建议使用file的GLOB指令来收集工程的源文件。
file(GLOB variable [RELATIVE path] [globbing expressions])

set_directory_properties() 设置某个路径的一种属性

1
set_directory_properties(PROPERITIES prop1 value1 prop2 value2)

prop1 prop2,取值范围INCLUDE_DIRECTORIES,LINK_DIRECTORIES,INCLUDE_REGULAR_EXPRESSION,ADDITIONNAL_MAKE_CLEAN_FILES

set_property():在给定的作用域内设置一个命名的属性

1
2
3
4
5
6
7
set_property(<GLOBAL | DIRECTORY [dir] |
TARGET [target...] |
SOURCE [src1...] |
TEST [test1...] |
CACHE [entry1 ...]>
[APPENT]
PROPERTY <name> [value...])

PROPERTY参数是必须的;第一个参数决定了属性可以影响的作用域:

  • GLOBAL:全局作用域
  • DIRECTORY:默认当前路径,也可以用[dir]指定路径
  • TARGET:目标作用域,可以是0个或多个已有目标
  • SOURCE:源文件作用域,可以是0个或多个源文件,源文件只对同目录有用
  • TEST:测试作用域,可以是0个或多个已有的测试
  • CACHE:必须指定0个或多个cache中已有的条目

如果源文件很多,把所有文件一个一个加入很麻烦,可以使用aux_source_directory命令或file命令,会查找指定目录下的所有源文件,然后将结果存进指定的变量名中。

1
2
3
4
5
6
7
8
9
10
#查找当前目录多有源文件 并将名称到存到DIR_SRCS
#中,不能查找子目录
aux_source_directory(. DIR_SRCS)

#也可以使用
#file(GLOB DIR_SRCS *.c *.cpp)

add_library(native-lib
SHARED
${DIR_SRCS})

多目录多源文件处理

  • 主目录中的CMakeList.txt中添加add_subdirectory(child)命令,指明本项目包含一个子项目child并在target_link_libraries指明本项目所需要链接一个名为chlid的库
  • 子目录child中创建CMakeLists.txt,这里child编译为共享库

主目录:

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 3.4.1)
aux_source_directory(. DIR_SRCS)
#添加child子目录下的CMakeLists.txt
add_subdirectory(child)

add_library(
native_lib
SHARED
${DIR_SRCS}
)
target_link_libraries(native-lib child)

child目录:

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.4.1)
aux_source_directory(. DIR_LIB_SRCS)
add_library(
child
SHARED
${DIR_SRCS}
)

添加预编译库

  • 假如项目中引入了libimported-lib.so
  • 添加add_library命令,第一个参数是模块名,第二个参数SHARED表示动态库,第三个参数IMPORTED表示以导入的形式添加
  • 添加set_targetOproperties命令设置导入路径属性到target_link_libraries命令参数中,表示native-lib需要连接imported-lib模块

Android 6.0之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cmake_minimum_required(VERSION 3.4.1)
#使用IMPORTED标志告诉CMake 只希望将库导入到项目中
#如果静态库则将shared改为static
add_library( imported-lib
SHARED
IMPORTED)
#参数分别为库、属性、导入地址、库所在地址
set_target_properties(
imported-lib
PROPERTIES
IMPORTED_LOCATION
<路径>/libimported-lib.so
)
aux_source_directory(. DIR_SRCS)
add_library(
native-lib
SHARED
${dir_SECS}
)
target_link_lbraries(native-lib imported)

Android 6.0之后

1
2
3
4
5
#set命令定义一个变量
#CMAK_C_FLAGS:c的参数会传递给编译器
#付过是c++文件,需要用CMAKE_CXX_FLAGS
#-L:库的查找路径
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -L[SO所在目录]")

添加头文件的目录

为了确保CMake可以在编译时定位头文件,使用include_directories,相当于g++选项中的-l参数。这样就可以使用#include<xx.h>,否则需要使用#include”path/xx.h”

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 3.4.1)
#设置头文件目录
include_directiories(<文件目录>)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -L[SO所在目录]")
aux_source_directory(. DIR_SRCS)
add_library(
native-lib
SHARED
${dir_SECS}
)
target_link_lbraries(native-lib imported)

build.gradle配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
android {
defaultConfig {
externalNativeBuild {
cmake {
//使用编译器clang/gcc
//cmake默认就是gnustl_static
arguments "-DANDROID_TOOLCHAIN=clang","-DANDROID_STL=gnustl_static"
//指定cflags和cppflags,效果和cmakelist使用一样
cppFlags ""
cppFlags ""
//指定需要编译的cpu架构
abiFilters "armeabi-v7a"
}
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}

练习

使用AndoridStudio新建一个C++项目,系统会为我们建好一个CmakeLists.txt文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#指定cmake最小支持版本
cmake_minimum_required(VERSION 3.4.1)
#添加一个库,根据native-lib.cpp文件编译一个native-lib动态库
add_library(
native-lib
SHARED
native-lib.cpp)
#查找系统库,这里查找的是系统日志库
#默认会到 skd的ndk路径下面去找比如:sdk\ndk-bundle\platforms\android-28\arch-arm\usr\lib
find_library(
log-lib
log)
#设置依赖的库,第一个参数必须为依赖的模块,顺序不能改
target_link_libraries(
native-lib
${log-lib})

如何集成第三方库比如现在继承fmod库到自己的项目(fmod是一个音频引擎,可以制作各种各样的魔幻声音)

  1. 首先去官网下载sdk https://www.fmod.com/download 它有各个平台的这里选择Android的

  2. 下载之后解压进入到api/core目录可以看到3个文件夹examples(例子),inc(头文件),lib(动态库)

  3. 先把头文件复制到我们工程的cpp目录下面

  4. 然后在cpp目录的同级新建jinLibs文件夹,把动态库都复制进去,lib文件夹中有个fmod.jar,这个jar包也放到我们工程的libs文件中引入,防止运行的时候找不到类。

等会使用模拟器测试,所以app.gradle中配置cpu的类型为x86

1
2
3
4
5
6
7
8
9
externalNativeBuild {
cmake {
abiFilters "x86"
}
}

ndk {
abiFilters 'x86'
}

  1. 配置CMakeLists.txt文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#指定cmake最小支持版本
cmake_minimum_required(VERSION 3.4.1)
#设置头文件目录
include_directories(${CMAKE_SOURCE_DIR}/inc)
#添加一个库,根据native-lib.cpp文件编译一个native-lib动态库
add_library(
native-lib
SHARED
native-lib.cpp)
#设置第三方so库的路径
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}")
#查找系统库,这里查找的是系统日志库
#默认会到 skd的ndk路径下面去找比如:sdk\ndk-bundle\platforms\android-28\arch-arm\usr\lib
find_library(
log-lib
log)
#设置依赖的库,第一个参数必须为依赖的模块,顺序不能改。fmod和fmodL是动态库的名字
target_link_libraries(
native-lib
fmod
fmodL
${log-lib})
  1. 到我们自己的cpp文件中编写代码

这里引入fmod的头文件,然后打印一下其版本号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <jni.h>
#include <string>
#include <fmod.hpp>
#include <android/log.h>

using namespace FMOD;

extern "C" JNIEXPORT jstring JNICALL
Java_com_chs_cmake_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {

System *system;
System_Create(&system);
unsigned version;
system->getVersion(&version);
__android_log_print(ANDROID_LOG_ERROR,"text","FMOD_VERSION:%08x",version);
return env->NewStringUTF(hello.c_str());
}

运行项目可以在log中看到打印日志

1
2019-05-25 15:37:50.261 29585-29585/com.chs.cmake E/text: FMOD_VERSION:00020001

OK继承成功。

# c/c++

コメント

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×