Android CMake学习

什么是CMake?

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

使用Androidstudio创建一个C/C++Support的项目,默认在app/src/main目录下会生成cpp目录,里面包含CMake Lists.txt和native-lib.cpp。如下代码为CMakeLists.txt去掉英文注释格式化后的内容。

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_SRCS变量
#  不能查找子目录

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

#  导入预编译库
add_library (<name>
<SHARED | STATIC | MODULE | UNKNOWN> IMPORTED)
# 比如
add_library(test SHARED IMPORTED)
set_target_properties(
      test  # 指明目标库名
      PROPERTIES IMPORTED_ LOCATION  # 指明要设置的参数
      库路径/${ANDROID_ABI}/libtest.so  # 导入库的路径
)
#  添加一个已存在的预编译库,名为<name>
#  一般配合set_target_properties 使用

#set命令 设置CMake变量
# 设置可执行文件的输出路径(EXCUTABLE_OUTPUT_PATH是全局变量)
set (EXECUTABLE_OUTPUT_PATH  [output_path])

# 设置库文件的输出路径(LIBRARY_ OUTPUT_ PATH是全局变量)
Set (LIBRARY_ OUTPUT_ PATH [output path])

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

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


 include_directories(./include ${MY_INCLUDE})
# 可以用相对或绝对路径,也可以用自定义的变量值
# 设置于头文件目录
# 相当于g++选项中的-I参数

add_executable(<name> ${SRC_LIST})
# 添加可执行文件

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)
# 将若干库连接到目标库文件
# 链接的顺序应当符合gcc链接顺序规则,被链接的库放在依赖它的库的后面,即如果上面的的命令中,lib1依赖于lib2,lib2又依赖于lib3,则在上面的命令中必须严格按照  lib1 lib2 lib3的顺序排列,否则会报错

add_definitions(-DF00 -DDEBUG ...)
# 为当期路径以及子目录的源文件加入由-D引入的define flag

add_subdirecroty(sub_dir [binary_dir])
# sub_dir 指定包含CMakeLists.txt 和 源码文件的子目录位置
# binary_dir 是输出路径,一般可以不指定
#如果当前目录还有子目录可以使用add_subdirectory,子目录也需要包含有CMakeLists.txt

# file 文件操作命令
# 将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 numeytes]  [OFFSET offset]  [HEX])
# 重命名文件
file (RENAME <oldname> <newname>)
# 删除文件,同rm命令
file (REMOVE [file1 ...])
# 递归的执行删除文件命令,等于rm -r
file (REMOVE_RECURSE  [file1 ...])
# 根据指定的url下载文件
# timeout超时时间;下载的状态会保存到status中;下载日志会被保存到log;sum指定所下载文件预期的MD5值,如果指定会自动进行比,
# 如果不一致,则返回一个错误;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 path result)
# 它会把cmake风格的路径转换为本地路径风格:windows下用”\”,而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(PROPERTIES prop1 value1 prop2 value2)
# 设置某个路径的一种属性
# prop1,prop2代表属性,取值为 INCLUDE_DIRECTORIES,LINK_DIRECTORIES,INCLUDE_REGULAR_EXPRESSION,ADDITIONAL_MAKE_CLEAN_FILES

set_property(<GLOBAL |
                      DIRECTORY [dir] |
                      TARGET  [target ...] |
                      SOURCE  [src1 ...]  |
                      TEST  [test1 ...]  |
                      CHCHE  [entry1 ...]  |  >
                     [APPEND]
                     PROPERTY <name> [value ...] )
# 在给定的作用域内设置一个命名的属性
# PROPERTY 参数是必须的
# 第一个参数決定了属性可以影响的作用域:
            GLOBAL: 全局作用域
            DIRECTORY:默认当前路径,也可以用[din]指定路径
            TARGET:目标作用域,可以是0个或多个已有目标
            SOURCE:源文件作用域,可以是0个或多个源文件
            TEST:测试作用域,可以是0个或多个已有的测试
            CACHE:必须指定0个或多个cache中己有的条目

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

cmake_minimum_required(VERSION 3.4.1)
# 查找当前目录所在源文件 并将名称保存到DIR_SRCS 变量
# 不能查找子目录
aux_source_directory(.  DIR_SRCS)
# 也可以使用
# file(GLOB DIR_SRCE  *.c  *.cpp)

