Android Build System 以c800为例, 调用如下: Android Build System[一] Android Build System[二] Android build system[三]

以c800为例, 调用如下:

android/makefile(include build/core/main.mk)-->main.mk ($(BUILD_SYSTEM)/config.mk) config.mk (定义了很多的系统变量以及makefile的依赖关系  $(BUILD_SYSTEM)/envsetup.mk) ->envsetup.mk(指定board_config_mk=/$(TARGET_DEVICE)/BoardConfig.mk (include $(board_config_mk));  以及include $(BUILD_SYSTEM)/product_config.mk)---> 1. BoardConfig.mk   2. product_config.mk (include $(BUILD_SYSTEM)/product.mk -->product.mk(定义了get_product_makefile 函数, 然后通过 $leval include(f) 来导入AndroidProduct.mk--> AndroidProduct.mk($(LOCAL_DIR)/c800.mk -->c800.mk

============================================================================================================


Android Build System[一]

1. 写在最开始

Android Build System是AOSP(Android Open Source Project)中既重要又复杂的一部分,且涉及的知识点非常多,比如shell script, Makefile, python, aapt等等。但若从事Android ROM开发工作,需要通过修改编译系统来实现系统的定制,所以适当了解AOSP编译系统,对我们的工作会有非常大的帮助。

Android Build System非常庞大和复杂,一篇文章不能介绍清楚,所以后续我通过一系列的文章来讲解,并用示例和原理相结合的方式来帮助大家深入理解编译系统。Android的编译是基于linux的,并且使用make命令编译,需要有一些基本的Makefile知识,文章中会涉及到一些基本的Makefile知识,但若先对Makefile有一定的了解会有助于大家理解后续文章。

2. 建立环境

这里的建立环境并不是讲如何配置你的电脑,而是我们在编译源码之前常执行的几步操作。

$ source build/envsetup.sh
$ lunch

执行完这两步之后,我们就可以使用lunch命令或者mm, mmm之类的命令编译某个模块。

我知道有些朋友肯定发现了,如果不运行source build/envsetup.sh这条命令,或者已经在一个console里运行过这条命令,但是在一个新开的console里,仍然是无法编译或者运行lunch和mm等命令。 
本文以剖析这两条命令来作为Android Build System系列的开端。

3. source build/envsetup.sh

source是一条标准的linux命令,相当于.命令,这条命令和

$ ./build/envsetup.sh

功能差不多。只不过source后加的脚本不需要有可执行权限。但是其结果也就是执行一遍build/envsetup.sh这个shell脚本,而envsetup.sh脚本里定义了很多函数,执行完这个脚本之后,就可以在当前console里直接像使用linux命令一样执行这些函数,比如lunch, mm, mmm等(使用hmm可以看到所有可用命令)。所以lunch, mmm它们实质上是一个shell函数,也就相当于shell脚本。我们可以在build/envsetup.sh里找到这些命令的实现原理。

当然envsetup.sh不仅仅只是现实了lunch, mmm等这些命令,它还有一个重要的功能就是找到所有当前源码支持的平板编译选项,并放到一个数组中去,也就是后面使用lunch命令时列出来的那些选项。先来看看是如何实现的:

[build/envsetup.sh]

... ...

unset LUNCH_MENU_CHOICES
function add_lunch_combo()
{
    local new_combo=$1
    local c
    for c in ${LUNCH_MENU_CHOICES[@]} ; do
        if [ "$new_combo" = "$c" ] ; then
            return
        fi
    done
    LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)
}

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

... ...

for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \
         `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`
do
    echo "including $f"
    . $f
done

这里做了下面几件事: 
* 使用add_lunch_combo函数添加了几个默认的平台编译选项,比如: aosp_arm-eng … 
* 在源码的device和vendor目录下找到名为vendorsetup.sh的脚本,并依次执行它们,需要注意的是,vendorsetup.sh文件所在目录层级不能超过4级 
* device或者vendor目录下的vendorsetup.sh里其实也是通过add_lunch_combo xxx添加平台编译选项

上面代码中有add_lunch_combo的实现,它将所 有add_lunch_combo后的参数全部保存到LUNCH_MENU_CHOICES这个数组中去(${LUNCH_MENU_CHOICES[@]}是取数组的所有元素的意思),后面lunch命令直接使用。

4. lunch

上面说过,lunch命令实质上也是envsetup.sh里的一个函数,以我的代码选择”aosp_mangosteen-userdebug”为例,看看它的实现:

[build/envsetup.sh]

function lunch()
{
    local answer

    // 如果lunch命令后接有参数,赋值给answer
    if [ "$1" ] ; then
        answer=$1
    else // 否则将所有平台编译选项打印出来,并等待输入
        print_lunch_menu
        read answer
    fi

    local selection=

    if [ -z "$answer" ]    // 如果answer的值是0,设置aosp_arm-eng为默认
    then
        selection=aosp_arm-eng
    elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
        // answer是数字的情况,从LUNCH_MENU_CHOICES数组中选择
    then
        if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]
        then
            selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}
        fi
    elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$")
        // answer也可以直接是名字,但要符合规则
    then
        selection=$answer
    fi

    export TARGET_BUILD_APPS=

    // 结果的前半部分aosp_mangosteen作为产品名,通过check_product函数检测它是否可用
    local product=$(echo -n $selection | sed -e "s/-.*$//")
    check_product $product

    // 结果的后半部分userdebug作为编译变量,通过check_variant函数检测它是否可用
    // variant必须严格的是user userdebug eng这几个中的一个,否则lunch结束
    local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")
    check_variant $variant


    if [ -z "$product" -o -z "$variant" ]
    then
        echo
        return 1
    fi

    // export几个重要的环境变量,后面make使用
    export TARGET_PRODUCT=$product
    export TARGET_BUILD_VARIANT=$variant
    export TARGET_BUILD_TYPE=release

    echo

    // 设置一些重要的环境变量
    set_stuff_for_environment
    // lunch完成结束后,打印所有和平台相关重要的环境变量
    printconfig
}

