Android build编译过程及Android.mk与Android.bp分析

Android的官方网站:http://source.android.com/source/building.html

按照google给出的编译步骤如下:
   1> source build/envsetup.sh:加载命令
   2> lunch:选择平台编译选项
   3> make:执行编译
如V3的编译命令为
source build/envsetup.sh
lunch gwmv3-userdebug
make -j6 2>&1 | tee build_$(date +%Y%m%d_%H%M%S).log

1. source build/envsetup.sh
这个命令是用来将envsetup.sh里的所有用到的命令加载到环境变量里去,我们来分析下它。

envsetup.sh里的主要命令如下:
function help()                  # 显示帮助信息
function get_abs_build_var()           # 获取绝对变量
function get_build_var()             # 获取绝对变量
function check_product()             # 检查product
function check_variant()             # 检查变量
function setpaths()                # 设置文件路径
function printconfig()              # 打印配置
function set_stuff_for_environment()        # 设置环境变量
function set_sequence_number()            # 设置序号
function settitle()                # 设置标题
function choosetype()               # 设置type
function chooseproduct()              # 设置product
function choosevariant()              # 设置variant
function tapas()                  # 功能同choosecombo
function choosecombo()               # 设置编译参数
function add_lunch_combo()             # 添加lunch项目
function print_lunch_menu()            # 打印lunch列表
function lunch()                 # 配置lunch
function m()                   # make from top
function findmakefile()              # 查找makefile
function mm()                   # make from current directory
function mmm()                   # make the supplied directories
function croot()                 # 回到根目录
function cproj()
function pid()
function systemstack()
function gdbclient()
function jgrep()                 # 查找java文件
function cgrep()                  # 查找c/cpp文件
function resgrep()
function tracedmdump()
function runhat()
function getbugreports()
function startviewserver()
function stopviewserver()
function isviewserverstarted()
function smoketest()
function runtest()
function godir ()                 # 跳到指定目录

# add_lunch_combo 函数被多次调用,就是它来添加Android编译选项

envsetup.sh其主要作用如下:
1. 加载了编译时使用到的函数命令,如:help,lunch,m,mm,mmm等
2. 添加了编译选项:generic-eng和simulator,这两个选项是系统默认选项
3. 查找vendor/<-厂商目录>/和vendor/<厂商目录>/build/目录下的vendorsetup.sh,如果存在的话,加载执行它,添加厂商自己定义产品的编译选项
其实,上述第3条是向编译系统添加了厂商自己定义产品的编译选项,里面的代码就是:add_lunch_combo xxx-xxx。
根据上面的内容,可以推测出,如果要想定义自己的产品编译项,简单的办法是直接在envsetup.sh最后,添加上add_lunch_combo myProduct-eng;但不建议这么做;
建议在vendor目录下创建自己公司名字,然后在公司目录下创建一个新的vendorsetup.sh,在里面添加上自己的产品编译项
当我们在终端执行命令source build/envsetup.sh时,其实是完整的加载了脚本envsetup.sh中的变量和方法,其中最重要的函数比如lunch就是在这一步加载到shell环境变量中去的;

