Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python3
      2 #
      3 # Copyright (C) 2018 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 """
     18 Given a OTA package file, produces update config JSON file.
     19 
     20 Example:
     21       $ PYTHONPATH=$ANDROID_BUILD_TOP/build/make/tools/releasetools:$PYTHONPATH \\
     22             bootable/recovery/updater_sample/tools/gen_update_config.py \\
     23                 --ab_install_type=STREAMING \\
     24                 ota-build-001.zip  \\
     25                 my-config-001.json \\
     26                 http://foo.bar/ota-builds/ota-build-001.zip
     27 """
     28 
     29 import argparse
     30 import json
     31 import os.path
     32 import sys
     33 import zipfile
     34 
     35 import ota_from_target_files  # pylint: disable=import-error
     36 
     37 
     38 class GenUpdateConfig(object):
     39     """
     40     A class that generates update configuration file from an OTA package.
     41 
     42     Currently supports only A/B (seamless) OTA packages.
     43     TODO: add non-A/B packages support.
     44     """
     45 
     46     AB_INSTALL_TYPE_STREAMING = 'STREAMING'
     47     AB_INSTALL_TYPE_NON_STREAMING = 'NON_STREAMING'
     48 
     49     def __init__(self,
     50                  package,
     51                  url,
     52                  ab_install_type,
     53                  ab_force_switch_slot,
     54                  ab_verify_payload_metadata):
     55         self.package = package
     56         self.url = url
     57         self.ab_install_type = ab_install_type
     58         self.ab_force_switch_slot = ab_force_switch_slot
     59         self.ab_verify_payload_metadata = ab_verify_payload_metadata
     60         self.streaming_required = (
     61             # payload.bin and payload_properties.txt must exist.
     62             'payload.bin',
     63             'payload_properties.txt',
     64         )
     65         self.streaming_optional = (
     66             # care_map.txt is available only if dm-verity is enabled.
     67             'care_map.txt',
     68             # compatibility.zip is available only if target supports Treble.
     69             'compatibility.zip',
     70         )
     71         self._config = None
     72 
     73     @property
     74     def config(self):
     75         """Returns generated config object."""
     76         return self._config
     77 
     78     def run(self):
     79         """Generates config."""
     80         self._config = {
     81             '__': '*** Generated using tools/gen_update_config.py ***',
     82             'name': self.ab_install_type[0] + ' ' + os.path.basename(self.package)[:-4],
     83             'url': self.url,
     84             'ab_config': self._gen_ab_config(),
     85             'ab_install_type': self.ab_install_type,
     86         }
     87 
     88     def _gen_ab_config(self):
     89         """Builds config required for A/B update."""
     90         with zipfile.ZipFile(self.package, 'r') as package_zip:
     91             config = {
     92                 'property_files': self._get_property_files(package_zip),
     93                 'verify_payload_metadata': self.ab_verify_payload_metadata,
     94                 'force_switch_slot': self.ab_force_switch_slot,
     95             }
     96 
     97         return config
     98 
     99     @staticmethod
    100     def _get_property_files(package_zip):
    101         """Constructs the property-files list for A/B streaming metadata."""
    102 
    103         ab_ota = ota_from_target_files.AbOtaPropertyFiles()
    104         property_str = ab_ota.GetPropertyFilesString(package_zip, False)
    105         property_files = []
    106         for file in property_str.split(','):
    107             filename, offset, size = file.split(':')
    108             inner_file = {
    109                 'filename': filename,
    110                 'offset': int(offset),
    111                 'size': int(size)
    112             }
    113             property_files.append(inner_file)
    114 
    115         return property_files
    116 
    117     def write(self, out):
    118         """Writes config to the output file."""
    119         with open(out, 'w') as out_file:
    120             json.dump(self.config, out_file, indent=4, separators=(',', ': '), sort_keys=True)
    121 
    122 
    123 def main():  # pylint: disable=missing-docstring
    124     ab_install_type_choices = [
    125         GenUpdateConfig.AB_INSTALL_TYPE_STREAMING,
    126         GenUpdateConfig.AB_INSTALL_TYPE_NON_STREAMING]
    127     parser = argparse.ArgumentParser(description=__doc__,
    128                                      formatter_class=argparse.RawDescriptionHelpFormatter)
    129     parser.add_argument('--ab_install_type',
    130                         type=str,
    131                         default=GenUpdateConfig.AB_INSTALL_TYPE_NON_STREAMING,
    132                         choices=ab_install_type_choices,
    133                         help='A/B update installation type')
    134     parser.add_argument('--ab_force_switch_slot',
    135                         default=False,
    136                         action='store_true',
    137                         help='if set device will boot to a new slot, otherwise user '
    138                               'manually switches slot on the screen')
    139     parser.add_argument('--ab_verify_payload_metadata',
    140                         default=False,
    141                         action='store_true',
    142                         help='if set the app will verify the update payload metadata using '
    143                              'update_engine before downloading the whole package.')
    144     parser.add_argument('package',
    145                         type=str,
    146                         help='OTA package zip file')
    147     parser.add_argument('out',
    148                         type=str,
    149                         help='Update configuration JSON file')
    150     parser.add_argument('url',
    151                         type=str,
    152                         help='OTA package download url')
    153     args = parser.parse_args()
    154 
    155     if not args.out.endswith('.json'):
    156         print('out must be a json file')
    157         sys.exit(1)
    158 
    159     gen = GenUpdateConfig(
    160         package=args.package,
    161         url=args.url,
    162         ab_install_type=args.ab_install_type,
    163         ab_force_switch_slot=args.ab_force_switch_slot,
    164         ab_verify_payload_metadata=args.ab_verify_payload_metadata)
    165     gen.run()
    166     gen.write(args.out)
    167     print('Config is written to ' + args.out)
    168 
    169 
    170 if __name__ == '__main__':
    171     main()
    172