从上面lunch函数的代码注释可以看到,lunch实际上就是确定编译平台和编译选项,并且设置了一些关键环境变量。下面来详细讲下lunch中几个关键的地方。

4.1 check_product

小小的check_product函数,后面引入了一大堆makefile,并且执行这个函数过程中,会设置相当多你眼熟的环境变量,并且会检查lunch选择的平台是否可用,来看一看它是如何实现的吧。

check_product设置一些变量后,调用get_build_var函数,所以check_product $product相当于:

TARGET_PRODUCT=$1 \
TARGET_BUILD_VARIANT= \
TARGET_BUILD_TYPE= \
TARGET_BUILD_APPS= \
CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \
make -f build/core/config.mk dumpvar-TARGET_DEVICE > /dev/null

直接点,就是以build/core/config.mk作为makefile执行make命令,目标是dumpvar-TARGET_DEVICE。这里TARGET_PRODUCT就是lunch时选择的名字里’-‘前的部分,即aosp_mangosteen。

先看看build/core/config.mk里对其它makefile文件的依赖关系:

  • 这里只列出了部分makefile包含关系,这些makefile里同时还定义了非常多的变量,比如BUILD_STATIC_LIBRARY, TARGET_OUT_JAVA_LIBRARIES等等,感兴趣可以自己去看一下
  • 图中board_config_mk的定义如下:
