Image打包流程-Android10.0编译系统(四)

[Android取经之路] 的源码都基于Android-Q(10.0) 进行分析

[Android取经之路] 系列文章:

《系统启动篇》

Android系统架构
Android是怎么启动的
Android 10.0系统启动之init进程
Android10.0系统启动之Zygote进程
Android 10.0 系统启动之SystemServer进程
Android 10.0 系统服务之ActivityMnagerService
Android10.0系统启动之Launcher(桌面)启动流程
Android10.0应用进程创建过程以及Zygote的fork流程
Android 10.0 PackageManagerService(一)工作原理及启动流程
Android 10.0 PackageManagerService(二)权限扫描
Android 10.0 PackageManagerService(三)APK扫描
Android 10.0 PackageManagerService(四)APK安装流程
《日志系统篇》

Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性
Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化
Android10.0 日志系统分析(三)-logd、logcat读写日志源码分析
Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现​
《Binder通信原理》:

Android10.0 Binder通信原理(一)Binder、HwBinder、VndBinder概要
Android10.0 Binder通信原理(二)-Binder入门篇
Android10.0 Binder通信原理(三)-ServiceManager篇
Android10.0 Binder通信原理(四)-Native-C\C++实例分析
Android10.0 Binder通信原理(五)-Binder驱动分析
Android10.0 Binder通信原理(六)-Binder数据如何完成定向打击
Android10.0 Binder通信原理(七)-Framework binder示例
Android10.0 Binder通信原理(八)-Framework层分析
Android10.0 Binder通信原理(九)-AIDL Binder示例
Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub设计模式
Android10.0 Binder通信原理(十一)-Binder总结

《HwBinder通信原理》

HwBinder入门篇-Android10.0 HwBinder通信原理(一)
 HIDL详解-Android10.0 HwBinder通信原理(二)
HIDL示例-C++服务创建Client验证-Android10.0 HwBinder通信原理(三)
HIDL示例-JAVA服务创建-Client验证-Android10.0 HwBinder通信原理(四)
HwServiceManager篇-Android10.0 HwBinder通信原理(五)
Native层HIDL服务的注册原理-Android10.0 HwBinder通信原理(六)
Native层HIDL服务的获取原理-Android10.0 HwBinder通信原理(七)
JAVA层HIDL服务的注册原理-Android10.0 HwBinder通信原理(八)
JAVA层HIDL服务的获取原理-Android10.0 HwBinder通信原理(九)
HwBinder驱动篇-Android10.0 HwBinder通信原理(十)
HwBinder原理总结-Android10.0 HwBinder通信原理(十一)
《编译原理》

编译系统入门篇-Android10.0编译系统(一)
编译环境初始化-Android10.0编译系统(二)
make编译过程-Android10.0编译系统(三)
Image打包流程-Android10.0编译系统(四
Kati详解-Android10.0编译系统(五)
Blueprint简介-Android10.0编译系统(六)
Blueprint代码详细分析-Android10.0编译系统(七)
Android.bp 语法浅析-Android10.0编译系统(八)
Ninja简介-Android10.0编译系统(九)
Ninja提升编译速度的方法-Android10.0编译系统(十)
Android10.0编译系统(十一)

1 概述
  前面我们讲完了Android10.0 编译的初始化和make的完整流程,从make中我们看到了,最终编译会生成system.img、super.img、ramdisk.img等镜像文件,我们把这些镜像文件烧录到手机中,即可完成版本的替换升级。

  这一节我们来一起看看这些image是如何打包生成的

 

2 image打包入口
  在上一节的main.mk中,最后两步定义了需要编译的image和构建一个rom的过程.

  image构建和打包的一些依赖关系如下图所示:

[build/make/core/main.mk]
       ...
.PHONY: ramdisk
ramdisk: $(INSTALLED_RAMDISK_TARGET)
 
.PHONY: userdataimage
userdataimage: $(INSTALLED_USERDATAIMAGE_TARGET)
DATATARBALL_TARGET)
 
.PHONY: cacheimage
cacheimage: $(INSTALLED_CACHEIMAGE_TARGET)
 
.PHONY: odmimage
odmimage: $(INSTALLED_ODMIMAGE_TARGET)
 
.PHONY: systemotherimage
systemotherimage: $(INSTALLED_SYSTEMOTHERIMAGE_TARGET)
 
.PHONY: superimage_empty
superimage_empty: $(INSTALLED_SUPERIMAGE_EMPTY_TARGET)
 
.PHONY: bootimage
bootimage: $(INSTALLED_BOOTIMAGE_TARGET)
 
.PHONY: bootimage_debug
bootimage_debug: $(INSTALLED_DEBUG_BOOTIMAGE_TARGET)
 
...
 
