Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0b55fc40 authored by Tao Bao's avatar Tao Bao Committed by android-build-merger
Browse files

Merge "releasetools: Move BuildInfo into common."

am: 163df7d4

Change-Id: Ide728c5134a14f9b564c5d2dccb9de031ccfe12a
parents 7600946b 163df7d4
Loading
Loading
Loading
Loading
+219 −0
Original line number Original line Diff line number Diff line
@@ -284,6 +284,225 @@ def CloseInheritedPipes():
      pass
      pass




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.
  """

  _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
                               "ro.product.manufacturer", "ro.product.model",
                               "ro.product.name"]
  _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor",
                                            "system_ext", "system"]

  def __init__(self, info_dict, oem_dicts):
    """Initializes a BuildInfo instance with the given dicts.

    Note that it only wraps up the given dicts, without making copies.

    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).

    Raises:
      ValueError: On invalid inputs.
    """
    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()

    # Sanity check the build fingerprint.
    if (' ' in self._fingerprint or
        any(ord(ch) > 127 for ch in self._fingerprint)):
      raise ValueError(
          'Invalid build fingerprint: "{}". See the requirement in Android CDD '
          '3.2.2. Build Parameters.'.format(self._fingerprint))

  @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 vendor_fingerprint(self):
    return self._fingerprint_of("vendor")

  @property
  def product_fingerprint(self):
    return self._fingerprint_of("product")

  @property
  def odm_fingerprint(self):
    return self._fingerprint_of("odm")

  def _fingerprint_of(self, partition):
    if partition + ".build.prop" not in self.info_dict:
      return None
    build_prop = self.info_dict[partition + ".build.prop"]
    if "ro." + partition + ".build.fingerprint" in build_prop:
      return build_prop["ro." + partition + ".build.fingerprint"]
    if "ro." + partition + ".build.thumbprint" in build_prop:
      return build_prop["ro." + partition + ".build.thumbprint"]
    return None

  @property
  def oem_props(self):
    return self._oem_props

  def __getitem__(self, key):
    return self.info_dict[key]

  def __setitem__(self, key, value):
    self.info_dict[key] = value

  def get(self, key, default=None):
    return self.info_dict.get(key, default)

  def items(self):
    return self.info_dict.items()

  def GetBuildProp(self, prop):
    """Returns the inquired build property."""
    if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
      return self._ResolveRoProductBuildProp(prop)

    try:
      return self.info_dict.get("build.prop", {})[prop]
    except KeyError:
      raise ExternalError("couldn't find %s in build.prop" % (prop,))

  def _ResolveRoProductBuildProp(self, prop):
    """Resolves the inquired ro.product.* build property"""
    prop_val = self.info_dict.get("build.prop", {}).get(prop)
    if prop_val:
      return prop_val

    source_order_val = self.info_dict.get("build.prop", {}).get(
        "ro.product.property_source_order")
    if source_order_val:
      source_order = source_order_val.split(",")
    else:
      source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER

    # Check that all sources in ro.product.property_source_order are valid
    if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
            for x in source_order]):
      raise ExternalError(
          "Invalid ro.product.property_source_order '{}'".format(source_order))

    for source in source_order:
      source_prop = prop.replace(
          "ro.product", "ro.product.{}".format(source), 1)
      prop_val = self.info_dict.get(
          "{}.build.prop".format(source), {}).get(source_prop)
      if prop_val:
        return prop_val

    raise ExternalError("couldn't resolve {}".format(prop))

  def GetVendorBuildProp(self, prop):
    """Returns the inquired vendor build property."""
    try:
      return self.info_dict.get("vendor.build.prop", {})[prop]
    except KeyError:
      raise 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:
      try:
        return self.GetBuildProp("ro.build.fingerprint")
      except ExternalError:
        return "{}/{}/{}:{}/{}/{}:{}/{}".format(
            self.GetBuildProp("ro.product.brand"),
            self.GetBuildProp("ro.product.name"),
            self.GetBuildProp("ro.product.device"),
            self.GetBuildProp("ro.build.version.release"),
            self.GetBuildProp("ro.build.id"),
            self.GetBuildProp("ro.build.version.incremental"),
            self.GetBuildProp("ro.build.type"),
            self.GetBuildProp("ro.build.tags"))
    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 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 ExternalError(
            "The OEM file is missing the property %s" % (prop,))
      script.AssertOemProperty(prop, values, oem_no_mount)


def LoadInfoDict(input_file, repacking=False):
def LoadInfoDict(input_file, repacking=False):
  """Loads the key/value pairs from the given input target_files.
  """Loads the key/value pairs from the given input target_files.


+8 −227
Original line number Original line Diff line number Diff line
@@ -258,225 +258,6 @@ SECONDARY_PAYLOAD_SKIPPED_IMAGES = [
    'system_ext', 'vbmeta', 'vbmeta_system', 'vbmeta_vendor', 'vendor']
    'system_ext', 'vbmeta', 'vbmeta_system', 'vbmeta_vendor', 'vendor']




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.
  """

  _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
                               "ro.product.manufacturer", "ro.product.model",
                               "ro.product.name"]
  _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor",
                                            "system_ext", "system"]

  def __init__(self, info_dict, oem_dicts):
    """Initializes a BuildInfo instance with the given dicts.

    Note that it only wraps up the given dicts, without making copies.

    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).

    Raises:
      ValueError: On invalid inputs.
    """
    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()

    # Sanity check the build fingerprint.
    if (' ' in self._fingerprint or
        any(ord(ch) > 127 for ch in self._fingerprint)):
      raise ValueError(
          'Invalid build fingerprint: "{}". See the requirement in Android CDD '
          '3.2.2. Build Parameters.'.format(self._fingerprint))

  @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 vendor_fingerprint(self):
    return self._fingerprint_of("vendor")

  @property
  def product_fingerprint(self):
    return self._fingerprint_of("product")

  @property
  def odm_fingerprint(self):
    return self._fingerprint_of("odm")

  def _fingerprint_of(self, partition):
    if partition + ".build.prop" not in self.info_dict:
      return None
    build_prop = self.info_dict[partition + ".build.prop"]
    if "ro." + partition + ".build.fingerprint" in build_prop:
      return build_prop["ro." + partition + ".build.fingerprint"]
    if "ro." + partition + ".build.thumbprint" in build_prop:
      return build_prop["ro." + partition + ".build.thumbprint"]
    return None

  @property
  def oem_props(self):
    return self._oem_props

  def __getitem__(self, key):
    return self.info_dict[key]

  def __setitem__(self, key, value):
    self.info_dict[key] = value

  def get(self, key, default=None):
    return self.info_dict.get(key, default)

  def items(self):
    return self.info_dict.items()

  def GetBuildProp(self, prop):
    """Returns the inquired build property."""
    if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
      return self._ResolveRoProductBuildProp(prop)

    try:
      return self.info_dict.get("build.prop", {})[prop]
    except KeyError:
      raise common.ExternalError("couldn't find %s in build.prop" % (prop,))

  def _ResolveRoProductBuildProp(self, prop):
    """Resolves the inquired ro.product.* build property"""
    prop_val = self.info_dict.get("build.prop", {}).get(prop)
    if prop_val:
      return prop_val

    source_order_val = self.info_dict.get("build.prop", {}).get(
        "ro.product.property_source_order")
    if source_order_val:
      source_order = source_order_val.split(",")
    else:
      source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER

    # Check that all sources in ro.product.property_source_order are valid
    if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
            for x in source_order]):
      raise common.ExternalError(
          "Invalid ro.product.property_source_order '{}'".format(source_order))

    for source in source_order:
      source_prop = prop.replace(
          "ro.product", "ro.product.{}".format(source), 1)
      prop_val = self.info_dict.get(
          "{}.build.prop".format(source), {}).get(source_prop)
      if prop_val:
        return prop_val

    raise common.ExternalError("couldn't resolve {}".format(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:
      try:
        return self.GetBuildProp("ro.build.fingerprint")
      except common.ExternalError:
        return "{}/{}/{}:{}/{}/{}:{}/{}".format(
            self.GetBuildProp("ro.product.brand"),
            self.GetBuildProp("ro.product.name"),
            self.GetBuildProp("ro.product.device"),
            self.GetBuildProp("ro.build.version.release"),
            self.GetBuildProp("ro.build.id"),
            self.GetBuildProp("ro.build.version.incremental"),
            self.GetBuildProp("ro.build.type"),
            self.GetBuildProp("ro.build.tags"))
    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)


class PayloadSigner(object):
class PayloadSigner(object):
  """A class that wraps the payload signing works.
  """A class that wraps the payload signing works.


@@ -904,7 +685,7 @@ def GetBlockDifferences(target_zip, source_zip, target_info, source_info,




def WriteFullOTAPackage(input_zip, output_file):
def WriteFullOTAPackage(input_zip, output_file):
  target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
  target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)


  # We don't know what version it will be installed on top of. We expect the API
  # 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
  # just won't change very often. Similarly for fstab, it might have changed in
@@ -1130,8 +911,8 @@ def GetPackageMetadata(target_info, source_info=None):
  Returns:
  Returns:
    A dict to be written into package metadata entry.
    A dict to be written into package metadata entry.
  """
  """
  assert isinstance(target_info, BuildInfo)
  assert isinstance(target_info, common.BuildInfo)
  assert source_info is None or isinstance(source_info, BuildInfo)
  assert source_info is None or isinstance(source_info, common.BuildInfo)


  metadata = {
  metadata = {
      'post-build' : target_info.fingerprint,
      'post-build' : target_info.fingerprint,
@@ -1544,8 +1325,8 @@ def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):




def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file):
def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file):
  target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
  target_info = common.BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
  source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
  source_info = common.BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)


  target_api_version = target_info["recovery_api_version"]
  target_api_version = target_info["recovery_api_version"]
  source_api_version = source_info["recovery_api_version"]
  source_api_version = source_info["recovery_api_version"]
@@ -2024,10 +1805,10 @@ def GenerateAbOtaPackage(target_file, output_file, source_file=None):
                               compression=zipfile.ZIP_DEFLATED)
                               compression=zipfile.ZIP_DEFLATED)


  if source_file is not None:
  if source_file is not None:
    target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
    target_info = common.BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
    source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
    source_info = common.BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
  else:
  else:
    target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
    target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
    source_info = None
    source_info = None


  # Metadata to comply with Android OTA package format.
  # Metadata to comply with Android OTA package format.
+204 −0

File changed.

Preview size limit exceeded, changes collapsed.

+95 −269

File changed.

Preview size limit exceeded, changes collapsed.