board_config_mk := \
    $(strip $(wildcard \
        $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \
        $(shell test -d device && find device -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
        $(shell test -d vendor && find vendor -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
    ))

也就是$(TARGET_DEVICE)目录下的BoardConfig.mk文件。从图中可以看到,TARGET_DEVICE变量的值是在build/core/product_config.mk里确定的,在这个文件中有一段很重要的代码来确定TARGET_DEVICE:

... ...
// 找到所有的AndroidProducts.mk文件
all_product_configs := $(get-all-product-makefiles)

current_product_makefile :=
all_product_makefiles :=
$(foreach f, $(all_product_configs),\
    $(eval _cpm_words := $(subst :,$(space),$(f)))\
    $(eval _cpm_word1 := $(word 1,$(_cpm_words)))\
    $(eval _cpm_word2 := $(word 2,$(_cpm_words)))\
    $(if $(_cpm_word2),\
        $(eval all_product_makefiles += $(_cpm_word2))\
        $(if $(filter $(TARGET_PRODUCT),$(_cpm_word1)),\
            $(eval current_product_makefile += $(_cpm_word2)),),\
        $(eval all_product_makefiles += $(f))\
        $(if $(filter $(TARGET_PRODUCT),$(basename $(notdir $(f)))),\
            $(eval current_product_makefile += $(f)),)))
_cpm_words :=
_cpm_word1 :=
_cpm_word2 :=
current_product_makefile := $(strip $(current_product_makefile))
all_product_makefiles := $(strip $(all_product_makefiles))

... ...

ifneq (,$(filter product-graph dump-products, $(MAKECMDGOALS)))
...
else
    ifndef current_product_makefile
        error
    endif
    ifneq (1,$(words $(current_product_makefile)))
        error
    endif
// MAKECMDGOALS = dumpvar-TARGET_DEVICE, 所以走这个分支
$(call import-products, $(current_product_makefile))
endif

INTERNAL_PRODUCT := $(call resolve-short-product-name, $(TARGET_PRODUCT))
ifneq ($(current_product_makefile),$(INTERNAL_PRODUCT))
    error
endif

TARGET_DEVICE := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE)

这段代码如果根据传入的TARGET_PRODUCT = aosp_mangosteen来查找它需要的makefile文件,如果能够找到,然后就设置TARGET_DEVICE;否则,直接报错,结束lunch。

  • 函数get-all-product-makefiles定义在build/core/product.mk里,它的作用是返回device, vendor, build/target目录下所有AndroidProducts.mk文件中PRODUCT_MAKEFILES变量的值的列表
  • all_product_makefiles的值是all_product_configs里所有.mk文件路径的列表
  • current_product_makefile是根据TARGET_PRODUCT = aosp_mangosteen来找到的目标.mk路径,文件名它必须与TARGET_PRODUCT的值相同,在我们这里它的值是device/mstar/mangosteen/aosp_mangosteen.mk
  • MAKECMDGOALS是make定义的一个全局变量,它的值是make命令指定的目标,在这里MAKECMDGOALS = dumpvar-TARGET_DEVICE
  • 函数import-products定义在build/core/product.mk里, 它的作用是把current_product_makefile的值赋给PRODUCTS,并且将current_product_makefile代表的.mk文件加载进来, 并将它里的变量值保存到变量PRODUCTS.$(current_product_makefile).xxx中, 
    xxx就是.mk文件中每个变量名字,比如 PRODUCTS.\$(INTERNAL_PRODUCT).PRODUCT_DEVICE = PRODUCT_DEVICE = mangosteen
  • 函数resolve-short-product-name定义在build/core/product.mk里,它将TARGET_PRODUCT的值和PRODUCTS列表里的.mk文件中PRODUCT_NAME的值一一对比,返回和TARGET_PRODUCT值相同PRODUCT_NAME变量所在的那个.mk文件的路径
  • 根据上面的分析TARGET_DEVICE的值,其实就是device/mstar/mangosteen/aosp_mangosteen.mk文件中PRODUCT_DEVICE的值,它的值mangosteen
  • 如果上面的正常步骤中有任何一项没找到或者匹配不成功,则lunch就以失败结束

再回头看看那条make语句: make -f build/core/config.mk dumpvar-TARGET_DEVICE > /dev/null, 结合build/core/dumpvar.mk中下面这段看:

dumpvar_goals := \
    $(strip $(patsubst dumpvar-%,%,$(filter dumpvar-%,$(MAKECMDGOALS))))
ifdef dumpvar_goals

  absolute_dumpvar := $(strip $(filter abs-%,$(dumpvar_goals)))
  ifdef absolute_dumpvar
      ...
  else
    DUMPVAR_VALUE := $($(dumpvar_goals))
    dumpvar_target := dumpvar-$(dumpvar_goals)
  endif

.PHONY: $(dumpvar_target)
$(dumpvar_target):
    @echo $(DUMPVAR_VALUE)
  • 上一段有说过,MAKECMDGOALS在这里就是dumpvar-TARGET_DEVICE
  • 这段代码中,dumpvar_goals = TARGET_DEVICE;DUMPVAR_VALUE = $(TARGET_DEVICE) = mangosteen; dumpvar_target = dumpvar-mangosteen
  • 所以make -f build/core/config.mk dumpvar-TARGET_DEVICE > /dev/null最终就是一句”echo $(DUMPVAR_VALUE)”, 因为”> /dev/null”将make输出的正确信息重定向到/dev/null, 所以正常情况下,在console里看不到任何输出

到这里check_product函数就执行完了。

4.2 set_stuff_for_environment

函数set_stuff_for_environment里有一个比较重要的函数setpaths(),它的作用是往PATH环境变量中添加一些路径,让我们可以在执行过lunch的console里使用Android源码里的一些工具。

function setpaths() {
    ... ...
    export ANDROID_BUILD_PATHS=$(get_build_var ANDROID_BUILD_PATHS):$ANDROID_TOOLCHAIN:$ANDROID_TOOLCHAIN_2ND_ARCH:$ANDROID_DEV_SCRIPTS:

    export PATH=$ANDROID_BUILD_PATHS$PATH
    ... ...
}

可以使用echo $ANDROID_BUILD_PATHS 命令查看新增了哪些目录。

4.3 printconfig

printconfig函数的核心是调用”get_build_var report_config”, 和check_product函数流程类似,在build/core/dumpvar.mk里可以看到:

ifneq ($(PRINT_BUILD_CONFIG),)
HOST_OS_EXTRA:=$(shell python -c "import platform; print(platform.platform())")
$(info ============================================)
$(info   PLATFORM_VERSION_CODENAME=$(PLATFORM_VERSION_CODENAME))
$(info   PLATFORM_VERSION=$(PLATFORM_VERSION))
$(info   TARGET_PRODUCT=$(TARGET_PRODUCT))
$(info   TARGET_BUILD_VARIANT=$(TARGET_BUILD_VARIANT))
$(info   TARGET_BUILD_TYPE=$(TARGET_BUILD_TYPE))
$(info   TARGET_BUILD_APPS=$(TARGET_BUILD_APPS))
$(info   TARGET_ARCH=$(TARGET_ARCH))
$(info   TARGET_ARCH_VARIANT=$(TARGET_ARCH_VARIANT))
$(info   TARGET_CPU_VARIANT=$(TARGET_CPU_VARIANT))
$(info   TARGET_2ND_ARCH=$(TARGET_2ND_ARCH))
$(info   TARGET_2ND_ARCH_VARIANT=$(TARGET_2ND_ARCH_VARIANT))
$(info   TARGET_2ND_CPU_VARIANT=$(TARGET_2ND_CPU_VARIANT))
$(info   HOST_ARCH=$(HOST_ARCH))
$(info   HOST_OS=$(HOST_OS))
$(info   HOST_OS_EXTRA=$(HOST_OS_EXTRA))
$(info   HOST_BUILD_TYPE=$(HOST_BUILD_TYPE))
$(info   BUILD_ID=$(BUILD_ID))
$(info   OUT_DIR=$(OUT_DIR))
$(info ============================================)
endif

有没有发现,这就是lunch命令执行成功之后,console里打印出来的一串信息。

到这里lunch命令就执行完了,总得来说,lunch就是根据我们的输入,找到对应平台的所有相关makefile, 并根据makefile里定义的变量值,设置编译需要的变量的值;同时也添加一些源码中的目录到PATH中,方便使用源码目录中的一些命令。

5. mmm命令

这里顺带再简单介绍下mmm命令,如果你看懂了lunch命令的原理后,mmm也就很容易了。我们都知道使用mmm可以编译单个模块,比如:mmm packages/apps/Launcher3

function mmm() {
    ... ...

    for DIR in $DIRS ; do

        MODULES=`echo $DIR | sed -n -e 's/.*:\(.*$\)/\1/p' | sed 's/,/ /'`
        if [ "$MODULES" = "" ]; then
            MODULES=all_modules
        fi
        ... ...
    done

    ONE_SHOT_MAKEFILE="$MAKEFILE" $DRV make -C $T -f build/core/main.mk $DASH_ARGS $MODULES $ARGS

    ... ...
}
  • mmm先从参数中分离出需要编译模块的目录,mmm可以指定多个目录下的模块进行编译,比如:mmm packages/apps/Launcher3 packages/apps/Launcher2
  • 如果Android.mk中有多个module, 可以通过命令指定编译某一个module, 使用:和目录分开, 例如: mmm packages/apps/Launcher3:Launcher3; 若不指定,则默认编译Android.mk中所有的module
  • 可以看到mmm最终是定义了一个变量ONE_SHOT_MAKEFILE,然后使用make -C Tfbuild/core/main.mkT−fbuild/core/main.mkDASH_ARGS MODULESMODULESARGS,还是一个make命令。注意make后加了-C T,packages/apps/使mmm.makefmakefileCT,因为我们可能在packages/apps/下使用mmm.编译,而make−f指定的makefile文件是源码根目录下的相对路径,所以−CT是必要的

6. 总结

本文以介绍lunch和mmm命令作为编译系统的开篇,特别是lunch命令,虽然它没有编译任何文件,但是它仍然使用了make命令,并且加载了一大堆的makefile,后面编译都以lunch为基础,并使用了lunch设置的很多变量。

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Android Build System[二]

上一篇文章向大家介绍Android Build System的lunch和mmm的原理,本文继续阐述Android系统编译时property生成原理。希望阅读完本文后大家懂得如何在自己制作的ROM中生成系统默认的property属性。本想将Android系统中property读写的实现机制一起写的,但是我还是坚持自己的观点,每篇文章不能太长 ,因为我自己没有耐心看一篇很长的技术文章,所以关于property读写机制的原理,准备放在下一篇推送。

另外,Adroid Build System将会做为一个系列长久延续下去,并在中间穿插分享一些Android其它知识。希望有兴趣的朋友持续关注

1. property介绍

Android property是是Android系统中非常重要的一个机制,它类似于Windows系统中的注册表,每条属性都是key/value形式的键值对。property里保存了很多系统相关的重要信息,比如dalvik.vm.heapsize之类的。

使用property的方式有很多:

  1. console里:
$ getprop
$ setprop dalvik.vm.heapsize value

  1. c/cpp代码:
#include "cutils/properties.h"

property_set("dalvik.vm.heapsize", "256m");

char value[32];
property_get("dalvik.vm.heapsize", value, "");
  1. java代码:
import android.os.SystemProperties;

SystemProperties.set("dalvik.vm.heapsize","256m");
String value = SystemProperties.get("dalvik.vm.heapsize", "");

当然,大部分property是在编译制作ROM的时候,由编译系统将预置的property放到image的指定文件中去,然后开机时,init自动加载这些文件,并提供proterty的读写机制。这篇文章就来介绍下Build System是如何预制property到自己编译的ROM里的。

2. 预置property

上一篇推文中介绍了,lunch命令后,编译系统会根据目标平台的AndroidProducts.mk文件(一般在device/或vendor/目录下)找到所有需包含进行编译的makefile文件(一般是.mk文件),以我工作中接触的mstar平台代码为例,它们全在device/mstar/mangosteen/下,同时在这个目录下有很多.mk结尾的文件,认真分析它们之间include关系后会发现,这些全是lunch所选的平台编译所需要的。

在这些.mk文伯里,PRODUCT_PROPERTY_OVERRIDES,PRODUCT_DEFAULT_PROPERTY_OVERRIDES和PRODUCT_OEM_PROPERTIES就是和预置property相关的变量,通过设置这个变量,就能实现系统预置property,可以这样写:

PRODUCT_PROPERTY_OVERRIDES += persist.sys.country=CN

因为不同芯片厂商的差异性,可能您所看的Makefile的文件名字和写法会和我的有差异,但是原理肯定是一样的

那编译系统是如何来处理这个变量的呢?我们需要先来看看编译系统是如何处理property的。

2.1 PRODUCT_DEFAULT_PROPERTY_OVERRIDES

PRODUCT_DEFAULT_PROPERTY_OVERRIDES变量的值通过build/core/Makefile文件中下面这段代码写到image镜像中/default.prop文件中。

[build/core/Makefile]

INSTALLED_DEFAULT_PROP_TARGET := $(TARGET_ROOT_OUT)/default.prop
ADDITIONAL_DEFAULT_PROPERTIES := \
    $(call collapse-pairs, $(ADDITIONAL_DEFAULT_PROPERTIES))
ADDITIONAL_DEFAULT_PROPERTIES += \
    $(call collapse-pairs, $(PRODUCT_DEFAULT_PROPERTY_OVERRIDES))
ADDITIONAL_DEFAULT_PROPERTIES := $(call uniq-pairs-by-first-component, \
    $(ADDITIONAL_DEFAULT_PROPERTIES),=)

intermediate_system_build_prop := $(call intermediates-dir-for,ETC,system_build_prop)/build.prop

$(INSTALLED_DEFAULT_PROP_TARGET): $(intermediate_system_build_prop)
    @echo Target buildinfo: $@
    @mkdir -p $(dir $@)
    $(hide) echo "#" > $@; \
            echo "# ADDITIONAL_DEFAULT_PROPERTIES" >> $@; \
            echo "#" >> $@;
    $(hide) $(foreach line,$(ADDITIONAL_DEFAULT_PROPERTIES), \
        echo "$(line)" >> $@;)
    $(hide) echo "#" >> $@; \
            echo "# BOOTIMAGE_BUILD_PROPERTIES" >> $@; \
            echo "#" >> $@;
    $(hide) echo ro.bootimage.build.date=`date`>>$@
    $(hide) echo ro.bootimage.build.date.utc=`date +%s`>>$@
    $(hide) echo ro.bootimage.build.fingerprint="$(BUILD_FINGERPRINT)">>$@
    $(hide) build/tools/post_process_props.py $@
  • TARGET_ROOT_OUT在这里就是out/target/product/mangosteen/root/default.prop, 运行起来之后,在开发板上运行起来就是/default.prop文件。
  • 函数collapse-pairs定义在build/core/definitions.mk文件里,它的作用是将ADDITIONAL_DEFAULT_PROPERTIES和PRODUCT_DEFAULT_PROPERTY_OVERRIDES两个变量里的所有赋值语句’=’两端的空格去掉。
  • 变量ADDITIONAL_DEFAULT_PROPERTIES在make的过程中,根据我们的编译参数赋一些默认的property值,比如: ro.zygote=zygote64_32; 具体有哪些值,可以自行分析makefile
  • 这里的变量PRODUCT_DEFAULT_PROPERTY_OVERRIDES值是:

PRODUCT_DEFAULT_PROPERTY_OVERRIDES := 
$(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEFAULT_PROPERTY_OVERRIDES))

