📚
handbook
  • Introduction
  • 1.前言
    • 前言
    • 鸣谢
  • 2.环境篇
    • 工具部署和使用
      • 团队协作工具
        • Confluence
      • 开发工具
        • Docker
          • 镜像导入导出
          • 安装
        • Docker Compose
      • 持续集成工具
        • Gerrit
        • Sonarqube
          • 分析参数设定
          • Prerequisite
          • 服务端设置
        • Ubuntu Ci Deploy
          • ubuntu使用docker部署jenkins+sonarqube
        • 持续集成部署
      • 文本编辑工具
        • Gitbook相关注意事项
        • Markdown快速入门
      • 版本控制
        • Git
          • 1.基础
            • Git基础(一)
            • Git基础(二)
            • Git基础(三)
            • Git基础(四)
            • Git基础(五)
          • 2.命令详解
            • 命令速查
          • 3.进阶技巧
            • git技巧
      • 自动化测试工具
        • Appium
          • capability参数配置
          • 安装
          • 简介
      • 项目管理工具
        • Jira
    • 开发环境配置
      • 通用
        • Homebrew安装与使用
        • Git服务器添加SSH Key
        • koroFileHeader使用
        • nodejs与npm的安装
        • npm更换国内源
        • pip使用相关
        • PostgreSQL安装与使用
        • proxychain安装与使用
        • shell配置环境变量
        • snapd安装与使用
        • terminal走代理
    • 快捷键速查
      • shell常用快捷键
  • 3.语言篇
    • C
      • 代码规范
      • 语言技巧
    • Cpp
      • 代码规范
      • 基础知识
        • 理解C++中的左值和右值
      • 语言技巧
        • 并发编程
          • 简单的线程池实现
    • Golang
      • 代码规范
        • 避免使用转义字符串
        • 避免参数语义不明确
        • 嵌套式结构体
        • 函数的分组与顺序
        • 函数命名
        • 声明一致性
        • 导入别名
        • 使用字段名初始化结构体
        • 本地变量声明
        • map初始化
        • nil用法
        • 包命名
        • 命名Printf样式的函数
        • 减少嵌套
        • 缩小变量作用域
        • struct引用初始化
        • 测试表声明
        • 顶层变量声明
        • 不必要的else
      • 环境配置
        • 代码检查格式化工具
          • Go Fmt
          • Goimports
          • Golint
          • Go Vet
        • go mod详解
        • golang安装
        • Golang开发环境
        • Troubleshooting
      • 语言技巧
        • 如何分包
    • Java
      • 代码规范
      • 语言技巧
        • 注解编程
        • 动态代理
    • Js
      • 语言技巧
        • Rollup
    • Kotlin
      • 基础知识
        • 写给开发者Kotlin指引(一)
        • 写给开发者Kotlin指引(二)
    • Python
      • 语言技巧
        • Best Practice Of Python S Project Structure
  • 4.规范篇
    • Git message规范
  • 5.技术篇
    • Android技术
      • Hook
        • EdXposed例子
        • Android 10 上安装Magisk和EdXposed
      • Tinker
        • 1.Tinker及其使用
      • 准备
        • ADB连接设备步骤及注意事项
        • adb连接设备
        • aosp编译
      • 基础
        • Binder接口调用的鉴权方法
        • Make 及 Android 编译系统介绍
        • 使用Content Provider为其他应用提供数据
      • 源码阅读
        • Framework源码分析 Looper Handler
        • Framework源码分析 启动流程 ServiceManager的初始化
        • Framework源码分析 启动流程 Zygote启动SystemServer
    • JS Bridge
      • JSBridge初探
    • Kernel技术
      • kallsyms子系统
    • Test技术
      • 软件测试
        • jnekin+sonar 部署 问题总结
        • 性能测试基础
        • 软件测试的背景
        • 测试基础
        • 测试人员的核心竞争力
    • 操作系统原理
      • 处理器如何实现原子操作
Powered by GitBook
On this page
  • 一、Makefile 的写法
  • 1.1 Make
  • 1.2 Makefile 的语法规则
  • 1.3 Android Makefile
  • 二、Android 编译流程介绍
  • 2.1 Android 编译系统简介
  • 2.2 Android 编译流程介绍
  • 三、编译单个模块的流程介绍

Was this helpful?

  1. 5.技术篇
  2. Android技术
  3. 基础

Make 及 Android 编译系统介绍