.PHONY: droidcore
droidcore: $(filter $(HOST_OUT_ROOT)/%,$(modules_to_install)) \
    $(INSTALLED_SYSTEMIMAGE_TARGET) \
    $(INSTALLED_RAMDISK_TARGET) \
    $(INSTALLED_BOOTIMAGE_TARGET) \
    $(INSTALLED_DEBUG_RAMDISK_TARGET) \
    $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) \
    $(INSTALLED_RECOVERYIMAGE_TARGET) \
    $(INSTALLED_VBMETAIMAGE_TARGET) \
    $(INSTALLED_USERDATAIMAGE_TARGET) \
    $(INSTALLED_CACHEIMAGE_TARGET) \
    $(INSTALLED_BPTIMAGE_TARGET) \
    $(INSTALLED_VENDORIMAGE_TARGET) \
    $(INSTALLED_ODMIMAGE_TARGET) \
    $(INSTALLED_SUPERIMAGE_EMPTY_TARGET) \
    ...\
    auxiliary \
    soong_docs
...

main.mk中只是做了一些定义和启动编译流程,正在的image打包在build/core/Makefile中完成

[build/make/core/main.mk]
ifdef FULL_BUILD
  ...
 
# TODO: Remove the 3 places in the tree that use ALL_DEFAULT_INSTALLED_MODULES
# and get rid of it from this list.
modules_to_install := $(sort \
    $(ALL_DEFAULT_INSTALLED_MODULES) \
    $(product_target_FILES) \
    $(product_host_FILES) \
    $(call get-tagged-modules,$(tags_to_install)) \
    $(CUSTOM_MODULES) \
  )
...
# build/make/core/Makefile contains extra stuff that we don't want to pollute this
# top-level makefile with.  It expects that ALL_DEFAULT_INSTALLED_MODULES
# contains everything that's built during the current make, but it also further
# extends ALL_DEFAULT_INSTALLED_MODULES.
ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install)
include $(BUILD_SYSTEM)/Makefile
modules_to_install := $(sort $(ALL_DEFAULT_INSTALLED_MODULES))
ALL_DEFAULT_INSTALLED_MODULES :=
 
...
#endif
在build/core/Makefile中定义了很多image的生成规则,例如:system.img,boot.img,recovery.img,vendor.img,super.img,下面我们就以system.img为例,详细的来看看image的具体打包细节。
[build/core/Makefile]
...
.PHONY: systemimage
.PHONY: event-log-tags
.PHONY: ramdisk-nodeps
.PHONY: bootimage-nodeps
.PHONY: bootimage-nodeps
.PHONY: bootimage-nodeps
.PHONY: bootimage-nodeps
.PHONY: notice_files
.PHONY: otacerts
.PHONY: recoveryimage-nodeps
.PHONY: recoveryimage
.PHONY: ramdisk_debug-nodeps
.PHONY: bootimage_debug-nodeps
.PHONY: installed-file-list
.PHONY: systemimage-nodeps snod
.PHONY: sync syncsys
.PHONY: systemtarball-nodeps
.PHONY: stnod
.PHONY: platform
.PHONY: platform-java
.PHONY: boottarball-nodeps btnod
.PHONY: userdataimage-nodeps
.PHONY: userdatatarball-nodeps
.PHONY: bptimage-nodeps
.PHONY: cacheimage-nodeps
.PHONY: systemotherimage-nodeps
.PHONY: vendorimage-nodeps vnod
.PHONY: productimage-nodeps pnod
.PHONY: productservicesimage-nodeps psnod
.PHONY: odmimage-nodeps onod
.PHONY: vbmetaimage-nodeps
.PHONY: otatools
.PHONY: otatools-package
.PHONY: target-files-package
.PHONY: otapackage
.PHONY: otardppackage
.PHONY: superimage_dist
.PHONY: superimage
.PHONY: superimage-nodeps supernod
...

3.systemimage 打包

  system.img打包的是system分区中的文件,相关打包内容如下:

# Rules that need to be present for the all targets, even
# if they don't do anything.
.PHONY: systemimage
systemimage:
 
...
INSTALLED_SYSTEMIMAGE_TARGET := $(PRODUCT_OUT)/system.img
SYSTEMIMAGE_SOURCE_DIR := $(TARGET_OUT)
 
$(INSTALLED_SYSTEMIMAGE_TARGET): $(BUILT_SYSTEMIMAGE) $(RECOVERY_FROM_BOOT_PATCH)
       @echo "Install system fs image: $@"
       $(copy-file-to-target)
       $(hide) $(call assert-max-image-size,$@ $(RECOVERY_FROM_BOOT_PATCH),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))
 
systemimage: $(INSTALLED_SYSTEMIMAGE_TARGET)
 
.PHONY: systemimage-nodeps snod
systemimage-nodeps snod: $(filter-out systemimage-nodeps snod,$(MAKECMDGOALS)) \
                   | $(INTERNAL_USERIMAGES_DEPS)
       @echo "make $@: ignoring dependencies"
       $(call build-systemimage-target,$(INSTALLED_SYSTEMIMAGE_TARGET))
       $(hide) $(call assert-max-image-size,$(INSTALLED_SYSTEMIMAGE_TARGET),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))