后面这个变量看着熟悉吗,在上一篇里说过,device/mstar/mangosteen/目录中定义的变量会被赋值给PRODUCTS.$(INTERNAL_PRODUCT).xxx这种形式的变量,不明白原因的可以回过头去看看上篇文章。所以上面代码中变量PRODUCT_DEFAULT_PROPERTY_OVERRIDES和上文提到的device/mstar/mangosteen/目录下.mk文件中的PRODUCT_DEFAULT_PROPERTY_OVERRIDES变量,虽然名字一样,实际上的值也一样,但其实并不是同一个东西,注意这里的理解。 
* 函数uniq-pairs-by-first-component定义在build/core/definitions.mk文件里,它的作用就是对ADDITIONAL_DEFAULT_PROPERTIES里的赋值语句去重,如果有发现对同一个property_name赋值多次,则只保留第一个值。 
* 除了ADDITIONAL_DEFAULT_PROPERTIES里所有的property值以外,还会写入ro.bootimage.build.date这些,标识系统的编译时间 
* 在最后会执行build/tools/post_process_props.py这个python脚本,它的作用是删除default.prop文件中指定的property(在函数的第二个参数中指定,没有则表明不指定);并且检查每个property名字和值的长度是否超过最大值,并且在这个python脚本中还可以根据需求再修改一次property值作为预置。

