AndroidStudio CMakeList的总结

如果遇到什么有问题的请在这里联系我:https://www.jianshu.com/p/445d5cbe166d

背景

CMakeList这个东西对于所有的Linux,Android开发者都是很熟悉的东西。但是Android的Java应用开发很少关注这个,这段时间跟着辉哥系统的学了CMakeList,发现之前对CMakeList的理解很浅,特此写一篇学习总结。

正文

CMakeList本质上是cmake的一个脚本文件,用于跨平台管理c/c++/java项目的。聊到CMake,就不得不聊一下Linux开发中的makefile 工具。

C的程序编译流程

在理解make工具如何构建的时候,我们先要熟悉一个c语言的程序是经过几个步骤编译的。实际上就是指gcc的编译流程。
gcc将会分为如下几个步骤:

  • 1.预处理阶段
    调用命令:
gcc -E -o hello.i hello.c

这个命令则作用是生成.i文件。这个步骤完成的行为是,展开宏定义,展开inlcude文件。

  • 2.预编译阶段
    调用命令:
gcc -S -o hello.s hello.i

gcc在该命令才会检测代码规范性,检测代码是否有误,最后生成汇编文件.s文件。

  • 3.汇编阶段
    调用命令:
gcc -c -o hello.o hello.s

把.s文件翻译成.o文件,里面全是机器指令

  • 4.链接阶段
    调用命令:
gcc -o hello hello.o

计算逻辑地址,合并数据段,有些函数是在另一个函数的。

makefile 工具

如果每一次编写好一个文件都要调用一次gcc的命令,这样实在是太过繁琐,而且在在每一次编译的流程都一致情况下,就诞生了makefile,这个工具帮助我们管理编译项目。

我们看看makefile如何帮助我们编译项目。
首先创建一个Makefile文件。接着按照以下三个原则编写makefile脚本。
记住一个规则,两个函数,三个自动变量。

一个规则

一个规则有两个思想,若想生成目标文件,先要检测规则中的依赖是否存在。

    1. 如果不存在则会寻找是否有完整的规则来完成该依赖条件
  • 2.会检查规则中的目标是否需要更新,必须先检查他所有依赖目标
a.out:a.c
    gcc a.c -o a.out

这里的意思是,要生成a.out需要依赖a.c文件,而这个依赖对应的命令是

gcc a.c -o a.out

记住这中间不存在任何空格,当编写对应的命令时候需要直接使用Tab按键进行缩紧,才能被makefile识别。

因此我们每一次改动文件,makefile会根据依赖文件的更改日期来决定当前要生成的目标是否需要更新

两个函数

第一个函数
src = $(wildcard *.cpp)

找到当前目录下所有后缀为 .cpp 的文件,然后赋值给 src

第二个函数
obj = $(patsubst %cpp,%o,$(src))

就是把src变量里的所有后缀为 .cpp 的文件替换成 .o 文件

三个自动变量

@:表示规则中的目标^ :表示规则中所有的依赖条件,组成一个列表,以空格隔开,如果这个列表有重复项则消除重复
$< : 表示规则中的第一个依赖条件,如果运行在模式套用中,相当于依次取出依赖条件套用该模式规则

举个例子:

src = $(wildcard *.cpp)
obj = $(patsubst %cpp,%o,$(src))


hello.out:$(obj)
    gcc $^ -o $@

div.o:div.cpp
    gcc -c $< -o $@

add.o:add.cpp
    gcc -c $< -o $@

sub.o:sub.cpp
    gcc -c $< -o $@

hello.o:hello.cpp
    gcc -c $< -o $@



# $(***)取变量值
clean:
    rm -f $(obj) hello.out

# 生成伪目标
.PHONY:clean

能看到,这里要生成一个hello.out一个执行文件。首先要依赖所有.o文件。这里的.o文件,分别是指div.o,add.o,sub.o,hello.o。而这些文件都分别依赖了对应的.cpp文件。

每个依赖下面都有对应的 gcc -c < -o@ 实际上就是指gcc -c add.cpp -o add.o.这样通过依赖树,就能生成一个执行文件。但是实际上还能更加简化:

src = $(wildcard *.cpp)
obj = $(patsubst %cpp,%o,$(src))


hello.out:$(obj)
    gcc $^ -o $@

#模式规则
# gcc -c $(src) -o $(obj)
# o文件依赖cpp文件
%o:%cpp
    gcc -c $< -o $@

# $(***)取变量值
clean:
    rm -f $(obj) hello.out

# 生成伪目标
.PHONY:clean

实际上能够通过上面%o:%cpp,通过一条简单的依赖规则,就能简化上面大量的依赖规则。

makefile的总结暂时到这里,接下来总结cmake。

cmake

