概述
本文的目的是用比较容易理解的方式,介绍一下整个Android项目的编译。至少知道大概的编译流程是怎么样的,项目里面的Android.mk文件包含些什么内容。
makefile的作用
makefile文件用来描述文件之间的依赖关系,并描述文件的编译规则。我们知道从源代码到可执行程序,中间要经历编译生成中间文件(windows里面的obj文件,Linux里面的.o文件),链接这些中间文件生成可执行文件的过程。
一个最简单的makefile文件为:
main.o : main.c defs.h
cc -c main.c
main.o依赖main.c和defs.h两个文件,使用命令cc来编译main.c最终生成main.o这个中间文件。
当然我们在Android中极少看到这种形式的makefile文件。我想讲的重点是,Android中的mk文件会定义一些全局变量,描述模块编译的先后顺序,声明模块编译依赖的其他模块(包括一些三方库)。整个Android项目是由很多模块组成,项目的编译涉及到譬如Java源码的编译,C源码的编译,python源码的编译等等。在make文件中同样也定义了这些编译工具的路径,这样我们就可以调用这些工具来编译不同种类的源码。
Android项目编译系统
我们一般编译项目会执行如下命令:
source build/envsetup.sh
lunch 3 #或者lunch full_eng 取决于你当前的项目配置情况
make -j8
第一条命令引入了shell脚本envsetup.sh,在这个文件头的注释里面就描述清楚了该文件的作用。我简单翻译如下:
将下面这些命令引入到编译环境中:
- lunch: lunch <product_name>-<build_variant>,指定编译哪个目标设备。
- tapas: tapas [<App1> <App2> ...] [arm|x86|mips|armv5|arm64|x86_64|mips64] [eng|userdebug|user]
- croot: 切换工作目录到项目根目录.
- m: 在源码根目录执行make命令.
- mm: 仅编译当前目录下面的模块,但是不包括他们的依赖(比如某个模块依赖的模块再另一个地址,所依赖的模块就不会被编译).
- mmm: 同mm,不过是编译该命令后面制定的目录下的所有模块,同样不包含模块的依赖。
To limit the modules being built use the syntax: mmm dir/:target1,target2.
- mma: 编译当前目录下所有的模块和它们的依赖。
- mmma: 编译制定目录下所有的模块和它们的依赖。
- cgrep: 在所有的C/C++文件中搜索,用法同grep。
- ggrep: 在所有的gradle文件中搜索。
- jgrep: 在所有的Java文件中搜索。
- resgrep: 在所有的res/*.xml资源文件中搜索。
- mangrep: 在所有的AndroidManifest.xml资源文件中搜索。
- sepgrep: 在所有的sepolicy文件(后缀为te的文件)中搜索。
- sgrep: Greps on all local source files.
- godir: Go to the directory containing a file.
- carrier: for open cmcc cu and others
上面的命令在执行完第一条命令之后都是可以使用的,在平常使用中可以节省大量的时间,特别是m/mm/mmm/mma/mmma编译命令和grep的变种命令。
lunch执行载入某个项目的编译环境和设置,最后执行make命令即可开始编译。-j8的意思是开启8个工作线程(j代表job),8这个数值可以根据自己的cpu内核来决定,一般一个内核可以开双线程,所以如果是4核cpu,那么就可以用8。执行make命令后,Android编译系统会根据当前的编译环境设置,以根目录下面的Makefile文件内容作为编译启动入口,执行编译指令。包括编译系统核心源码,编译厂商自己添加的模块源码,编译应用模块的源码(Android.mk),再将编译结果链接在一起输出,就是我们最后烧机的img文件了。
编译结果输出到源码根目录下面的out/文件夹。
/out/host/:在Android编译过程中要用到的各种主机工具,包括各种SDK tools:emulator,adb,aapt等。所谓主机,是相对你的目标工程机来说的,也就是你用来编译这套代码的电脑。
/out/target/common/:针对目标设备的编译结果,主要是JAVA应用代码和库文件。
/out/target/product//:特定产品的编译结果,里面包含具体平台的代码,比如C/C++库和二进制文件。其实就是库文件和镜像文件。
/out/dist/。这个比较少看到就不说了。参考文献里面有详细的说明。
三种不同的make类型
如果太深入细节去看,我们就很容易晕头,所以要分模块和分种类。譬如说建房子,我们从一块块砖头来复述建房子过程,那铁定是痛苦和漫长的过程。但是如果我们先搭建钢筋混凝土框架,再把砖头往框架里面塞。这样理解起来是不是很快。
上面讲到Android编译系统中make的三种类型,核心代码make,厂商项目特定make,应用模块源码Android.mk。
核心代码make
前文讲到make的入口是根目录的Makefile文件,内容如下:
### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###
其实就是跳转到了build/core/目录下面的main.mk,这个目录就是整个系统核心源码编译的所有make文件都在这个目录了。包括厂商项目特定make和应用模块的make都会在编译过程中被覆盖到。这个文件是顶重要的,描述了编译的整个流程,串联起了所有的make文件。
subdir_makefiles :=
$(shell build/tools/findleaves.py $(FIND_LEAVES_EXCLUDES) $(subdirs) Android.mk)
上面的代码摘自main.mk,可以看到其实所有的Android.mk文件是使用python脚本来搜索的。而且main.mk里面include了很多make文件,设置了一些编译变量,在分析Android.mk的时候,我们会分析其中一部分变量。
注意:所有make文件里面定义的变量都是在同一个命名空间里面,所以要考虑到重命名和变量在使用前清空。这个就是为什么Android.mk里都包含include $(CLEAR_VARS)这个语句。同样CLEAR_VARS这个变量对应的是”build/core/”文件夹下面的clear_vars.mk文件。很多我们在Android.mk里面看到的变量都是在main.mk里面引用的make文件里面定义了。
下面是从参考文献里面引入的一张main.mk调用图
像一棵树一张,串联起了整个项目的编译过程。这些mk文件的作用,请仔细阅读参考文献2.
Android源码包含的模块也是分类的,比如Java库,APK应用,主机库,目标机器共享库,可执行文件等等。对于同一种类型的模块,不适用的编译方法也是一样的,所以Android将不同模块的编译方法抽取出来,定义了下面的变量:
BUILD_HOST_STATIC_LIBRARY/BUILD_HOST_SHARED_LIBRARY:编译主机静态/共享库
BUILD_STATIC_LIBRARY/BUILD_SHARED_LIBRARY:编译目标机器静态共享库
BUILD_EXECUTABLE/BUILD_HOST_EXECUTABLE:编译目标机器可执行文件/编译主机可执行文件
BUILD_PACKAGE:编译APK文件
BUILD_PREBUILT/BUILD_MULTI_PREBUILT:如何处理一个或多个已经编译好的文件(例如一个jar包)
BUILD_HOST_PREBUILT:同上,针对主机。
BUILD_JAVA_LIBRARY/ BUILD_STATIC_JAVA_LIBRARY:如何编译设备上的Java库/如何编译设备上的静态Java库
BUILD_HOST_JAVA_LIBRARY:如何编译主机上的Java库
这些变量都是在同目录下面的config.mk文件里面定义的,这个mk文件同样在main.mk里面被include了。以BUILD_PACKAGE为例,定义如下:
#config.mk
BUILD_PACKAGE:= $(BUILD_SYSTEM)/package.mk
其实引入的是package.mk模块。这个跟我们上面说的针对不同模块,Android抽取了不同的编译方法。所以针对APK包的编译,Android使用的是package.mk进行处理,这个也是一种复用,防止出现冗余的编译代码。
厂商项目特定make
一般在同一套代码下面,不同的厂商会定义自家的多个产品。通常这些产品差异是硬件配置,而不同的硬件对应不同的驱动。所以不同的产品都需要有自己的make文件来告诉编译系统应该编译哪些产品本身的文件。一般都会在”/device“目录下面定义厂商的文件夹,而这个文件夹下面会定义具体产品相关的文件夹,这个文件夹下面就放置的是具体产品相关的make文件。device目录结构如下:
在这些mk文件里面,最重要的是AndroidProducts.mk和BoardConfig.mk文件。一般产品只是有部分配置改变,所以其实这些make文件可以继承某个make文件,仅仅修改差异的部分。AndroidProducts.mk其实定义了如下语句:
$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
所以可以看到这个make文件其实是继承了core_64_bit.mk文件的,这个也简化了我们建立新项目的步骤。
应用源码Android.mk
Android编译系统会将源码分成不同的模块。大家经常看到Android.mk文件,这个文件一般放在模块的根目录下面,描述的是该模块如何编译,依赖哪些模块,最后以什么形式输出。我们以Settings模块为例,介绍Android.mk里面的元素和用法:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES :=
javalib.jar:libs/javalib.jar
include $(BUILD_MULTI_PREBUILT)
include $(CLEAR_VARS)
LOCAL_JAVA_LIBRARIES := bouncycastle conscrypt telephony-common ims-common audiopostprocess
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v13 common_zxing jsr305 letv-domain-common
LOCAL_MODULE_TAGS := optional
res_dirs := cus_res res
LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
LOCAL_SRC_FILES :=
$(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := Settings
LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
ifneq ($(INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
endif
include frameworks/base/packages/SettingsLib/common.mk
include $(BUILD_PACKAGE)
一般开头都会有两行代码:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
作用是:
将当前模块的根目录赋值为一个变量LOCAL_PATH,方便后面引用。
我们前面说过,所有的mk文件都是被编译系统集成在一起的,所以所有的变量都是在同一个命名空间里面。CLEAR_VARS用于清楚一些变量的值,防止影响到本模块的编译。从include语法看,这个变量应该指向的是某个mk文件。
剩下的大部分都是变量定义,我们看看这些变量含义:
LOCAL_SRC_FILES:当前模块包含的所有源代码文件。
LOCAL_MODULE:当前模块的名称,这个名称应当是唯一的,模块间的依赖关系就是通过这个名称来引用的。
LOCAL_C_INCLUDES:C 或 C++ 语言需要的头文件的路径。
LOCAL_STATIC_LIBRARIES:当前模块在静态链接时需要的库的名称。
LOCAL_SHARED_LIBRARIES:当前模块在运行时依赖的动态库的名称。
LOCAL_CFLAGS:提供给 C/C++ 编译器的额外编译参数。
LOCAL_JAVA_LIBRARIES:当前模块依赖的 Java 共享库。
LOCAL_STATIC_JAVA_LIBRARIES:当前模块依赖的 Java 静态库。
LOCAL_PACKAGE_NAME:当前 APK 应用的名称。
LOCAL_CERTIFICATE:签署当前应用的证书名称。
LOCAL_MODULE_TAGS:当前模块所包含的标签,一个模块可以包含多个标签。标签的值可能是 debug, eng, user,development 或者 optional。其中,optional 是默认标签。标签是提供给编译类型使用的。
这些都不是最关键的,仅仅只是变量赋值罢了,点睛之笔是最后的
include $(BUILD_PACKAGE)
这个变量我们上面提到过
BUILD_PACKAGE:编译APK文件
#config.mk
BUILD_PACKAGE:= $(BUILD_SYSTEM)/package.mk
所以这个地方是引入package.mk文件,而这个文件会使用在这个Android.mk文件里面赋值的各种变量,编译生成制定的目标。
参考文献
Makefile经典教程(掌握这些足够)
特别推荐: 理解 Android Build 系统
Android build system & Android.mk 规范
IT SPA Club(注:需科学上网)
最后
以上就是完美吐司为你收集整理的当我们谈Android编译系统的时候,我们在干吗?makefile的作用Android项目编译系统三种不同的make类型参考文献的全部内容,希望文章能够帮你解决当我们谈Android编译系统的时候,我们在干吗?makefile的作用Android项目编译系统三种不同的make类型参考文献所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复