除了加载上述函数,还执行了以下初始化和动作:
1> 提前定义3种编译模式,供后面使用:
VARIANT_CHOICES=(user userdebug eng)
2> 使用变量LUNCH_MENU_CHOICES之前,先将它清空:
unset LUNCH_MENU_CHOICES
3> 加载一些默认的板类型选项,后续的lunch菜单中就可以看到这几个板类型: 
add_lunch_combo aosp_arm-eng
add_lunch_combo aosp_arm64-eng
add_lunch_combo aosp_mips-eng
add_lunch_combo aosp_mips64-eng
add_lunch_combo aosp_x86-eng
add_lunch_combo aosp_x86_64-eng
调用 add_lunch_combo 函数实际是向全局数组变量 LUNCH_MENU_CHOICES 中追加选项,LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo);
add_lunch_combo 的处理逻辑就是先从 LUNCH_MENU_CHOICES 中查找,看存不存在要添加的板型,如果存在就直接返回,如果不存在就追加到LUNCH_MENU_CHOICES中;
4> 当敲入lunch命令后用Tab键进行补全时,会执行_lunch()函数
complete -F _lunch lunch
函数_lunch的实现:
function _lunch()
{
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    COMPREPLY=( $(compgen -W "${LUNCH_MENU_CHOICES[*]}" -- ${cur}) )
    return 0
}
数组LUNCH_MENU_CHOICES的内容会在补全时打印出来,而数组其实就是调用函数add_lunch_combo增加进来的;
5> shell检查和警告,这里只支持bash,如果是其他的shell会发出这个WARNING:
echo "WARNING: Only bash is supported, use of other shell would lead to erroneous results"
6> 整个source过程中最重要的步骤:
1665 # Execute the contents of any vendorsetup.sh files we can find.
1666 for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \
1667          `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \
1668          `test -d product && find -L product -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`
1669 do
1670     echo "including $f"
1671     . $f
1672 done
1673 unset f
主要执行以下3个动作
1.搜索所有vendorsetup.sh脚本;
2.打印所有找到的vendorsetup.sh文件路径;
3.加载所有找到的vendorsetup.sh脚本
其中-maxdepth 4表示查找的最大目录深度为4层,如果添加的vendorsetup.sh脚本的目录层级太深,会发生找不到的情况。


2. lunch操作
在source之后,紧接着就是执行lunch操作,其实就是build/envsetup.sh脚本中的lunch函数
1> 获取用户编译目标到answer变量:
576     local answer
577 
578     if [ "$1" ] ; then
579         answer=$1
580     else
581         print_lunch_menu
582         echo -n "Which would you like? [aosp_arm-eng] "
583         read answer
584     fi
如果lunch命令后跟有参数,则直接赋给answer变量;
如果lunch命令后没有参数,则调用函数print_lunch_menu打印出板型列表供用户选择,并将用户的选择幅值给answer变量;
print_lunch_menu就是打印数组LUNCH_MENU_CHOICES的内容;
2> 从answer变量到selection变量
如果answer为空,则selection默认赋值为aosp_arm-eng;
如果answer是纯数字,则将answer作为数组下标从LUNCH_MENU_CHOICES数组中取出板型名称;
如果anwser是字符串,并且字符串使用”-”连接,而且”-”连接的前后两个子串中都没有”-”,则认为是板型名称字符串,直接赋给selection。
经过上述3步,如果发现selection仍然为空,则直接报错并退出
3> 从selection变量到variant变量
从selection中取出“-”后面的字符串到variant,类似从字符串"aosp_arm-eng"中取出"eng",然后调用函数check_variant判断variant是否符合要求。
check_variant函数主要就是用到前文source流程中初始化为"user userdebug eng"的VARIANT_CHOICES数组,判断variant是否是数组成员之一,是则返回0,不是则返回1。
4> 从selection变量到product变量
从selection中取出“-”前面的字符串到product,类似从字符串"aosp_arm-eng"中取出"aosp_arm",
然后将product赋值给TARGET_PRODUCT,将variant赋值给TARGET_BUILD_VARIANT,
然后再调用函数build_build_var_cache对编译时所必需的环境变量进行赋值和处理,并根据函数返回结果做对应处理。
5> 导出参数到环境变量
export TARGET_PRODUCT=$product
export TARGET_BUILD_VARIANT=$variant
export TARGET_BUILD_TYPE=release
6> 最后三个函数调用:
641     set_stuff_for_environment
642     printconfig
643     destroy_build_var_cache
函数set_stuff_for_environment主要就是设置PROMPT_COMMAND,ANDROID_BUILD_PATHS,JAVA_HOME和BUILD_ENV_SEQUENCE_NUMBER等环境变量;
函数printconfig用来打印最终准备好的环境变量
函数destroy_build_var_cache用来清除不再需要的中间环节产生的变量的值。
看一下环境变量,会看到增加了很多Android相关的环境变量

3. make执行编译
3.1 编译入口
当我们在Android源码根目录下执行make的时候,会查找当前目录下的Makefie文件或者makefile文件并且执行,在android/Makefile文件中,它只有一行有用的内容
include build/core/main.mk
因此,执行make时真正的入口是android/build/core/main.mk文件。

3.2 整体依赖
执行make命令的时候,并没有传入目标,那么就会执行默认的目标。那默认的目标是什么呢?在android/build/core/main.mk中有这样几行
39 # This is the default target.  It must be the first declared target.
40 .PHONY: droid
41 DEFAULT_GOAL := droid
42 $(DEFAULT_GOAL): droid_targets
可知默认编译的就是droid这个伪目标,make工具遇到伪目标以后,会检查解析伪目标的依赖,如果伪目标存在依赖,就会检查这些依赖,
如果这些依赖是伪目标,就继续检查这个伪目标的依赖,如果不是伪目标,就会生成这个目标,如此一层层递归下去。
1168:droid_targets: apps_only
1216:droid_targets: droidcore dist_files
droid这个伪目标依赖droidcore和dist_files两大部分(整体编译时TARGET_BUILD_APPS为空),然后再将这两个依赖逐步解析下去,可以得到编译droid的整体依赖关系
请参考 https://blog.csdn.net/lizekun2010/article/details/52598105

1109 droidcore: files \
1110     systemimage \
1111     $(INSTALLED_BOOTIMAGE_TARGET) \
1112     $(INSTALLED_RECOVERYIMAGE_TARGET) \
1113     $(INSTALLED_VBMETAIMAGE_TARGET) \
1114     $(INSTALLED_USERDATAIMAGE_TARGET) \
1115     $(INSTALLED_CACHEIMAGE_TARGET) \
1116     $(INSTALLED_BPTIMAGE_TARGET) \
1117     $(INSTALLED_VENDORIMAGE_TARGET) \
1118     $(INSTALLED_PRODUCTIMAGE_TARGET) \
1119     $(INSTALLED_SYSTEMOTHERIMAGE_TARGET) \
1120     $(INSTALLED_FILES_FILE) \
1121     $(INSTALLED_FILES_FILE_VENDOR) \
1122     $(INSTALLED_FILES_FILE_PRODUCT) \
1123     $(INSTALLED_FILES_FILE_SYSTEMOTHER) \
1124     soong_docs

droid 所依赖的其他 Make 目标的说明
名称    说明
apps_only    该目标将编译出当前配置下不包含 user,userdebug,eng 标签(关于标签,请参见后文“添加新的模块”)的应用程序。
droidcore    该目标仅仅是所依赖的几个目标的组合,其本身不做更多的处理。
dist_files    该目标用来拷贝文件到 /out/dist 目录。
files    该目标仅仅是所依赖的几个目标的组合,其本身不做更多的处理。
prebuilt    该目标依赖于 $(ALL_PREBUILT),$(ALL_PREBUILT)的作用就是处理所有已编译好的文件。
$(modules_to_install)    modules_to_install 变量包含了当前配置下所有会被安装的模块(一个模块是否会被安装依赖于该产品的配置文件,模块的标签等信息),因此该目标将导致所有会被安装的模块的编译。
$(modules_to_check)    该目标用来确保我们定义的构建模块是没有冗余的。
$(INSTALLED_ANDROID_INFO_TXT_TARGET)    该目标会生成一个关于当前 Build 配置的设备信息的文件,该文件的生成路径是:out/target/product//android-info.txt
systemimage    生成 system.img。
$(INSTALLED_BOOTIMAGE_TARGET)    生成 boot.img。
$(INSTALLED_RECOVERYIMAGE_TARGET)    生成 recovery.img。
$(INSTALLED_USERDATAIMAGE_TARGET)    生成 userdata.img。
$(INSTALLED_CACHEIMAGE_TARGET)    生成 cache.img。
$(INSTALLED_FILES_FILE)    该目标会生成 out/target/product// installed-files.txt 文件,该文件中内容是当前系统镜像中已经安装的文件列表。

其他主要 Make 目标
Make 目标    说明
make clean    执行清理,等同于:rm -rf out/。
make sdk    编译出 Android 的 SDK。
make clean-sdk    清理 SDK 的编译产物。
make update-api    更新 API。在 framework API 改动之后,需要首先执行该命令来更新 API,公开的 API 记录在 frameworks/base/api 目录下。
make dist    执行 Build,并将 MAKECMDGOALS 变量定义的输出文件拷贝到 /out/dist 目录。
make all    编译所有内容,不管当前产品的定义中是否会包含。
make help    帮助信息,显示主要的 make 目标。
make snod    从已经编译出的包快速重建系统镜像。
make libandroid_runtime    编译所有 JNI framework 内容。
makeframework    编译所有 Java framework 内容。
makeservices    编译系统服务和相关内容。
make     编译一个指定的模块,local_target 为模块的名称。
make clean-    清理一个指定模块的编译结果。
makedump-products    显示所有产品的编译配置信息,例如:产品名,产品支持的地区语言,产品中会包含的模块等信息。
makePRODUCT-xxx-yyy    编译某个指定的产品。
makebootimage    生成 boot.img
makerecoveryimage    生成 recovery.img
makeuserdataimage    生成 userdata.img
makecacheimage    生成 cache.img

补充说明
1.有些依赖(比如INSTALLED_BOOTIMAGE_TARGET)在android/build/core/main.mk中没有定义,而是在android/build/core/Makefile中定义的;
2.上面dist_files也是个伪目标,并且它没有任何依赖,利用dist-for-goals方法来拷贝库文件,可忽略。

3.3 编译主流程
3.3.1 加载板型配置 
首先各mk文件调用关系如下
main.mk -> config.mk -> envsetup.mk -> product_config.mk
其中product_config.mk文件,首先调用方法get-all-product-makefiles找出所有的AndroidProducts.mk文件:
178 ifneq ($(strip $(TARGET_BUILD_APPS)),)
179 # An unbundled app build needs only the core product makefiles.
180 all_product_configs := $(call get-product-makefiles,\
181     $(SRC_TARGET_DIR)/product/AndroidProducts.mk)
182 else
183 # Read in all of the product definitions specified by the AndroidProducts.mk
184 # files in the tree.
185 all_product_configs := $(get-all-product-makefiles)
186 endif
方法get-all-product-makefiles的定义在文件product.mk中

然后从all_product_configs中找出我们当前产品的 AndroidProducts.mk 文件
190 # Find the product config makefile for the current product.
191 # all_product_configs consists items like:
192 # :
193 # or just in case the product name is the                                  
194 # same as the base filename of the product config makefile.
195 current_product_makefile :=
196 all_product_makefiles :=
197 $(foreach f, $(all_product_configs),\
198     $(eval _cpm_words := $(subst :,$(space),$(f)))\
199     $(eval _cpm_word1 := $(word 1,$(_cpm_words)))\
200     $(eval _cpm_word2 := $(word 2,$(_cpm_words)))\
201     $(if $(_cpm_word2),\
202         $(eval all_product_makefiles += $(_cpm_word2))\
203         $(eval all_named_products += $(_cpm_word1))\
204         $(if $(filter $(TARGET_PRODUCT),$(_cpm_word1)),\
205             $(eval current_product_makefile += $(_cpm_word2)),),\
206         $(eval all_product_makefiles += $(f))\
207         $(eval all_named_products += $(basename $(notdir $(f))))\
208         $(if $(filter $(TARGET_PRODUCT),$(basename $(notdir $(f)))),\
209             $(eval current_product_makefile += $(f)),)))
210 _cpm_words :=
211 _cpm_word1 :=
212 _cpm_word2 :=
213 current_product_makefile := $(strip $(current_product_makefile))
在执行lunch时选择的板型会保存到 TARGET_PRODUCT , 此处会从所有的 AndroidProducts.mk 文件路径中过滤出包含 TARGET_PRODUCT 路径的那一个;
比如 ./device/generic/arm64/AndroidProducts.mk
而这个AndroidProducts.mk的内容其实就是指定板型配置信息文件;

接着调用import-products导入产品配置信息:
228 ifeq ($(load_all_product_makefiles),true)
229 # Import all product makefiles.
230 $(call import-products, $(all_product_makefiles))
231 else
232 # Import just the current product.
233 ifndef current_product_makefile
234 $(error Can not locate config makefile for product "$(TARGET_PRODUCT)")
235 endif
236 ifneq (1,$(words $(current_product_makefile)))
237 $(error Product "$(TARGET_PRODUCT)" ambiguous: matches $(current_product_makefile))
238 endif
239 $(call import-products, $(current_product_makefile))
240 endif  # Import all or just the current product makefile
方法import-products的定义也在文件android/build/core/product.mk中

接着设置TARGET_DEVICE的值,
274 # Find the device that this product maps to.
275 TARGET_DEVICE := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE)

接着设置PRODUCT_COPY_FILES,这个变量指定了需要拷贝的文件
337 # A list of words like :[:].
338 # The file at the source path should be copied to the destination path
339 # when building  this product.   is relative to
340 # $(PRODUCT_OUT), so it should look like, e.g., "system/etc/file.xml".
341 # The rules for these copy steps are defined in build/make/core/Makefile.
342 # The optional : is used to indicate the owner of a vendor file.
343 PRODUCT_COPY_FILES := \
344     $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_COPY_FILES))

接着设置PRODUCT_PROPERTY_OVERRIDES属性:
346 # A list of property assignments, like "key = value", with zero or more
347 # whitespace characters on either side of the '='.
348 PRODUCT_PROPERTY_OVERRIDES := \
349     $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PROPERTY_OVERRIDES))

接着回到 build/core/envsetup.mk 中,include了板型配置文件BoardConfig.mk
240 # Boards may be defined under $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)
241 # or under vendor/*/$(TARGET_DEVICE).  Search in both places, but
242 # make sure only one exists.
243 # Real boards should always be associated with an OEM vendor.
244 board_config_mk := \
245     $(strip $(sort $(wildcard \
246         $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \
247         $(shell test -d device && find -L device -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
248         $(shell test -d vendor && find -L vendor -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
249     )))
250 ifeq ($(board_config_mk),)
251   $(error No config file found for TARGET_DEVICE $(TARGET_DEVICE))
252 endif
253 ifneq ($(words $(board_config_mk)),1)
254   $(error Multiple board config files for TARGET_DEVICE $(TARGET_DEVICE): $(board_config_mk))
255 endif
256 include $(board_config_mk)
至此,板型配置基本加载完毕。

3.3.2 加载所有模块
加载完板型配置信息后,回到main.mk文件中,如果 ONE_SHOT_MAKEFILE 这个变量被定义了,那么就是编译一个模块,如果没有被定义,就说明是编译整个系统。
MAKECMDGOALS 是make的一个环境变量,当我们执行make的时候并没有设置它,因此它为空。所以 dont_bother 不等于true,因此会加载所有的 Android.mk,
查找系统中所有的 Android.mk, 然后循环include进来;
448 #
449 # Include all of the makefiles in the system
450 #
451 
452 subdir_makefiles := $(SOONG_ANDROID_MK) $(file <$(OUT_DIR)/.module_paths/Android.mk.list)
453 subdir_makefiles_total := $(words $(subdir_makefiles))
454 .KATI_READONLY := subdir_makefiles_total
455 
456 $(foreach mk,$(subdir_makefiles),$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_to     tal)] including $(mk) ...)$(eval include $(mk)))

core/soong_config.mk:7:SOONG_ANDROID_MK := $(SOONG_OUT_DIR)/Android-$(TARGET_PRODUCT).mk
subdir_makefiles 包含 SOONG_ANDROID_MK 和 $(OUT_DIR)/.module_paths/Android.mk.list 中的所有makefile 列表;

执行m命令相当于是在执行make命令。对整个Android源码进行编译。m命令其实定义在 build/make/envsetup.sh;
_wrap_build $T/build/soong/soong_ui.bash --make-mode [email protected]
55 soong_build_go soong_ui android/soong/cmd/soong_ui
56 
57 cd ${TOP}
58 exec "$(getoutdir)/soong_ui" "[email protected]"

build/soong/ui/build/finder.go 
78     androidMks := f.FindFirstNamedAt(".", "Android.mk")
79     err := dumpListToFile(androidMks, filepath.Join(dumpDir, "Android.mk.list"))

在definitions.mk文件中还有很多这样的定义
981:define transform-l-to-c-or-cpp
992:define transform-y-to-c-or-cpp
1025:define transform-renderscripts-to-java-and-bc
1044:define transform-bc-to-so
1064:define transform-renderscripts-to-cpp-and-bc
1087:define transform-aidl-to-java
1094:define transform-aidl-to-cpp
1132:define transform-vts-to-cpp
1157:define transform-logtags-to-java
1169:define transform-proto-to-java
1190:define transform-proto-to-cc
1223:define transform-cpp-to-o-compiler-args
1246:define transform-cpp-to-o
1252:define transform-cpp-to-o
1268:define transform-c-or-s-to-o-compiler-args
1279:define transform-c-to-o-compiler-args
1294:define transform-c-to-o
1300:define transform-c-to-o
1310:define transform-s-to-o
1319:define transform-asm-to-o
1334:define transform-m-to-o
1343:define transform-host-cpp-to-o-compiler-args
1364:define transform-host-cpp-to-o
1370:define transform-host-cpp-to-o
1385:define transform-host-c-or-s-to-o-common-args
1395:define transform-host-c-or-s-to-o
1403:define transform-host-c-to-o-compiler-args
1416:define transform-host-c-to-o
1422:define transform-host-c-to-o
1432:define transform-host-s-to-o
1441:define transform-host-m-to-o
1450:define transform-host-mm-to-o
1579:define transform-o-to-static-lib
1625:define transform-o-to-aux-static-lib
1635:define transform-o-to-aux-executable-inner
1648:define transform-o-to-aux-executable
1654:define transform-o-to-aux-static-executable-inner
1668:define transform-o-to-aux-static-executable
1734:define transform-host-o-to-static-lib
1755:define transform-host-o-to-shared-lib-inner
1780:define transform-host-o-to-shared-lib
1786:define transform-host-o-to-package
1797:define transform-o-to-shared-lib-inner
1821:define transform-o-to-shared-lib
1836:define transform-to-stripped
1843:define transform-to-stripped-keep-mini-debug-info
1864:define transform-to-stripped-keep-symbols
1886:define transform-o-to-executable-inner
1912:define transform-o-to-executable
1929:define transform-o-to-static-executable-inner
1954:define transform-o-to-static-executable
1975:define transform-host-o-to-executable-inner
1999:define transform-host-o-to-executable
2289:define transform-java-to-header.jar
2382:define transform-classes.jar-to-dex
2400:define transform-classes-d8.jar-to-dex
2607:define transform-host-java-to-package
2614:define transform-host-java-to-dalvik-package
2724:define transform-prebuilt-to-target
2730:define transform-prebuilt-to-target-strip-comments
2823:define transform-jar-to-proguard
2828:define transform-jar-to-proguard
2839:define transform-jar-to-dex-r8
2853:define transform-generated-source

加载完所有的makefile之后会对 droidcore 目标的所有依赖逐个编译;
1109 droidcore: files \
1110     systemimage \
1111     $(INSTALLED_BOOTIMAGE_TARGET) \
1112     $(INSTALLED_RECOVERYIMAGE_TARGET) \
1113     $(INSTALLED_VBMETAIMAGE_TARGET) \
1114     $(INSTALLED_USERDATAIMAGE_TARGET) \
1115     $(INSTALLED_CACHEIMAGE_TARGET) \
1116     $(INSTALLED_BPTIMAGE_TARGET) \
1117     $(INSTALLED_VENDORIMAGE_TARGET) \
1118     $(INSTALLED_PRODUCTIMAGE_TARGET) \
1119     $(INSTALLED_SYSTEMOTHERIMAGE_TARGET) \
1120     $(INSTALLED_FILES_FILE) \
1121     $(INSTALLED_FILES_FILE_VENDOR) \
1122     $(INSTALLED_FILES_FILE_PRODUCT) \
1123     $(INSTALLED_FILES_FILE_SYSTEMOTHER) \
1124     soong_docs

m
  相当于是在执行make命令。对整个Android源码进行编译。
mm
  如果是在Android源码根目录下执行,那么就相当于是执行make命令对整个源码进行编译。
  如果是在Android源码根目录下的某一个子目录执行,那么就会从该子目录开始,一直往上一个目录直至到根目录,
  寻找是否存在一个Android.mk文件。如果存在的话,就通过make命令对该Android.mk文件描述的模块进行编译。
mmm
  后面可以跟一个或者若干个目录。如果指定了多个目录,那么目录之间以空格分隔,并且每一个目录下都必须存在一个Android,mk文件。
  如果没有在目录后面通过冒号指定模块名称,那么在Android.mk文件中描述的所有模块都会被编译,否则只有指定的模块会被编译。
  如果需要同时指定多个模块,那么这些模块名称必须以逗号分隔。

如果用户想个性定制自己的产品,应该有以下流程:
1. 创建公司目录
    #mkdir vendor/farsight
2. 创建一个vendorsetup.sh文件,将当前产品编译项添加到lunch里,让lunch能找到用户个性定制编译项
    #echo "add_lunch_combo fs100-eng" > vendor/farsight/vendorsetup.sh
3. 仿着Android示例代码,在公司目录下创建products目录
    #mkdir -p vendor/farsight/products
4. 仿着Android示例代码,在products目录下创建两个mk文件
    #touch vendor/farsight/products/AndroidProduct.mk vendor/farsight/products/fs100.mk
build/core/main.mk       包含了config.mk,它主要定义了编译全部代码的依赖关系
build/core/config.mk     定义了大量的编译脚本命令,编译时用到的环境变量,引入了 envsetup.mk 文件,加载board相关配置文件。
build/core/envsetup.mk   定义了编译时用到的大量OUT输出目录,加载product_config.mk文件
build/core/product_config.mk 定义了Vendor目录下Product相关配置文件解析脚本,读取AndrodProducts.mk生成TARGET_DEVICE变量

Build 结果的目录结构
所有的编译产物都将位于 out 目录下,该目录下主要有以下几个子目录:
out/host/:该目录下包含了针对主机的 Android 开发工具的产物。即 SDK 中的各种工具,例如:emulator,adb,aapt 等。
out/target/common/:该目录下包含了针对设备的共通的编译产物,主要是 Java 应用代码和 Java 库。
out/target/product//:包含了针对特定设备的编译结果以及平台相关的 C/C++ 库和二进制文件。其中,是具体目标设备的名称。
out/dist/:包含了为多种分发而准备的包,通过“make disttarget”将文件拷贝到该目录,默认的编译目标不会产生该目录。

Build 生成的镜像文件
Build 的产物中最重要的是三个镜像文件,它们都位于 /out/target/product// 目录下。
这三个文件是:
system.img:包含了 Android OS 的系统文件,库,可执行文件以及预置的应用程序,将被挂载为根分区。
ramdisk.img:在启动时将被 Linux 内核挂载为只读分区,它包含了 /init 文件和一些配置文件。它用来挂载其他系统镜像并启动 init 进程。
userdata.img:将被挂载为 /data,包含了应用程序相关的数据以及和用户相关的数据。

各种模块的编译方式的定义文件
文件名    说明
host_static_library.mk    定义了如何编译主机上的静态库。
host_shared_library.mk    定义了如何编译主机上的共享库。
static_library.mk    定义了如何编译设备上的静态库。
shared_library.mk    定义了如何编译设备上的共享库。
executable.mk    定义了如何编译设备上的可执行文件。
host_executable.mk    定义了如何编译主机上的可执行文件。
package.mk    定义了如何编译 APK 文件。
prebuilt.mk    定义了如何处理一个已经编译好的文件 ( 例如 Jar 包 )。
multi_prebuilt.mk    定义了如何处理一个或多个已编译文件,该文件的实现依赖 prebuilt.mk。
host_prebuilt.mk    处理一个或多个主机上使用的已编译文件,该文件的实现依赖 multi_prebuilt.mk。
java_library.mk    定义了如何编译设备上的共享 Java 库。
static_java_library.mk    定义了如何编译设备上的静态 Java 库。
host_java_library.mk    定义了如何编译主机上的共享 Java 库。
为了减少代码冗余,需要将共同的代码复用起来,复用的方式是将共同代码放到专门的文件中,然后在其他文件中包含这些文件的方式来实现的。


Android.mk 专题

Android.mk 编写
LOCAL_PATH := $(call my-dir) # 定义了当前模块的相对路径,必须在include $(CLEAR_VARS)之前;
include $(CLEAR_VARS)        # 清空当前环境变量,除了 LOCAL_PATH;
LOCAL_MODULE := test         # 编译生成的目标名称
LOCAL_SRC_FILES := test.c    # 编译该模块需要的源文件
include $(BUILD_EXECUTABLE)  # 编译所生成的目标文件格式


多源码文件编译
将工程下的所有源码文件添加到变量中的方式有以下两种
1. 将每个文件添加到 Android.mk 中;
2. 使用系统提供的函数处理; 定义在 build/core/definitions.mk all-c-files-under;
LOCAL_C_ALL_FILES := $(call all-c-files-under,.)
LOCAL_SRC_FILES := $(LOCAL_C_ALL_FILES)
类似的还有
all-cpp-files-under
all-java-files-under
如:
###############################################################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := test
LOCAL_C_ALL_FILES := $(call all-c-files-under,src)
LOCAL_SRC_FILES := $(LOCAL_C_ALL_FILES)
#LOCAL_MODULE_PATH := $(LOCAL_PATH)/bin
include $(BUILD_EXECUTABLE)
###############################################################


定义目标生成路径,默认在out的对应目录
LOCAL_MODULE_RELATIVE_PATH := $(LOCAL_PATH)/lib
LOCAL_MODULE_PATH := $(LOCAL_PATH)/bin


编译生成动态库
设置编译生成的目标文件类型即可
include $(BUILD_SHARED_LIBRARY)

编译生成静态库
设置编译生成的目标文件类型即可
include $(BUILD_STATIC_LIBRARY)

如何引入系统库
LOCAL_SHARED_LIBRARIES += liblog

如何引入第三方库
LOCAL_LDFLAGS += -L Path -lxxx

如何引入静态库
LOCAL_LDFLAGS += $(LOCAL_PATH)/lib/libxxx.so

如何引入头文件
LOCAL_C_INCLUDES += $(LOCAL_PATH)/inc/test.h


如何编译 jar 包
LOCAL_MODULE              # 编译的 jar 包的名字;
BUILD_STATIC_JAVA_LIBRARY # 编译生成静态 jar 包;使用.class文件打包而成,可以在任何java虚拟机上运行;
BUILD_JAVA_LIBRARY        # 编译生成共享 jar 包;在静态jar包基础上使用.dex打包而成的,.dex是android系统使用的文件格式;
###############################################################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := com.demomk.myjar
LOCAL_SRC_FILES := $(call all-subdir-java-files)

include $(BUILD_STATIC_JAVA_LIBRARY)
#include $(BUILD_JAVA_LIBRARY)
###############################################################

如何编译 apk
BUILD_PACKAGE      # 编译生成 apk
LOCAL_PACKAGE_NAME # 编译生成的 apk 名字
###############################################################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := LocalPackage
include $(BUILD_PACKAGE)
###############################################################

apk 中如何导入 jar 包和库文件
LOCAL_STATIC_JAVA_LIBRARY # 引用静态 jar 包;
LOCAL_JAVA_LIBRARY        # 应用动态 jar 包;
###############################################################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_STATIC_JAVA_LIBRARY := static-lib 
#LOCAL_JAVA_LIBRARY := shared-lib

LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := LocalPackage
include $(BUILD_PACKAGE)
###############################################################

如何预编译 jar 包
BUILD_PREBUILT     # 预编译
LOCAL_MODULE_CLASS # 指定编译生成的文件类型,包括
  JAVA_LIBRARIES: dex归档文件
  APPS: apk文件
  SHARED_LIBRARIES: 动态库文件
  EXECUTABLES: 二进制文件
  ETC: 其他文件格式
LOCAL_MODULE       # 预编译出的包文件名
###############################################################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_MODULE := com.demomk.share
LOCAL_SRC_FILES := com.demomk.static # 源文件可以指定原有的一个jar包

include $(BUILD_PREBUILT)
###############################################################

Android.mk 中如何加判断语句
ifeq($(VALUE),x) #ifneq
  do_yes
else
  do_no
endif
ifeq/ifneq 根据判断条件,执行相关编译指令;


Android.bp
Android.bp是用来替换Android.mk的配置文件,它使用Blueprint框架来解析。Blueprint是生成、解析Android.bp的工具,是Soong的一部分。
Soong则是专为Android编译而设计的工具,Blueprint只是解析文件的形式,而Soong则解释内容的含义,最终转换成Ninja文件。

Ninja 是Google的一名程序员推出的小而快的构建工具,一般在Unix/Linux上的程序通过make/makefile来构建编译,而Ninja通过将编译任务并行组织,大大提高了构建速度。
# 安装
sudo apt install ninja-build
一般是通过cmake来生成ninja的配置,进而进行编译。
# 示例
cmake -G "Ninja" 
ninja

从android N之后,我们发现好多模块下面没有了android.mk文件,多了一个android.bp文件。这个是google在android N之后新的编译配置文件;
在实际项目中,我们需要把android.mk转换为android.bp,sdk中提供了androidmk工具,可以直接把android.mk转换为android.bp.
源码在:build/soong/androidmk
在out下面找下androidmk工具,如果没有可以执行命令:m -j blueprint_tools
然后在 out/soong/host/linux-x86/bin/androidmk 生成工具

$ androidmk  android.mk > android.bp
这样即可把android.mk转换为android.bp文件了

Android.bp 语法
Android.bp文件用类似JSON的简洁声明来描述需要构建的模块。
1、模块
定义一个模块从模块的类型开始,模块有不同的类型,如下例子中的“cc_binary”,
模块包含一些属性格式为 “property-name: property-value”,其中name属性必须指定,其属性值必须是全局唯一的。
cc_binary{
    name: "gzip",
    srcs: ["src/test/minigzip.c"],
    shared_libs: ["libz"],
    stl: "none",
}
默认模块“cc_defaults”的用法如下。
cc_defaults{
    name: "gzip_defaults",
    shared_libs: ["libz"],
    stl: "none",
}
cc_binary{
    name: "gzip",
    defaults: ["gzip_defaults"],
    srcs: ["src/test/minigzip.c"],
}

2、变量
变量赋值可以通过“=”号赋值。
变量是不可变的,但有一个例外,可以附上 += 赋值,但仅在变量被引用之前。
gzip_srcs = ["src/test/minigzip.c"],
cc_binary {
    name: "gzip",
    srcs: gzip_srcs,
    shared_libs: ["libz"],
    stl: "none",
}

3、注释
注释包括单行注释//和多行注释/* */。

4、类型
具体支持以下几种类型:
Bool            ('true' or 'false')
Integers        ('int')
Strings         ('"string"')
Listsof strings ('["string1", "string2"]')
Maps            ('{key1: "value1", key2: ["value2"]}')

5、操作符
除了 = 赋值以外;
String类型、字符串列表类型和Map类型支持操作符“+”。

6、格式控制工具
bpfmt是一个bp文件的格式控制工具,包括4个空格的缩进、列表有多个元素时每个元素一行、列表和map的最后一个元素多一个冗余的逗号等等。
工具目录为:build/blueprint/bpfmt/
在当前目录下执行以下命令:
$ bpfmt-w .

7、Android.mk文件转为Android.bp
androidmk 工具可以把mk文件转换为bp文件,但一些复杂的用法和自定义的规则需要手动转换。
Android.mk --Soong--Blueprint--> Android.bp
Android.bp --Blueprint--Soong--> Ninja
工具源码目录为 build/soong/androidmk/
编译后会生成到 out/soong/host/linux-x86/bin/androidmk
$ androidmk Android.mk > Android.bp

8、支持的模块类型
Android.bp可以支持android_app、cc_binary、cc_binary_host等多种类型,具体如下

732 var moduleTypes = map[string]string{
733     "BUILD_SHARED_LIBRARY":        "cc_library_shared",
734     "BUILD_STATIC_LIBRARY":        "cc_library_static",
735     "BUILD_HOST_SHARED_LIBRARY":   "cc_library_host_shared",
736     "BUILD_HOST_STATIC_LIBRARY":   "cc_library_host_static",
737     "BUILD_HEADER_LIBRARY":        "cc_library_headers",                                                                      
738     "BUILD_EXECUTABLE":            "cc_binary",
739     "BUILD_HOST_EXECUTABLE":       "cc_binary_host",
740     "BUILD_NATIVE_TEST":           "cc_test",
741     "BUILD_HOST_NATIVE_TEST":      "cc_test_host",
742     "BUILD_NATIVE_BENCHMARK":      "cc_benchmark",
743     "BUILD_HOST_NATIVE_BENCHMARK": "cc_benchmark_host",
744 
745     "BUILD_JAVA_LIBRARY":             "java_library",
746     "BUILD_STATIC_JAVA_LIBRARY":      "java_library_static",
747     "BUILD_HOST_JAVA_LIBRARY":        "java_library_host",
748     "BUILD_HOST_DALVIK_JAVA_LIBRARY": "java_library_host_dalvik",
749     "BUILD_PACKAGE":                  "android_app",
750 }
源码位于 build/soong/androidmk/cmd/androidmk/android.go

9、支持预编译类型
Android.bp可以支持4种预编译类型,如下
752 var prebuiltTypes = map[string]string{
753     "SHARED_LIBRARIES": "cc_prebuilt_library_shared",
754     "STATIC_LIBRARIES": "cc_prebuilt_library_static",
755     "EXECUTABLES":      "cc_prebuilt_binary",
756     "JAVA_LIBRARIES":   "java_import",
757 }

10、模块名称解析
soong提供了可以在不同目录中配置相同的模块名称,只要每个模块的名称在不同的命名空间中声明。
soong_namespace{
    imports: ["path/to/otherNamespace1", "path/to/otherNamespace2"],
}

11、条件编译
如下面的arch属性,会根据具体的值是arm还是x86来设置不同的srcs属性值;
cc_library{
    ...
    srcs: ["generic.cpp"],
    arch: {
        arm: {
            srcs: ["arm.cpp"],
        },
        x86: {
            srcs: ["x86.cpp"],
        },
    },
}

Android 利用Blueprint和Soong 来解析bp文件,经过最终转换为ninja files。
Blueprint和Soong都是由Golang写的项目。


参考资料
https://blog.csdn.net/wang92453/article/details/21964491
https://blog.csdn.net/lizekun2010/article/details/52598105
https://www.cnblogs.com/wangzhe1635/articles/8674914.html
https://www.ibm.com/developerworks/cn/opensource/os-cn-android-build/index.html
https://www.jianshu.com/p/7fe1beb38817
https://blog.csdn.net/csdn66_2016/article/details/80258627
 

更多相关文章

  1. Android三种方式实现ProgressBar自定义圆形进度条
  2. android FTP上传下载文件
  3. Android build.prop配置 定义一些宏定义
  4. android 自定义menu菜单按键功能
  5. Android实现在ServiceManager中加入自定义服务的方法详解
  6. 自定义xml属性attr
  7. Android Studio添加so文件并打包到APK的lib文件夹中
  8. android studio打包 so文件

随机推荐

  1. Android 读取doc文件
  2. 【Arcgis android】 离线编辑实现及一些
  3. Android:TextSwitcher、imageSwitcher
  4. Android一些网站介绍
  5. Android中,把XML文件转换成Object对象的方
  6. android ndk编译getevent
  7. Android(安卓)MediaCodec踩坑笔记
  8. Android开发利器之Data Binding Compiler
  9. Android应用程序组件Content Provider在
  10. android SQLite操作