AOSP构建系统
一、先从Make讲起
Make用来干什么的?
Make是linux环境下控制编译过程的工具,makefile文件描述了一个程序中各个文件之间的关系,并且提供文件的更新命令,在一个程序中,可执行程序文件的更新依靠OBJ文件,而OBJ文件是由源文件编译而来的。
Make程序根据Makefile文件中的数据和每个文件更改的时间戳决定哪些文件需要更新。对于这些需要更新的文件,Make基于Makefile文件发布命令进行更新,进行更新的方式由提供的命令行参数控制。
总结一下就是:make是一款用来构建项目的工具,而makefile会告诉make如何用源文件来构建一个程序,文件之间的依赖关系是怎么样的,依赖一旦更新,使用依赖的文件也会随之重新编译。
Makfile文件介绍
Make程序需要一个所谓的Makefile文件来告诉它干什么。在大多数情况下,Makefile文件告诉Make怎样编译和连接成一个程序。
先从一个简单的Makfile文件入手:
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.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
上面的makefile描述了一个文本编辑器的可执行文件(edit)生成方法,这个可执行文件依赖8个OBJ文件,而这8个OBJ文件又各自依赖一些.c文件和.h文件。
一个简单的Makefile文件包含一系列的“规则”,其样式如下:
目标(target): 依赖(prerequiries)…
<tab>命令(command)
…
…
目标(target)
通常是要产生的文件的名称,目标可以是可执行文件或OBJ文件。目标也可是一个执行的动作名称,这种目标被称为‘伪目标’。依赖(prerequires)
表示产生这个目标需要的文件,一个目标经常有几个依赖。命令
是Make执行的动作,一个规则可以含有几个命令,每个命令占一行。注意:每个命令行前面必须是一个Tab字符,即命令行第一个字符是Tab。(这是很容易出错的地方)
上面的例子中,target包括名字为edit的文件,还包括了上面的每一个.o文件,可以看出来,.o文件既是.c和.h文件的目标,也是edit的依赖,这种可以称作edit目标的中间产物。
通常,如果一个依赖被修改,那么make会调用命令对这个依赖进行处理,重新编译目标。但不是每个规则都有依赖,比如伪目标就不需要依赖。
规则一般是用于解释怎样和何时重建文件的,这些特定文件是这个规则的目标。Make需首先调用命令对依赖进行处理,进而才能创建或更新目标。当然,一个规则也可以是用于解释怎样和何时执行一个动作,后面会说到(伪目标)。一个Makefile文件可以包含规则以外的其它文本,但一个简单Makefile文件仅仅需要包含规则。虽然真正的规则比这里展示的例子复杂,但格式是完全一样的。
这里简要介绍一下伪目标:
clean :
rm edit main.o kbd.o command.o display.o \
目标‘clean’不是一个文件,仅仅是一个动作的名称。正常情况下,在规则中‘clean’这个动作并不执行,目标‘clean’也不需要任何依赖。一般情况下,除非特意告诉make执行‘clean’命令,否则‘clean’命令永远不会执行。即执行make时,不会执行clean规则,但是执行make clean就会执行删除edit目标产物的动作。
像clean这样的规则不需要任何依赖,它们存在的目的仅仅是执行一些特殊的指令。像这些不需要依赖仅仅表达动作的目标称为伪目标。
某些行的反斜杠没有特殊含义,只是为了把一个长行分裂成多行,阅读方便。
Make处理makefile文件的过程
缺省的情况下,make会把makefile文件的第一个目标作为最终目标,就像上面例子的edit。
当执行make命令时,make就会读当前目录下的makefile文件,并开始处理第一条规则。第一个条规则是连接生成edit可执行文件,但在生成终极目标之前,必须先处理目标的依赖(即‘edit’所依靠的OBJ文件)。因此,make会去编译那些.o文件。这些OBJ文件按照各自的规则被处理更新,每个OBJ文件的更新规则是编译其源文件。重新编译是根据它依赖的源文件或头文件是否比现存的OBJ文件更‘新’,或者OBJ文件是否存在来判断的。
其它规则的处理根据它们的目标是否和缺省最终目标的依赖相关联来判断。如果makefile文件里面部分规则和缺省最终目标无任何关联,那么这部分规则不会被执行,除非告诉Make强制执行(如输入执行make clean命令)。
在OBJ文件重新编译之前,Make首先检查它的依赖C语言源文件(.c)和C语言头文件(.h)是否需要更新。如果部分C语言源文件和C语言头文件不是任何规则的目标,make不会对这部分文件做任何处理。Make也可以自动产生C语言源程序,这需要特定的规则,比如可以根据Bison或Yacc产生C语言源程序。
在OBJ文件重新编译(如果需要的话)之后,make决定是否重新连接生成edit可执行文件。如果edit可执行文件不存在或任何一个OBJ文件比存在的edit可执行文件‘新’,则make直接重新连接生成edit可执行文件。但是如果我们修改了部分.c文件或者.h文件,那么make会重新编译这部分文件生成.o文件,再重新连接生成edit目标文件。
用变量简化makefile文件
在上面生成edit的规则中,依赖的.o文件被列举了两次。要是我们想添加依赖,就需要在两处添加同样的文件。这样很容易出错,因此可以在makefile文件中定义变量来简化,变量是定义一个字符串一次,而能在多处替代该字符串使用。比如可以用变量修改上面的makefile文件:
Objects=main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit: $(Objects)
cc -o $(Objects)
... ...
在每一个需要列举.o文件的地方,都可以用变量Object来代替。
让make推断命令
编译单独的C语言源程序并不需要写出命令,因为make可以把它推断出来:make有一个使用‘CC –c’命令的把C语言源程序编译更新为相同文件名的OBJ文件的隐含规则。例如make可以自动使用‘cc -c main.c -o main.o’命令把‘main.c’编译 ‘main.
o’。因此,我们可以省略OBJ文件的更新规则。因此可以修改上面makefile文件为:
Objects=main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(Objects)
cc -o $(Objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
隐含规则十分方便,而且十分重要。
其他风格的makefile文件
上面的makefile文件还可以写成下面的风格:
Objects=main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(Object) \
cc -o $(Object)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
上面的改写表示,所有的OBJ文件都依赖 defs.h文件,kbd.o command.o files.o都依赖command.h文件。display.o insert.o search.o files.o都依赖buffer.h文件。但不建议这种风格。
介绍‘clean’规则
make规则不仅仅可以用来编译程序,还可以执行其他任务。比如 删除上面makefile文件生成的edit可执行文件和OBJ文件等。其实就是上面提到的伪目标。
clean :
rm edit $(Objects) \
当执行 make clean
时,rm 指令就会执行,edit可执行文件和OBJ文件会被删除。就不用手动的执行rm指令了。
在实际应用中,应该编写较为复杂的规则以防不能预料的情况发生。更通用的写法如下:
.PHONY : clean
clean :
-rm edit $(objects)
这样可以防止make因为存在名为’clean’的文件而发生混乱,并且导致它在执行rm命令时发生错误.像clean这样的规则不要放在makefile文件的开始,因为不希望它变为缺省最终目标。
应该像上面那个makefile文件例子一样,把关于edit的规则放在前面,从而把编译更新edit可执行程序定为缺省最终目标。
如何编写makefile文件?
makefile文件包含五个方面的内容:
- 具体规则
具体规则用于阐述什么时间或如何重新生成目标依赖的一个或多个文件的。它列举了目标所依赖的文件,具体规则可能同时提供了创建或更新该目标的命令。 - 隐含规则
隐含规则用于阐述什么时间或如何重新生成同一文件名的一系列文件的。它描述的目标是根据和它名字相同的文件进行创建或更新的,同时提供了创建或更新该目标的命令。 - 定义变量
定义变量是为一个变量赋一个固定的字符串值,从而在以后的文件中能够使用该变量代替这个字符串。注意在makefile文件中定义变量占一独立行。 - 指令
指令是make根据makefile文件执行一定任务的命令。这些包括如下几方面:
- 读其它makefile文件(详细内容参见包含其它的makefile文件)。
- 判定(根据变量的值)是否使用或忽略makefile文件的部分内容.
- 定义多行变量,即定义变量值可以包含多行字符的变量
- 注释
以‘#’开始的行是注释行。注释行在处理时将被make忽略,如果一个注释行在行尾是‘\’则表示下一行继续为注释行,这样注释可以持续多行。除在define指令内部外,注释可以出现在makefile文件的任何地方,甚至在命令内
部(这里shell决定什么是注释内容)。
make文件的命名
缺省情况下,当make寻找makefile文件时,它会按照顺序搜寻具有如下的名字的文件:‘GNUmakefile’、‘makefile’和‘Makefile’。
通常情况应该把makefile文件命名为‘makefile’或‘Makefile’。(推荐使用‘Makefile’,因为它基本出现在目录列表的前面,后面挨着其它重要的文件如‘README’等。)。虽然首先搜寻‘GNUmakefile’,但并不推荐使用。除非这个makefile文件是特为GNU make编写的,在其它make版本上不能执行,才应该使用‘GNUmakefile’作为您的makefile的文件名。
如果make没有搜寻到具有上面所述名字的文件,它就不使用任何makefile文件。这样就必须使用命令参数指定目标makefile文件,如果要使用非标准名字makefile文件,可以使用‘-f’或‘–file’参数指定makefile文件。参数‘-f name’或‘–file=name’能够告诉make读名字为‘name’的文件作为makefile文件。如果您使用 ‘-f’或‘–file’参数多于一个,意味着如果指定了多个makefile文件,所有的makefile文件按具
体的顺序发生作用。一旦使用了‘-f’或‘–file’参数,将不再自动检查是否存在名为 ‘GNUmakefile’、 ‘makefile’ 或 ‘Makefile’的makefile文件。
makfile文件里面还可以包含其他makefile文件
include指令告诉make暂停读取当前的makefile文件,先读完include指令指定的makefile文件后再继续。即Makefile的include命令可以用来在 Makefile 中包含其他 Makefile 文件。这样可以使 Makefile 更加结构化,方便维护。指令在makefile文件占单独一行,其格式如下:
include filename...
注意:filenames还可以包含shell文件名的格式(可以包含通配符和路径)。
在include指令行,行开始处的多余的空格是允许的,但make处理时忽略这些空格,注意该行不能以Tab字符开始(因为,以Tab字符开始的行,make认为是命令行)。include和文件名之间以空格隔开,两个文件名之间也以空格隔开,多余的空格make处理时会忽略。
filename 可以是一个相对路径或者绝对路径,表示要包含的 Makefile 文件的路径。
在执行 make 命令时,Makefile 中的 include 命令会被执行,被包含的Makefile中的规则和变量也会被加载进来。这些规则和变量将会和原来的 Makefile 中的规则和变量合并,如果有重名的规则或变量,那么被包含的 Makefile 中的定义将会覆盖原来的定义。
使用include指令的一种情况是几个程序分别有单独的makefile文件,但它们需要一系列共同的变量定义,或者一系列共同的格式规则.
举个例子,如果我们有一个 Makefile 文件 foo.mk,其中定义了一个变量 FOO,值为 “foo”,有几个程序都需要这个变量,就可以在makefile文件中include这个foo.mk,就可以使用这个变量了,规则亦如是。
基础部分了解到这儿就差不多了。下面总结一下Android.mk文件
Android.mk文件
Android构建系统的发展
在Android 7.0 以前,Android都是基于 GNU Make进行构建项目的,随着Android项目越来越庞大,基于Make的构建项目越来越慢,于是Android推出了Soong构建系统,Soong构建系统的控制脚本是Android.bp,这个脚本是一个纯配置文件,不支持逻辑判断和流程控制,这些复杂的逻辑都被放在go文件中去处理。由于这个改动比较大,aosp里面很多Android.mk文件还没有被转换成Android.bp文件,为了兼容这两套构建系统,Android引入了kati来将Android.mk文件转换成.ninja文件,引入了blueprint将Android.bp转换成.ninja文件,然后ninja编译系统会开始基于.ninja文件开始构建项目。
Android下的makefile文件–Android.mk
Android.mk 文件用于向构建系统描述源文件和共享库。它实际上是一个微小的 GNU makefile 片段,构建系统会将其解析一次或多次。Android.mk 文件用于定义 Application.mk、构建系统和环境变量所未定义的项目级设置。它还可替换特定模块的项目级设置。
Android.mk 的语法支持将源文件分组为“模块”。模块是静态库、共享库或独立的可执行文件。可以在每个 Android.mk 文件中定义一个或多个模块,也可在多个模块中使用同一个源文件。构建系统只将共享库放入您的应用软件包。此外,静态库可生成共享库。
除了封装库之外,构建系统还可为您处理各种其他事项。例如,无需在 Android.mk 文件中列出头文件或生成的文件之间的显式依赖关系。NDK 构建系统会自动计算这些关系。
Android.mk基本语法
Android.mk文件常用变量和函数总结:
LOCAL_PATH
:常规值为 $(call my-dir),这个变量必须定义——本地路径,表示当前mk文件所在的目录,每个Android.mk文件必须先定义 LOCAL_PATH 变量,此变量表示源文件在开发树中的位置。
include $(CLEAR_VARS)
:必须定义 ,清空所有LOCAL_xxx变量,但不清理LOCAL_PATH变量。
LOCAL_PACKAGE_NAME
:必须定义 ,模块名称,名称必须是唯一的且不包含空格,除应用(apk)可以另外使用LOCAL_PACKAGE_NAME外,其余的模块都以LOCAL_MODULE指定名称。
LOCAL_SRC_FILES
:可调用函数$(call all-java-files-under, 指定目录)
(
c
a
l
l
a
l
l
−
I
a
i
d
l
−
f
i
l
e
s
−
u
n
d
e
r
,指定目录
)
这个会扫描指定目录下所有的
I
x
x
.
a
i
d
l
文件
,
必须定义,变量
L
O
C
A
L
S
R
C
F
I
L
E
S
为
B
u
i
l
d
M
o
d
u
l
e
s
提供的
a
p
p
所有源码
S
o
u
r
c
e
文件列表,不需要列出依赖文件。
(call all-Iaidl-files-under,指定目录)这个会扫描指定目录下所有的Ixx.aidl文件,必须定义 ,变量 LOCAL_SRC_FILES 为Build Modules提供的app所有源码Source文件列表,不需要列出依赖文件。
(callall−Iaidl−files−under,指定目录)这个会扫描指定目录下所有的Ixx.aidl文件,必须定义,变量LOCALSRCFILES为BuildModules提供的app所有源码Source文件列表,不需要列出依赖文件。(call all-java-files-under, src/java)宏函数,可以得到指定目录src/java下的所有java源码,宏all-java-files-under定义在build/core/definitions.mk中。其他如aidl、c/c++、so等文件需手动添加。
LOCAL_PROGUARD_ENABLED
:不使用代码混淆的工具进行代码混淆。可选定义 ,默认LOCAL_PROGUARD_ENABLED := full,即代码全部混淆。如果是user或userdebug。取值full, disabled, custom。
LOCAL_MANIFEST_FILE
:表示 Manifest 的路径,可以区分配置。
LOCAL_STATIC_ANDROID_LIBRARIES
:指定依赖的Android类库。
LOCAL_STATIC_JAVA_LIBRARIES
:指定依赖的静态java类库,最终会打包到apk里面(添加的静态库编译会以拷贝的方式加到需要他的应用中)。
LOCAL_STATIC_JAVA_AAR_LIBRARIES
:表示依赖的静态库具体路径,也可以是aar包。其中冒号前面为包别名,与LOCAL_STATIC_JAVA_LIBRARIES的值对应。冒号后面为包的实际路径。如果如上编写的,那么你可以使用AAR包中的java代码和so库了(如果有so库的话)。
LOCAL_AAPT_FLAGS
:常规值:–auto-add-overlay——设置aapt(Android Asset Packaging Tool)的参数:–auto-add-overlay : 配置应用程序打包标志,设置为自动添加并覆盖 –extra-packages : com.xxxx.111aar。如果需要使用的aar包中的R文件资源,那么需要在LOCAL_AAPT_FLAGS 变量后面添加AAR包中的包名,这AAR打包过程中,会有一个清单文件打包进去,只需要拷贝清单文件中package的包名并赋值给LOCAL_AAPT_FLAGS 即可。
LOCAL_USE_AAPT2
:常规值:true/false——使用appt2,aapt2是在aapt上做了优化。因为appt2在编译打包的时候,会有一些错误bug?,所以可以关闭appt2,这样就是默认使用appt了。
LOCAL_JAVA_LIBRARIES
:指定依赖的共享java类库,只是编译的时候引用。例如:当前APK的Android.mk文件中出现该属性:
LOCAL_JAVA_LIBRARIES += mediatek-framework telephony-common mediatek-telephony-base
表示在编译当前APK的时候,导入framework-base中的一下部分jar,具体看源码该使用类所在的位置。
include $(BUILD_STATIC_JAVA_LIBRARY)
:将当前模块编译成一个Java静态库
include $(BUILD_PACKAGE)
:编译生成apk
include$(BUILD_MULTI_PREBUILT)
:编译构建,Android提供了Prebuilt编译方法,两个文件prebuilt.mk和multi_prebuilt.mk,对应的方法宏是BUILD_PREBUILT和 BUILD_MULTI_PREBUILT。
prebuilt.mk就是prebuilt的具体实现,它是针对独立一个文件的操作,multi_prebuilt.mk 可以针对多个文件的,它对多个文件进行判断,然后调用prebuilt对独立一个文件进行处理。
在 Android Build 系统中,编译是以模块(而不是文件)作为单位的,每个模块都有一个唯一的名称,一个模块的依赖对象只能是另外一个模块,而不能是其他类型的对象。对于已经编译好的二进制库,如果要用来被当作是依赖对象,那么应当将这些已经编译好的库作为单独的模块。对于这些已经编译好的库使用 BUILD_PREBUILT 或 BUILD_MULTI_PREBUILT。
例如:当编译某个 Java 库需要依赖一些 Jar 包时,并不能直接指定 Jar 包的路径作为依赖,而必须首先将这些 Jar 包定义为一个模块,然后在编译 Java 库的时候通过模块的名称来依赖这些 Jar 包。
LOCAL_RESOURCE_DIR
:表示res的路径
LOCAL_STATIC_JAVA_AAR_LIBRARIES
:指定依赖的aar包。冒号后为aar别名,可以配置在此变量,也可以配置在LOCAL_STATIC_JAVA_LIBRARIES中,方式相同。
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES
:表示依赖的静态库具体路径,也可以是aar包。其中冒号前面为包别名,与LOCAL_STATIC_JAVA_LIBRARIES的值对应。冒号后面为包的实际路径。
LOCAL_JARJAR_RULES
:是用来进行包名映射的
LOCAL_PRIVATE_PLATFORM_API
:常用值:true/false——设置后,会使用sdk的hide的api來编译,在Android.mk中如果有LOCAL_SDK_VERSION 这个编译配置,就会使编译的应用不能访问hide的api,有时一些系统的class被import后编译时说找不到这个类,就是这个原
因造成的。
LOCAL_REQUIRED_MODULES
:指定依赖的模块。一旦本模块被安装,通过此变量指定的模块也将被安装。
LOCAL_MODULE_TAGS
:默认值:optional。 user: 指该模块只在user版本下才编译,eng: 指该模块只在eng版本下才编译,tests: 指该模块只在tests版本下才编译,optional:指该模块在所有版本下都编译。这个配置跟TARGET_BUILD_VARIANT息息相关。
include $(BUILD_HOST_PREBUILT)
:预编译(适用于主机),编译后的产物在out/host目录下。
LOCAL_ANNOTATION_PROCESSORS
:声明用到的注解依赖包。
LOCAL_ANNOTATION_PROCESSOR_CLASSES
:声明使用的注解处理器,例如Androidx.room.RoomProcessor。
LOCAL_MODULE
:LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块。名字必须唯一且不包含空格。
LOCAL_MODULE_CLASS
:将用于决定编译时的中间文件存放的位置。比如LOCAL_MODULE_CLASS := ETC ,那么该模块编译的中间文件将存放于 out/target/product/…/obj/ETC/目录下。
Android.mk文件示例
- 编译一个基本的apk
LOCAL_PATH := $(call my-dir)
services-sources += $(call all-java-files-under, src/main/java)
ifeq ($(PLATFORM_SDK_VERSION), 23)
services-sources += $(call all-java-files-under, src/1.0/java)
else ifeq ($(TARGET_PRODUCT), xxx)
services-sources += $(call all-java-files-under, src/1.2/java)
else ifeq ($(TARGET_PRODUCT),xxx)
nio-account-services-sources += $(call all-java-files-under, src/2.0/java)
endif
#####################################################################################
# Build service
#####################################################################################
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(services-sources)
LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/../src/main/aidl
ifneq ($(TARGET_PRODUCT), anakin)
LOCAL_AIDL_INCLUDES += $(LOCAL_PATH)/../src/main/aidl
endif
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/src/main/res
LOCAL_PACKAGE_NAME := XXXX
LOCAL_CERTIFICATE := platform
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_MANIFEST_FILE := src/main/AndroidManifest.xml
LOCAL_USE_AAPT2 := true
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_PRIVILEGED_MODULE := true
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := logging-interceptor collection lifecycle-livedata-core room-common gson-2.6.2 account-sdk \
user-nt3-0.2.1-1 room-common-2.4.0 androidx.sqlite \
okhttp-3.14.9 okio-jvm-2.8.0 safe-parcel.1.7.0 \
version_info := --version-name 1.0.0.0 --version-code 100000
LOCAL_AAPT_FLAGS := $(version_info)
include $(BUILD_PACKAGE)
#######################################################
### Build Dependencies
#######################################################
include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := logging-interceptor:libs/logging-interceptor-3.14.9.jar collection:libs/collection-1.1.0.jar lifecycle-livedata-core:libs/lifecycle-livedata-core-2.3.1.aar \
room-common:libs/room-runtime-2.4.0.aar room-common-2.4.0:libs/room-common-2.4.0.jar \
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += gson-2.6.2:libs/gson-2.6.2.jar androidx.sqlite:libs/androidx.sqlite.jar\
okhttp-3.14.9:libs/okhttp-3.14.9.jar okio-jvm-2.8.0:libs/okio-jvm-2.8.0.jar safe-parcel.1.7.0:libs/safe-parcel.1.7.0.jar \
include $(BUILD_MULTI_PREBUILT)
- 编译一个Java静态库
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := xxx-sdk
LOCAL_SRC_FILES := $(call all-java-files-under, src/main/java)
LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src/main/aidl
LOCAL_STATIC_JAVA_LIBRARIES := androidx.annotation kotlin-std javapoet-1.13.0
include $(BUILD_STATIC_JAVA_LIBRARY)
#####################################################################################
# Build dependencies
#####################################################################################
include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := androidx.annotation:libs/annotation-1.3.0.jar javapoet-1.13.0:libs/javapoet-1.13.0.jar kotlin-std:libs/kotlin-stdlib-1.7.22.jar \
include $(BUILD_MULTI_PREBUILT)
- 编译动态库
只需要把静态库 include $(BUILD_STATIC_JAVA_LIBRARY) 替换成 include $(BUILD_JAVA_LIBRARY) 即可
Android编译命令
基本流程
source build/envsetup.sh
lunch userdebug
make -j8
-
build/envsetup.sh这个文件中(其实这个只是一个软链接)定义了一些变量和函数,执行source build/envsetup.sh之后(也可以执行 . build/envsetup.sh),envsetup.sh中的变量成了全局变量,而其中的函数也可以直接在当前终端命令行中使用了。这些函数可以帮助我们切换目录,查找文件,可以使我们在编译源码时更方便。这些函数可通过hmm函数来查看,下面列出了一些常用的函数:
- lunch: lunch <product_name>-<build_variant> (选择要编译的目标产品和版本)
- tapas: tapas [ …] [arm|x86|mips|armv5] [eng|userdebug|user]
- croot: Changes directory to the top of the tree.(切换到源码的顶层目录)
- m: Makes from the top of the tree.(从顶层目录build整个系统)
- mm: Builds all of the modules in the current directory, but not their dependencies.(构建当前目录下所有的模块,但不包括它们的依赖)
- mmm: Builds all of the modules in the supplied directories, but not their dependencies.(构建指定目录下所有的模块,但不包括它们的依赖)
- mma: Builds all of the modules in the current directory, and their dependencies.(构建当前目录下所有的模块以及它们所依赖的模块)
- mmma: Builds all of the modules in the supplied directories, and their dependencies.(构建指定目录下所有的模块以及它们所依赖的模块)
- provision: Flash device with all required partitions. Options will be passed on to fastboot.(将设备所有需要的分区刷入,选项将传递给fastboot)
- cgrep: Greps on all local C/C++ files.(在C,C++文件中搜索指定关键字)
- ggrep: Greps on all local Gradle files.(在gradle文件中搜索指定关键字)
- jgrep: Greps on all local Java files.(在java文件中搜索指定关键字)
- resgrep: Greps on all local res/*.xml files.(在资源xml文件中搜索指定关键字)
- mangrep: Greps on all local AndroidManifest.xml files.(在AndroidManifest.xml文件中搜索指定关键字)
- mgrep: Greps on all local Makefiles files.(在Makefiles和android.mk文件中搜索指定关键字)
- sepgrep: Greps on all local sepolicy files.(在sepolicy文件中搜索指定关键字)
- sgrep: Greps on all local source files.(在所有本地文件中搜索指定关键字)
- godir: Go to the directory containing a file.(切换到包含某个文件的目录下)
-
执行lunch函数时,如果用户指定了product,获取指定的product。如果用户未指定product,调用print_lunch_menu函数输出上一步生成的lunch menu choices让用户选择。如用户指定了product或者提示后选择了product,会获取用户指定的product,并提取 p r o d u c t 和 product和 product和variant。然后检查是否支持product,如不支持,提示不支持并退出。如支持,接着会调用set_stuff_for_environment函数设置一系列环境变量,还会调用printconfig输出相关变量和配置信息。
暂时无法在飞书文档外展示此内容- userdebug build 的运行方式应该与 user build 一样,且能够启用通常不符合平台安全模型的额外调试功能。这就使得 userdebug build 具有更强大的诊断功能,因此是进行 user 测试的最佳选择。使用 userdebug build 进行开发时,请遵循 userdebug 指南。
- eng build 会优先考虑在平台上工作的工程师的工程生产率。eng 构建系统会关闭用于提供良好用户体验的各种优化。除此之外,eng build 的运行方式类似于 user 和 userdebug build,以便设备开发者能够看到代码在这些环境下的运行方式。
-
make -8
- -j8是用于指定编译的cpu核心,如果编译服务器配置好,可以略为改大一些。
- make则是执行make命令,寻找源码根目录下的Makefile,解析Makefile并开始整个源码的编译。
Android编译输出目录
如果mk文件没有指定编译产物输出路径,那么会默认输出到out目录下,下面是out的目录结构简介:
|-- host/ # 构建源码需要的工具和库文件
|-- target/product/generic/ # 生成最后产品的目录
|-- data # 这个目录是用来生成<数据文件系统镜像>(data file system image)userdata.img
|-- obj # 生成的中间文件,最后都要拷贝到root或system文件夹中,最后生成镜像img文件
| |-- APPS # android应用
| |-- ETC
| |-- EXECUTABLES # 所有本地运行工具 ping toolbox
| |-- include
| |-- JAVA_LIBRARIES
| |-- lib # 从SHARED_LIBRARIES拷贝,各种.so共享库
| |
| |-- PACKAGING
| |-- SHARED_LIBRARIES # 共享库
| | |-- {LOCAL_MODULE_NAME}_intermediates # 各种共享库 {LOCAL_MODULE_NAME}模块名称
| | |
| | -- LINKED # 链接到二进制文件, e.g, .so文件
| -- STATIC_LIBRARIES # 静态库
|-- root # 这个目录用来创建<root文件系统>(root file system), 生成的ramdisk.img是用这个文件夹生成的镜像
| |-- data
| |-- dev
| |-- proc
| |-- sbin
| |-- sys
| -- system
|-- symbols # 带调试信息的
| |-- data
| |-- sbin
| -- system
-- system # 用来创建system.img, 大部分的应用程序和库都在system中
|-- app
|-- bin
|-- etc
|-- fonts
|-- framework
|-- lib
|-- media
|-- tts
|-- usr
-- xbin
Soong构建系统
Soong是Google开发的,基于go语言编写的构建系统,在Android 7.0 被引入,目的是取代make,Soong构建系统引入了Blueprint模块,它负责将Soong里面的Android.bp文件解析成一个ninja文件,ninja根据这些.ninja文件来构建项目。
Android.bp文件和Android.mk文件比较
Android源码里面有一个工具可以将Android.mk转换成Android.bp,但不能做到完全适配。因为Android.bp只是一个类似于JSON的配置文件,没有mk脚本复杂的逻辑判断和流程控制。
# 先编译出android.mk工具,在源码根目录下执行
make androidmk
# 来到自己项目的目录下,名字可以随便取,可以不是Android.bp
androidmk Android.mk >> Android.bp
Android.mk和Android.bp文件变量对应关系可以在build/soong/androidmk/androidmk/android.go里面找到,下面是截取的一个代码片段
func init() {
addStandardProperties(bpparser.StringType,
map[string]string{
"LOCAL_MODULE": "name",
"LOCAL_CXX_STL": "stl",
"LOCAL_MULTILIB": "compile_multilib",
"LOCAL_ARM_MODE_HACK": "instruction_set",
"LOCAL_SDK_VERSION": "sdk_version",
"LOCAL_MIN_SDK_VERSION": "min_sdk_version",
"LOCAL_NDK_STL_VARIANT": "stl",
"LOCAL_JAR_MANIFEST": "manifest",
"LOCAL_CERTIFICATE": "certificate",
"LOCAL_PACKAGE_NAME": "name",
"LOCAL_MODULE_RELATIVE_PATH": "relative_install_path",
"LOCAL_PROTOC_OPTIMIZE_TYPE": "proto.type",
"LOCAL_MODULE_OWNER": "owner",
"LOCAL_RENDERSCRIPT_TARGET_API": "renderscript.target_api",
"LOCAL_NOTICE_FILE": "notice",
"LOCAL_JAVA_LANGUAGE_VERSION": "java_version",
"LOCAL_INSTRUMENTATION_FOR": "instrumentation_for",
"LOCAL_MANIFEST_FILE": "manifest",
"LOCAL_DEX_PREOPT_PROFILE_CLASS_LISTING": "dex_preopt.profile",
"LOCAL_TEST_CONFIG": "test_config",
"LOCAL_RRO_THEME": "theme",
})
addStandardProperties(bpparser.ListType,
map[string]string{
"LOCAL_SRC_FILES": "srcs",
"LOCAL_SRC_FILES_EXCLUDE": "exclude_srcs",
"LOCAL_HEADER_LIBRARIES": "header_libs",
"LOCAL_SHARED_LIBRARIES": "shared_libs",
"LOCAL_STATIC_LIBRARIES": "static_libs",
"LOCAL_WHOLE_STATIC_LIBRARIES": "whole_static_libs",
"LOCAL_SYSTEM_SHARED_LIBRARIES": "system_shared_libs",
"LOCAL_ASFLAGS": "asflags",
"LOCAL_CLANG_ASFLAGS": "clang_asflags",
"LOCAL_COMPATIBILITY_SUPPORT_FILES": "data",
"LOCAL_CONLYFLAGS": "conlyflags",
"LOCAL_CPPFLAGS": "cppflags",
"LOCAL_REQUIRED_MODULES": "required",
"LOCAL_HOST_REQUIRED_MODULES": "host_required",
"LOCAL_TARGET_REQUIRED_MODULES": "target_required",
"LOCAL_OVERRIDES_MODULES": "overrides",
"LOCAL_LDLIBS": "host_ldlibs",
"LOCAL_CLANG_CFLAGS": "clang_cflags",
"LOCAL_YACCFLAGS": "yacc.flags",
"LOCAL_SANITIZE_RECOVER": "sanitize.recover",
"LOCAL_LOGTAGS_FILES": "logtags",
"LOCAL_EXPORT_HEADER_LIBRARY_HEADERS": "export_header_lib_headers",
"LOCAL_EXPORT_SHARED_LIBRARY_HEADERS": "export_shared_lib_headers",
"LOCAL_EXPORT_STATIC_LIBRARY_HEADERS": "export_static_lib_headers",
"LOCAL_INIT_RC": "init_rc",
"LOCAL_VINTF_FRAGMENTS": "vintf_fragments",
"LOCAL_TIDY_FLAGS": "tidy_flags",
// TODO: This is comma-separated, not space-separated
"LOCAL_TIDY_CHECKS": "tidy_checks",
"LOCAL_RENDERSCRIPT_INCLUDES": "renderscript.include_dirs",
"LOCAL_RENDERSCRIPT_FLAGS": "renderscript.flags",
"LOCAL_JAVA_RESOURCE_DIRS": "java_resource_dirs",
"LOCAL_JAVA_RESOURCE_FILES": "java_resources",
"LOCAL_JAVACFLAGS": "javacflags",
"LOCAL_ERROR_PRONE_FLAGS": "errorprone.javacflags",
"LOCAL_DX_FLAGS": "dxflags",
"LOCAL_JAVA_LIBRARIES": "libs",
"LOCAL_STATIC_JAVA_LIBRARIES": "static_libs",
"LOCAL_JNI_SHARED_LIBRARIES": "jni_libs",
"LOCAL_AAPT_FLAGS": "aaptflags",
"LOCAL_PACKAGE_SPLITS": "package_splits",
"LOCAL_COMPATIBILITY_SUITE": "test_suites",
"LOCAL_OVERRIDES_PACKAGES": "overrides",
"LOCAL_ANNOTATION_PROCESSORS": "plugins",
"LOCAL_PROGUARD_FLAGS": "optimize.proguard_flags",
"LOCAL_PROGUARD_FLAG_FILES": "optimize.proguard_flags_files",
// These will be rewritten to libs/static_libs by bpfix, after their presence is used to convert
// java_library_static to android_library.
"LOCAL_SHARED_ANDROID_LIBRARIES": "android_libs",
"LOCAL_STATIC_ANDROID_LIBRARIES": "android_static_libs",
"LOCAL_ADDITIONAL_CERTIFICATES": "additional_certificates",
// Jacoco filters:
"LOCAL_JACK_COVERAGE_INCLUDE_FILTER": "jacoco.include_filter",
"LOCAL_JACK_COVERAGE_EXCLUDE_FILTER": "jacoco.exclude_filter",
"LOCAL_FULL_LIBS_MANIFEST_FILES": "additional_manifests",
})
addStandardProperties(bpparser.BoolType,
map[string]string{
// Bool properties
"LOCAL_IS_HOST_MODULE": "host",
"LOCAL_CLANG": "clang",
"LOCAL_FORCE_STATIC_EXECUTABLE": "static_executable",
"LOCAL_NATIVE_COVERAGE": "native_coverage",
"LOCAL_NO_CRT": "nocrt",
"LOCAL_ALLOW_UNDEFINED_SYMBOLS": "allow_undefined_symbols",
"LOCAL_RTTI_FLAG": "rtti",
"LOCAL_PACK_MODULE_RELOCATIONS": "pack_relocations",
"LOCAL_TIDY": "tidy",
"LOCAL_USE_CLANG_LLD": "use_clang_lld",
"LOCAL_PROPRIETARY_MODULE": "proprietary",
"LOCAL_VENDOR_MODULE": "vendor",
"LOCAL_ODM_MODULE": "device_specific",
"LOCAL_PRODUCT_MODULE": "product_specific",
"LOCAL_SYSTEM_EXT_MODULE": "system_ext_specific",
"LOCAL_EXPORT_PACKAGE_RESOURCES": "export_package_resources",
"LOCAL_PRIVILEGED_MODULE": "privileged",
"LOCAL_AAPT_INCLUDE_ALL_RESOURCES": "aapt_include_all_resources",
"LOCAL_DONT_MERGE_MANIFESTS": "dont_merge_manifests",
"LOCAL_USE_EMBEDDED_NATIVE_LIBS": "use_embedded_native_libs",
"LOCAL_USE_EMBEDDED_DEX": "use_embedded_dex",
"LOCAL_DEX_PREOPT": "dex_preopt.enabled",
"LOCAL_DEX_PREOPT_APP_IMAGE": "dex_preopt.app_image",
"LOCAL_DEX_PREOPT_GENERATE_PROFILE": "dex_preopt.profile_guided",
"LOCAL_PRIVATE_PLATFORM_APIS": "platform_apis",
"LOCAL_JETIFIER_ENABLED": "jetifier",
"LOCAL_IS_UNIT_TEST": "unit_test",
"LOCAL_ENFORCE_USES_LIBRARIES": "enforce_uses_libs",
})
}
有了前面Android.mk的基础,Android.bp文件就简单多了。
比如想要编译一个java库
filegroup {
name: "sdk-sources",
srcs: [
"src/main/java/**/*.java",
"src/main/aidl/**/I*.aidl",
],
path: "src",
}
java_library {
name: "nio-account-sdk",
srcs: [":sdk-sources" ],
aidl: {
local_include_dirs: ["src/main/aidl"],
},
static_libs: [
"annotation-1.3.0",
"compiler-0.3.8",
"javapoet-1.13.0",
"kotlin-stdlib-1.7.22",
"safe-parcel-1.7.0",
]
}
--------然后要在libs目录下,或者直接写在一个bp文件也行,引入依赖库,这些name必须是唯一的,除非定义了命名空间---------------
java_import {
name: "annotation-1.3.0",
jars: ["annotation-1.3.0.jar"],
sdk_version: "current",
}
java_import {
name: "compiler-0.3.8",
jars: ["compiler-0.3.8.jar"],
sdk_version: "current",
}
java_import {
name: "javapoet-1.13.0",
jars: ["javapoet-1.13.0.jar"],
sdk_version: "current",
}
java_import {
name: "kotlin-stdlib-1.7.22-apex",
jars: ["kotlin-stdlib-1.7.22.jar"],
sdk_version: "current",
}
java_import {
name: "safe-parcel-1.7.0-apex",
jars: ["safe-parcel-1.7.0.jar"],
sdk_version: "current",
}
编译一个apk,省略了依赖库的引入,参照上面就行,但是aar写法不同
filegroup {
name: "service-sources",
srcs: [
"src/main/java/**/*.java",
"src/main/aidl/**/*.aidl",
],
path: "src",
}
filegroup {
name: "service-manifest",
srcs: [
"src/main/AndroidManifest.xml",
],
path: "src",
}
android_app {
name: "XXXService",
srcs: [
":service-sources",
],
manifest: ":service-manifest",
resource_dirs: [ "src/main/res" ],
platform_apis: true,
certificate: "platform",
privileged: true,
system_ext_specific: true,
min_sdk_version: "32",
installable: true,
compile_multilib: "both",
export_package_resources: true,
static_libs: [
"annotation-1.3.0",
"gson-2.6.2",
"collection-1.1.0",
...
],
libs: [
"xxx.xxx",
],
}
--------------补充一个引入aar包的示例-------------------
android_library_import {
name: "room-runtime-2.4.0",
aars: ["room-runtime-2.4.0.aar"],
sdk_version: "current",
}
注意:Android.mk可以引用Android.bp声明的模块,但是Android.bp不能引用到Android.mk中声明的模块。