make命令执行完上面这段之后,文件out/target/product/${target_device}/root/default.prop就会创建成功,并且里面会有一些property值。

2.2 PRODUCT_PROPERTY_OVERRIDES

PRODUCT_PROPERTY_OVERRIDES和PRODUCT_DEFAULT_PROPERTY_OVERRIDES变量类似,不过它最终在被放到image镜像中的/system/build.prop文件中。我们来看看它的生成makefile。

[build/core/Makefile]

INSTALLED_BUILD_PROP_TARGET := $(TARGET_OUT)/build.prop
ADDITIONAL_BUILD_PROPERTIES := \
    $(call collapse-pairs, $(ADDITIONAL_BUILD_PROPERTIES))
ADDITIONAL_BUILD_PROPERTIES := $(call uniq-pairs-by-first-component, \
    $(ADDITIONAL_BUILD_PROPERTIES),=)

$(intermediate_system_build_prop): $(BUILDINFO_SH) $(INTERNAL_BUILD_ID_MAKEFILE) $(BUILD_SYSTEM)/version_defaults.mk $(system_prop_file) $(INSTALLED_ANDROID_INFO_TXT_TARGET)
    $(hide) echo > $@
ifneq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_OEM_PROPERTIES),)
    $(hide) echo "#" >> $@; \
            echo "# PRODUCT_OEM_PROPERTIES" >> $@; \
            echo "#" >> $@;
    $(hide) $(foreach prop,$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_OEM_PROPERTIES), \
        echo "import /oem/oem.prop $(prop)" >> $@;)
