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:
parent
a169e4f7f6
commit
c7441e5907
5 changed files with 179 additions and 25 deletions
|
@ -344,6 +344,7 @@ python_binary_host {
|
||||||
},
|
},
|
||||||
srcs: [
|
srcs: [
|
||||||
"merge_ota.py",
|
"merge_ota.py",
|
||||||
|
"ota_signing_utils.py",
|
||||||
],
|
],
|
||||||
libs: [
|
libs: [
|
||||||
"ota_metadata_proto",
|
"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 {
|
python_binary_host {
|
||||||
name: "ota_package_parser",
|
name: "ota_package_parser",
|
||||||
defaults: ["releasetools_binary_defaults"],
|
defaults: ["releasetools_binary_defaults"],
|
||||||
|
@ -590,6 +611,7 @@ python_defaults {
|
||||||
"sign_target_files_apks.py",
|
"sign_target_files_apks.py",
|
||||||
"validate_target_files.py",
|
"validate_target_files.py",
|
||||||
"merge_ota.py",
|
"merge_ota.py",
|
||||||
|
"ota_signing_utils.py",
|
||||||
":releasetools_merge_sources",
|
":releasetools_merge_sources",
|
||||||
":releasetools_merge_tests",
|
":releasetools_merge_tests",
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import shlex
|
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import update_payload
|
import update_payload
|
||||||
|
@ -31,6 +30,7 @@ from update_payload import Payload
|
||||||
|
|
||||||
from payload_signer import PayloadSigner
|
from payload_signer import PayloadSigner
|
||||||
from ota_utils import PayloadGenerator, METADATA_PROTO_NAME, FinalizeMetadata
|
from ota_utils import PayloadGenerator, METADATA_PROTO_NAME, FinalizeMetadata
|
||||||
|
from ota_signing_utils import AddSigningArgumentParse
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -190,6 +190,7 @@ def CheckDuplicatePartitions(payloads: List[Payload]):
|
||||||
f"OTA {partition_to_ota[part].name} and {payload.name} have duplicating partition {part}")
|
f"OTA {partition_to_ota[part].name} and {payload.name} have duplicating partition {part}")
|
||||||
partition_to_ota[part] = payload
|
partition_to_ota[part] = payload
|
||||||
|
|
||||||
|
|
||||||
def ApexInfo(file_paths):
|
def ApexInfo(file_paths):
|
||||||
if len(file_paths) > 1:
|
if len(file_paths) > 1:
|
||||||
logger.info("More than one target file specified, will ignore "
|
logger.info("More than one target file specified, will ignore "
|
||||||
|
@ -201,33 +202,19 @@ def ApexInfo(file_paths):
|
||||||
return apex_info_bytes
|
return apex_info_bytes
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def ParseSignerArgs(args):
|
|
||||||
if args is None:
|
|
||||||
return None
|
|
||||||
return shlex.split(args)
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
parser = argparse.ArgumentParser(description='Merge multiple partial OTAs')
|
parser = argparse.ArgumentParser(description='Merge multiple partial OTAs')
|
||||||
parser.add_argument('packages', type=str, nargs='+',
|
parser.add_argument('packages', type=str, nargs='+',
|
||||||
help='Paths to OTA packages to merge')
|
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,
|
parser.add_argument('--output', type=str,
|
||||||
help='Paths to output merged ota', required=True)
|
help='Paths to output merged ota', required=True)
|
||||||
parser.add_argument('--metadata_ota', type=str,
|
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')
|
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,
|
parser.add_argument('-v', action="store_true",
|
||||||
help='Suffix to be appended to package_key path', default=".pk8")
|
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, '
|
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 be written to output. When merging multiple OTAs, '
|
||||||
'apex_info.pb will not be written.')
|
'apex_info.pb will not be written.')
|
||||||
|
@ -301,8 +288,6 @@ def main(argv):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
sys.exit(main(sys.argv))
|
sys.exit(main(sys.argv))
|
||||||
|
|
109
tools/releasetools/ota_from_raw_img.py
Normal file
109
tools/releasetools/ota_from_raw_img.py
Normal 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)
|
38
tools/releasetools/ota_signing_utils.py
Normal file
38
tools/releasetools/ota_signing_utils.py
Normal 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")
|
|
@ -934,9 +934,9 @@ class PayloadGenerator(object):
|
||||||
# 4. Dump the signed payload properties.
|
# 4. Dump the signed payload properties.
|
||||||
properties_file = common.MakeTempFile(prefix="payload-properties-",
|
properties_file = common.MakeTempFile(prefix="payload-properties-",
|
||||||
suffix=".txt")
|
suffix=".txt")
|
||||||
cmd = ["brillo_update_payload", "properties",
|
cmd = ["delta_generator",
|
||||||
"--payload", self.payload_file,
|
"--in_file=" + self.payload_file,
|
||||||
"--properties_file", properties_file]
|
"--properties_file=" + properties_file]
|
||||||
self._Run(cmd)
|
self._Run(cmd)
|
||||||
|
|
||||||
if self.secondary:
|
if self.secondary:
|
||||||
|
|
Loading…
Reference in a new issue