...

关于system.img,这里定义了两个伪目标systemimage 和 systemimage-nodeps。

systemimage表示在打包system.img之前,要根据依赖规则重新生成所有要进行打包的文件。

systemimage-nodeps则不需要根据依赖规则重新生成所有需要打包的文件而直接打包system.img文件。

systemimage 依赖于$(INSTALLED_SYSTEMIMAGE_TARGET)。

 

3.1 INSTALLED_SYSTEMIMAGE_TARGET
  从上面的代码看到,systemimage 依赖于INSTALLED_SYSTEMIMAGE_TARGET,最终生成目标文件

$(PRODUCT_OUT)/system.img
$(INSTALLED_SYSTEMIMAGE_TARGET): $(BUILT_SYSTEMIMAGE) $(RECOVERY_FROM_BOOT_PATCH)
       @echo "Install system fs image: $@"
       $(copy-file-to-target)
       $(hide) $(call assert-max-image-size,$@ $(RECOVERY_FROM_BOOT_PATCH),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))

其中,INSTALLED_SYSTEMIMAGE_TARGET依赖于BUILT_SYSTEMIMAGE和RECOVERY_FROM_BOOT_PATCH,再调用了函数copy-file-to-target进行文件拷贝。

 

3.1.1  copy-file-to-target
  copy-file-to-target 在/build/make/core/definitions.mk 中被定义,主要用于是拷贝文件,并且在拷贝的过程中会保留文件的权限和覆盖已有的文件。  它会创建/out/target/product/xxx 目录, xxx表示产品的名称,然后把文件拷贝到该目录中
 

[/build/make/core/definitions.mk]
# Copy a single file from one place to another,
# preserving permissions and overwriting any existing
# file.
# When we used acp, it could not handle high resolution timestamps
# on file systems like ext4. Because of that, '-t' option was disabled
# and copy-file-to-target was identical to copy-file-to-new-target.
# Keep the behavior until we audit and ensure that switching this back
# won't break anything.
define copy-file-to-target
@mkdir -p $(dir $@)
$(hide) rm -f $@
$(hide) cp "$<" "$@"
endef

3.1.2 RECOVERY_FROM_BOOT_PATCH

  RECOVERY_FROM_BOOT_PATCH 描述的是一个patch文件,依赖规则如下所示:

ifneq (,$(filter true, $(BOARD_BUILD_SYSTEM_ROOT_IMAGE) $(BOARD_INCLUDE_RECOVERY_DTBO) $(BOARD_INCLUDE_RECOVERY_ACPIO)))
diff_tool := $(HOST_OUT_EXECUTABLES)/bsdiff
else
diff_tool := $(HOST_OUT_EXECUTABLES)/imgdiff
endif
intermediates := $(call intermediates-dir-for,PACKAGING,recovery_patch)
RECOVERY_FROM_BOOT_PATCH := $(intermediates)/recovery_from_boot.p
$(RECOVERY_FROM_BOOT_PATCH): PRIVATE_DIFF_TOOL := $(diff_tool)
$(RECOVERY_FROM_BOOT_PATCH): \
           $(INSTALLED_RECOVERYIMAGE_TARGET) \
           $(INSTALLED_BOOTIMAGE_TARGET) \
           $(diff_tool)
       @echo "Construct recovery from boot"
       mkdir -p $(dir $@)
       $(PRIVATE_DIFF_TOOL) $(INSTALLED_BOOTIMAGE_TARGET) $(INSTALLED_RECOVERYIMAGE_TARGET) $@

RECOVERY_FROM_BOOT_PATCH 依赖的patch文件为:$(intermediates)/recovery_from_boot.p,表示的是recovery.img和boot.img之间的差异,存在于system分区中,可以通过boot.img和recovery_from_boot.p构造一个recovery.img。

 

3.1.3 BUILT_SYSTEMIMAGE
 BUILT_SYSTEMIMAGE 最终会把system.img编译到 out/target/product/generic/obj/PACKAGING/systemimage_intermediates/system.img中。BUILT_SYSTEMIMAGE 依赖于FULL_SYSTEMIMAGE_DEPS、INSTALLED_FILES_FILE和BUILD_IMAGE_SRCS,通过调用 函数build-systemimage-target 来编译systemimage,相关依赖如下所示:
 

systemimage_intermediates := \
    $(call intermediates-dir-for,PACKAGING,systemimage)
BUILT_SYSTEMIMAGE := $(systemimage_intermediates)/system.img
 
$(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE) $(BUILD_IMAGE_SRCS)
       $(call build-systemimage-target,$@)

BUILD_IMAGE_SRCS 在[/build/make/core/config.mk] 中定义,配置了build/make/tools/releasetools中的python脚本参与编译

