blob: cb9bd0119aaf4489e174c1dfaec6a0697cdd24a6 [file] [log] [blame]
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -07001#!/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"""
18Given a OTA package file, produces update config JSON file.
19
20Example: tools/gen_update.config.py \\
21 --ab_install_type=STREAMING \\
22 ota-build-001.zip \\
23 my-config-001.json \\
24 http://foo.bar/ota-builds/ota-build-001.zip
25"""
26
27import argparse
28import json
29import os.path
30import sys
31import zipfile
32
33
Zhomart Mukhamejanov72a4d462018-04-26 15:49:08 -070034class GenUpdateConfig(object):
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -070035 """
36 A class that generates update configuration file from an OTA package.
37
38 Currently supports only A/B (seamless) OTA packages.
39 TODO: add non-A/B packages support.
40 """
41
42 AB_INSTALL_TYPE_STREAMING = 'STREAMING'
43 AB_INSTALL_TYPE_NON_STREAMING = 'NON_STREAMING'
44 METADATA_NAME = 'META-INF/com/android/metadata'
45
Zhomart Mukhamejanov72a4d462018-04-26 15:49:08 -070046 def __init__(self, package, url, ab_install_type):
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -070047 self.package = package
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -070048 self.url = url
49 self.ab_install_type = ab_install_type
50 self.streaming_required = (
51 # payload.bin and payload_properties.txt must exist.
52 'payload.bin',
53 'payload_properties.txt',
54 )
55 self.streaming_optional = (
56 # care_map.txt is available only if dm-verity is enabled.
57 'care_map.txt',
58 # compatibility.zip is available only if target supports Treble.
59 'compatibility.zip',
60 )
Zhomart Mukhamejanov72a4d462018-04-26 15:49:08 -070061 self._config = None
62
63 @property
64 def config(self):
65 """Returns generated config object."""
66 return self._config
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -070067
68 def run(self):
Zhomart Mukhamejanov72a4d462018-04-26 15:49:08 -070069 """Generates config."""
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -070070 streaming_metadata = None
71 if self.ab_install_type == GenUpdateConfig.AB_INSTALL_TYPE_STREAMING:
72 streaming_metadata = self._gen_ab_streaming_metadata()
73
Zhomart Mukhamejanov72a4d462018-04-26 15:49:08 -070074 self._config = {
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -070075 '__': '*** Generated using tools/gen_update_config.py ***',
76 'name': self.ab_install_type[0] + ' ' + os.path.basename(self.package)[:-4],
77 'url': self.url,
78 'ab_streaming_metadata': streaming_metadata,
79 'ab_install_type': self.ab_install_type,
80 }
81
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -070082 def _gen_ab_streaming_metadata(self):
Zhomart Mukhamejanov72a4d462018-04-26 15:49:08 -070083 """Builds metadata for files required for streaming update."""
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -070084 with zipfile.ZipFile(self.package, 'r') as package_zip:
85 property_files = self._get_property_files(package_zip)
86
87 metadata = {
88 'property_files': property_files
89 }
90
91 return metadata
92
93 def _get_property_files(self, zip_file):
Zhomart Mukhamejanov72a4d462018-04-26 15:49:08 -070094 """Constructs the property-files list for A/B streaming metadata."""
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -070095
96 def compute_entry_offset_size(name):
97 """Computes the zip entry offset and size."""
98 info = zip_file.getinfo(name)
99 offset = info.header_offset + len(info.FileHeader())
100 size = info.file_size
101 return {
102 'filename': os.path.basename(name),
103 'offset': offset,
104 'size': size,
105 }
106
107 property_files = []
108 for entry in self.streaming_required:
109 property_files.append(compute_entry_offset_size(entry))
110 for entry in self.streaming_optional:
111 if entry in zip_file.namelist():
112 property_files.append(compute_entry_offset_size(entry))
113
114 # 'META-INF/com/android/metadata' is required
115 property_files.append(compute_entry_offset_size(GenUpdateConfig.METADATA_NAME))
116
117 return property_files
118
Zhomart Mukhamejanov72a4d462018-04-26 15:49:08 -0700119 def write(self, out):
120 """Writes config to the output file."""
121 with open(out, 'w') as out_file:
122 json.dump(self.config, out_file, indent=4, separators=(',', ': '), sort_keys=True)
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -0700123
Zhomart Mukhamejanov72a4d462018-04-26 15:49:08 -0700124
125def main(): # pylint: disable=missing-docstring
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -0700126 ab_install_type_choices = [
127 GenUpdateConfig.AB_INSTALL_TYPE_STREAMING,
128 GenUpdateConfig.AB_INSTALL_TYPE_NON_STREAMING]
129 parser = argparse.ArgumentParser(description=__doc__,
130 formatter_class=argparse.RawDescriptionHelpFormatter)
131 parser.add_argument('--ab_install_type',
132 type=str,
133 default=GenUpdateConfig.AB_INSTALL_TYPE_NON_STREAMING,
134 choices=ab_install_type_choices,
135 help='A/B update installation type')
136 parser.add_argument('package',
137 type=str,
138 help='OTA package zip file')
139 parser.add_argument('out',
140 type=str,
141 help='Update configuration JSON file')
142 parser.add_argument('url',
143 type=str,
144 help='OTA package download url')
145 args = parser.parse_args()
146
147 if not args.out.endswith('.json'):
148 print('out must be a json file')
149 sys.exit(1)
150
151 gen = GenUpdateConfig(
152 package=args.package,
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -0700153 url=args.url,
154 ab_install_type=args.ab_install_type)
155 gen.run()
Zhomart Mukhamejanov72a4d462018-04-26 15:49:08 -0700156 gen.write(args.out)
157 print('Config is written to ' + args.out)
Zhomart Mukhamejanovd5a41822018-04-24 18:17:57 -0700158
159
160if __name__ == '__main__':
161 main()