cmake是基于makefile之上的跨平台管理工具,比起makefile又简单了不少。会通过简单的命令生成一个Makefile进行编译工程。
先介绍几个基础的API。

PROJECT (HELLO) 为cmake的工程命名

INCLUDE_DIRECTORIES (${PROJECT_SOURCE_DIR}/include) 指定头文件目录在哪里

LINK_DIRECTORIES (${PROJECT_SOURCE_DIR}/lib) 指定生成的文件对应的链接库

ADD_EXECUTABLE (hello hello.cpp) 生成一个执行文件

TARGET_LINK_LIBRARIES (hello math) 链接其他的动态/静态库

AUX_SOURCE_DIRECTORY(${CMAKE_SOURCE_DIR}/src/main/cpp SRC_LIST) 收集该目录下的源码文件到SRC_LIST变量中

ADD_LIBRARY(native-lib SHARED ${SRC_LIST}) 根据源文件生成一个动态库

最后在通过上述api的编写生成一个CMakeLists.txt目录下,使用cmake .就能自动生成对应的Makefile文件,最后再make命令就能完成编译了。

但是对于AndroidStudio来说,没必要调用命令,当使用点击了make的按钮时候,就会如果cmake .一样的效果了。

案例一

一个简单的例子,如果有如下这么一个目录文件


image.png

那么我们要做的事情首先先以CMakeList.txt作为基准文件坐标,去寻找其他目录文件。

首先确定include文件在app/main/cpp/include中,接着确定其他需要链接的第三方库在jniLibs中。那么我们就写下如下两行

cmake_minimum_required(VERSION 3.4.1)

#PROJECT(music-player)

include_directories(src/main/cpp/include)

