Build super.img from images in target_files

For non-retrofit (launch) devices, super.img is used for factory, so
source images should be from target_files.

In this change, build-superimage-target procedure is converted to a
more flexible script so that it can be built.

Bug: 119322123
Test: build target files for device launch with dynamic partitions
Change-Id: I6ee0cc3e145357dfc74be248f81f5f8f4e51fc5c
diff --git a/core/Makefile b/core/Makefile
index 931085d..dd2f393 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -2967,19 +2967,6 @@
     --output $(1)
 endef
 
-# For A/B devices, super partition always contains sub-partitions in the _a slot, because this
-# image should only be used for bootstrapping / initializing the device. When flashing the image,
-# bootloader fastboot should always mark _a slot as bootable.
-
-ifneq (true,$(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS))
-INSTALLED_SUPERIMAGE_TARGET := $(PRODUCT_OUT)/super.img
-$(INSTALLED_SUPERIMAGE_TARGET): $(LPMAKE) $(call images-for-partitions,$(BOARD_SUPER_PARTITION_PARTITION_LIST))
-	$(call pretty,"Target super fs image: $@")
-	$(call build-superimage-target,$@,$(call super-slot-suffix),true)
-endif
-
-$(call dist-for-goals,dist_files,$(INSTALLED_SUPERIMAGE_TARGET))
-
 INSTALLED_SUPERIMAGE_EMPTY_TARGET := $(PRODUCT_OUT)/super_empty.img
 $(INSTALLED_SUPERIMAGE_EMPTY_TARGET): $(LPMAKE)
 	$(call pretty,"Target empty super fs image: $@")
@@ -3329,6 +3316,31 @@
 (cd $(1); find . -type d | sed 's,$$,/,'; find . \! -type d) | cut -c 3- | sort | sed 's,^,$(2),' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) -R "$(2)"
 endef
 
+# $(1): file
+define dump-dynamic-partitions-info
+  $(if $(filter true,$(PRODUCT_USE_DYNAMIC_PARTITIONS)), \
+    echo "use_dynamic_partitions=true" >> $(1))
+  $(if $(filter true,$(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS)), \
+    echo "dynamic_partition_retrofit=true" >> $(1))
+  $(if $(BOARD_SUPER_PARTITION_SIZE), \
+    echo "lpmake=$(notdir $(LPMAKE))" >> $(1); \
+    echo -n "lpmake_args=" >> $(1); \
+    echo $(call build-superimage-target-args,$(call super-slot-suffix)) >> $(1))
+  echo "super_metadata_device=$(BOARD_SUPER_PARTITION_METADATA_DEVICE)" >> $(1)
+  $(if $(BOARD_SUPER_PARTITION_BLOCK_DEVICES), \
+    echo "super_block_devices=$(BOARD_SUPER_PARTITION_BLOCK_DEVICES)" >> $(1))
+  $(foreach device,$(BOARD_SUPER_PARTITION_BLOCK_DEVICES), \
+    echo "super_$(device)_device_size=$(BOARD_SUPER_PARTITION_$(call to-upper,$(device))_DEVICE_SIZE)" >> $(1);)
+  $(if $(BOARD_SUPER_PARTITION_PARTITION_LIST), \
+    echo "dynamic_partition_list=$(BOARD_SUPER_PARTITION_PARTITION_LIST)" >> $(1))
+  $(if $(BOARD_SUPER_PARTITION_GROUPS),
+    echo "super_partition_groups=$(BOARD_SUPER_PARTITION_GROUPS)" >> $(1))
+  $(foreach group,$(BOARD_SUPER_PARTITION_GROUPS), \
+    echo "super_$(group)_group_size=$(BOARD_$(call to-upper,$(group))_SIZE)" >> $(1); \
+    $(if $(BOARD_$(call to-upper,$(group))_PARTITION_LIST), \
+      echo "super_$(group)_partition_list=$(BOARD_$(call to-upper,$(group))_PARTITION_LIST)" >> $(1);))
+endef
+
 # Depending on the various images guarantees that the underlying
 # directories are up-to-date.
 $(BUILT_TARGET_FILES_PACKAGE): \
@@ -3644,6 +3656,7 @@
 endif # BOARD_AVB_DTBO_KEY_PATH
 endif # BOARD_AVB_ENABLE
 endif # BOARD_PREBUILT_DTBOIMAGE
+	$(call dump-dynamic-partitions-info,$(zip_root)/META/misc_info.txt)
 	@# The radio images in BOARD_PACK_RADIOIMAGES will be additionally copied from RADIO/ into
 	@# IMAGES/, which then will be added into <product>-img.zip. Such images must be listed in
 	@# INSTALLED_RADIOIMAGE_TARGET.
@@ -3689,24 +3702,6 @@
 ifdef BUILT_VENDOR_MATRIX
 	$(hide) cp $(BUILT_VENDOR_MATRIX) $(zip_root)/META/vendor_matrix.xml
 endif
