Android编译系统入门(一)
做过Android平台开发的朋友对make,mm或make clean命令应该很熟悉,但也许大家只是熟知这些命令的作用却不知道这些命令底下有些什么原理?那么今天我就带着大家推开Android编译系统的大门,探索一下这片未知的恐怖之森(问啥要用恐怖之森后面大家就知道了)。
Makefile入门
在讲解Android编译系统之前首先来了解一下什么是Makefile:
简单的说,Makefile提供了一种机制,让使用者可以有效的组织工作。
注意这里用的是“工作”而非“编译”,这是因为Makefile并不是用来完成编译工作的,它只是一种规则的执行者,而使用者用它来执行什么规则没有任何限制。如既可以用它来编译系统,也能用它备份文档或只是打印log。
既然它是用来执行规则的,那么我们就来看看Makefile的规则。
TARGET:PREREQUISITES
COMMANDS
注意:COMMANDS前面必须有一个TAB制表符
在Makefile的规则中TARGET是需要生成的目标文件,PREREQUISITES是目标的先决条件,也是另一个规则中的target。COMMANDS是生成目标文件的命令,当prerequisites中的任何一个文件比target文件要新的时候就会触发commands。commands的具体内容取决于使用者的需求,如调用gcc编译器。
下面我们用一个简单的例子说明一下Makefile规则的用法。
涉及到的文件如下:
| 文件名 | 描述 |
|---|---|
| mian.c | 主函数所在文件 |
| test.h | 提供一个测试函数getNumber的声明 |
| test.c | 提供getNumber的函数实现,用于返回一个值 |
| Makefile | 我们的主角,用于编译整个例子 |
文件内容如下:
(1)test.h对getNumber进行声明。
int getNumber();
(2)test.c实现getNumber函数
#include "test.h"
int getNumber()
{
return 2333;
}
(3)main.c打印getNumber的返回值
#include <stdio.h>
#include "test.h"
int main()
{
printf("Hello,getNumber=%d\n",getNumber());
return 0;
}
(4)Makefile文件,用于组织这个小例子的编译工作
MakefileTest : main.o test.o
gcc -o MakefileTest main.o test.o
main.o : main.c
gcc -c main.c
test.o : test.c
gcc -c test.c
对上面这段代码简单解释一下,MakefileTest为TAEGET即目标产物,main.o和test.o是MakefileTest的先决条件。gcc -o MakefileTest main.o test.o则是MakefileTest对应的COMMANDS,这条命令使用gcc命令将main.o和test.o编译成MakefileTest可执行文件。
最后通过使用make命令就会在当前目录生成一个MakefileTest可执行文件,运行结果如下:
上面这个是一个非常简单的Makefile的示例,但“麻雀虽小,五脏俱全”,它清晰的展示了一个Makefile的编写过程。另外Make工具本身是非常强大的,它有很多隐含的规则来帮助开发者快速搭建复杂的编译体系。如利用它的自动推导功能可以简化对象间的依赖关系,还可以加入变量来减少重复输入。
上面的Makefile还可以写成这样:
OBJECT = main.o test.o
MakefileTest : $(OBJECT)
gcc -o MakefileTest $(OBJECT)
关于Makefile的更多用法可以查看how to write makefile。希望大家多了解一下makefile的用法,这有助于理解Android的编译系统。
Android编译系统入门
像我这样从事过Android平台开发的开发人员对make,mm等命令应该很熟悉,同样也知道Android.mk文件,用它结合mm命令来编译单个的Android模块。如果这就是你对Android编译系统的全部了解,那只能说你是一个普通的Android平台开发人员。想要在Android平台开发界混的话,学习和掌握Android编译系统的原理将是必不可少的。前面说道Android编译系统是一个恐怖之森,应为它真好符合恐怖之森的两个特点,让人望而却步和容易迷失方向。这么说一点也不过分,在学习Android编译系统的时候,如果缺少很明确的指引很容易迷失方向,或者让你根本就不敢去试着闯一闯。
makefile依赖树的概念
不难发现在makefile中的target的依赖关系实际上可以组成一棵树,我将其称为makefile依赖树,仍然以上一个MakefileTest为例,给出它的makefile依赖树:
只要照个这个树形结构顺藤摸瓜,那么分析Android编译系统也就变得有目的性了。
恐怖之森里的树
有了上面的知识做铺垫,我们也就可以大胆的往恐怖之森闯了。
根节点
既然是树,肯定有它的根节点,我们就来找一找Android编译系统的根节点。
如果make命令没有指定文件的话默认会在当前目录寻找Makefile这个文件,所以先看看Android源码根目录的那个Makefile文件:
### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###
原来这里只是指路牌,真正的文件是build/core/main.mk。
在 Makefile 使用 include 关键字可以把别的 Makefile 包含进来,这很像 C 语言的
#include,被包含的文件会原模原样的放在当前文件的包含位置。
打开main.mk发现它有上千行代码,并且在其中又包含了很多其他makefile脚本,所以整个文件的内部结构让人感觉杂乱无章,无法入手。这时候我们就不能一行行的去读这个文件,这样很可能会事倍功半。我们需要找出它其中依赖树的根节点,以此为突破口。但往往大型的工程中不止一棵依赖树,这时候如果没有指定依赖树的话make命令会以从上至下第一个target作为默认依赖树的根节点。其实大家非常熟悉的make clean中的clean就是这些依赖树中的一棵。
.PHONY: clean
clean:
@rm -rf $(OUT_DIR)/*
@echo "Entire build directory removed."
clean是一个伪目标,伪目标一般没有目标文件。且使用
.PHONY显示声明。
从上面clean的COMMANDS知道它的作用是删除$(OUT_DIR)下的所有目录和文件,而$(OUT_DIR)就是out目录。
OK,那么我们就跟着上面的思路找寻那棵默认依赖树,对照main.mk代码很快就发现了它的默认依赖树根节点:
# This is the default target. It must be the first declared target.
.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL):
从注释中可以看出来droid就是我们找的依赖树的根节点,不过这里只是定义了一下,没有给出真正的规则。根据关键字搜索的话会发现main.mk中有几处对droid规则的定义:
ifneq ($(TARGET_BUILD_APPS),)
# If this build is just for apps, only build apps and not the full system by default.
...
.PHONY: apps_only
apps_only: $(unbundled_build_modules)
droid: apps_only
...
else # TARGET_BUILD_APPS
...
# Building a full system-- the default is to build droidcore
droid: droidcore dist_files
上面会根据TARGET_BUILD_APPS变量的值是否为空来走不同的分支,如果不为空,则droid的先决条件是apps_only,如果为空droid的先决条件是droidcore和dist_files,我们使用默认的make命令编译android系统的话这里的TARGET_BUILD_APPS的值为空,所以走下面这个分支。TARGET_BUILD_APPS何时不为空,感兴趣的朋友可以自行分析一下。
main.mk总览
在分析droidcore和dist_files两个先决条件之前先来看一下main.mk的一个文件结构,它除了构建droid等依赖树外,有一大半内容是在做一下这些事情。
- 对编译环境的检测
比如java环境是否符合要求,当前是linux系统还是mac系统。如果这些检测中有任何一项不符合要求,则会终止编译。 - 进行一些必要的前期处理
比如整个项目工程是否要进行清理操作,部分工具的安装等。 - 引用其他Makefile文件
比如引用config.mk,cleanbuild.mk等。 - 设置全局变量
- 各种函数的实现
Android编译系统中定义了很多实用的函数,它们提供了整个编译系统的统一解决方案。比如my-dir这个在Android.mk中出镜率最高的函数,就是用来获得当前的路径。
下表是对Android编译系统中涉及的主要Makefile文件的解释,可供参考。
| Name | Description |
|---|---|
| main.mk | 整个编译系统的主导文件 |
| config.mk | 产品配置的主导文件 |
| base_rules.mk | 编译系统需要遵循的基础规则定义 |
| build_id.mk | 版本id号的定义 |
| cleanbuild.mk | clean操作的定义 |
| clear_vars.mk | LOCAL开头的相关系统变量 |
| definitions.mk | 提供了大量实用的函数定义 |
| envsetup.mk | 配置编译时的环境变量 |
| executable.mk | 负责BUILD_EXECUTABLE的具体实现 |
| host_executable.mk | 负责BUILD_HOST_EXECUTABLE的具体实现 |
| host_static_library.mk | 负责BUILD_HOST_STATIC_LIBRARY的具体实现,其他类型的BUILD_XX这里不再赘述 |
| product_config.mk | 产品级别的配置,属于config的一部分 |
| version_defaults.mk | 负责生成版本信息,如版本号BUILD_NUMBER := eng.\((USER).\)(shell date +%Y%m%d.%H%M%S) |
droidcore节点
droid规则的定义如下:
# Build files and then package it into the rom formats
.PHONY: droidcore
droidcore: files \
systemimage \
$(INSTALLED_BOOTIMAGE_TARGET) \
$(INSTALLED_RECOVERYIMAGE_TARGET) \
$(INSTALLED_USERDATAIMAGE_TARGET) \
$(INSTALLED_CACHEIMAGE_TARGET) \
$(INSTALLED_VENDORIMAGE_TARGET) \
$(INSTALLED_FILES_FILE)
可以看出来droidcore有如下几个先决条件,
| Prerequisite | Description |
|---|---|
| files | 代表其所依赖的先决条件的集合,没有实际意义 |
| systemimage | 将生成system.img |
| INSTALLED_BOOTIMAGE_TARGET | 将生成boot.img |
| INSTALLED_RECOVERYIMAGE_TARGET | 将生成recovery.img |
| INSTALLED_USERDATAIMAGE_TARGET | 将生成userdata.img |
| INSTALLED_CACHEIMAGE_TARGET | 将生成cache.img |
| INSTALLED_VENDORIMAGE_TARGET | 将生成vendor.img |
| INSTALLED_FILES_FILE | 将生成install-files.txt,用于记录当前系统中预装的程序、库等模块 |
这几个先决条件的生成原理类似,这里挑几个做重点分析。
1.files
定义如下:
# All the droid stuff, in directories
.PHONY: files
files: prebuilt \
$(modules_to_install) \
$(INSTALLED_ANDROID_INFO_TXT_TARGET)
prebuilt
"prebuilt"也是一个伪目标,它依赖于 $(ALL_PREBUILT),不过ALL_PREBUILT只用于早起的版本,目前已经废弃,建议使用它的替代品PRODUCT_COPY_FILES.modules_to_install
描述了系统所需要安装的模块。
# TODO: Remove the 3 places in the tree that use ALL_DEFAULT_INSTALLED_MODULES
# and get rid of it from this list.
modules_to_install := $(sort \
$(ALL_DEFAULT_INSTALLED_MODULES) \
$(product_FILES) \
$(foreach tag,$(tags_to_install),$($(tag)_MODULES)) \
$(CUSTOM_MODULES) \
)
其中product_FILES用于编译该产品涉及的相关文件,与product_FILES有直接联系的是product_MODULES,而product_MODULES是基于PRODUCT_PACKAGES的处理结果。简单说product_FILES是各个modules需要安装的文件列表。
tags_to_install是指那些被指定编译标志的modules。在选择了不同的编译标志,如user,debug,eng等后只有与之相关的模块会被编译进系统。
2.systemimage
systemimage的定义规则在Makefile中。
$(INSTALLED_SYSTEMIMAGE): $(BUILT_SYSTEMIMAGE) $(RECOVERY_FROM_BOOT_PATCH) | $(ACP)
@echo "Install system fs image: $@"
$(copy-file-to-target)
$(hide) $(call assert-max-image-size,$@ $(RECOVERY_FROM_BOOT_PATCH),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))
systemimage: $(INSTALLED_SYSTEMIMAGE)
可以看出来systemimage的先决条件为INSTALLED_SYSTEMIMAGE指代的内容,而后者的先决条件中BUILT_SYSTEMIMAGE是关键。
它的定义是
BUILT_SYSTEMIMAGE := $(systemimage_intermediates)/system.img
它的规则是
$(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)
$(call build-systemimage-target,$@)
最后就是通过build-systemimage-target这个函数生成system.img
dist_files节点
根节点droid还有一个依赖是dist_files,它在整个编译过程中就出现了一次,如下
# dist_files only for putting your library into the dist directory with a full build.
.PHONY: dist_files
可以看出来它是提供给我们用于存储多种分发包的,通常情况下它不做任何工作,我们可已跳过它。
Android编译系统入门的第一部分就写到这里,以上内容参考《深入理解Android内核设计思想》。下一部分将分析Android.mk的编写规则。
Android编译系统入门(一)的更多相关文章
- Android编译系统入门(二)
Android.mk的使用方法 在上一篇Android编译系统入门(一)中我们只要介绍了Android系统使用make命令默认编译的依赖树是droid,而droid是一个伪目标,它有两个先决条件dro ...
- [译]:Xamarin.Android开发入门——Hello,Android Multiscreen深入理解
原文链接:Hello, Android Multiscreen_DeepDive. 译文链接:Xamarin.Android开发入门--Hello,Android Multiscreen深入理解. 本 ...
- [译]:Xamarin.Android开发入门——Hello,Android深入理解
返回索引目录 原文链接:Hello, Android_DeepDive. 译文链接:Xamarin.Android开发入门--Hello,Android深入理解 本部分介绍利用Xamarin开发And ...
- [译]:Xamarin.Android开发入门——Hello,Android快速上手
返回索引目录 原文链接:Hello, Android_Quickstart. 译文链接:Xamarin.Android开发入门--Hello,Android快速上手 本部分介绍利用Xamarin开发A ...
- Hello, Android 快速入门
Hello, Android Android 开发与 Xamarin 简介 在这两节指南中,我们将 (使用 Xamarin Studio或 Visual Studio)建立我们的第一个 Xamarin ...
- Android工程师入门(二)——不忙不累怎么睡。。
安卓开发迫在眉睫,这周入个门吧! Android工程师入门(二) 四.在界面中显示图片 ImageView 是显示图片的一个控件. --属性 src——内容图片: background——背景图片/背 ...
- [电子书] 《Android编程入门很简单》
<Android编程入门很简单>是一本与众不同的Android学习读物,是一本化繁为简,把抽象问题具体化,把复杂问题简单化的书.本书避免出现云山雾罩.晦涩难懂的讲解,代之以轻松活泼.由浅入 ...
- Android实现入门界面布局
Android实现入门界面布局 开发工具:Andorid Studio 1.3 运行环境:Android 4.4 KitKat 代码实现 首先是常量的定义,安卓中固定字符串应该定义在常量中. stri ...
- Android编译系统详解(一)
++++++++++++++++++++++++++++++++++++++++++ 本文系本站原创,欢迎转载! 转载请注明出处: http://blog.csdn.net/mr_raptor/art ...
随机推荐
- 一个官翻教程集合:ASP.NET Core 和 EF Core 系列教程
通过一个大学课程案例讲解了复杂实体的创建过程及讲解 1.ASP.NET Core 和 Entity Framework Core 系列教程——入门 (1 / 10) 2.ASP.NET Core 和 ...
- BZOJ 3000(Big Number-Stirling公式求n!近似值)
3000: Big Number Time Limit: 2 Sec Memory Limit: 128 MB Submit: 220 Solved: 62 [Submit][Status] De ...
- 查看selinux的状态
sh-4.1# getenforce Disabled sh-4.1# getenforce Disabled 永久方法 – 需要重启服务器 修改/etc/selinux/config文件中设置SEL ...
- C/C++ 错误笔记-解决swap函数与标准库的std::swap函数冲突的问题
下午写了一份代码: #include <iostream> using namespace std; // 模板1:交换基本类型的值 template<typename T> ...
- neo4j使用笔记
#coding:utf- __author__ = 'similarface' # 安装驱动:pip install neo4j-driver from neo4j.v1 import GraphDa ...
- Oracle之配置客户端登陆多个远程数据库
一.引言 一直搞不明白Oracle数据库的客户端是怎么回事,怎么配置,前几天由于工作中需要用到Oracle,而且需要连接两个不同的数据库,就通过上网和请教同事终于把客户端的配置搞定了,记录之,学习之 ...
- CCNA2.0笔记_WAN技术-专线
WAN拓扑 Routers.CSU/DSU.WAN switches.Core routers.Modems 使用 数据链路层协议(二层) 来建立对端连接 WAN的三种连接方式 广域网链路类型: ·V ...
- mysql替换成指定字符
,,, ), 'XXXX' )-- 隐藏从第四位开始的6个字符,包括第四个字符,替换成X
- hadoop节点挂死的一次分析报表。
hadoop的一个节点unused了.然后重启启动hadoop的服务,仍有有一个hadoop的节点起不来.多次重启hadoop和杀进程之后,发现hadoop的master和slave节点上的状态在切换 ...
- 跟着百度学PHP[14]-初识PDO数据库抽象层
目录: 00x1 php中的pdo是什么? 00x2 pdo创建一个PDO对象 00x1 php中的pdo是什么? 就是操作数据库的方法,pdo就是把操作数据库的函数封装成一个pdo类,其间做了安全验 ...