Merge "releasetools: Add a BuildInfo class."

This commit is contained in:
Tao Bao 2018-01-10 20:37:41 +00:00 committed by Gerrit Code Review
commit b54f6157a4
3 changed files with 593 additions and 203 deletions

View file

@ -77,14 +77,14 @@ class EdifyGenerator(object):
with temporary=True) to this one."""
self.script.extend(other.script)
def AssertOemProperty(self, name, values):
def AssertOemProperty(self, name, values, oem_no_mount):
"""Assert that a property on the OEM paritition matches allowed values."""
if not name:
raise ValueError("must specify an OEM property")
if not values:
raise ValueError("must specify the OEM value")
get_prop_command = None
if common.OPTIONS.oem_no_mount:
if oem_no_mount:
get_prop_command = 'getprop("%s")' % name
else:
get_prop_command = 'file_getprop("/oem/oem.prop", "%s")' % name

View file

@ -49,8 +49,10 @@ Usage: ota_from_target_files [flags] input_target_files output_ota_package
-o (--oem_settings) <main_file[,additional_files...]>
Comma seperated list of files used to specify the expected OEM-specific
properties on the OEM partition of the intended device.
Multiple expected values can be used by providing multiple files.
properties on the OEM partition of the intended device. Multiple expected
values can be used by providing multiple files. Only the first dict will
be used to compute fingerprint, while the rest will be used to assert
OEM-specific properties.
--oem_no_mount
For devices with OEM-specific properties but without an OEM partition,
@ -128,16 +130,11 @@ Usage: ota_from_target_files [flags] input_target_files output_ota_package
from __future__ import print_function
import sys
if sys.hexversion < 0x02070000:
print("Python 2.7 or newer is required.", file=sys.stderr)
sys.exit(1)
import multiprocessing
import os.path
import subprocess
import shlex
import subprocess
import sys
import tempfile
import zipfile
@ -145,6 +142,11 @@ import common
import edify_generator
import sparse_img
if sys.hexversion < 0x02070000:
print("Python 2.7 or newer is required.", file=sys.stderr)
sys.exit(1)
OPTIONS = common.OPTIONS
OPTIONS.package_key = None
OPTIONS.incremental_source = None
@ -179,6 +181,136 @@ METADATA_NAME = 'META-INF/com/android/metadata'
UNZIP_PATTERN = ['IMAGES/*', 'META/*']
class BuildInfo(object):
"""A class that holds the information for a given build.
This class wraps up the property querying for a given source or target build.
It abstracts away the logic of handling OEM-specific properties, and caches
the commonly used properties such as fingerprint.
There are two types of info dicts: a) build-time info dict, which is generated
at build time (i.e. included in a target_files zip); b) OEM info dict that is
specified at package generation time (via command line argument
'--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
having "oem_fingerprint_properties" in build-time info dict), all the queries
would be answered based on build-time info dict only. Otherwise if using
OEM-specific properties, some of them will be calculated from two info dicts.
Users can query properties similarly as using a dict() (e.g. info['fstab']),
or to query build properties via GetBuildProp() or GetVendorBuildProp().
Attributes:
info_dict: The build-time info dict.
is_ab: Whether it's a build that uses A/B OTA.
oem_dicts: A list of OEM dicts.
oem_props: A list of OEM properties that should be read from OEM dicts; None
if the build doesn't use any OEM-specific property.
fingerprint: The fingerprint of the build, which would be calculated based
on OEM properties if applicable.
device: The device name, which could come from OEM dicts if applicable.
"""
def __init__(self, info_dict, oem_dicts):
"""Initializes a BuildInfo instance with the given dicts.
Arguments:
info_dict: The build-time info dict.
oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
that it always uses the first dict to calculate the fingerprint or the
device name. The rest would be used for asserting OEM properties only
(e.g. one package can be installed on one of these devices).
"""
self.info_dict = info_dict
self.oem_dicts = oem_dicts
self._is_ab = info_dict.get("ab_update") == "true"
self._oem_props = info_dict.get("oem_fingerprint_properties")
if self._oem_props:
assert oem_dicts, "OEM source required for this build"
# These two should be computed only after setting self._oem_props.
self._device = self.GetOemProperty("ro.product.device")
self._fingerprint = self.CalculateFingerprint()
@property
def is_ab(self):
return self._is_ab
@property
def device(self):
return self._device
@property
def fingerprint(self):
return self._fingerprint
@property
def oem_props(self):
return self._oem_props
def __getitem__(self, key):
return self.info_dict[key]
def get(self, key, default=None):
return self.info_dict.get(key, default)
def GetBuildProp(self, prop):
"""Returns the inquired build property."""
try:
return self.info_dict.get("build.prop", {})[prop]
except KeyError:
raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
def GetVendorBuildProp(self, prop):
"""Returns the inquired vendor build property."""
try:
return self.info_dict.get("vendor.build.prop", {})[prop]
except KeyError:
raise common.ExternalError(
"couldn't find %s in vendor.build.prop" % (prop,))
def GetOemProperty(self, key):
if self.oem_props is not None and key in self.oem_props:
return self.oem_dicts[0][key]
return self.GetBuildProp(key)
def CalculateFingerprint(self):
if self.oem_props is None:
return self.GetBuildProp("ro.build.fingerprint")
return "%s/%s/%s:%s" % (
self.GetOemProperty("ro.product.brand"),
self.GetOemProperty("ro.product.name"),
self.GetOemProperty("ro.product.device"),
self.GetBuildProp("ro.build.thumbprint"))
def WriteMountOemScript(self, script):
assert self.oem_props is not None
recovery_mount_options = self.info_dict.get("recovery_mount_options")
script.Mount("/oem", recovery_mount_options)
def WriteDeviceAssertions(self, script, oem_no_mount):
# Read the property directly if not using OEM properties.
if not self.oem_props:
script.AssertDevice(self.device)
return
# Otherwise assert OEM properties.
if not self.oem_dicts:
raise common.ExternalError(
"No OEM file provided to answer expected assertions")
for prop in self.oem_props.split():
values = []
for oem_dict in self.oem_dicts:
if prop in oem_dict:
values.append(oem_dict[prop])
if not values:
raise common.ExternalError(
"The OEM file is missing the property %s" % (prop,))
script.AssertOemProperty(prop, values, oem_no_mount)
def SignOutput(temp_zip_name, output_zip_name):
pw = OPTIONS.key_passwords[OPTIONS.package_key]
@ -186,37 +318,15 @@ def SignOutput(temp_zip_name, output_zip_name):
whole_file=True)
def AppendAssertions(script, info_dict, oem_dicts=None):
oem_props = info_dict.get("oem_fingerprint_properties")
if not oem_props:
device = GetBuildProp("ro.product.device", info_dict)
script.AssertDevice(device)
else:
if not oem_dicts:
raise common.ExternalError(
"No OEM file provided to answer expected assertions")
for prop in oem_props.split():
values = []
for oem_dict in oem_dicts:
if oem_dict.get(prop):
values.append(oem_dict[prop])
if not values:
raise common.ExternalError(
"The OEM file is missing the property %s" % prop)
script.AssertOemProperty(prop, values)
def _LoadOemDicts(script, recovery_mount_options=None):
def _LoadOemDicts(oem_source):
"""Returns the list of loaded OEM properties dict."""
oem_dicts = None
if OPTIONS.oem_source is None:
raise common.ExternalError("OEM source required for this build")
if not OPTIONS.oem_no_mount and script:
script.Mount("/oem", recovery_mount_options)
if not oem_source:
return None
oem_dicts = []
for oem_file in OPTIONS.oem_source:
oem_dicts.append(common.LoadDictionaryFromLines(
open(oem_file).readlines()))
for oem_file in oem_source:
with open(oem_file) as fp:
oem_dicts.append(common.LoadDictionaryFromLines(fp.readlines()))
return oem_dicts
@ -267,25 +377,30 @@ def HasVendorPartition(target_files_zip):
return False
def HasTrebleEnabled(target_files_zip, info_dict):
def HasTrebleEnabled(target_files_zip, target_info):
return (HasVendorPartition(target_files_zip) and
GetBuildProp("ro.treble.enabled", info_dict) == "true")
target_info.GetBuildProp("ro.treble.enabled") == "true")
def GetOemProperty(name, oem_props, oem_dict, info_dict):
if oem_props is not None and name in oem_props:
return oem_dict[name]
return GetBuildProp(name, info_dict)
def WriteFingerprintAssertion(script, target_info, source_info):
source_oem_props = source_info.oem_props
target_oem_props = target_info.oem_props
def CalculateFingerprint(oem_props, oem_dict, info_dict):
if oem_props is None:
return GetBuildProp("ro.build.fingerprint", info_dict)
return "%s/%s/%s:%s" % (
GetOemProperty("ro.product.brand", oem_props, oem_dict, info_dict),
GetOemProperty("ro.product.name", oem_props, oem_dict, info_dict),
GetOemProperty("ro.product.device", oem_props, oem_dict, info_dict),
GetBuildProp("ro.build.thumbprint", info_dict))
if source_oem_props is None and target_oem_props is None:
script.AssertSomeFingerprint(
source_info.fingerprint, target_info.fingerprint)
elif source_oem_props is not None and target_oem_props is not None:
script.AssertSomeThumbprint(
target_info.GetBuildProp("ro.build.thumbprint"),
source_info.GetBuildProp("ro.build.thumbprint"))
elif source_oem_props is None and target_oem_props is not None:
script.AssertFingerprintOrThumbprint(
source_info.fingerprint,
target_info.GetBuildProp("ro.build.thumbprint"))
else:
script.AssertFingerprintOrThumbprint(
target_info.fingerprint,
source_info.GetBuildProp("ro.build.thumbprint"))
def GetImage(which, tmpdir):
@ -313,9 +428,8 @@ def GetImage(which, tmpdir):
return sparse_img.SparseImage(path, mappath, clobbered_blocks)
def AddCompatibilityArchiveIfTrebleEnabled(target_zip, output_zip,
target_info_dict,
source_info_dict=None):
def AddCompatibilityArchiveIfTrebleEnabled(target_zip, output_zip, target_info,
source_info=None):
"""Adds compatibility info into the output zip if it's Treble-enabled target.
Metadata used for on-device compatibility verification is retrieved from
@ -328,9 +442,9 @@ def AddCompatibilityArchiveIfTrebleEnabled(target_zip, output_zip,
Args:
target_zip: Zip file containing the source files to be included for OTA.
output_zip: Zip file that will be sent for OTA.
target_info_dict: The dict that holds the target build info.
source_info_dict: The dict that holds the source build info, if generating
an incremental OTA; None otherwise.
target_info: The BuildInfo instance that holds the target build info.
source_info: The BuildInfo instance that holds the source build info, if
generating an incremental OTA; None otherwise.
"""
def AddCompatibilityArchive(system_updated, vendor_updated):
@ -353,8 +467,8 @@ def AddCompatibilityArchiveIfTrebleEnabled(target_zip, output_zip,
# Create new archive.
compatibility_archive = tempfile.NamedTemporaryFile()
compatibility_archive_zip = zipfile.ZipFile(compatibility_archive, "w",
compression=zipfile.ZIP_DEFLATED)
compatibility_archive_zip = zipfile.ZipFile(
compatibility_archive, "w", compression=zipfile.ZIP_DEFLATED)
# Add metadata.
for file_name in compatibility_files:
@ -375,59 +489,55 @@ def AddCompatibilityArchiveIfTrebleEnabled(target_zip, output_zip,
# Will only proceed if the target has enabled the Treble support (as well as
# having a /vendor partition).
if not HasTrebleEnabled(target_zip, target_info_dict):
if not HasTrebleEnabled(target_zip, target_info):
return
# We don't support OEM thumbprint in Treble world (which calculates
# fingerprints in a different way as shown in CalculateFingerprint()).
assert not target_info_dict.get("oem_fingerprint_properties")
assert not target_info.oem_props
# Full OTA carries the info for system/vendor both.
if source_info_dict is None:
if source_info is None:
AddCompatibilityArchive(True, True)
return
assert not source_info_dict.get("oem_fingerprint_properties")
assert not source_info.oem_props
source_fp = GetBuildProp("ro.build.fingerprint", source_info_dict)
target_fp = GetBuildProp("ro.build.fingerprint", target_info_dict)
source_fp = source_info.fingerprint
target_fp = target_info.fingerprint
system_updated = source_fp != target_fp
source_fp_vendor = GetVendorBuildProp("ro.vendor.build.fingerprint",
source_info_dict)
target_fp_vendor = GetVendorBuildProp("ro.vendor.build.fingerprint",
target_info_dict)
source_fp_vendor = source_info.GetVendorBuildProp(
"ro.vendor.build.fingerprint")
target_fp_vendor = target_info.GetVendorBuildProp(
"ro.vendor.build.fingerprint")
vendor_updated = source_fp_vendor != target_fp_vendor
AddCompatibilityArchive(system_updated, vendor_updated)
def WriteFullOTAPackage(input_zip, output_zip):
# TODO: how to determine this? We don't know what version it will
# be installed on top of. For now, we expect the API just won't
# change very often. Similarly for fstab, it might have changed
# in the target build.
script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
oem_dicts = None
if oem_props:
recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
oem_dicts = _LoadOemDicts(script, recovery_mount_options)
# We don't know what version it will be installed on top of. We expect the API
# just won't change very often. Similarly for fstab, it might have changed in
# the target build.
target_api_version = target_info["recovery_api_version"]
script = edify_generator.EdifyGenerator(target_api_version, target_info)
if target_info.oem_props and not OPTIONS.oem_no_mount:
target_info.WriteMountOemScript(script)
target_fp = CalculateFingerprint(oem_props, oem_dicts and oem_dicts[0],
OPTIONS.info_dict)
metadata = {
"post-build": target_fp,
"pre-device": GetOemProperty("ro.product.device", oem_props,
oem_dicts and oem_dicts[0],
OPTIONS.info_dict),
"post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
"post-build": target_info.fingerprint,
"pre-device": target_info.device,
"post-timestamp": target_info.GetBuildProp("ro.build.date.utc"),
"ota-type" : "BLOCK",
}
device_specific = common.DeviceSpecificParams(
input_zip=input_zip,
input_version=OPTIONS.info_dict["recovery_api_version"],
input_version=target_api_version,
output_zip=output_zip,
script=script,
input_tmp=OPTIONS.input_tmp,
@ -436,13 +546,12 @@ def WriteFullOTAPackage(input_zip, output_zip):
assert HasRecoveryPatch(input_zip)
metadata["ota-type"] = "BLOCK"
ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict)
ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict)
# Assertions (e.g. downgrade check, device properties check).
ts = target_info.GetBuildProp("ro.build.date.utc")
ts_text = target_info.GetBuildProp("ro.build.date")
script.AssertOlderBuild(ts, ts_text)
AppendAssertions(script, OPTIONS.info_dict, oem_dicts)
target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
device_specific.FullOTA_Assertions()
# Two-step package strategy (in chronological order, which is *not*
@ -468,9 +577,9 @@ def WriteFullOTAPackage(input_zip, output_zip):
recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
OPTIONS.input_tmp, "RECOVERY")
if OPTIONS.two_step:
if not OPTIONS.info_dict.get("multistage_support", None):
if not target_info.get("multistage_support"):
assert False, "two-step packages not supported by this build"
fs = OPTIONS.info_dict["fstab"]["/misc"]
fs = target_info["fstab"]["/misc"]
assert fs.fs_type.upper() == "EMMC", \
"two-step packages only supported on devices with EMMC /misc partitions"
bcb_dev = {"bcb_dev": fs.device}
@ -492,7 +601,7 @@ else if get_stage("%(bcb_dev)s") == "3/3" then
script.Comment("Stage 3/3")
# Dump fingerprints
script.Print("Target: %s" % target_fp)
script.Print("Target: {}".format(target_info.fingerprint))
device_specific.FullOTA_InstallBegin()
@ -525,10 +634,9 @@ else if get_stage("%(bcb_dev)s") == "3/3" then
vendor_diff = common.BlockDifference("vendor", vendor_tgt)
vendor_diff.WriteScript(script, output_zip)
AddCompatibilityArchiveIfTrebleEnabled(input_zip, output_zip,
OPTIONS.info_dict)
AddCompatibilityArchiveIfTrebleEnabled(input_zip, output_zip, target_info)
common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
common.CheckSize(boot_img.data, "boot.img", target_info)
common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
script.ShowProgress(0.05, 5)
@ -592,12 +700,12 @@ def GetVendorBuildProp(prop, info_dict):
"couldn't find %s in vendor.build.prop" % (prop,))
def HandleDowngradeMetadata(metadata):
def HandleDowngradeMetadata(metadata, target_info, source_info):
# Only incremental OTAs are allowed to reach here.
assert OPTIONS.incremental_source is not None
post_timestamp = GetBuildProp("ro.build.date.utc", OPTIONS.target_info_dict)
pre_timestamp = GetBuildProp("ro.build.date.utc", OPTIONS.source_info_dict)
post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
is_downgrade = long(post_timestamp) < long(pre_timestamp)
if OPTIONS.downgrade:
@ -607,72 +715,65 @@ def HandleDowngradeMetadata(metadata):
metadata["ota-downgrade"] = "yes"
elif OPTIONS.timestamp:
if not is_downgrade:
raise RuntimeError("--timestamp specified but no timestamp hack needed: "
"pre: %s, post: %s" % (pre_timestamp, post_timestamp))
raise RuntimeError("--override_timestamp specified but no timestamp hack "
"needed: pre: %s, post: %s" % (pre_timestamp,
post_timestamp))
metadata["post-timestamp"] = str(long(pre_timestamp) + 1)
else:
if is_downgrade:
raise RuntimeError("Downgrade detected based on timestamp check: "
"pre: %s, post: %s. Need to specify --timestamp OR "
"--downgrade to allow building the incremental." % (
pre_timestamp, post_timestamp))
"pre: %s, post: %s. Need to specify "
"--override_timestamp OR --downgrade to allow "
"building the incremental." % (pre_timestamp,
post_timestamp))
metadata["post-timestamp"] = post_timestamp
def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
source_version = OPTIONS.source_info_dict["recovery_api_version"]
target_version = OPTIONS.target_info_dict["recovery_api_version"]
target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
if source_version == 0:
target_api_version = target_info["recovery_api_version"]
source_api_version = source_info["recovery_api_version"]
if source_api_version == 0:
print("WARNING: generating edify script for a source that "
"can't install it.")
script = edify_generator.EdifyGenerator(
source_version, OPTIONS.target_info_dict,
fstab=OPTIONS.source_info_dict["fstab"])
source_oem_props = OPTIONS.source_info_dict.get("oem_fingerprint_properties")
target_oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties")
oem_dicts = None
if source_oem_props or target_oem_props:
recovery_mount_options = OPTIONS.source_info_dict.get(
"recovery_mount_options")
oem_dicts = _LoadOemDicts(script, recovery_mount_options)
script = edify_generator.EdifyGenerator(
source_api_version, target_info, fstab=source_info["fstab"])
if target_info.oem_props or source_info.oem_props:
if not OPTIONS.oem_no_mount:
source_info.WriteMountOemScript(script)
metadata = {
"pre-device": GetOemProperty("ro.product.device", source_oem_props,
oem_dicts and oem_dicts[0],
OPTIONS.source_info_dict),
"pre-device": source_info.device,
"ota-type": "BLOCK",
}
HandleDowngradeMetadata(metadata)
HandleDowngradeMetadata(metadata, target_info, source_info)
device_specific = common.DeviceSpecificParams(
source_zip=source_zip,
source_version=source_version,
source_version=source_api_version,
target_zip=target_zip,
target_version=target_version,
target_version=target_api_version,
output_zip=output_zip,
script=script,
metadata=metadata,
info_dict=OPTIONS.source_info_dict)
info_dict=source_info)
source_fp = CalculateFingerprint(source_oem_props, oem_dicts and oem_dicts[0],
OPTIONS.source_info_dict)
target_fp = CalculateFingerprint(target_oem_props, oem_dicts and oem_dicts[0],
OPTIONS.target_info_dict)
metadata["pre-build"] = source_fp
metadata["post-build"] = target_fp
metadata["pre-build-incremental"] = GetBuildProp(
"ro.build.version.incremental", OPTIONS.source_info_dict)
metadata["post-build-incremental"] = GetBuildProp(
"ro.build.version.incremental", OPTIONS.target_info_dict)
metadata["pre-build"] = source_info.fingerprint
metadata["post-build"] = target_info.fingerprint
metadata["pre-build-incremental"] = source_info.GetBuildProp(
"ro.build.version.incremental")
metadata["post-build-incremental"] = target_info.GetBuildProp(
"ro.build.version.incremental")
source_boot = common.GetBootableImage(
"/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
OPTIONS.source_info_dict)
"/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", source_info)
target_boot = common.GetBootableImage(
"/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
"/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT", target_info)
updating_boot = (not OPTIONS.two_step and
(source_boot.data != target_boot.data))
@ -683,19 +784,18 @@ def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
system_tgt = GetImage("system", OPTIONS.target_tmp)
blockimgdiff_version = max(
int(i) for i in
OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
int(i) for i in target_info.get("blockimgdiff_versions", "1").split(","))
assert blockimgdiff_version >= 3
# Check the first block of the source system partition for remount R/W only
# if the filesystem is ext4.
system_src_partition = OPTIONS.source_info_dict["fstab"]["/system"]
system_src_partition = source_info["fstab"]["/system"]
check_first_block = system_src_partition.fs_type == "ext4"
# Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
# in zip formats. However with squashfs, a) all files are compressed in LZ4;
# b) the blocks listed in block map may not contain all the bytes for a given
# file (because they're rounded to be 4K-aligned).
system_tgt_partition = OPTIONS.target_info_dict["fstab"]["/system"]
system_tgt_partition = target_info["fstab"]["/system"]
disable_imgdiff = (system_src_partition.fs_type == "squashfs" or
system_tgt_partition.fs_type == "squashfs")
system_diff = common.BlockDifference("system", system_tgt, system_src,
@ -711,7 +811,7 @@ def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
# Check first block of vendor partition for remount R/W only if
# disk type is ext4
vendor_partition = OPTIONS.source_info_dict["fstab"]["/vendor"]
vendor_partition = source_info["fstab"]["/vendor"]
check_first_block = vendor_partition.fs_type == "ext4"
disable_imgdiff = vendor_partition.fs_type == "squashfs"
vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src,
@ -722,10 +822,10 @@ def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
vendor_diff = None
AddCompatibilityArchiveIfTrebleEnabled(
target_zip, output_zip, OPTIONS.target_info_dict,
OPTIONS.source_info_dict)
target_zip, output_zip, target_info, source_info)
AppendAssertions(script, OPTIONS.target_info_dict, oem_dicts)
# Assertions (e.g. device properties check).
target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
device_specific.IncrementalOTA_Assertions()
# Two-step incremental package strategy (in chronological order,
@ -751,7 +851,7 @@ def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
# (allow recovery to mark itself finished and reboot)
if OPTIONS.two_step:
if not OPTIONS.source_info_dict.get("multistage_support", None):
if not source_info.get("multistage_support"):
assert False, "two-step packages not supported by this build"
fs = OPTIONS.source_info_dict["fstab"]["/misc"]
assert fs.fs_type.upper() == "EMMC", \
@ -776,27 +876,14 @@ else if get_stage("%(bcb_dev)s") != "3/3" then
script.Comment("Stage 1/3")
# Dump fingerprints
script.Print("Source: %s" % (source_fp,))
script.Print("Target: %s" % (target_fp,))
script.Print("Source: {}".format(source_info.fingerprint))
script.Print("Target: {}".format(target_info.fingerprint))
script.Print("Verifying current system...")
device_specific.IncrementalOTA_VerifyBegin()
if source_oem_props is None and target_oem_props is None:
script.AssertSomeFingerprint(source_fp, target_fp)
elif source_oem_props is not None and target_oem_props is not None:
script.AssertSomeThumbprint(
GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
elif source_oem_props is None and target_oem_props is not None:
script.AssertFingerprintOrThumbprint(
source_fp,
GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict))
else:
script.AssertFingerprintOrThumbprint(
target_fp,
GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
WriteFingerprintAssertion(script, target_info, source_info)
# Check the required cache size (i.e. stashed blocks).
size = []
@ -806,8 +893,7 @@ else if get_stage("%(bcb_dev)s") != "3/3" then
size.append(vendor_diff.required_cache)
if updating_boot:
boot_type, boot_device = common.GetTypeAndDevice(
"/boot", OPTIONS.source_info_dict)
boot_type, boot_device = common.GetTypeAndDevice("/boot", source_info)
d = common.Difference(target_boot, source_boot)
_, _, d = d.ComputePatch()
if d is None:
@ -984,7 +1070,8 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file,
cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
rsa_key = common.MakeTempFile(prefix="key-", suffix=".key")
cmd.extend(["-out", rsa_key])
p1 = common.Run(cmd, verbose=False, stdout=log_file, stderr=subprocess.STDOUT)
p1 = common.Run(cmd, verbose=False, stdout=log_file,
stderr=subprocess.STDOUT)
p1.communicate()
assert p1.returncode == 0, "openssl pkcs8 failed"
@ -993,35 +1080,32 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file,
output_zip = zipfile.ZipFile(temp_zip_file, "w",
compression=zipfile.ZIP_DEFLATED)
# Metadata to comply with Android OTA package format.
oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties", None)
oem_dicts = None
if oem_props:
oem_dicts = _LoadOemDicts(None)
if source_file is not None:
target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
else:
target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
source_info = None
# Metadata to comply with Android OTA package format.
metadata = {
"post-build": CalculateFingerprint(oem_props, oem_dicts and oem_dicts[0],
OPTIONS.info_dict),
"post-build-incremental" : GetBuildProp("ro.build.version.incremental",
OPTIONS.info_dict),
"pre-device": GetOemProperty("ro.product.device", oem_props,
oem_dicts and oem_dicts[0],
OPTIONS.info_dict),
"post-build" : target_info.fingerprint,
"post-build-incremental" : target_info.GetBuildProp(
"ro.build.version.incremental"),
"ota-required-cache" : "0",
"ota-type" : "AB",
}
if source_file is not None:
metadata["pre-build"] = CalculateFingerprint(oem_props,
oem_dicts and oem_dicts[0],
OPTIONS.source_info_dict)
metadata["pre-build-incremental"] = GetBuildProp(
"ro.build.version.incremental", OPTIONS.source_info_dict)
metadata["pre-device"] = source_info.device
metadata["pre-build"] = source_info.fingerprint
metadata["pre-build-incremental"] = source_info.GetBuildProp(
"ro.build.version.incremental")
HandleDowngradeMetadata(metadata)
HandleDowngradeMetadata(metadata, target_info, source_info)
else:
metadata["post-timestamp"] = GetBuildProp(
"ro.build.date.utc", OPTIONS.info_dict)
metadata["pre-device"] = target_info.device
metadata["post-timestamp"] = target_info.GetBuildProp("ro.build.date.utc")
# 1. Generate payload.
payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
@ -1120,8 +1204,8 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file,
# If dm-verity is supported for the device, copy contents of care_map
# into A/B OTA package.
target_zip = zipfile.ZipFile(target_file, "r")
if (OPTIONS.info_dict.get("verity") == "true" or
OPTIONS.info_dict.get("avb_enable") == "true"):
if (target_info.get("verity") == "true" or
target_info.get("avb_enable") == "true"):
care_map_path = "META/care_map.txt"
namelist = target_zip.namelist()
if care_map_path in namelist:
@ -1131,12 +1215,12 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file,
else:
print("Warning: cannot find care map file in target_file package")
# OPTIONS.source_info_dict must be None for incrementals.
# source_info must be None for full OTAs.
if source_file is None:
assert OPTIONS.source_info_dict is None
assert source_info is None
AddCompatibilityArchiveIfTrebleEnabled(
target_zip, output_zip, OPTIONS.info_dict, OPTIONS.source_info_dict)
target_zip, output_zip, target_info, source_info)
common.ZipClose(target_zip)
@ -1289,12 +1373,15 @@ def main(argv):
# Load the dict file from the zip directly to have a peek at the OTA type.
# For packages using A/B update, unzipping is not needed.
if OPTIONS.extracted_input is not None:
OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input, OPTIONS.extracted_input)
OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input,
OPTIONS.extracted_input)
else:
input_zip = zipfile.ZipFile(args[0], "r")
OPTIONS.info_dict = common.LoadInfoDict(input_zip)
common.ZipClose(input_zip)
OPTIONS.oem_dicts = _LoadOemDicts(OPTIONS.oem_source)
ab_update = OPTIONS.info_dict.get("ab_update") == "true"
# Use the default key to sign the package if not specified with package_key.
@ -1337,7 +1424,8 @@ def main(argv):
if OPTIONS.extracted_input is not None:
OPTIONS.input_tmp = OPTIONS.extracted_input
OPTIONS.target_tmp = OPTIONS.input_tmp
OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.input_tmp, OPTIONS.input_tmp)
OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.input_tmp,
OPTIONS.input_tmp)
input_zip = zipfile.ZipFile(args[0], "r")
else:
print("unzipping target target-files...")

View file

@ -0,0 +1,302 @@
#
# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import copy
import unittest
import common
from ota_from_target_files import (
_LoadOemDicts, BuildInfo, WriteFingerprintAssertion)
class MockScriptWriter(object):
"""A class that mocks edify_generator.EdifyGenerator.
It simply pushes the incoming arguments onto script stack, which is to assert
the calls to EdifyGenerator functions.
"""
def __init__(self):
self.script = []
def Mount(self, *args):
self.script.append(('Mount',) + args)
def AssertDevice(self, *args):
self.script.append(('AssertDevice',) + args)
def AssertOemProperty(self, *args):
self.script.append(('AssertOemProperty',) + args)
def AssertFingerprintOrThumbprint(self, *args):
self.script.append(('AssertFingerprintOrThumbprint',) + args)
def AssertSomeFingerprint(self, *args):
self.script.append(('AssertSomeFingerprint',) + args)
def AssertSomeThumbprint(self, *args):
self.script.append(('AssertSomeThumbprint',) + args)
class BuildInfoTest(unittest.TestCase):
TEST_INFO_DICT = {
'build.prop' : {
'ro.product.device' : 'product-device',
'ro.product.name' : 'product-name',
'ro.build.fingerprint' : 'build-fingerprint',
'ro.build.foo' : 'build-foo',
},
'vendor.build.prop' : {
'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint',
},
'property1' : 'value1',
'property2' : 4096,
}
TEST_INFO_DICT_USES_OEM_PROPS = {
'build.prop' : {
'ro.product.name' : 'product-name',
'ro.build.thumbprint' : 'build-thumbprint',
'ro.build.bar' : 'build-bar',
},
'vendor.build.prop' : {
'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint',
},
'property1' : 'value1',
'property2' : 4096,
'oem_fingerprint_properties' : 'ro.product.device ro.product.brand',
}
TEST_OEM_DICTS = [
{
'ro.product.brand' : 'brand1',
'ro.product.device' : 'device1',
},
{
'ro.product.brand' : 'brand2',
'ro.product.device' : 'device2',
},
{
'ro.product.brand' : 'brand3',
'ro.product.device' : 'device3',
},
]
def test_init(self):
target_info = BuildInfo(self.TEST_INFO_DICT, None)
self.assertEqual('product-device', target_info.device)
self.assertEqual('build-fingerprint', target_info.fingerprint)
self.assertFalse(target_info.is_ab)
self.assertIsNone(target_info.oem_props)
def test_init_with_oem_props(self):
target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
self.TEST_OEM_DICTS)
self.assertEqual('device1', target_info.device)
self.assertEqual('brand1/product-name/device1:build-thumbprint',
target_info.fingerprint)
# Swap the order in oem_dicts, which would lead to different BuildInfo.
oem_dicts = copy.copy(self.TEST_OEM_DICTS)
oem_dicts[0], oem_dicts[2] = oem_dicts[2], oem_dicts[0]
target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, oem_dicts)
self.assertEqual('device3', target_info.device)
self.assertEqual('brand3/product-name/device3:build-thumbprint',
target_info.fingerprint)
# Missing oem_dict should be rejected.
self.assertRaises(AssertionError, BuildInfo,
self.TEST_INFO_DICT_USES_OEM_PROPS, None)
def test___getitem__(self):
target_info = BuildInfo(self.TEST_INFO_DICT, None)
self.assertEqual('value1', target_info['property1'])
self.assertEqual(4096, target_info['property2'])
self.assertEqual('build-foo', target_info['build.prop']['ro.build.foo'])
def test___getitem__with_oem_props(self):
target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
self.TEST_OEM_DICTS)
self.assertEqual('value1', target_info['property1'])
self.assertEqual(4096, target_info['property2'])
self.assertRaises(KeyError,
lambda: target_info['build.prop']['ro.build.foo'])
def test_get(self):
target_info = BuildInfo(self.TEST_INFO_DICT, None)
self.assertEqual('value1', target_info.get('property1'))
self.assertEqual(4096, target_info.get('property2'))
self.assertEqual(4096, target_info.get('property2', 1024))
self.assertEqual(1024, target_info.get('property-nonexistent', 1024))
self.assertEqual('build-foo', target_info.get('build.prop')['ro.build.foo'])
def test_get_with_oem_props(self):
target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
self.TEST_OEM_DICTS)
self.assertEqual('value1', target_info.get('property1'))
self.assertEqual(4096, target_info.get('property2'))
self.assertEqual(4096, target_info.get('property2', 1024))
self.assertEqual(1024, target_info.get('property-nonexistent', 1024))
self.assertIsNone(target_info.get('build.prop').get('ro.build.foo'))
self.assertRaises(KeyError,
lambda: target_info.get('build.prop')['ro.build.foo'])
def test_GetBuildProp(self):
target_info = BuildInfo(self.TEST_INFO_DICT, None)
self.assertEqual('build-foo', target_info.GetBuildProp('ro.build.foo'))
self.assertRaises(common.ExternalError, target_info.GetBuildProp,
'ro.build.nonexistent')
def test_GetBuildProp_with_oem_props(self):
target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
self.TEST_OEM_DICTS)
self.assertEqual('build-bar', target_info.GetBuildProp('ro.build.bar'))
self.assertRaises(common.ExternalError, target_info.GetBuildProp,
'ro.build.nonexistent')
def test_GetVendorBuildProp(self):
target_info = BuildInfo(self.TEST_INFO_DICT, None)
self.assertEqual('vendor-build-fingerprint',
target_info.GetVendorBuildProp(
'ro.vendor.build.fingerprint'))
self.assertRaises(common.ExternalError, target_info.GetVendorBuildProp,
'ro.build.nonexistent')
def test_GetVendorBuildProp_with_oem_props(self):
target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
self.TEST_OEM_DICTS)
self.assertEqual('vendor-build-fingerprint',
target_info.GetVendorBuildProp(
'ro.vendor.build.fingerprint'))
self.assertRaises(common.ExternalError, target_info.GetVendorBuildProp,
'ro.build.nonexistent')
def test_WriteMountOemScript(self):
target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
self.TEST_OEM_DICTS)
script_writer = MockScriptWriter()
target_info.WriteMountOemScript(script_writer)
self.assertEqual([('Mount', '/oem', None)], script_writer.script)
def test_WriteDeviceAssertions(self):
target_info = BuildInfo(self.TEST_INFO_DICT, None)
script_writer = MockScriptWriter()
target_info.WriteDeviceAssertions(script_writer, False)
self.assertEqual([('AssertDevice', 'product-device')], script_writer.script)
def test_WriteDeviceAssertions_with_oem_props(self):
target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
self.TEST_OEM_DICTS)
script_writer = MockScriptWriter()
target_info.WriteDeviceAssertions(script_writer, False)
self.assertEqual(
[
('AssertOemProperty', 'ro.product.device',
['device1', 'device2', 'device3'], False),
('AssertOemProperty', 'ro.product.brand',
['brand1', 'brand2', 'brand3'], False),
],
script_writer.script)
def test_WriteFingerprintAssertion_without_oem_props(self):
target_info = BuildInfo(self.TEST_INFO_DICT, None)
source_info_dict = copy.deepcopy(self.TEST_INFO_DICT)
source_info_dict['build.prop']['ro.build.fingerprint'] = (
'source-build-fingerprint')
source_info = BuildInfo(source_info_dict, None)
script_writer = MockScriptWriter()
WriteFingerprintAssertion(script_writer, target_info, source_info)
self.assertEqual(
[('AssertSomeFingerprint', 'source-build-fingerprint',
'build-fingerprint')],
script_writer.script)
def test_WriteFingerprintAssertion_with_source_oem_props(self):
target_info = BuildInfo(self.TEST_INFO_DICT, None)
source_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
self.TEST_OEM_DICTS)
script_writer = MockScriptWriter()
WriteFingerprintAssertion(script_writer, target_info, source_info)
self.assertEqual(
[('AssertFingerprintOrThumbprint', 'build-fingerprint',
'build-thumbprint')],
script_writer.script)
def test_WriteFingerprintAssertion_with_target_oem_props(self):
target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
self.TEST_OEM_DICTS)
source_info = BuildInfo(self.TEST_INFO_DICT, None)
script_writer = MockScriptWriter()
WriteFingerprintAssertion(script_writer, target_info, source_info)
self.assertEqual(
[('AssertFingerprintOrThumbprint', 'build-fingerprint',
'build-thumbprint')],
script_writer.script)
def test_WriteFingerprintAssertion_with_both_oem_props(self):
target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
self.TEST_OEM_DICTS)
source_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS)
source_info_dict['build.prop']['ro.build.thumbprint'] = (
'source-build-thumbprint')
source_info = BuildInfo(source_info_dict, self.TEST_OEM_DICTS)
script_writer = MockScriptWriter()
WriteFingerprintAssertion(script_writer, target_info, source_info)
self.assertEqual(
[('AssertSomeThumbprint', 'build-thumbprint',
'source-build-thumbprint')],
script_writer.script)
class LoadOemDictsTest(unittest.TestCase):
def tearDown(self):
common.Cleanup()
def test_NoneDict(self):
self.assertIsNone(_LoadOemDicts(None))
def test_SingleDict(self):
dict_file = common.MakeTempFile()
with open(dict_file, 'w') as dict_fp:
dict_fp.write('abc=1\ndef=2\nxyz=foo\na.b.c=bar\n')
oem_dicts = _LoadOemDicts([dict_file])
self.assertEqual(1, len(oem_dicts))
self.assertEqual('foo', oem_dicts[0]['xyz'])
self.assertEqual('bar', oem_dicts[0]['a.b.c'])
def test_MultipleDicts(self):
oem_source = []
for i in range(3):
dict_file = common.MakeTempFile()
with open(dict_file, 'w') as dict_fp:
dict_fp.write(
'ro.build.index={}\ndef=2\nxyz=foo\na.b.c=bar\n'.format(i))
oem_source.append(dict_file)
oem_dicts = _LoadOemDicts(oem_source)
self.assertEqual(3, len(oem_dicts))
for i, oem_dict in enumerate(oem_dicts):
self.assertEqual('2', oem_dict['def'])
self.assertEqual('foo', oem_dict['xyz'])
self.assertEqual('bar', oem_dict['a.b.c'])
self.assertEqual('{}'.format(i), oem_dict['ro.build.index'])