Add a tool to generate OTA from images

During build, we will need to generate an OTA for boot partition using a
16K boot image. Typically, OTA is generated from target_files.zip . To
avoid relying on target_files.zip as a dependency for 16K OTA, add a
tool to generate OTA directly from a raw image.

Test: th, ota_from_raw_img --partition_name boot --output ota.zip $OUT/boot_16k.img
Bug: 293313353
Change-Id: I2076332faf2a8dc573450597efd481e285a49545
This commit is contained in:
Kelvin Zhang 2023-08-22 08:56:30 -07:00
parent a169e4f7f6
commit c7441e5907
5 changed files with 179 additions and 25 deletions

View file

@ -344,6 +344,7 @@ python_binary_host {
},
srcs: [
"merge_ota.py",
"ota_signing_utils.py",
],
libs: [
"ota_metadata_proto",
@ -492,6 +493,26 @@ python_binary_host {
],
}
python_binary_host {
name: "ota_from_raw_img",
srcs: [
"ota_from_raw_img.py",
"ota_signing_utils.py",
],
main: "ota_from_raw_img.py",
defaults: [
"releasetools_binary_defaults",
],
required: [
"delta_generator",
],
libs: [
"ota_metadata_proto",
"releasetools_common",
"ota_utils_lib",
],
}
python_binary_host {
name: "ota_package_parser",
defaults: ["releasetools_binary_defaults"],
@ -590,6 +611,7 @@ python_defaults {
"sign_target_files_apks.py",
"validate_target_files.py",
"merge_ota.py",
"ota_signing_utils.py",
":releasetools_merge_sources",
":releasetools_merge_tests",

View file

@ -14,7 +14,6 @@
import argparse
import logging
import shlex
import struct
import sys
import update_payload
@ -31,6 +30,7 @@ from update_payload import Payload
from payload_signer import PayloadSigner
from ota_utils import PayloadGenerator, METADATA_PROTO_NAME, FinalizeMetadata
from ota_signing_utils import AddSigningArgumentParse
logger = logging.getLogger(__name__)
@ -126,7 +126,7 @@ def MergeManifests(payloads: List[Payload]) -> DeltaArchiveManifest:
ExtendPartitionUpdates(output_manifest.partitions, manifest.partitions)
try:
MergeDynamicPartitionMetadata(
output_manifest.dynamic_partition_metadata, manifest.dynamic_partition_metadata)
output_manifest.dynamic_partition_metadata, manifest.dynamic_partition_metadata)
except DuplicatePartitionError:
logger.error(
"OTA %s has duplicate partition with some of the previous OTAs", payload.name)
@ -190,6 +190,7 @@ def CheckDuplicatePartitions(payloads: List[Payload]):
f"OTA {partition_to_ota[part].name} and {payload.name} have duplicating partition {part}")
partition_to_ota[part] = payload
def ApexInfo(file_paths):
if len(file_paths) > 1:
logger.info("More than one target file specified, will ignore "
@ -201,33 +202,19 @@ def ApexInfo(file_paths):
return apex_info_bytes
return None
def ParseSignerArgs(args):
if args is None:
return None
return shlex.split(args)
def main(argv):
parser = argparse.ArgumentParser(description='Merge multiple partial OTAs')
parser.add_argument('packages', type=str, nargs='+',
help='Paths to OTA packages to merge')
parser.add_argument('--package_key', type=str,
help='Paths to private key for signing payload')
parser.add_argument('--search_path', type=str,
help='Search path for framework/signapk.jar')
parser.add_argument('--payload_signer', type=str,
help='Path to custom payload signer')
parser.add_argument('--payload_signer_args', type=ParseSignerArgs,
help='Arguments for payload signer if necessary')
parser.add_argument('--payload_signer_maximum_signature_size', type=str,
help='Maximum signature size (in bytes) that would be '
'generated by the given payload signer')
parser.add_argument('--output', type=str,
help='Paths to output merged ota', required=True)
parser.add_argument('--metadata_ota', type=str,
help='Output zip will use build metadata from this OTA package, if unspecified, use the last OTA package in merge list')
parser.add_argument('--private_key_suffix', type=str,
help='Suffix to be appended to package_key path', default=".pk8")
parser.add_argument('-v', action="store_true", help="Enable verbose logging", dest="verbose")
parser.add_argument('-v', action="store_true",
help="Enable verbose logging", dest="verbose")
AddSigningArgumentParse(parser)
parser.epilog = ('This tool can also be used to resign a regular OTA. For a single regular OTA, '
'apex_info.pb will be written to output. When merging multiple OTAs, '
'apex_info.pb will not be written.')
@ -301,8 +288,6 @@ def main(argv):
return 0
if __name__ == '__main__':
logging.basicConfig()
sys.exit(main(sys.argv))

View file

@ -0,0 +1,109 @@
#!/usr/bin/env python
#
# Copyright (C) 2008 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.
"""
Given a series of .img files, produces an OTA package that installs thoese images
"""
import sys
import os
import argparse
import subprocess
import tempfile
import logging
import zipfile
import common
from payload_signer import PayloadSigner
from ota_utils import PayloadGenerator
from ota_signing_utils import AddSigningArgumentParse
logger = logging.getLogger(__name__)
def ResolveBinaryPath(filename, search_path):
if not search_path:
return filename
if not os.path.exists(search_path):
return filename
path = os.path.join(search_path, "bin", filename)
if os.path.exists(path):
return path
path = os.path.join(search_path, filename)
if os.path.exists(path):
return path
return path
def main(argv):
parser = argparse.ArgumentParser(
prog=argv[0], description="Given a series of .img files, produces a full OTA package that installs thoese images")
parser.add_argument("images", nargs="+", type=str,
help="List of images to generate OTA")
parser.add_argument("--partition_names", nargs='+', type=str,
help="Partition names to install the images, default to basename of the image(no file name extension)")
parser.add_argument('--output', type=str,
help='Paths to output merged ota', required=True)
parser.add_argument("-v", action="store_true",
help="Enable verbose logging", dest="verbose")
AddSigningArgumentParse(parser)
args = parser.parse_args(argv[1:])
if args.verbose:
logger.setLevel(logging.INFO)
logger.info(args)
if not args.partition_names:
args.partition_names = [os.path.os.path.splitext(os.path.basename(path))[
0] for path in args.images]
with tempfile.NamedTemporaryFile() as unsigned_payload:
cmd = [ResolveBinaryPath("delta_generator", args.search_path)]
cmd.append("--partition_names=" + ",".join(args.partition_names))
cmd.append("--new_partitions=" + ",".join(args.images))
cmd.append("--out_file=" + unsigned_payload.name)
logger.info("Running %s", cmd)
subprocess.run(cmd)
generator = PayloadGenerator()
generator.payload_file = unsigned_payload.name
logger.info("Payload size: %d", os.path.getsize(generator.payload_file))
# Get signing keys
key_passwords = common.GetKeyPasswords([args.package_key])
if args.package_key:
logger.info("Signing payload...")
# TODO: remove OPTIONS when no longer used as fallback in payload_signer
common.OPTIONS.payload_signer_args = None
common.OPTIONS.payload_signer_maximum_signature_size = None
signer = PayloadSigner(args.package_key, args.private_key_suffix,
key_passwords[args.package_key],
payload_signer=args.payload_signer,
payload_signer_args=args.payload_signer_args,
payload_signer_maximum_signature_size=args.payload_signer_maximum_signature_size)
generator.payload_file = unsigned_payload.name
generator.Sign(signer)
logger.info("Payload size: %d", os.path.getsize(generator.payload_file))
logger.info("Writing to %s", args.output)
with zipfile.ZipFile(args.output, "w") as zfp:
generator.WriteToZip(zfp)
if __name__ == "__main__":
logging.basicConfig()
main(sys.argv)

View file

@ -0,0 +1,38 @@
# Copyright (C) 2022 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 argparse
import shlex
def ParseSignerArgs(args):
if args is None:
return None
return shlex.split(args)
def AddSigningArgumentParse(parser: argparse.ArgumentParser):
parser.add_argument('--package_key', type=str,
help='Paths to private key for signing payload')
parser.add_argument('--search_path', '--path', type=str,
help='Search path for framework/signapk.jar')
parser.add_argument('--payload_signer', type=str,
help='Path to custom payload signer')
parser.add_argument('--payload_signer_args', type=ParseSignerArgs,
help='Arguments for payload signer if necessary')
parser.add_argument('--payload_signer_maximum_signature_size', type=str,
help='Maximum signature size (in bytes) that would be '
'generated by the given payload signer')
parser.add_argument('--private_key_suffix', type=str,
help='Suffix to be appended to package_key path', default=".pk8")

View file

@ -934,9 +934,9 @@ class PayloadGenerator(object):
# 4. Dump the signed payload properties.
properties_file = common.MakeTempFile(prefix="payload-properties-",
suffix=".txt")
cmd = ["brillo_update_payload", "properties",
"--payload", self.payload_file,
"--properties_file", properties_file]
cmd = ["delta_generator",
"--in_file=" + self.payload_file,
"--properties_file=" + properties_file]
self._Run(cmd)
if self.secondary: