当我们谈Android编译系统的时候,我们在干吗?

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/murphykwu/article/details/52460394

本文的目的是用比较容易理解的方式,介绍一下整个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/文件夹。

  1. /out/host/:在Android编译过程中要用到的各种主机工具,包括各种SDK tools:emulator,adb,aapt等。所谓主机,是相对你的目标工程机来说的,也就是你用来编译这套代码的电脑。

    扫描二维码关注公众号,回复: 4372673 查看本文章
  2. /out/target/common/:针对目标设备的编译结果,主要是JAVA应用代码和库文件。

  3. /out/target/product//:特定产品的编译结果,里面包含具体平台的代码,比如C/C++库和二进制文件。其实就是库文件和镜像文件。

  4. /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将不同模块的编译方法抽取出来,定义了下面的变量:

  1. BUILD_HOST_STATIC_LIBRARY/BUILD_HOST_SHARED_LIBRARY:编译主机静态/共享库

  2. BUILD_STATIC_LIBRARY/BUILD_SHARED_LIBRARY:编译目标机器静态共享库

  3. BUILD_EXECUTABLE/BUILD_HOST_EXECUTABLE:编译目标机器可执行文件/编译主机可执行文件

  4. BUILD_PACKAGE:编译APK文件

  5. BUILD_PREBUILT/BUILD_MULTI_PREBUILT:如何处理一个或多个已经编译好的文件(例如一个jar包)

  6. BUILD_HOST_PREBUILT:同上,针对主机。

  7. BUILD_JAVA_LIBRARY/ BUILD_STATIC_JAVA_LIBRARY:如何编译设备上的Java库/如何编译设备上的静态Java库

  8. 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)

作用是:

  1. 将当前模块的根目录赋值为一个变量LOCAL_PATH,方便后面引用。

  2. 我们前面说过,所有的mk文件都是被编译系统集成在一起的,所以所有的变量都是在同一个命名空间里面。CLEAR_VARS用于清楚一些变量的值,防止影响到本模块的编译。从include语法看,这个变量应该指向的是某个mk文件。

剩下的大部分都是变量定义,我们看看这些变量含义:

  1. LOCAL_SRC_FILES:当前模块包含的所有源代码文件。

  2. LOCAL_MODULE:当前模块的名称,这个名称应当是唯一的,模块间的依赖关系就是通过这个名称来引用的。

  3. LOCAL_C_INCLUDES:C 或 C++ 语言需要的头文件的路径。

  4. LOCAL_STATIC_LIBRARIES:当前模块在静态链接时需要的库的名称。

  5. LOCAL_SHARED_LIBRARIES:当前模块在运行时依赖的动态库的名称。

  6. LOCAL_CFLAGS:提供给 C/C++ 编译器的额外编译参数。

  7. LOCAL_JAVA_LIBRARIES:当前模块依赖的 Java 共享库。

  8. LOCAL_STATIC_JAVA_LIBRARIES:当前模块依赖的 Java 静态库。

  9. LOCAL_PACKAGE_NAME:当前 APK 应用的名称。

  10. LOCAL_CERTIFICATE:签署当前应用的证书名称。

  11. 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文件里面赋值的各种变量,编译生成制定的目标。

参考文献

  1. Makefile经典教程(掌握这些足够)

  2. 特别推荐: 理解 Android Build 系统

  3. Android build system & Android.mk 规范

  4. IT SPA Club(注:需科学上网

猜你喜欢

转载自blog.csdn.net/murphykwu/article/details/52460394