本篇文档主要分三个部分, 第一部分是 Makefile 的写法, 第二部分概括性地介绍一下 Android 编译 过程, 第三部分以 libstagefright.so 为例, 介绍编译单个模块的整个流程.

一、Makefile 的写法

1.1 Make

Make 是一种组织程序构建过程的工具. 以 C 程序构建过程为例, 整个构建过程有预处理, 编译, 汇编, 链接等诸多步骤. 当需要构建的程序非常大时, 就需要类似 Make 的工具, 把整个构建过程有条不紊地 组织起来.

Make 通过读取 Makefile 配置文件进行工作, Makefile 是一系列构建规则的集合.

举个例子:

edit : main.o insert.o search.o files.o utils.o
    cc -o edit main.o insert.o search.o files.o utils.o
main.o : main.c defs.h
    cc -c main.c
insert.o : insert.c defs.h buffer.h
    cc -c insert.c
search.o : search.c defs.h buffer.h
    cc -c search.c
files.o : files.c defs.h buffer.h command.h
    cc -c files.c
utils.o : utils.c defs.h
    cc -c utils.c
clean :
    rm edit main.o insert.o search.o files.o utils.o

假定现在在某个 src 文件夹下, 文件夹中有例子中的各种 *.h, *.c 文件, 还有一个名为 Makefile 的文件, Makefile 中的内容就是上面例子中的文本. 那么在命令行执行 make 命 令就会把 *.c 的源文件编译成目标文件, 并最终生成可执行文件 edit, 也就完成了一次程序构 建过程. 也可以执行 make clean 命令, 这个命令相当于执行 rm edit main.o insert.o search.o files.o utils.o, 会把 edit 及构建 edit 的过程中生成的目标文件都删除.

从上面的例子中, 可以提取出 Makefile 中的核心结构如下:

<target>: <prerequisites>
    <commands>

其中,

  • target 是一个构建目标, 可以是目标文件, 可执行文件, 也可以就是一个"标签"

  • prerequisites 是生成构建目标 target 所需要的依赖

  • commands 是生成构建目标 target 需要执行的命令

1.2 Makefile 的语法规则

1.2.1 包含其他 Makefile 文件

include <filename>
-include <filename>

两条语句的区别是, 使用 -include <filename> 时, 如果找不到需要包含的文件, make 不会报 错, 会继续执行.

另外, 执行 make 命令时, 可以使用 -l 或者 --include-dir 参数, 来指定去哪些目录寻找 <filename> 对应的文件.

1.2.2 使用变量

makefile 中可以使用 = 或者 := 来定义变量, 使用 $(var) 来获取变量 var 的值:

objects = main.o insert.o search.o files.o utils.o
edit: $(objects)
    cc -o edit $(objects)

= 和 := 的区别在于, = 可以使用整个 makefile 中任意位置的变量定义新变量, 而 := 只能 使用在新变量定义之前的变量:

bar = $(ugh)
ugh = Huh
x = foo
y := $(x) bar

可以使用 += 操作符对变量进行追加:的

x += z
y += u

这里 += 中的 = 是 := 还是 = 与变量在定义时使用的符号有关.

1.2.3 使用条件判断

makefile 支持简单的条件判断:

libs_for_gcc = -lgnu
normal_libs =
ifeq ($(CC), gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo: $(objects)
$(CC) -o foo $(objects) $(libs)

这里的 ifeq-else-endif 就是一组完整的条件判断关键字, 类似的关键字还有 ifneq, ifdef, ifndef, 它们的含义分别是: 如果等于, 如果不等于, 如果已定义, 如果未定义.

1.2.4 使用函数

makefile 支持一些 make 内置的函数, 函数调用的语法是:

$(<funcName> <arguments>)

其中多个 arguments 以逗号隔开, 比如:

$(subst a,b,c)

makefile 支持的字符串处理函数有:

  • $(subst <from>,<to>,<text>): 把 text 中的 from 换成 to

  • $(strip <string>): 去除 string 首尾的空格

  • $(sort <list>) : 排序 list 字符串中的单词, $(sort foo bar lose) 返回 bar foo lose

  • $(firstword <text>) : 取 text 中的首个单词

  • 其他字符串函数: patsubst, findstring, filter, filter-out, word, wordlist, words

makefile 支持的文件名操作函数有:

  • $(dir <names...>) : 从文件名序列中取出目录部分, 包括反斜杠 /

  • $(notdir <names...>): 从文件名序列中取出非目录部分

  • $(suffix <names...>): 取后缀函数, 包括 .

  • $(basename <names...>) : 取前缀部分, . 之前的所有内容

  • $(addsuffix <suffix>,<names...>) : 把后缀 加到 中的每个单词后面

  • $(addprefix <prefix>,<names...>): 把前缀 加到 中的每个单词前面

  • $(join <list1>,<list2>) : 把 中的单词对应地加到 的单词后面,

    比如 $(join aaa bbb , 111 222 333) 返回值是 aaa111 bbb222 333

1.2.5 使用隐式规则

下面是一个 makefile:

foo : foo.o bar.o
        cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

1.3 Android Makefile

Android 源码中有很多名为 Android.mk 的文件, 这些文件是组织 Android 源码编译的 makefile. 一个典型的 Android.mk 如下:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES := \
      SoftAAC2.cpp \
      DrcPresModeWrap.cpp

LOCAL_C_INCLUDES := \
      frameworks/av/media/libstagefright/include \
      frameworks/native/include/media/openmax \
      external/aac/libAACdec/include \
      external/aac/libPCMutils/include \
      external/aac/libFDK/include \
      external/aac/libMpegTPDec/include \
      external/aac/libSBRdec/include \
      external/aac/libSYS/include

LOCAL_CFLAGS :=

LOCAL_CFLAGS += -Werror

LOCAL_STATIC_LIBRARIES := libFraunhoferAAC

LOCAL_SHARED_LIBRARIES := \
      libstagefright_omx libstagefright_foundation libutils libcutils liblog

LOCAL_MODULE := libstagefright_soft_aacdec
LOCAL_MODULE_TAGS := optional

include $(BUILD_SHARED_LIBRARY)

在最后一行 include $(BUILD_SHARED_LIBRARY) 之前, 都在定义或者清除变量, BUILD_SHARED_LIBRARY 的值是某一个文件的文件名, 把该文件包含进来, 并由该 文件中相关的语句执行编译, 生成动态库的过程.

由此可见, Android makefile 与普通 makefile 的区别, 只是 Android makefile 中有一些具有特定含 义的变量. 考虑到 Android 源码中定义了相当多的模块, 使用相同的变量名使得 Android makefile 的 书写和管理变得非常方便.

Android makefile 中常见的变量如下:

  • LOCAL_PATH := $(call my-dir), 这条语句获取当前 Android.mk 所在的路径, 并将其赋

    给 LOCAL_PATH

  • include $(CLEAR_VARS), 这条语句会将构建过程中使用的变量置为空, 避免之前的模块编译

    时定义的变量, 对此次编译产生影响.

  • LOCAL_SRC_FILES, 是编译当前模块需要的源文件.

  • LOCAL_C_INCLUDES, 是查找头文件的路径.

  • LOCAL_CFLAGS, 是编译时的选项.

  • LOCAL_STATIC_LIBRARIES, 是编译模块依赖的静态库.

  • LOCAL_SHARED_LIBRARIES, 是编译模块依赖的动态库.

  • LOCAL_MODULE, 是当前编译的模块的名字.

  • LOCAL_MODULE_TAGS, 是模块的标签, 决定当前模块是否被编译进某个产品

  • include $(BUILD_SHARED_LIBRARY), 将编译动态库的 makefile 文件包含进来, 决定如何

    通过上面的源文件, 依赖库等编译出当前编译的模块.

二、Android 编译流程介绍

2.1 Android 编译系统简介

在 Android 7.0 之前, 整个 Android 编译系统就是通过 makefile 来组织的, 模块目录下的 Android.mk 就是编译模块对应的 makefile.

从 Android 7.0 开始, Android 引入了新的编译配置文件 Android.bp, bp 是 blueprint 的缩写, blueprint 文件与 Android.mk 文件功能类似, 都是描述通过哪些源文件, 哪些依赖库 等将对应模块编译出来. Android.bp 是一种类似于 json 格式的文件, 相对于 Android.mk 写法更简单, 统一.

Android.bp 是 Soong/Ninja 组织的编译系统的配置文件, blueprint 和 Soong 是两个转换工具 用来将 Android.bp 转换成 Ninja 格式的文件, 与 make 对应的工具 ninja 会读取 Ninja 格式的 文件来进行编译过程. 在 Android.bp 完全取代 Android.mk 之前, Android 项目中的 Android.mk 文件会被叫做 kali 的转换工具转成 Ninja 格式的文件, 也由 ninja 读取并进行相应的 编译过程.

2.2 Android 编译流程介绍

2.2.1 在当前 Shell 环境下引入相关变量和函数

进行 Android 源码编译的第一步是执行 source build/envsetup.sh, envsetup.sh 是一个 shell 脚本文件, 其中定义了很多重要函数:

与编译相关的函数, 比如 lunch, m, mm, mmm, mma, mmma 等;

一些功能性的函数, 比如 printconfig 打印出当前的配置信息, print_lunch_menu 打印出 当前可选的编译产品列表, croot 回到源码的根目录, cgrep 在所有的 C 文件中查找, jgrep 在所有的 Java 文件中查找等.

2.2.2 选择编译的产品

编译的第二步执行命令 lunch, lunch 是第一步中引入的函数, 执行时可跟参数. 如果执行时 没有传入参数, 则会先使用 print_lunch_menu 函数打印出当前支持的所有产品, 再读取命令行 的输入作为选择. 确定了编译的产品之后, lunch 函数会对相关的环境变量进行设置, 并最终调用 print_config 函数打印出当前产品的配置.

2.2.3 执行 make 命令进行编译

做完前两步之后, 在源码根目录下执行 make 命令, 就开始了对整个 Android 源码的编译. 也可以进 到某一个模块目录下, 执行 mm 等单个模块的编译命令来编译单个模块. 编译单个模块的情形, 在本文的第三部分中进行介绍.

根据 make 的规则, make 在当前目录下寻找并读取 Makefile 文件, 根目录下 Makefile 中的内容 如下:

### DO NOT EDIT THIS FILE ###
include build/make/core/main.mk
### DO NOT EDIT THIS FILE ###

也就是把 build/make/core/main.mk 包含了进来, main.mk 才是 make 真正的入口.

在 main.mk 中定义了很多编译目标, 其中第一个也是默认的编译目标是 droid:

.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL): droid_targets

