在Android源码环境中,我们开发好一个模块后,再写一个Android.mk文件,就可通过m/mm/mmm/make等命令进行编译。此外,通过make命令还可制作各种系统镜像文件,例如system.img、boot.img和recovery.img等。这一切都得益于Android编译系统,它为我们处理了各种依赖关系,以及提供各种有用工具。本文对Android编译系统进行简单介绍以及制定学习计划。
在研究Android编译系统之前,我们首先需要了解Linux系统的make命令。在Linux系统中,我们可以通过make命令来编译代码。Make命令在执行的时候,默认会在当前目录找到一个Makefile文件,然后根据Makefile文件中的指令来对代码进行编译。也就是说,make命令执行的是Makefile文件中的指令。Makefile文件中的指令可以是编译命令,例如gcc,也可以是其它命令,例如Linux系统中的shell命令cp、rm等等。理解这一点非常重要,因为虽然通常我们说make命令是可以编译代码的,但是它实际上可以做任何事情。
看到这里,有的小伙伴可能会说,在Linux系统中,直接通过shell命令也可以做很多事情啊,它和make命令有什么区别呢?通过前面的介绍可以知道,make命令事实也是通过shell命令来完成任务的,但是它的神奇之处是可以帮我们处理好文件之间的依赖关系。我们通常都有会这样的一个需求,假设有一个文件T,它依赖于另外一个文件D,要求只有当文件D的内容发生变化,才重新生成文件T。这种需求在编译系统中表现得尤其典型,当一个.c文件include的.h文件发生变化时,需要重新编译该*.c文件,或者当一个模块A所引用的模块B发生变化时,重新编译模块B。正是由于编译系统中存在这种典型的文件依赖需求,而make命令又是专门用来解决这种文件依赖问题的,因此我们通常认为make命令是用来编译代码的。
Make命令是怎么知道两个文件之间存在依赖关系,以及当被依赖文件发生变化时如何处理目标文件的呢?答案就在前面提到的Makefile文件。Makefile文件实际上是一个脚本文件,就像普通的shell脚本文件一样,只不过它遵循的是Makefile语法。Makefile文件最基础的功能就是描述文件之间的依赖关系,以及怎么处理这些依赖关系。例如,假设有一个目录文件target,它依赖于文件dependency,并且当文件dependency发生变化时,需要通过command命令来重新生成文件T,这时候我们就可以在Makefile编写以下语句:
target: dependency
<tab>command -o target -i dependency
我们假设命令command的-o选项指定的是输出文件,而-i选项指定的是输入文件。此外,命令command必须是另起一行,并且以tab键开头。
这就是最基础也是最主要的Makefile文件语法。当然,Makefile文件还有很多其它的语法,这里不可能一一描述。老罗推荐一本书《GNU make中文手册》,里面非常详细地介绍了make以及Makefile文件语法。
通常我们在一个源代码工程中,包含有非常多模块,模块之间保持相对独立。我们说相对独立,是指模块之间只在接口上存在依赖,但是在实现上是相全独立的。例如,我们有一个SO模块A,它引用了另一个SO模块B的一个导出函数F。只要函数F的原型不变,那么模块B就可以独立于模块A来实现,而模块A只需要一个包含有函数F原型声明的头文件即可对模块B进行引用。在一个源代码工程中,使得模块之间保持上述的相对独立非常重要,否则的话,当模块多了之后,就会很容易造成混乱。
当一个源代码工程被划分成很多个相对独立的模块之后,我们就很直觉地想到,为每一个模块都编译写一个Makefile文件,使得它们可以独立编译,然后再在工程的根目录下编写一个Makefile文件来递归执行这些Makefile文件。事实上,这种做法是错误的。正确的做法无论工程有多少个模块,都应该只有一个Makefile文件。注意,即使整个工程只有一个Makefile文件,但是我们仍然是要求对工程进行模块划分,并且需要保持这些模块之间的相对独立性,否则的话,就会同样引发混乱。在整个工程只有一个Makefile文件的情况下,我们要求工程的各个模块拥有自己的Makefile片段。这些Makefile片段统统直接或者间接地通过Makefile语法中的include指令包含在工程的Makefile文件中,然后再进行编译。
为什么我们要求整个工程只有一个Makefile文件,而不是工程的每一个模块都拥有一个Makefile文件呢?直觉告诉我们,每一个模块都拥有一个Makefile文件就会显得更加独立。事实的确如此,每一个模块都拥有自己的Makefile文件可以使得它更加独立。但是这种方法在处理模块之间的依赖关系时会显得力不从心,导致make命令要么是做得太少(do too little),要么是做得太多(do too much)。Make命令做得太少意味着编译结果不正确,而做得太多意味着编译速度慢。注意,出现这种情况并不是make命令本身的设计有问题,而是因为我们给了它不好的输入,就是所谓的"垃圾进,垃圾出"(Garbage In, Garbage Out)。
早在1998年的时候,Peter Miller就发现了为工程的每一个模块都编写一个Makefile文件的种种坏处,并且发表有一篇论文Recursive Make Considered Harmful。有兴趣的小伙伴可以读一下,写得非常好。这里我们简单从这篇论文摘录一下为什么Recursive Make会导致"do too little"和"do too much"。
假设我们有一个工程,它的目录结构如下所示:
Project
----Makefile
----ant
----Makefile
----main.c
----bee
----Makefile
----parse.c
----parse.h
顶层目录的Makefile的内容如下所示:
MODULES = ant bee
all:
for dir in $(MODULES); do \
(cd $$dir; $(MAKE) all); \
done
ant目录下的Makefile的内容如下所示:
all: main.o
main.o: main.c ../bee/parse.h
$(CC) -I../bee -c main.c
通过图1的无回路有向图(DAG)可以清楚看到ant目录的Makefile所描述的文件依赖关系:
图1 ant模块的文件依赖关系图
bee目录下的Makefile的内容如下所示:
OBJ = ../ant/main.o parse.o
all: prog
prog: $(OBJ)
$(CC) -o $@ $(OBJ)
parse.o: parser.c parse.h
$(CC) -c parse.c
通过图2的无回路有向图(DAG)可以清楚看到bee目录的Makefile所描述的文件依赖关系:
图2 bee模块的文件依赖关系图
到目前为止,一切都正常,我们在工程根目录下执行make命令,就可以递归对ant和bee模块进行编译,并且最终生成可执行文件prog。
考虑一个情景,bee目录的parse.h和parse.c文件是通过yacc工具自动生成的,也就是我们在bee/Makefile文件增加以下内容来生成parse.h和parse.c文件:
parse.c parse.h: parse.y
$(YACC) -d parse.y
mv y.tab.c parse.c
mv y.tab.h parse.h
这时候bee模块的文件依赖关系图如图3所示:
图3 修改后的bee模块文件依赖关系图
这时候如果我们修改了parse.y文件,那么在工程根目录执行make命令时,先会对ant模块进行编译,然后再对bee模块进行编译。由于对ant模块进行编译时,parse.h文件的内容还没有被修改,因此该文件就认为是最新的,于是就不会重新生成main.o文件。接下来对bee模块进行编译时,就会重新生成parse.h和parse.c文件,并且重新生成parse.o文件。很遗憾,在重新生成prog文件时,使用的是过旧的main.o文件,于是就会得到不正确的prog文件。
产生这个问题的原因就在于ant模块缺乏对parse.h文件的掌控,也就是不知道parse.h文件是如何产生的。如果我们坚持使用这种递归Makefile的方法来编译工程,并且希望解决上述问题,那么有三种方法:
1. 修改工程目录的Makefile,使得它先对bee模块进行编译,再对ant模块进行编译。然而,这就相当于是要求我们需要明确地指定工程的每一个模块的编译顺序。在一个包含有非常多模块的工程里面,要确定每一个模块的编译顺序是相当困难的。而且当我们新增、修改或者删除模块之后,又需要重新确定各个模块的编译顺序。理想的做法是让make自动地帮我们决定各个模块的编译顺序,这也是我们使用make命令来编译工程的初衷。
2. 对工程的各个模块进行重复编译,也就是将工程目录的Makefile修改为以下的内容:
MODULES = ant bee
all:
for dir in $(MODULES); do \
(cd $$dir; $(MAKE) all); \
done
for dir in $(MODULES); do \
(cd $$dir; $(MAKE) all); \
done
这里我们只对每一个模块进行了一次重复编译。然而,在一个复杂的工程中,有可能需要多次进行重复编译才能得到正确的编译结果。与前一种方法(do too little)相比,这里明显就是"do too much"。这会使得我们浪费CPU资源,并且拖慢整个工程的编译速度。
3. 修改ant/Makefile文件,增加如下所示的内容:
.PHONY: ../bee/parse.h
../bee/parse.h:
cd ../bee; \
make clean; \
make all
这种方法在每次编译ant模块之前,都先对bee模块进行重新编译,无论它是否需要。与第2种方法相比,这种方法不用对工程的每一个模块都进行重新编译,但是它仍然是"do too much",因为不管bee模块是否需要重新编译,它都会被重新编译。
由此可见,上述三种方法,要么是"do too little",要么是"do too much",它们虽然都能解决问题,但都不是理想的解决方案。如果我们进一步分析问题的根源,就会发现工程的文件依赖关系是属于工程级别的,也就是它们属于一个整体,而当我们将它们划分成模块级别的时候,就会造成各个模块只看到一部分的文件依赖关系,因此就不能得到正确的编译结果。
为了保持工程文件依赖关系的整体性,我们就必须使得整个工程只存在一个Makefile。当整个工程只存在一个Makefile时,我们就可以很容易地构造出如图4所示的文件依赖图:
图4 完整的文件依赖关系图
整个工程只有一个Makefile,听起来似乎是一件很疯狂的事情,因为这个Makefile可能会变得无比庞大和复杂。其实不用担心,我们可以按照模块来将这个Makefile划分成一个个Makefile片段(fragement),然后通过Makefile的include指令来将这些Makefile片段组装在一个Makefile中。与递归Makefile相比,每一个模块现在拥有的是一个Makefile片段,而不是一个Makefile文件。这正是Android编译系统的设计思想和原则,也就是说,我们平时所编写的Android.mk编译脚本都只不过是整个Android编译系统的一个Makefile片段。
明白了Android编译系统的设计思想和原则之后,我们就可以通过图5来观察一下Android编译系统的整体架构了:
图5 Android编译系统架构
在使用Android编译系统之前,我们需要打开一个shell进入到Android源码根目录中,并且在该shell中将build/envsetup.sh脚本文件source进来。脚本文件build/envsetup.sh被source到当前shell的过程中,会在vendor和device两个目录将厂商指定的envsetup.sh也source到当前shell当中,这样就可以获得厂商提供的产品配置信息。此外,脚本文件build/envsetup.sh还提供了以下几个重要的命令来帮助我们编译Android源码:
1. lunch
用来初始化编译环境,例如设置环境变量和指定目标产品型号。Lunch命令在执行的时候,主要做两件事情。第一件事情是设置TARGET_PRODUCT、TARGET_BUILD_VARIANT、TARGET_BUILD_TYPE和TARGET_BUILD_APPS等环境变量,用来指定目标产品类型和编译类型。第二件事情是通过make命令执行build/core/config.mk脚本,并且通过加载另外一个脚本build/core/dumpvar.mk打印出当前的编译环境配置信息。注意,build/core/config.mk和build/core/dumpvar.mk均为Makefile脚本,因此它们可以通过make命令来执行。另外,build/core/config.mk脚本还会加载一个名称为BoradConfig.mk的脚本以及build/core/envsetup.mk脚本来配置目标产品型号的相关信息。
2. m
相当于是在执行make命令。对整个Android源码进行编译。
3. mm
如果是在Android源码根目录下执行,那么就相当于是执行make命令对整个源码进行编译。如果是在Android源码根目录下的某一个子目录执行,那么就在会在从该子目录开始,一直往上一个目录直至到根目录,寻找是否存在一个Android.mk文件。如果存在的话,那么就通过make命令对该Android.mk文件描述的模块进行编译。
4. mmm
后面可以跟一个或者若干个目录。如果指定了多个目录,那么目录之间以空格分隔,并且每一个目录下都必须存在一个Android,mk文件。如果没有在目录后面通过冒号指定模块名称,那么在Android.mk文件中描述的所有模块都会被编译,否则只有指定的模块会被编译。如果需要同时指定多个模块,那么这些模块名称必须以逗号分隔。它的语法如下所示:
mmm <dir-1> <dir-2> ... <dir-N>[:module-1,module-2,...,module-M]
该命令会通过make命令来执行Android源码根目录下的Makefile文件,该Makefile文件又会将build/core/main.mk加载进来。文件build/core/main.mk在加载的过程中,还会加载以下几个主要的文件:
(1). build/core/config.mk
该文件根据lunch命令所配置的产品信息在build/target/board、vendor或者device目录中找到对应的BoradConfig.mk文件,以及通过加载build/core/product_config.mk文件在build/target/product、vendor或者device目录中找到对应的AndroidProducts.mk文件,来进一步对编译环境进行配置,以便接下来编译指定模块时可以获得必要的信息。
(2). build/core/definitions.mk
该文件定义了在编译过程需要调用到的各种自定义函数。
(3). 指定的Android.mk
这些指定的Android.mk环境是由mmm命令通过环境变量ONE_SHOT_MAKEFILE传递给build/core/main.mk文件使用的。这些Android.mk文件一般还会通过环境变量BUILD_PACKAGE、BUILD_JAVA_LIBRARY、BUILD_STATIC_JAVA_LIBRARY、BUILD_SHARED_LIBRARY、BUILD_STATIC_LIBRARY、BUILD_EXECUTABLE和BUILD_PREBUILT将build/core/package.mk、build/core/java_library.mk、build/core/static_java_library.mk、build/core/shared_library.mk、build/core/static_library.mk、build/core/executable.mk和build/core/prebuilt.mk等编译片段模板文件加载进来,来表示要编译是APK、Java库、Linux动态库/静态库/可执行文件或者预先编译好的文件等等。
(4). build/core/Makefile
该文件包含了用来制作system.img、ramdisk.img、boot.img和recovery.img等镜像文件的脚本。
在接下来的一系列文章中,我们将按照以下情景来进一步分析Android的编译系统:
2. 模块编译命令mmm的执行过程;
3. Android镜像文件的制作过程;
希望通过这三个情景的分析,使得我们对Android的编译系统有一个深刻的认识,以提高我们的Android系统开发效率,敬请关注!更多信息可以关注老罗的新浪微博:http://weibo.com/shengyangluo
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。