xref: /aosp_15_r20/build/make/tools/releasetools/ota_from_raw_img.py (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1#!/usr/bin/env python
2#
3# Copyright (C) 2008 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 series of .img files, produces an OTA package that installs thoese images
19"""
20
21import sys
22import os
23import argparse
24import subprocess
25import tempfile
26import logging
27import zipfile
28
29import common
30from payload_signer import PayloadSigner
31from ota_utils import PayloadGenerator
32from ota_signing_utils import AddSigningArgumentParse
33
34
35logger = logging.getLogger(__name__)
36
37
38def ResolveBinaryPath(filename, search_path):
39  if not search_path:
40    return filename
41  if not os.path.exists(search_path):
42    return filename
43  path = os.path.join(search_path, "bin", filename)
44  if os.path.exists(path):
45    return path
46  path = os.path.join(search_path, filename)
47  if os.path.exists(path):
48    return path
49  return path
50
51
52def main(argv):
53  parser = argparse.ArgumentParser(
54      prog=argv[0], description="Given a series of .img files, produces a full OTA package that installs thoese images")
55  parser.add_argument("images", nargs="+", type=str,
56                      help="List of images to generate OTA")
57  parser.add_argument("--partition_names", nargs='?', type=str,
58                      help="Partition names to install the images, default to basename of the image(no file name extension)")
59  parser.add_argument('--output', type=str,
60                      help='Paths to output merged ota', required=True)
61  parser.add_argument('--max_timestamp', type=int,
62                      help='Maximum build timestamp allowed to install this OTA')
63  parser.add_argument("-v", action="store_true",
64                      help="Enable verbose logging", dest="verbose")
65  AddSigningArgumentParse(parser)
66
67  args = parser.parse_args(argv[1:])
68  if args.verbose:
69    logger.setLevel(logging.INFO)
70  logger.info(args)
71  old_imgs = [""] * len(args.images)
72  for (i, img) in enumerate(args.images):
73    if ":" in img:
74      old_imgs[i], args.images[i] = img.split(":", maxsplit=1)
75
76  if not args.partition_names:
77    args.partition_names = [os.path.splitext(os.path.basename(path))[
78        0] for path in args.images]
79  else:
80    args.partition_names = args.partition_names.split(",")
81  with tempfile.NamedTemporaryFile() as unsigned_payload, tempfile.NamedTemporaryFile() as dynamic_partition_info_file:
82    dynamic_partition_info_file.writelines(
83        [b"virtual_ab=true\n", b"super_partition_groups=\n"])
84    dynamic_partition_info_file.flush()
85    cmd = [ResolveBinaryPath("delta_generator", args.search_path)]
86    cmd.append("--partition_names=" + ":".join(args.partition_names))
87    cmd.append("--dynamic_partition_info_file=" +
88               dynamic_partition_info_file.name)
89    cmd.append("--old_partitions=" + ":".join(old_imgs))
90    cmd.append("--new_partitions=" + ":".join(args.images))
91    cmd.append("--out_file=" + unsigned_payload.name)
92    cmd.append("--is_partial_update")
93    if args.max_timestamp:
94      cmd.append("--max_timestamp=" + str(args.max_timestamp))
95      cmd.append("--partition_timestamps=boot:" + str(args.max_timestamp))
96    logger.info("Running %s", cmd)
97
98    subprocess.check_call(cmd)
99    generator = PayloadGenerator()
100    generator.payload_file = unsigned_payload.name
101    logger.info("Payload size: %d", os.path.getsize(generator.payload_file))
102
103    # Get signing keys
104    key_passwords = common.GetKeyPasswords([args.package_key])
105
106    if args.package_key:
107      logger.info("Signing payload...")
108      signer = PayloadSigner(args.package_key, args.private_key_suffix,
109                             key_passwords[args.package_key],
110                             payload_signer=args.payload_signer,
111                             payload_signer_args=args.payload_signer_args,
112                             payload_signer_maximum_signature_size=args.payload_signer_maximum_signature_size)
113      generator.payload_file = unsigned_payload.name
114      generator.Sign(signer)
115
116    logger.info("Payload size: %d", os.path.getsize(generator.payload_file))
117
118    logger.info("Writing to %s", args.output)
119    with zipfile.ZipFile(args.output, "w") as zfp:
120      generator.WriteToZip(zfp)
121
122
123if __name__ == "__main__":
124  logging.basicConfig()
125  main(sys.argv)
126