add_library(
                  native-lib
                  SHARED
                  ${DIR_SRCS})

多目录对源文件处理
主目录中的CMakeLists.txt中添加add_subdirectory(child)命令,指明本项目包含一个子项目child。并在target_link_libraries指明本项目需要链接一个名为child的库。
子目录child中创建CMakeLists.txt,这里child编译为共享库。

cmake_minimum_required(VERSION 3.4.1)
aux_source_directory(.  DIR_SRCS)
# 添加child子目录下的cmakelist
add_subdirectory(child)

add_library(
                  native-lib
                  SHARED
                  ${DIR_SRCS})
target_link_libraries(native-lib child)
-----------------------------------------------------------
#child目录下的CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
aux_source_directory(.  DIR_SRCS)
add_library(
                  child
                  SHARED
                  ${DIR_LIB_SRCS})

添加预编译库(Android6.0之前)

  • 假设我们本地项目引用了libimported-lib.so.
  • 添加add_library命令,第一个参数是模块名,第二个参数SHARED表示动态库,STATIC表示静态库,第三
    个参数IMPORTED表示以导入的形式添加。
  • 添加set_target_properties命令设置导入路径属性。
  • 将import-lib添加到target_link_libraries命令参数中,表示native-lib需要链接imported-lib模块
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_SRCS})
target_link_libraries(native-lib  imported-lib)

添加预编译库(Android6.0以后)
在Android 6.0及以上版本,如果使用上面的方法添加预编译动态库的话,会有问题。可以使用另外一种方式来配置。

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

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

cake minimum required (VERSION 3.4.1)
# 设置头文件目录
include_directories(<文件目录>)
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} - L[so所在目录]")
aux_source_directory(.  DIR_SRCS)
add_library(
              native-lib
              SHARED
              ${DIR_SRCS})
target_link_libraries (native-lib imported-lib)

Build.gradle配置

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                // 使用编译器 clang/gcc
                // cmake默认就是gnustl_static 
                arguments "-DANDROID_TOOLCHAIN=clang",
                "-DANDROID_STL=gnustl_static"
                cFlags " "
                cppFlags ""
                // 指定需要编译的cpu架构 
                abiFilters "x86","armeabi-v7a","arm64-v8a" 
            }
        }
        ndk {
            abiFilters "x86","armeabi-v7a","arm64-v8a"//指定第三方库的cpu架构
        }
    } 
    externalNativeBuild {
        cmake {
            // 指定CMakeList.txt文件相对当前Build.gradle的路径
            path "src/main/cpp/CMakeLists.txt"
            version "3.6.0"
        }
    }
}

CMake

源文件

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

CMake 注释

  • 单行注释:#注释内容(注释从#开始到行尾结束)
  • 多行注释:可以使用括号来实现多行注释:#[[多行注释]]

CMake变量

  • CMake中所有变量都是string类型。可以使用set()和unset()命令来声明或移除一个变量
  • 变量的引用: ${变量名}
# 声明变量: set(变量名 变量值)
set(var 111)
# 引用变量 message命令来打印
message("var = ${var}")

CMake列表

  • 列表也是字符串,可以把列表看作一个特殊的变量,这个变量有多个值。
  • 语法格式:set(列表名 值1 值2… 值N) 或 set(列表名 "值1;值2;...;值N")
  • 列表的引用:${列表名}

CMake流程控制
操作符、布尔常量值、条件命令,循环命令、循环遍历的几种写法

CMake自定义宏命令

macro(ma x y z)
  message("call macro ma")
  message("x = ${x}") 
  message("y = ${y}")
  message("z = ${z}")
endmacro(ma)

ma(1 2 3)

// 自定义宏命令格式:
// macro(<name> [arg1 [arg2 [arg3...]]])
//            COMMAND()
//endmacro(<name>)
// 宏命令调用格式:name(实参列表)

CMake变量的作用域

  • 全局层:cache变量,在整个项目范围可见,一股在set定义变量时,指定CACHE参数就能定义为cache变量。
  • 目录层:在当前目录CMakelists.txt中定义,以及在该文件包含的其他cmake源文件中定义的变量。
    -函数层:在命令函数中定义的变量,属于函数作用域内的变量。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容