endif
    ... ...
            bash $(BUILDINFO_SH) >> $@
        $(hide) $(foreach file,$(system_prop_file), \
            if [ -f "$(file)" ]; then \
                echo "#" >> $@; \
                echo Target buildinfo from: "$(file)"; \
                echo "# from $(file)" >> $@; \
                echo "#" >> $@; \
                cat $(file) >> $@; \
            fi;)
        $(if $(ADDITIONAL_BUILD_PROPERTIES), \
            $(hide) echo >> $@; \
                    echo "#" >> $@; \
                    echo "# ADDITIONAL_BUILD_PROPERTIES" >> $@; \
                    echo "#" >> $@; )
        $(hide) $(foreach line,$(ADDITIONAL_BUILD_PROPERTIES), \
            echo "$(line)" >> $@;)
        $(hide) build/tools/post_process_props.py $@ $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SYSTEM_PROPERTY_BLACKLIST)
... ...

$(INSTALLED_BUILD_PROP_TARGET): $(intermediate_system_build_prop) $(INSTALLED_RECOVERYIMAGE_TARGET)
    @echo "Target build info: $@"
    $(hide) cat $(intermediate_system_build_prop) > $@                 

这段Makefile执行完成之后,build.prop文件就会在指定目录下生成,大概介绍一下生成过程:

  • system_prop_file变量代表的文件的内容会被复制到build.prop文件里去,system_prop_file代表的文件可以由TARGET_SYSTEM_PROP变量定义,也可以是device目录下的system.prop文件,在我这里是device/mstar/mangosteen/system.prop
  • BUILDINFO_SH = build/tools/buildinfo.sh脚本会输出很多property值给build.prop文件,这个脚本里很多值是从build/core/version_defaults.mk里获得的
  • ADDITIONAL_BUILD_PROPERTIES变量是build.prop文件内容的另一个主要来源之一,它的值由和上面的PRODUCT_DEFAULT_PROPERTY_OVERRIDES一样,由两部分组成,一部分是make时Makefile根据编译变量直接赋的值;另一部分就是device/mstar/mangosteen/目录下.mk文件中的 
    PRODUCT_PROPERTY_OVERRIDES变量
  • build.prop文件会先被放到out/target/product/${target_device}/obj/ETC/system_build_prop_intermediates里,然后再一起拷贝到out/target/product/${target_device}/system/目录下

