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
diff --git a/tools/releasetools/ota_from_raw_img.py b/tools/releasetools/ota_from_raw_img.py
new file mode 100644
index 0000000..ac4f9fb
--- /dev/null
+++ b/tools/releasetools/ota_from_raw_img.py
@@ -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)