因此, 在命令行执行 make 就等价于执行 make droid, 从上面也可以看到, droid 依赖 的目标是 droid_targets. 而 droid_targets 又依赖于 droid_core, apps_only, blueprint_tools, dist_files 等, 层层依赖, 最终包含了整个 Android.

main.mk 中还定义了其他的编译目标, 比如 clean, sdk, all, update-api, snod 等, 其中 make snod 可以快速地重新打包 image.

除了定义了诸多编译目标, main.mk 中还引入了很多其他 makefile, 其中比较重要的有 config.mk 和 definitions.mk.

config.mk 中又包含了其他的 makefile:

  • pathmap.mk: 定义了一组值为特定目录下子目录名字的变量, 以便在某些模块的 makefile 中使

    用

  • BUILD_STATIC_LIBRARY:= $(BUILD_SYSTEM)/static_library.mk 这里看到了之前

    Android.mk 介绍中的 BUILD_STATIC_LIBRARY 变量, 也确实看到了它的值是一个

    makefile 文件, static_library.mk 中定义了如何编译静态库

  • shared_library.mk, java_library.mk, package.mk 等功能都与

    static_library.mk 类似.

definitions.mk 中定义了一些名字, 比如 my-dir, 用在

include $(call my-dir)

中用来获取当前 makefile 所在的路径. 还有 all-java-files-under 等, 可以获取当前目录下 所有 Java 文件等.

这一部分简单介绍了一下 Android 编译的整体情况, 总之就是, 所有的 makefile 层层包含, 其中定义 的大量编译目标最终组成了一棵编译目标树, makefile 解析完成后再逐步从目标树的叶子开始, 一层一层地向目标树根部编译, 最终完成了整个编译过程.

三、编译单个模块的流程介绍

这一部分以 libstagefright.so 的编译过程为例, 详细介绍编译单个模块的全过程. 为了简化 流程, 避开 Soong/Ninja 编译系统的格式转换等过程, 这里的介绍基于 Android 5.1 的源码.

待续...

PreviousBinder接口调用的鉴权方法Next使用Content Provider为其他应用提供数据

Last updated 4 years ago

Was this helpful?

还有很多可用的函数, 可参考 , 不一一列举了.

如果相关源文件存在, 执行 make foo 就会得到编译目标 foo. 这个 makefile 并没有指出如何 构建 foo 的依赖 foo.o 和 bar.o, make 内置的一些隐含规则会去寻找 foo.c 和 bar.c 进而生成 foo.o 和 bar.o. 除了 make 的内置规则, 也可以定义自己的模式规则, 具体写法可参考 .

GNU make
GNU make