2.3 PRODUCT_OEM_PROPERTIES

Android系统中还定义了PRODUCT_OEM_PROPERTIES这个娈量,不过这个变量的使用方法和上面两个不一样;因为这个变量指定的property才会被init从/oem/oem.prop文件中load进来。它的使用方法如下:

PRODUCT_OEM_PROPERTIES := ro.config.ringtone
  • 1

在2.2节中的代码里会发现,make的时候会遍历PRODUCT_OEM_PROPERTIES变量,并往build.prop中输入”import /oem/oem.prop $(prop)”, 这样init就会去/oem/oem.prop中找到指定的property属性对应的值,并加载到系统中。后面关于property属性读写实现会涉及到。

3. 总结

如果你只是想在你自己的ROM中添预置一些你的产品所需要的property,但是并不想了解Android系统中是如何实现property读写机制的,那看完这篇文章我相信你一定已经知道要怎么做了。不过在这里我还是要总结一下:

  1. Android编译系统会将所有ROM里预置的property键值对放到/default.prop和/system/build.prop文件里
  2. make的时候,会根据lunch所选择的目标平台和编译选项设置一部分需要预置的property键值对
  3. 在自己ROM平台的.mk文件里,通过设置PRODUCT_PROPERTY_OVERRIDES,PRODUCT_DEFAULT_PROPERTY_OVERRIDES和PRODUCT_OEM_PROPERTIES这三个变量的值,可以添加我们自己ROM需要的property键值对

