赞
踩
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/<product_name>/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/<product_name>/ 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> 编译一个指定的模块,local_target 为模块的名称。
make clean-<local_target> 清理一个指定模块的编译结果。
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 # <product_name>:<path_to_the_product_makefile>
193 # or just <path_to_the_product_makefile> 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 <source path>:<destination path>[:<owner>].
338 # The file at the source path should be copied to the destination path
339 # when building this product. <destination path> 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 :<owner> 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 $@
55 soong_build_go soong_ui android/soong/cmd/soong_ui
56
57 cd ${TOP}
58 exec "$(getoutdir)/soong_ui" "$@"
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/<product_name>/:包含了针对特定设备的编译结果以及平台相关的 C/C++ 库和二进制文件。其中,<product_name>是具体目标设备的名称。
out/dist/:包含了为多种分发而准备的包,通过“make disttarget”将文件拷贝到该目录,默认的编译目标不会产生该目录。
Build 生成的镜像文件
Build 的产物中最重要的是三个镜像文件,它们都位于 /out/target/product/<product_name>/ 目录下。
这三个文件是:
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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。