-ifeq ($(PRODUCT_USE_DYNAMIC_PARTITIONS),true)
-	$(hide) echo "use_dynamic_partitions=true" >> $(zip_root)/META/misc_info.txt
-endif
-ifeq ($(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS),true)
-	$(hide) echo "dynamic_partition_retrofit=true" >> $(zip_root)/META/misc_info.txt
-endif
-ifneq ($(BOARD_SUPER_PARTITION_SIZE),)
-	$(hide) echo "lpmake=$(notdir $(LPMAKE))" >> $(zip_root)/META/misc_info.txt
-	$(hide) echo -n "lpmake_args=" >> $(zip_root)/META/misc_info.txt
-	$(hide) echo $(call build-superimage-target-args,$(call super-slot-suffix)) \
-	    >> $(zip_root)/META/misc_info.txt
-endif
-ifneq ($(BOARD_SUPER_PARTITION_BLOCK_DEVICES),)
-	$(hide) echo "super_block_devices=$(BOARD_SUPER_PARTITION_BLOCK_DEVICES)" >> $(zip_root)/META/misc_info.txt
-endif
-ifneq ($(BOARD_SUPER_PARTITION_PARTITION_LIST),)
-	$(hide) echo "dynamic_partition_list=$(BOARD_SUPER_PARTITION_PARTITION_LIST)" >> $(zip_root)/META/misc_info.txt
-endif
 ifneq ($(BOARD_SUPER_PARTITION_GROUPS),)
 	$(hide) echo "super_partition_groups=$(BOARD_SUPER_PARTITION_GROUPS)" > $(zip_root)/META/dynamic_partitions_info.txt
 	$(foreach group,$(BOARD_SUPER_PARTITION_GROUPS), \
@@ -3975,6 +3970,27 @@
 endif # TARGET_BUILD_APPS
 
 # -----------------------------------------------------------------
+# super partition image
+
+ifeq (true,$(PRODUCT_BUILD_SUPER_PARTITION))
+
+# BOARD_SUPER_PARTITION_SIZE must be defined to build super image.
+ifneq ($(BOARD_SUPER_PARTITION_SIZE),)
+
+ifneq (true,$(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS))
+INSTALLED_SUPERIMAGE_TARGET := $(PRODUCT_OUT)/super.img
+$(INSTALLED_SUPERIMAGE_TARGET): extracted_input_target_files := $(patsubst %.zip,%,$(BUILT_TARGET_FILES_PACKAGE))
+$(INSTALLED_SUPERIMAGE_TARGET): $(LPMAKE) $(BUILT_TARGET_FILES_PACKAGE) $(BUILD_SUPER_IMAGE)
+	$(call pretty,"Target super fs image: $@")
+	$(BUILD_SUPER_IMAGE) -v $(extracted_input_target_files) $@
+endif
+
+$(call dist-for-goals,dist_files,$(INSTALLED_SUPERIMAGE_TARGET))
+
+endif # BOARD_SUPER_PARTITION_SIZE != ""
+endif # PRODUCT_BUILD_SUPER_PARTITION == "true"
+
+# -----------------------------------------------------------------
 # dalvik something
 .PHONY: dalvikfiles
 dalvikfiles: $(INTERNAL_DALVIK_MODULES)
diff --git a/core/config.mk b/core/config.mk
index 77f5e6b..9e0ae99 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -698,6 +698,7 @@
 FAT16COPY := build/make/tools/fat16copy.py
 CHECK_LINK_TYPE := build/make/tools/check_link_type.py
 LPMAKE := $(HOST_OUT_EXECUTABLES)/lpmake$(HOST_EXECUTABLE_SUFFIX)
+BUILD_SUPER_IMAGE := build/make/tools/releasetools/build_super_image.py
 
 PROGUARD := external/proguard/bin/proguard.sh
 JAVATAGS := build/make/tools/java-event-log-tags.py
diff --git a/tools/releasetools/build_super_image.py b/tools/releasetools/build_super_image.py
new file mode 100755
index 0000000..6efd3f4
--- /dev/null
+++ b/tools/releasetools/build_super_image.py
@@ -0,0 +1,202 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 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.
+
+"""
+Usage: build_super_image input_file output_dir_or_file
+
+input_file: one of the following:
+  - directory containing extracted target files. It will load info from
+    META/misc_info.txt and build full super image / split images using source
+    images from IMAGES/.
+  - target files package. Same as above, but extracts the archive before
+    building super image.
+  - a dictionary file containing input arguments to build. Check
+    `dump_dynamic_partitions_info' for details.
+    In addition:
+    - "ab_update" needs to be true for A/B devices.
+    - If source images should be included in the output image (for super.img
+      and super split images), a list of "*_image" should be paths of each
+      source images.
+
+output_dir_or_file:
+    If a single super image is built (for super_empty.img, or super.img for
+    launch devices), this argument is the output file.
+    If a collection of split images are built (for retrofit devices), this
+    argument is the output directory.
+"""
+
+from __future__ import print_function
+
+import logging
+import os.path
+import shlex
+import sys
+import zipfile
+
+import common
+import sparse_img
+
+if sys.hexversion < 0x02070000:
+  print("Python 2.7 or newer is required.", file=sys.stderr)
+  sys.exit(1)
+
+logger = logging.getLogger(__name__)
+
+
+UNZIP_PATTERN = ["IMAGES/*", "META/*"]
+
+
+def GetPartitionSizeFromImage(img):
+  try:
+    simg = sparse_img.SparseImage(img)
+    return simg.blocksize * simg.total_blocks
+  except ValueError:
+    return os.path.getsize(img)
+
+
+def BuildSuperImageFromDict(info_dict, output):
+
+  cmd = [info_dict["lpmake"],
+         "--metadata-size", "65536",
+         "--super-name", info_dict["super_metadata_device"]]
+
+  ab_update = info_dict.get("ab_update") == "true"
+  retrofit = info_dict.get("dynamic_partition_retrofit") == "true"
+  block_devices = shlex.split(info_dict.get("super_block_devices", "").strip())
+  groups = shlex.split(info_dict.get("super_partition_groups", "").strip())
+
+  if ab_update:
+    cmd += ["--metadata-slots", "2"]
+  else:
+    cmd += ["--metadata-slots", "1"]
+
+  if ab_update and retrofit:
+    cmd.append("--auto-slot-suffixing")
+
+  for device in block_devices:
+    size = info_dict["super_{}_device_size".format(device)]
+    cmd += ["--device", "{}:{}".format(device, size)]
+
+  append_suffix = ab_update and not retrofit
+  has_image = False
+  for group in groups:
+    group_size = info_dict["super_{}_group_size".format(group)]
+    if append_suffix:
+      cmd += ["--group", "{}_a:{}".format(group, group_size),
+              "--group", "{}_b:{}".format(group, group_size)]
+    else:
+      cmd += ["--group", "{}:{}".format(group, group_size)]
+
+    partition_list = shlex.split(
+        info_dict["super_{}_partition_list".format(group)].strip())
+
+    for partition in partition_list:
+      image = info_dict.get("{}_image".format(partition))
+      image_size = 0
+      if image:
+        image_size = GetPartitionSizeFromImage(image)
+        has_image = True
+      if append_suffix:
+        cmd += ["--partition",
+                "{}_a:readonly:{}:{}_a".format(partition, image_size, group),
+                "--partition",
+                "{}_b:readonly:0:{}_b".format(partition, group)]
+        if image:
+          # For A/B devices, super partition always contains sub-partitions in
+          # the _a slot, because this image should only be used for
+          # bootstrapping / initializing the device. When flashing the image,
+          # bootloader fastboot should always mark _a slot as bootable.
+          cmd += ["--image", "{}_a={}".format(partition, image)]
+      else:
+        cmd += ["--partition",
+                "{}:readonly:{}:{}".format(partition, image_size, group)]
+        if image:
+          cmd += ["--image", "{}={}".format(partition, image)]
+
+  if has_image:
+    cmd.append("--sparse")
+
+  cmd += ["--output", output]
+
+  common.RunAndCheckOutput(cmd)
+
+  if retrofit and has_image:
+    logger.info("Done writing images to directory %s", output)
+  else:
+    logger.info("Done writing image %s", output)
+
+
+def BuildSuperImageFromExtractedTargetFiles(inp, out):
+  info_dict = common.LoadInfoDict(inp)
+  partition_list = shlex.split(
+      info_dict.get("dynamic_partition_list", "").strip())
+  for partition in partition_list:
+    info_dict["{}_image".format(partition)] = os.path.join(
+        inp, "IMAGES", "{}.img".format(partition))
+  return BuildSuperImageFromDict(info_dict, out)
+
+
+def BuildSuperImageFromTargetFiles(inp, out):
+  input_tmp = common.UnzipTemp(inp, UNZIP_PATTERN)
+  return BuildSuperImageFromExtractedTargetFiles(input_tmp, out)
+
+
+def BuildSuperImage(inp, out):
+
+  if isinstance(inp, dict):
+    logger.info("Building super image from info dict...")
+    return BuildSuperImageFromDict(inp, out)
+
+  if isinstance(inp, str):
+    if os.path.isdir(inp):
+      logger.info("Building super image from extracted target files...")
+      return BuildSuperImageFromExtractedTargetFiles(inp, out)
+
+    if zipfile.is_zipfile(inp):
+      logger.info("Building super image from target files...")
+      return BuildSuperImageFromTargetFiles(inp, out)
+
+    if os.path.isfile(inp):
+      with open(inp) as f:
+        lines = f.read()
+      logger.info("Building super image from info dict...")
+      return BuildSuperImageFromDict(common.LoadDictionaryFromLines(lines.split("\n")), out)
+
+  raise ValueError("{} is not a dictionary or a valid path".format(inp))
+
+
+def main(argv):
+
+  args = common.ParseOptions(argv, __doc__)
+
+  if len(args) != 2:
+    common.Usage(__doc__)
+    sys.exit(1)
+
+  common.InitLogging()
+
+  BuildSuperImage(args[0], args[1])
+
+
+if __name__ == "__main__":
+  try:
+    common.CloseInheritedPipes()
+    main(sys.argv[1:])
+  except common.ExternalError:
+    logger.exception("\n   ERROR:\n")
+    sys.exit(1)
+  finally:
+    common.Cleanup()