这篇文章只是介绍了Build System是如何往自己的ROM中定制property键值对的,在下一篇文章里,会仔细讲一下Android系统是如何实现property的读写机制的。

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Android build system[三]

工作中,你一定会遇到需要往ROM中添加jar包的需求,今天介绍一下如何往ROM中添加一个新的jar包(以Android4.4和Android6.0为例)。

如果你是一个APP开发者,那么在apk中添加引用第三方的jar包对你来说一定是家常便饭;但是在ROM的开发中,如果需要添加一个公用的jar包,最好的办法就是把jar包放到rom中。

1. Android.mk

在AOSP(Android Open Source Project)中添加jar包,需要编写Android.mk

LOCAL_PATH := $(call my-dir)

# Build the Java library.
include $(CLEAR_VARS)

LOCAL_MODULE := com.abc.android
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := \
    $(call all-java-files-under, java)
LOCAL_JAVA_LIBRARIES += com.xyz.android

include $(BUILD_JAVA_LIBRARY)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 这段Makefile因为使用了include $(BUILD_JAVA_LIBRARY), 所以最后编译出来的就是jar包。
  • LOCAL_MODULE指定了编译出来的jar包的名字
  • LOCAL_JAVA_LIBRARIES指定了编译这个jar包时,需要引用和依赖的别的jar包

在编写完jar包的源代码和Android.mk之后,还需要保证这个Android.mk可以被编译到。可以查看这篇文章:Android build system[一]

2. 打包到system.img

在第一步之后,jar包可以被正常编译出来,但是你会发现,在你生成的system.img中,一定找不到com.abc.android.jar这个文件。原因就是Android编译系统并没有将这里编译出来的jar文件打包到 
system.img里。我们需要通过PRODUCT_PACKAGES指定(一般是在device/对应的product目录下,或者直接在build目录下添加)。

PRODUCT_PACKAGES += com.abc.android
  • 1

这样,编译之后,就能够在system.img里找到com.abc.android.jar文件了。

3. dex优化jar包

前两步之后,jar包被成功打包到system.img中,但是这时,那些使用了com.abc.android.jar的apk是无法正常在手机上使用的。因为虽然jar包已经在system分区了,但是它并没有被预先加载到虚拟机中,当使用这个jar 
包的apk启动的时候,找不到jar包里编译出来的二进制文件。通过修改PRODUCT_BOOT_JARS可以解决(同样是在device/对应的product目录下,或者直接在build目录下添加)。

PRODUCT_BOOT_JARS = com.abc.android
  • 1

PRODUCT_BOOT_JARS变量里的值在编译时,会先被加上’system/framework/’前缀放到PRODUCT_BOOTCLASSPATH变量中去,然后再把PRODUCT_BOOTCLASSPATH的值放到BOOTCLASSPATH中去。BOOTCLASSPATH又在init.rc里被设置成系统环境变量。

export BOOTCLASSPATH /system/framework/core.jar:/system/framework/conscrypt.jar: ... :/system/framework/com.abc.androidd.jar
  • 1

在Android系统开机时Zygote启动dalvik时,会读BOOTCLASSPATH这个环境变量,然后把这里所有的jar包进行dexopt操作,变成虚拟机可以直接运行的格式。

到这里,就可以正常运行使用这个新加的jar包的app了.



猜你喜欢

转载自blog.csdn.net/weixin_38503885/article/details/80756069