BUILD_IMAGE_SRCS := $(wildcard build/make/tools/releasetools/*.py)
 INSTALLED_FILES_FILE 依赖的是文件$(PRODUCT_OUT)/installed-files.txt,这是已安装的文件列表,这些文件要打包到system.img中,他也依赖于FULL_SYSTEMIMAGE_DEPS

 INSTALLED_FILES_FILE 的依赖描述如下所示:

# installed file list
# Depending on anything that $(BUILT_SYSTEMIMAGE) depends on.
# We put installed-files.txt ahead of image itself in the dependency graph
# so that we can get the size stat even if the build fails due to too large
# system image.
INSTALLED_FILES_FILE := $(PRODUCT_OUT)/installed-files.txt
INSTALLED_FILES_JSON := $(INSTALLED_FILES_FILE:.txt=.json)
$(INSTALLED_FILES_FILE): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON)
$(INSTALLED_FILES_FILE): $(FULL_SYSTEMIMAGE_DEPS) $(FILESLIST)
       @echo Installed file list: $@
       @mkdir -p $(dir $@)
       @rm -f $@
       $(hide) $(FILESLIST) $(TARGET_OUT) > $(@:.txt=.json)
       $(hide) build/make/tools/fileslist_util.py -c $(@:.txt=.json) > $@

 FULL_SYSTEMIMAGE_DEPS依赖于INTERNAL_SYSTEMIMAGE_FILES 和INTERNAL_USERIMAGES_DEPS,列出了制作system.img所需要的工具和制作system.img所需要的文件。

INTERNAL_USERIMAGES_DEPS := $(SIMG2IMG)
INTERNAL_USERIMAGES_DEPS += $(MKEXTUSERIMG) $(MAKE_EXT4FS) $(E2FSCK) $(TUNE2FS)
ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
INTERNAL_USERIMAGES_DEPS += $(MKF2FSUSERIMG) $(MAKE_F2FS)
endif
...
INTERNAL_SYSTEMIMAGE_FILES := $(sort $(filter $(TARGET_OUT)/%, \
    $(ALL_GENERATED_SOURCES) \
    $(ALL_DEFAULT_INSTALLED_MODULES) \
    $(PDK_FUSION_SYSIMG_FILES) \
    $(RECOVERY_RESOURCE_ZIP)) \
    $(PDK_FUSION_SYMLINK_STAMP))
 
FULL_SYSTEMIMAGE_DEPS := $(INTERNAL_SYSTEMIMAGE_FILES) $(INTERNAL_USERIMAGES_DEPS)

INTERNAL_USERIMAGES_DEPS:列出了制作system.img所需要的工具,例如out/host/linux-x86/bin/simg2img、out/host/linux-x86/bin/mkuserimg_mke2fs 等,如果支持f2fs的文件系统,会加载out/host/linux-x86/bin/make_f2fs

INTERNAL_SYSTEMIMAGE_FILES:列出了制作system.img所需要的文件,释义如下:

ALL_GENERATED_SOURCES:描述的是要拷贝到目标设备上去的由工具自动生成的源代码文件。

ALL_DEFAULT_INSTALLED_MODULES:描述的是所有需要安装的module

PDK_FUSION_SYSIMG_FILES:是从PDK(Platform Development Kit)提取出来的相关文件

RECOVERY_RESOURCE_ZIP:描述的是Android的recovery系统要使用的资源文件,对应于/system/etc目录下的recovery-resource.dat文件。

PDK_FUSION_SYMLINK_STAMP:PDK的符号链接文件

 

3.1.4 build-systemimage-target
  BUILT_SYSTEMIMAGE 通过调用函数build-systemimage-target 来生成img。

  首先创进行了一些vendor\product\product_service的link操作,然后创建一个out/target/product/generic/obj/PACKAGING/systemimage_intermediates/ 目录,并先删除system_image_info.txt文件,接着调用generate-image-prop-dictionary,生成system.img的信息,保存到system_image_info.txt中。

  最后调用build/make/tools/releasetools/build_image.py来生成system.img。

  build-systemimage-target的定义如下所示:

define build-systemimage-target
  @echo "Target system fs image: $(1)"
  $(call create-system-vendor-symlink)
  $(call create-system-product-symlink)
  $(call create-system-product_services-symlink)
  $(call check-apex-libs-absence-on-disk)
  @mkdir -p $(dir $(1)) $(systemimage_intermediates) && rm -rf $(systemimage_intermediates)/system_image_info.txt
  $(call generate-image-prop-dictionary, $(systemimage_intermediates)/system_image_info.txt,system, \
      skip_fsck=true)
  $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH \
      build/make/tools/releasetools/build_image.py \
      $(TARGET_OUT) $(systemimage_intermediates)/system_image_info.txt $(1) $(TARGET_OUT) \
      || ( mkdir -p $${DIST_DIR}; cp $(INSTALLED_FILES_FILE) $${DIST_DIR}/installed-files-rescued.txt; \
           exit 1 )
endef

3.1.4.1 create-system-vendor-symlink

  如果存在vendor目录,就给vendor目录创建一个软连接。即/system/vendor 目录会被link到/vendor目录。

define create-system-vendor-symlink
$(hide) if [ -d $(TARGET_OUT)/vendor ] && [ ! -h $(TARGET_OUT)/vendor ]; then \
  echo 'Non-symlink $(TARGET_OUT)/vendor detected!' 1>&2; \
  echo 'You cannot install files to $(TARGET_OUT)/vendor while building a separate vendor.img!' 1>&2; \
  exit 1; \
fi

3.1.4.2 create-system-product-symlink

  如果存在product目录,就给product目录创建一个软连接。即/system/product 目录会被link到/product目录。

define create-system-product-symlink
$(hide) if [ -d $(TARGET_OUT)/product ] && [ ! -h $(TARGET_OUT)/product ]; then \
  echo 'Non-symlink $(TARGET_OUT)/product detected!' 1>&2; \
  echo 'You cannot install files to $(TARGET_OUT)/product while building a separate product.img!' 1>&2; \
  exit 1; \
fi

3.1.4.3 create-system-product_services-symlink
  如果存在product_services目录,就给product_services目录创建一个软连接。即/system/product_services 目录会被link到/product_services目录。
 

define create-system-product_services-symlink
$(hide) if [ -d $(TARGET_OUT)/product_services ] && [ ! -h $(TARGET_OUT)/product_services ]; then \
  echo 'Non-symlink $(TARGET_OUT)/product_services detected!' 1>&2; \
  echo 'You cannot install files to $(TARGET_OUT)/product_services while building a separate product_services.img!' 1>&2; \
  exit 1; \
fi

3.2 build_image.py编译system.img

 $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH \
      build/make/tools/releasetools/build_image.py \
      $(TARGET_OUT) $(systemimage_intermediates)/system_image_info.txt $(1) $(TARGET_OUT) \
      || ( mkdir -p $${DIST_DIR}; cp $(INSTALLED_FILES_FILE) $${DIST_DIR}/installed-files-rescued.txt; \
           exit 1 )

执行build_image.py时,传入了4个参数:

$(TARGET_OUT) $(systemimage_intermediates)/system_image_info.txt $(1) $(TARGET_OUT)

$(TARGET_OUT)  :对应目录out/target/product/generic/system

$(systemimage_intermediates)/system_image_info.txt :system.img的配置文件

 

3.2.1 main()
  首先进行参数检查,如果参数个数小于4,直接退出,如果要生成system.img,mount指向system,调用ImagePropFromGlobalDict()来获取image的参数,再调用BuildImage()进行image的编译。

def main(argv):
  if len(argv) != 4:
    print(__doc__)
    sys.exit(1)
 
  common.InitLogging()
 
  in_dir = argv[0]
  glob_dict_file = argv[1]
  out_file = argv[2]
  target_out = argv[3]
 
  glob_dict = LoadGlobalDict(glob_dict_file)
  if "mount_point" in glob_dict:
    # The caller knows the mount point and provides a dictionary needed by
    # BuildImage().
    image_properties = glob_dict
  else:
    image_filename = os.path.basename(out_file)
    mount_point = ""
    if image_filename == "system.img":
      mount_point = "system"
    elif image_filename == "system_other.img":
      mount_point = "system_other"
    elif image_filename == "userdata.img":
      mount_point = "data"
    elif image_filename == "cache.img":
      mount_point = "cache"
    elif image_filename == "vendor.img":
      mount_point = "vendor"
    elif image_filename == "odm.img":
      mount_point = "odm"
    elif image_filename == "oem.img":
      mount_point = "oem"
    elif image_filename == "product.img":
      mount_point = "product"
    elif image_filename == "product_services.img":
      mount_point = "product_services"
    else:
      logger.error("Unknown image file name %s", image_filename)
      sys.exit(1)
 
    image_properties = ImagePropFromGlobalDict(glob_dict, mount_point)
 
  try:
    BuildImage(in_dir, image_properties, out_file, target_out)
  except:
    logger.error("Failed to build %s from %s", out_file, in_dir)
    raise

3.2.2 ImagePropFromGlobalDict()

  传入的mount_point为system,把类似system_reserved_size这些参数,存入system_image_info.txt中。

def ImagePropFromGlobalDict(glob_dict, mount_point):
  """Build an image property dictionary from the global dictionary.
  Args:
    glob_dict: the global dictionary from the build system.
    mount_point: such as "system", "data" etc.
  """
  d = {}
 
  if "build.prop" in glob_dict:
    bp = glob_dict["build.prop"]
    if "ro.build.date.utc" in bp:
      d["timestamp"] = bp["ro.build.date.utc"]
 
  def copy_prop(src_p, dest_p):
    """Copy a property from the global dictionary.
    Args:
      src_p: The source property in the global dictionary.
      dest_p: The destination property.
    Returns:
      True if property was found and copied, False otherwise.
    """
    if src_p in glob_dict:
      d[dest_p] = str(glob_dict[src_p])
      return True
    return False
 
  common_props = (
      "extfs_sparse_flag",
      "squashfs_sparse_flag",
      "selinux_fc",
      "skip_fsck",
      "ext_mkuserimg",
      "verity",
      "verity_key",
      "verity_signer_cmd",
      "verity_fec",
      "verity_disable",
      "avb_enable",
      "avb_avbtool",
      "avb_salt",
      "use_dynamic_partition_size",
  )
  for p in common_props:
    copy_prop(p, p)
 
  d["mount_point"] = mount_point
  if mount_point == "system":
    copy_prop("avb_system_hashtree_enable", "avb_hashtree_enable")
    copy_prop("avb_system_add_hashtree_footer_args",
              "avb_add_hashtree_footer_args")
    copy_prop("avb_system_key_path", "avb_key_path")
    copy_prop("avb_system_algorithm", "avb_algorithm")
    copy_prop("fs_type", "fs_type")
    # Copy the generic system fs type first, override with specific one if
    # available.
    copy_prop("system_fs_type", "fs_type")
    copy_prop("system_headroom", "partition_headroom")
    copy_prop("system_size", "partition_size")
    if not copy_prop("system_journal_size", "journal_size"):
      d["journal_size"] = "0"
    copy_prop("system_verity_block_device", "verity_block_device")
    copy_prop("system_root_image", "system_root_image")
    copy_prop("root_dir", "root_dir")
    copy_prop("root_fs_config", "root_fs_config")
    copy_prop("ext4_share_dup_blocks", "ext4_share_dup_blocks")
    copy_prop("system_squashfs_compressor", "squashfs_compressor")
    copy_prop("system_squashfs_compressor_opt", "squashfs_compressor_opt")
    copy_prop("system_squashfs_block_size", "squashfs_block_size")
    copy_prop("system_squashfs_disable_4k_align", "squashfs_disable_4k_align")
    copy_prop("system_base_fs_file", "base_fs_file")
    copy_prop("system_extfs_inode_count", "extfs_inode_count")
    if not copy_prop("system_extfs_rsv_pct", "extfs_rsv_pct"):
      d["extfs_rsv_pct"] = "0"
    copy_prop("system_reserved_size", "partition_reserved_size")
  elif mount_point == "system_other":
    ...
  elif mount_point == "data":
    ...
  elif mount_point == "cache":
    ...
  elif mount_point == "vendor":
    ...
  elif mount_point == "product":
    ...
  elif mount_point == "product_services":
    ...
  elif mount_point == "odm":
    ...
  elif mount_point == "oem":
    ...
  d["partition_name"] = mount_point
  return d

3.2.3 BuildImage()

  上面得到system.img的参数后,接下来执行image的编译。

def BuildImage(in_dir, prop_dict, out_file, target_out=None):
  """Builds an image for the files under in_dir and writes it to out_file.
 
  Args:
    in_dir: Path to input directory.
    prop_dict: A property dict that contains info like partition size. Values
        will be updated with computed values.
    out_file: The output image file.
    target_out: Path to the TARGET_OUT directory as in Makefile. It actually
        points to the /system directory under PRODUCT_OUT. fs_config (the one
        under system/core/libcutils) reads device specific FS config files from
        there.
 
  Raises:
    BuildImageError: On build image failures.
  """
  in_dir, fs_config = SetUpInDirAndFsConfig(in_dir, prop_dict)
 
  build_command = []
  fs_type = prop_dict.get("fs_type", "")
 
  fs_spans_partition = True
  if fs_type.startswith("squash"):
    fs_spans_partition = False
  # Get a builder for creating an image that's to be verified by Verified Boot,
  # or None if not applicable.
  verity_image_builder = verity_utils.CreateVerityImageBuilder(prop_dict)
 
  if (prop_dict.get("use_dynamic_partition_size") == "true" and
      "partition_size" not in prop_dict):
    # If partition_size is not defined, use output of `du' + reserved_size.
    size = GetDiskUsage(in_dir)
    logger.info(
        "The tree size of %s is %d MB.", in_dir, size // BYTES_IN_MB)
    # If not specified, give us 16MB margin for GetDiskUsage error ...
    reserved_size = int(prop_dict.get("partition_reserved_size", BYTES_IN_MB * 16))
    partition_headroom = int(prop_dict.get("partition_headroom", 0))
    if fs_type.startswith("ext4") and partition_headroom > reserved_size:
      reserved_size = partition_headroom
    size += reserved_size
    # Round this up to a multiple of 4K so that avbtool works
    size = common.RoundUpTo4K(size)
    if fs_type.startswith("ext"):
      prop_dict["partition_size"] = str(size)
      prop_dict["image_size"] = str(size)
      if "extfs_inode_count" not in prop_dict:
        prop_dict["extfs_inode_count"] = str(GetInodeUsage(in_dir))
      logger.info(
          "First Pass based on estimates of %d MB and %s inodes.",
          size // BYTES_IN_MB, prop_dict["extfs_inode_count"])
      BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config)
      sparse_image = False
      if "extfs_sparse_flag" in prop_dict:
        sparse_image = True
      fs_dict = GetFilesystemCharacteristics(out_file, sparse_image)
      os.remove(out_file)
      block_size = int(fs_dict.get("Block size", "4096"))
      free_size = int(fs_dict.get("Free blocks", "0")) * block_size
      reserved_size = int(prop_dict.get("partition_reserved_size", 0))
      partition_headroom = int(fs_dict.get("partition_headroom", 0))
      if fs_type.startswith("ext4") and partition_headroom > reserved_size:
        reserved_size = partition_headroom
      if free_size <= reserved_size:
        logger.info(
            "Not worth reducing image %d <= %d.", free_size, reserved_size)
      else:
        size -= free_size
        size += reserved_size
        if reserved_size == 0:
          # add .3% margin
          size = size * 1003 // 1000
        # Use a minimum size, otherwise we will fail to calculate an AVB footer
        # or fail to construct an ext4 image.
        size = max(size, 256 * 1024)
        if block_size <= 4096:
          size = common.RoundUpTo4K(size)
        else:
          size = ((size + block_size - 1) // block_size) * block_size
      extfs_inode_count = prop_dict["extfs_inode_count"]
      inodes = int(fs_dict.get("Inode count", extfs_inode_count))
      inodes -= int(fs_dict.get("Free inodes", "0"))
      # add .2% margin or 1 inode, whichever is greater
      spare_inodes = inodes * 2 // 1000
      min_spare_inodes = 1
      if spare_inodes < min_spare_inodes:
        spare_inodes = min_spare_inodes
      inodes += spare_inodes
      prop_dict["extfs_inode_count"] = str(inodes)
      prop_dict["partition_size"] = str(size)
      logger.info(
          "Allocating %d Inodes for %s.", inodes, out_file)
    if verity_image_builder:
      size = verity_image_builder.CalculateDynamicPartitionSize(size)
    prop_dict["partition_size"] = str(size)
    logger.info(
        "Allocating %d MB for %s.", size // BYTES_IN_MB, out_file)
 
  prop_dict["image_size"] = prop_dict["partition_size"]
  # Adjust the image size to make room for the hashes if this is to be verified.
  if verity_image_builder:
    max_image_size = verity_image_builder.CalculateMaxImageSize()
    prop_dict["image_size"] = str(max_image_size)
 
  mkfs_output = BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config)
  # Check if there's enough headroom space available for ext4 image.
  if "partition_headroom" in prop_dict and fs_type.startswith("ext4"):
    CheckHeadroom(mkfs_output, prop_dict)
 
  if not fs_spans_partition and verity_image_builder:
    verity_image_builder.PadSparseImage(out_file)
  # Create the verified image if this is to be verified.
  if verity_image_builder:
    verity_image_builder.Build(out_file)

3.2.4 BuildImageMkfs()

  Android10.0 AOSP的system_image_info.txt中,几个变量如下:

 
  1. ext_mkuserimg=mkuserimg_mke2fs

  2. fs_type=ext4

  执行以下命令进行image的打包:

out/host/linux-x86/bin/mkuserimg_mke2fs '/out/soong/.temp/tmpK__WLx' 'out/target/product/generic/obj/PACKAGING/systemimage_intermediates/system.img' 'ext4' '/' '985493504' -j 0 -D 'out/target/product/generic/system' -L '/' -i 3280 -M 0 -c --inode_size 256 out/target/product/generic/obj/ETC/file_contexts.bin_intermediates/file_contexts.bin
def BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config):
  ...
  build_command = []
  fs_type = prop_dict.get("fs_type", "")
  run_e2fsck = False
 
  if fs_type.startswith("ext"):
    build_command = [prop_dict["ext_mkuserimg"]]
    if "extfs_sparse_flag" in prop_dict:
      build_command.append(prop_dict["extfs_sparse_flag"])
      run_e2fsck = True
    build_command.extend([in_dir, out_file, fs_type,
                          prop_dict["mount_point"]])
    build_command.append(prop_dict["image_size"])
    if "journal_size" in prop_dict:
      build_command.extend(["-j", prop_dict["journal_size"]])
    if "timestamp" in prop_dict:
      build_command.extend(["-T", str(prop_dict["timestamp"])])
    if fs_config:
      build_command.extend(["-C", fs_config])
    if target_out:
      build_command.extend(["-D", target_out])
    if "block_list" in prop_dict:
      build_command.extend(["-B", prop_dict["block_list"]])
    if "base_fs_file" in prop_dict:
      base_fs_file = ConvertBlockMapToBaseFs(prop_dict["base_fs_file"])
      build_command.extend(["-d", base_fs_file])
    build_command.extend(["-L", prop_dict["mount_point"]])
    if "extfs_inode_count" in prop_dict:
      build_command.extend(["-i", prop_dict["extfs_inode_count"]])
    if "extfs_rsv_pct" in prop_dict:
      build_command.extend(["-M", prop_dict["extfs_rsv_pct"]])
    if "flash_erase_block_size" in prop_dict:
      build_command.extend(["-e", prop_dict["flash_erase_block_size"]])
    if "flash_logical_block_size" in prop_dict:
      build_command.extend(["-o", prop_dict["flash_logical_block_size"]])
    # Specify UUID and hash_seed if using mke2fs.
    if prop_dict["ext_mkuserimg"] == "mkuserimg_mke2fs":
      if "uuid" in prop_dict:
        build_command.extend(["-U", prop_dict["uuid"]])
      if "hash_seed" in prop_dict:
        build_command.extend(["-S", prop_dict["hash_seed"]])
    if "ext4_share_dup_blocks" in prop_dict:
      build_command.append("-c")
    build_command.extend(["--inode_size", "256"])
    if "selinux_fc" in prop_dict:
      build_command.append(prop_dict["selinux_fc"])
  elif fs_type.startswith("squash"):
    ...
  elif fs_type.startswith("f2fs"):
    ...
  else:
    raise BuildImageError(
        "Error: unknown filesystem type: {}".format(fs_type))
 
  try:
    mkfs_output = common.RunAndCheckOutput(build_command)
  except:
    try:
      du = GetDiskUsage(in_dir)
      du_str = "{} bytes ({} MB)".format(du, du // BYTES_IN_MB)
    # Suppress any errors from GetDiskUsage() to avoid hiding the real errors
    # from common.RunAndCheckOutput().
    except Exception:  # pylint: disable=broad-except
      logger.exception("Failed to compute disk usage with du")
      du_str = "unknown"
    ...
    raise
 
  if run_e2fsck and prop_dict.get("skip_fsck") != "true":
    unsparse_image = UnsparseImage(out_file, replace=False)
 
    # Run e2fsck on the inflated image file
    e2fsck_command = ["e2fsck", "-f", "-n", unsparse_image]
    try:
      common.RunAndCheckOutput(e2fsck_command)
    finally:
      os.remove(unsparse_image)
 
  return mkfs_output

6.总结

 至此,Android10.0中的image打包过程基本上理清了,过程比较绕,相互依赖比较多,逐层解开后,就比较好理解了。

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我会尽力回答你的问题。以下是一个超简单配置Android持续集成自动化打包流程 - GitHub+GitLab-CI+蒲公英+钉钉 的步骤: 1. 在 GitHub 创建一个新的 repository,将你的 Android 项目代码上传到该仓库中。 2. 在 GitLab 中创建一个新的项目,并将该项目与 GitHub 中的相应仓库进行关联。 3. 在 GitLab 中创建一个新的 .gitlab-ci.yml 文件,用于配置持续集成自动化打包流程。以下是一个示例文件: ```yml image: openjdk:8-jdk stages: - build - deploy before_script: - chmod +x ./gradlew build: stage: build script: - ./gradlew assembleDebug artifacts: paths: - app/build/outputs/apk/debug/app-debug.apk deploy: stage: deploy script: - curl -F "file=@app/build/outputs/apk/debug/app-debug.apk" -F "uKey=YOUR_UKEY" -F "_api_key=YOUR_API_KEY" https://www.pgyer.com/apiv2/app/upload - curl -X POST -H 'Content-type: application/json' --data '{"msgtype":"text","text":{"content":"Android自动化打包完成,请前往蒲公英下载最新版本!"}}' https://oapi.dingtalk.com/robot/send?access_token=YOUR_ACCESS_TOKEN ``` 4. 在蒲公英网站中创建一个新的应用,并将该应用的 uKey 和 api_key 配置到 .gitlab-ci.yml 文件中的 deploy 阶段中。 5. 在钉钉开发者后台中创建一个新的机器人,并将该机器人的 access_token 配置到 .gitlab-ci.yml 文件中的 deploy 阶段中。 6. 确保 GitLab CI/CD Runner 已经正确地安装在你的 Android 项目所在的服务器上,然后在 GitLab 中启用 Runner。 7. 每次你向 GitHub 中的仓库提交新的代码时,GitLab CI/CD Runner 将会自动触发持续集成自动化打包流程,并将最新的 APK 文件上传到蒲公英网站,并通过钉钉机器人发送通知消息。 希望这些步骤能够对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值