# 指定so在哪个目录下
link_directories(${PROJECT_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a)

接着我们需要收集所有的源文件,到一个变量中,方法有两个。

# 收集下所有的源文件 到src_list
 aux_source_directory(${CMAKE_SOURCE_DIR}/src/main/cpp SRC_LIST)

# 收集所有的cpp文件以及c文件到SRC_LIST
#file(GLOB SRC_LIST ${PROJECT_SOURCE_DIR}/src/main/cpp/*.cpp ${PROJECT_SOURCE_DIR}/src/main/cpp/*.h)

接着我们还需要找到一个Android对应的日志库

# 查找一个动态库
find_library(log-lib log)

这个方法和link_directories有点相似。官方推荐使用find_library,因为找不到对应的第三方库文件,就会报错。但是要这么做的话,就需要先add_library进来。新手使用find_library,而熟悉一点的使用link_directories更好。

接下来就要生成我们c++工程对应的动态链接库。

# 指定生成动态库
add_library(music_player SHARED ${SRC_LIST})

可以看到的时候,由于我们收集了cpp目录下所有的源文件到了SRC_LIST变量,以后再也不需要每一次添加文件都在这里面写一个文件对应的目录。当每一次添加源文件,为了避免AndroidStudio找不到,还是需要点击以下refresh linked c++ project的按钮。

最后记得要链接所有的动态库到我们的music_player中。

target_link_libraries(music_player
        android
        ${log-lib}
        avutil-55
        avcodec-57
        avdevice-57
        avfilter-6
        avformat-57
        postproc-54
        swresample-2
        swscale-4)

案例二

这是来自张绍文的第一篇学习breakpad的cmake工程。
该目录文件结构如下:


image.png

能够看到的是这个工程有两个CMakeLists.txt。而这种情况更加常见,因为做大型一点的项目往往都是按照模块拆分开来,通过各自模块的CMakeList.txt管理各自子工程。这里要介绍一个新的api。

add_subdirectory 该api是控制子目录下的CMakeLists.txt。当通过这个api指定了目录,将会找到子目录下的CMakeLists.txt,并且执行里面的命令。

breakpad子目录CMakeLists.txt

我们先来看看子目录external/libbreakpad里面的CMakeLists.txt。

cmake_minimum_required(VERSION 3.4.1)

set(BREAKPAD_ROOT ${CMAKE_CURRENT_SOURCE_DIR})

include_directories(${BREAKPAD_ROOT}/src ${BREAKPAD_ROOT}/src/common/android/include)


file(GLOB BREAKPAD_SOURCES_COMMON
        ${BREAKPAD_ROOT}/src/client/linux/crash_generation/crash_generation_client.cc
        ${BREAKPAD_ROOT}/src/client/linux/dump_writer_common/thread_info.cc
        ${BREAKPAD_ROOT}/src/client/linux/dump_writer_common/ucontext_reader.cc
        ${BREAKPAD_ROOT}/src/client/linux/handler/exception_handler.cc
        ${BREAKPAD_ROOT}/src/client/linux/handler/minidump_descriptor.cc
        ${BREAKPAD_ROOT}/src/client/linux/log/log.cc
        ${BREAKPAD_ROOT}/src/client/linux/microdump_writer/microdump_writer.cc
        ${BREAKPAD_ROOT}/src/client/linux/minidump_writer/linux_dumper.cc
        ${BREAKPAD_ROOT}/src/client/linux/minidump_writer/linux_ptrace_dumper.cc
        ${BREAKPAD_ROOT}/src/client/linux/minidump_writer/minidump_writer.cc
        ${BREAKPAD_ROOT}/src/client/minidump_file_writer.cc
        ${BREAKPAD_ROOT}/src/common/convert_UTF.c
        ${BREAKPAD_ROOT}/src/common/md5.cc
        ${BREAKPAD_ROOT}/src/common/string_conversion.cc
        ${BREAKPAD_ROOT}/src/common/linux/elfutils.cc
        ${BREAKPAD_ROOT}/src/common/linux/file_id.cc
        ${BREAKPAD_ROOT}/src/common/linux/guid_creator.cc
        ${BREAKPAD_ROOT}/src/common/linux/linux_libc_support.cc
        ${BREAKPAD_ROOT}/src/common/linux/memory_mapped_file.cc
        ${BREAKPAD_ROOT}/src/common/linux/safe_readlink.cc

        )

file(GLOB BREAKPAD_ASM_SOURCE ${BREAKPAD_ROOT}/src/common/android/breakpad_getcontext.S
        )

set_source_files_properties(${BREAKPAD_ASM_SOURCE} PROPERTIES LANGUAGE C)

add_library(breakpad STATIC ${BREAKPAD_SOURCES_COMMON} ${BREAKPAD_ASM_SOURCE})

target_link_libraries(breakpad log)

能看到的是,首先通过set命令指定了一个BREAKPAD_ROOT变量是当前这个cmakelists.txt的目录。

接着,确定了头文件有两处,第一处是CMakeLists.txt所在的目录下的src,第二个是CMakeLists.txt所在的目录下src/common/android/include。

接着通过file收集源文件。因为收集的比较复杂,因此使用了file 命令收集符合标志的文件。

通过set_source_files_properties 来告诉BREAKPAD_ASM_SOURCE下面的汇编文件以C语言的方式编译。

醉着通过add_library生成breakpad文件,并且target_link_libraries链接上了Android的日志库。

breakpad父目录CMakeLists.txt 的控制

cmake_minimum_required(VERSION 3.4.1)
project(breakpad-core)

set(ENABLE_INPROCESS ON)
set(ENABLE_OUTOFPROCESS ON)
set(ENABLE_LIBCORKSCREW ON)
set(ENABLE_LIBUNWIND ON)
set(ENABLE_LIBUNWINDSTACK ON)
set(ENABLE_CXXABI ON)
set(ENABLE_STACKSCAN ON)

if (${ENABLE_INPROCESS})
    add_definitions(-DENABLE_INPROCESS)
endif ()
if (${ENABLE_OUTOFPROCESS})
    add_definitions(-DENABLE_OUTOFPROCESS)
endif ()


set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=implicit-function-declaration")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ")

# breakpad
include_directories(external/libbreakpad/src external/libbreakpad/src/common/android/include)
add_subdirectory(external/libbreakpad)
list(APPEND LINK_LIBRARIES breakpad)

add_library(breakpad-core SHARED
        breakpad.cpp)
target_link_libraries(breakpad-core ${LINK_LIBRARIES}
        log)

能看到在父目录中的CMakeLists的控制更加偏向整个工程的全局控制。首先打开了各种编译时候的开关。

设置了头文件目录,能够发现这里的子模块头文件,又一次被声明。当我们尝试的注释掉这一行。发现编译是无法通过。也就是说,子模块添加的头文件并不能被父模块识别。

add_subdirectory 告诉CMakeLists.txt要管理哪个子模块的内容。

其好处就是我们通过

list(APPEND LINK_LIBRARIES breakpad)

把先前的add_library生成的breakpad动态库动态放到LINK_LIBRARIES中。这样就添加了需要链接的动态库内容。

接着还是走老套路,add_library生成对应的动态/静态库,接着链接。

总结

以上就是CMakeLists.txt的基本内容,熟知这些操作之后,在AndroidStudio编写任何的c++项目不会为如何管理项目头疼了。

但是记住,当我们链接进新的的动态库的时候,请注意这个动态库是用什么编写的。如果我们写的是c++工程,但是缺直接引用了对应的头文件,就会爆链接异常。请使用extern “C”的方式引用头文件。

extern "C" {
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
#include <libavformat/avformat.h>
}

这个问题,在我刚搞CMakeLists.txt的时候,出现的,弄的我以为是我的脚本写错了呢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269