Tip: This article analyzes the execution process of the android ota command
Article directory
Preface
本文基于Android9.0从make命令开始一步步跟踪分析
1. Makefile
build/core/Makefile
ifeq ($(build_ota_package),true)
# -----------------------------------------------------------------
# OTA update package
name := $(TARGET_PRODUCT)
ifeq ($(TARGET_BUILD_TYPE),debug)
name := $(name)_debug
endif
name := $(name)-ota-$(FILE_NAME_TAG)
INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip
$(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)
ifeq ($(AB_OTA_UPDATER),true)
$(INTERNAL_OTA_PACKAGE_TARGET): $(BRILLO_UPDATE_PAYLOAD)
else
$(INTERNAL_OTA_PACKAGE_TARGET): $(BROTLI)
endif
$(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) \
build/make/tools/releasetools/ota_from_target_files
@echo "Package OTA: $@"
$(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \
build/make/tools/releasetools/ota_from_target_files -v \
--block \
--extracted_input_target_files $(patsubst %.zip,%,$(BUILT_TARGET_FILES_PACKAGE)) \
-p $(HOST_OUT) \
-k $(KEY_CERT_PAIR) \
$(if $(OEM_OTA_CONFIG), -o $(OEM_OTA_CONFIG)) \
$(BUILT_TARGET_FILES_PACKAGE) $@
.PHONY: otapackage
otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)
endif # build_ota_package
Here you can see that the target is otapackage, and the key script is ota_from_target_files. You can customize many parameters here.
Many manufacturers may have their own packaging commands instead of using the native otapackage. But nothing can change without its origin, and it cannot be separated from ota_from_target_files
2. ota_from_target_files file
build/tools/releasetools/ota_from_target_files
if __name__ == '__main__':
try:
common.CloseInheritedPipes()
main(sys.argv[1:])
except common.ExternalError as e:
print("\n ERROR: %s\n" % (e,))
sys.exit(1)
finally:
common.Cleanup()
sys.argv[1:] passes all the parameters after ota_from_target_files to main,
then here we can configure some custom parameters, such as –wipe_user_data, the user data will be cleared after ota
def main(argv):
def option_handler(o, a):
if o in ("-k", "--package_key"):
OPTIONS.package_key = a
elif o in ("-i", "--incremental_from"):
OPTIONS.incremental_source = a
elif o == "--full_radio":
OPTIONS.full_radio = True
elif o == "--full_bootloader":
OPTIONS.full_bootloader = True
elif o == "--wipe_user_data":
OPTIONS.wipe_user_data = True
elif o == "--downgrade":
............
elif o == "--skip_postinstall":
OPTIONS.skip_postinstall = True
else:
return False
return True
args = common.ParseOptions(argv, __doc__,
extra_opts="b:k:i:d:e:t:2o:",
extra_long_opts=[
"package_key=",
"incremental_from=",
"full_radio",
"full_bootloader",
"wipe_user_data",
"downgrade",
"override_timestamp",
"extra_script=",
"worker_threads=",
"two_step",
"include_secondary",
"no_signing",
"block",
"binary=",
"oem_settings=",
"oem_no_mount",
"verify",
"stash_threshold=",
"log_diff=",
"payload_signer=",
"payload_signer_args=",
"extracted_input_target_files=",
"skip_postinstall",
], extra_option_handler=option_handler)
if len(args) != 2:
common.Usage(__doc__)
sys.exit(1)
if OPTIONS.downgrade:
# We should only allow downgrading incrementals (as opposed to full).
# Otherwise the device may go back from arbitrary build with this full
# OTA package.
............
# If the caller explicitly specified the device-specific extensions path via
# -s / --device_specific, use that. Otherwise, use META/releasetools.py if it
# is present in the target target_files. Otherwise, take the path of the file
# from 'tool_extensions' in the info dict and look for that in the local
# filesystem, relative to the current directory.
if OPTIONS.device_specific is None:
from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
if os.path.exists(from_input):
print("(using device-specific extensions from target_files)")
OPTIONS.device_specific = from_input
else:
OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")
if OPTIONS.device_specific is not None:
OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
# Generate a full OTA.
if OPTIONS.incremental_source is None:
with zipfile.ZipFile(args[0], 'r') as input_zip:
WriteFullOTAPackage(
input_zip,
output_file=args[1])
......
Main will set different states according to the different parameters passed in.
In addition, ParseOptions in build/tools/releasetools/common.py is also called to supplement and set additional states.
def ParseOptions(argv,
docstring,
extra_opts="", extra_long_opts=(),
extra_option_handler=None):
"""Parse the options in argv and return any arguments that aren't
flags. docstring is the calling module's docstring, to be displayed
for errors and -h. extra_opts and extra_long_opts are for flags
defined by the caller, which are processed by passing them to
extra_option_handler."""
try:
opts, args = getopt.getopt(
argv, "hvp:s:x:" + extra_opts,
["help", "verbose", "path=", "signapk_path=",
"signapk_shared_library_path=", "extra_signapk_args=",
"java_path=", "java_args=", "public_key_suffix=",
"private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
"verity_signer_path=", "verity_signer_args=", "device_specific=",
"extra="] +
list(extra_long_opts))
except getopt.GetoptError as err:
Usage(docstring)
print("**", str(err), "**")
sys.exit(2)
for o, a in opts:
if o in ("-h", "--help"):
Usage(docstring)
sys.exit()
elif o in ("-v", "--verbose"):
OPTIONS.verbose = True
elif o in ("-p", "--path"):
OPTIONS.search_path = a
elif o in ("--signapk_path",):
OPTIONS.signapk_path = a
elif o in ("--signapk_shared_library_path",):
OPTIONS.signapk_shared_library_path = a
elif o in ("--extra_signapk_args",):
.........
Go back to ota_from_target_files and carefully analyze the content in main, and find that there is OPTIONS.device_specific here, which will read the absolute path of a file.
There is also a WriteFullOTAPackage call, which is where the entire OTA package is generated.
Analyzing the content of WriteFullOTAPackage, it is not difficult to find that device_specific is critical.
So how is it called here?
3. common.py file
We then analyze the flow chart above. The first is DeviceSpecificParams,
which is in build/tools/releasetools/common.py
class DeviceSpecificParams(object):
module = None
def __init__(self, **kwargs):
"""Keyword arguments to the constructor become attributes of this
object, which is passed to all functions in the device-specific
module."""
for k, v in kwargs.iteritems():
setattr(self, k, v)
self.extras = OPTIONS.extras
if self.module is None:
path = OPTIONS.device_specific
if not path:
return
try:
if os.path.isdir(path):
info = imp.find_module("releasetools", [path])
else:
d, f = os.path.split(path)
b, x = os.path.splitext(f)
if x == ".py":
f = b
info = imp.find_module(f, [d])
print("loaded device-specific extensions from", path)
self.module = imp.load_module("device_specific", *info)
except ImportError:
print("unable to load device-specific module; assuming none")
def _DoCall(self, function_name, *args, **kwargs):
"""Call the named function in the device-specific module, passing
the given args and kwargs. The first argument to the call will be
the DeviceSpecific object itself. If there is no module, or the
module does not define the function, return the value of the
'default' kwarg (which itself defaults to None)."""
if self.module is None or not hasattr(self.module, function_name):
return kwargs.get("default", None)
return getattr(self.module, function_name)(*((self,) + args), **kwargs)
Here all the modules in OPTIONS.device_specific will be recorded in self.module
and then device_specific.xxxxx calls the corresponding function of common.py,
for example
def FullOTA_InstallBegin(self):
"""Called at the start of full OTA installation."""
return self._DoCall("FullOTA_InstallBegin")
Actually calls itself _DoCall
def _DoCall(self, function_name, *args, **kwargs):
"""Call the named function in the device-specific module, passing
the given args and kwargs. The first argument to the call will be
the DeviceSpecific object itself. If there is no module, or the
module does not define the function, return the value of the
'default' kwarg (which itself defaults to None)."""
if self.module is None or not hasattr(self.module, function_name):
return kwargs.get("default", None)
return getattr(self.module, function_name)(*((self,) + args), **kwargs)
First determine whether self.module has a corresponding function. If so, the function of the same name in releasetools is called; if not, it is not called.
4. releasetools.py file
This file is basically reserved for customization by major manufacturers and will not be explained in detail here.
TARGET_RELEASETOOLS_EXTENSIONS determines which one to use.
The things inside are relatively simple.
......
def FullOTA_InstallEnd(info):
if os.path.exists(OUTPUT_PATH + '/uboot.bin'):
WriteToOTAPackage(info, "/uboot", OUTPUT_PATH, "uboot.bin")
if os.path.exists(OUTPUT_PATH + '/uenv.bin'):
WriteToOTAPackage(info, "/uboot_env", OUTPUT_PATH, "uenv.bin")
if os.path.exists(OUTPUT_PATH + '/logo.jpg'):
WriteToOTAPackage(info, "/logo", OUTPUT_PATH, "logo.jpg")
if os.path.exists(OUTPUT_PATH + '/tz.bin.lzhs'):
WriteToOTAPackage(info, "/tzbp", OUTPUT_PATH, "tz.bin.lzhs")
if os.path.exists(OUTPUT_PATH + '/AQ.bin'):
WriteToOTAPackage(info, "/aq", OUTPUT_PATH, "AQ.bin")
if os.path.exists(OUTPUT_PATH + '/pq.bin'):
WriteToOTAPackage(info, "/pq", OUTPUT_PATH, "pq.bin")
if os.path.exists(OUTPUT_PATH + '/rootfs.bin'):
WriteToOTAPackage(info, "/linux_rootfs", OUTPUT_PATH, "rootfs.bin")
if os.path.exists(OUTPUT_PATH + '/adsp.bin'):
WriteToOTAPackage(info, "/adsp", OUTPUT_PATH, "adsp.bin")
def WriteToOTAPackage(info, dev_name, bin_path, bin_name):
try:
common.ZipWriteStr(info.output_zip, bin_name,
open(bin_path + '/' + bin_name).read())
except KeyError:
print ("warning: no "+ bin_name +" in input target_files; ")
try:
info.script.WriteRawImage(dev_name, bin_name)
except KeyError:
print ("warning: "+ bin_name +" write script failed;")
......
Summarize
The whole process is almost over here.