diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp index d4c46732c..40cdce8b9 100644 --- a/tools/releasetools/Android.bp +++ b/tools/releasetools/Android.bp @@ -106,6 +106,19 @@ python_binary_host { ], } +python_binary_host { + name: "merge_builds", + defaults: ["releasetools_binary_defaults"], + srcs: [ + "build_super_image.py", + "merge_builds.py", + ], + main: "merge_builds.py", + libs: [ + "releasetools_common", + ], +} + python_defaults { name: "releasetools_test_defaults", srcs: [ diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index 913601fdb..117568892 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -554,6 +554,64 @@ def DumpInfoDict(d): logger.info("%-25s = (%s) %s", k, type(v).__name__, v) +def MergeDynamicPartitionInfoDicts(framework_dict, + vendor_dict, + include_dynamic_partition_list=True, + size_prefix="", + size_suffix="", + list_prefix="", + list_suffix=""): + """Merges dynamic partition info variables. + + Args: + framework_dict: The dictionary of dynamic partition info variables from the + partial framework target files. + vendor_dict: The dictionary of dynamic partition info variables from the + partial vendor target files. + include_dynamic_partition_list: If true, merges the dynamic_partition_list + variable. Not all use cases need this variable merged. + size_prefix: The prefix in partition group size variables that precedes the + name of the partition group. For example, partition group 'group_a' with + corresponding size variable 'super_group_a_group_size' would have the + size_prefix 'super_'. + size_suffix: Similar to size_prefix but for the variable's suffix. For + example, 'super_group_a_group_size' would have size_suffix '_group_size'. + list_prefix: Similar to size_prefix but for the partition group's + partition_list variable. + list_suffix: Similar to size_suffix but for the partition group's + partition_list variable. + + Returns: + The merged dynamic partition info dictionary. + """ + merged_dict = {} + # Partition groups and group sizes are defined by the vendor dict because + # these values may vary for each board that uses a shared system image. + merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"] + if include_dynamic_partition_list: + framework_dynamic_partition_list = framework_dict.get( + "dynamic_partition_list", "") + vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", + "") + merged_dict["dynamic_partition_list"] = ( + "%s %s" % (framework_dynamic_partition_list, + vendor_dynamic_partition_list)).strip() + for partition_group in merged_dict["super_partition_groups"].split(" "): + # Set the partition group's size using the value from the vendor dict. + key = "%s%s%s" % (size_prefix, partition_group, size_suffix) + if key not in vendor_dict: + raise ValueError("Vendor dict does not contain required key %s." % key) + merged_dict[key] = vendor_dict[key] + + # Set the partition group's partition list using a concatenation of the + # framework and vendor partition lists. + key = "%s%s%s" % (list_prefix, partition_group, list_suffix) + merged_dict[key] = ( + "%s %s" % + (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip() + return merged_dict + + def AppendAVBSigningArgs(cmd, partition): """Append signing arguments for avbtool.""" # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096" diff --git a/tools/releasetools/merge_builds.py b/tools/releasetools/merge_builds.py new file mode 100644 index 000000000..7724d6f9d --- /dev/null +++ b/tools/releasetools/merge_builds.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# +# Copyright (C) 2019 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. +# +"""Merges two non-dist partial builds together. + +Given two partial builds, a framework build and a vendor build, merge the builds +together so that the images can be flashed using 'fastboot flashall'. + +To support both DAP and non-DAP vendor builds with a single framework partial +build, the framework partial build should always be built with DAP enabled. The +vendor partial build determines whether the merged result supports DAP. + +This script does not require builds to be built with 'make dist'. +This script assumes that images other than super_empty.img do not require +regeneration, including vbmeta images. +TODO(b/137853921): Add support for regenerating vbmeta images. + +Usage: merge_builds.py [args] + + --framework_images comma_separated_image_list + Comma-separated list of image names that should come from the framework + build. + + --product_out_framework product_out_framework_path + Path to out/target/product/. + + --product_out_vendor product_out_vendor_path + Path to out/target/product/. +""" +from __future__ import print_function + +import logging +import os +import sys + +import build_super_image +import common + +logger = logging.getLogger(__name__) + +OPTIONS = common.OPTIONS +OPTIONS.framework_images = ("system",) +OPTIONS.product_out_framework = None +OPTIONS.product_out_vendor = None + + +def CreateImageSymlinks(): + for image in OPTIONS.framework_images: + image_path = os.path.join(OPTIONS.product_out_framework, "%s.img" % image) + symlink_path = os.path.join(OPTIONS.product_out_vendor, "%s.img" % image) + if os.path.exists(symlink_path): + if os.path.islink(symlink_path): + os.remove(symlink_path) + else: + raise ValueError("Attempting to overwrite built image: %s" % + symlink_path) + os.symlink(image_path, symlink_path) + + +def BuildSuperEmpty(): + framework_dict = common.LoadDictionaryFromFile( + os.path.join(OPTIONS.product_out_framework, "misc_info.txt")) + vendor_dict = common.LoadDictionaryFromFile( + os.path.join(OPTIONS.product_out_vendor, "misc_info.txt")) + # Regenerate super_empty.img if both partial builds enable DAP. If only the + # the vendor build enables DAP, the vendor build's existing super_empty.img + # will be reused. If only the framework build should enable DAP, super_empty + # should be included in the --framework_images flag to copy the existing + # super_empty.img from the framework build. + if (framework_dict.get("use_dynamic_partitions") == "true") and ( + vendor_dict.get("use_dynamic_partitions") == "true"): + merged_dict = dict(vendor_dict) + merged_dict.update( + common.MergeDynamicPartitionInfoDicts( + framework_dict=framework_dict, + vendor_dict=vendor_dict, + size_prefix="super_", + size_suffix="_group_size", + list_prefix="super_", + list_suffix="_partition_list")) + output_super_empty_path = os.path.join(OPTIONS.product_out_vendor, + "super_empty.img") + build_super_image.BuildSuperImage(merged_dict, output_super_empty_path) + + +def MergeBuilds(): + CreateImageSymlinks() + BuildSuperEmpty() + # TODO(b/137853921): Add support for regenerating vbmeta images. + + +def main(): + common.InitLogging() + + def option_handler(o, a): + if o == "--framework_images": + OPTIONS.framework_images = [i.strip() for i in a.split(",")] + elif o == "--product_out_framework": + OPTIONS.product_out_framework = a + elif o == "--product_out_vendor": + OPTIONS.product_out_vendor = a + else: + return False + return True + + args = common.ParseOptions( + sys.argv[1:], + __doc__, + extra_long_opts=[ + "framework_images=", + "product_out_framework=", + "product_out_vendor=", + ], + extra_option_handler=option_handler) + + if (args or OPTIONS.product_out_framework is None or + OPTIONS.product_out_vendor is None): + common.Usage(__doc__) + sys.exit(1) + + MergeBuilds() + + +if __name__ == "__main__": + main() diff --git a/tools/releasetools/merge_target_files.py b/tools/releasetools/merge_target_files.py index 7343f389b..81b95c807 100755 --- a/tools/releasetools/merge_target_files.py +++ b/tools/releasetools/merge_target_files.py @@ -402,64 +402,6 @@ def append_recovery_to_filesystem_config(output_target_files_temp_dir): 'selabel=u:object_r:install_recovery_exec:s0 capabilities=0x0\n') -def merge_dynamic_partition_info_dicts(framework_dict, - vendor_dict, - include_dynamic_partition_list=True, - size_prefix='', - size_suffix='', - list_prefix='', - list_suffix=''): - """Merges dynamic partition info variables. - - Args: - framework_dict: The dictionary of dynamic partition info variables from the - partial framework target files. - vendor_dict: The dictionary of dynamic partition info variables from the - partial vendor target files. - include_dynamic_partition_list: If true, merges the dynamic_partition_list - variable. Not all use cases need this variable merged. - size_prefix: The prefix in partition group size variables that precedes the - name of the partition group. For example, partition group 'group_a' with - corresponding size variable 'super_group_a_group_size' would have the - size_prefix 'super_'. - size_suffix: Similar to size_prefix but for the variable's suffix. For - example, 'super_group_a_group_size' would have size_suffix '_group_size'. - list_prefix: Similar to size_prefix but for the partition group's - partition_list variable. - list_suffix: Similar to size_suffix but for the partition group's - partition_list variable. - - Returns: - The merged dynamic partition info dictionary. - """ - merged_dict = {} - # Partition groups and group sizes are defined by the vendor dict because - # these values may vary for each board that uses a shared system image. - merged_dict['super_partition_groups'] = vendor_dict['super_partition_groups'] - if include_dynamic_partition_list: - framework_dynamic_partition_list = framework_dict.get( - 'dynamic_partition_list', '') - vendor_dynamic_partition_list = vendor_dict.get('dynamic_partition_list', - '') - merged_dict['dynamic_partition_list'] = ( - '%s %s' % (framework_dynamic_partition_list, - vendor_dynamic_partition_list)).strip() - for partition_group in merged_dict['super_partition_groups'].split(' '): - # Set the partition group's size using the value from the vendor dict. - key = '%s%s%s' % (size_prefix, partition_group, size_suffix) - if key not in vendor_dict: - raise ValueError('Vendor dict does not contain required key %s.' % key) - merged_dict[key] = vendor_dict[key] - - # Set the partition group's partition list using a concatenation of the - # framework and vendor partition lists. - key = '%s%s%s' % (list_prefix, partition_group, list_suffix) - merged_dict[key] = ( - '%s %s' % - (framework_dict.get(key, ''), vendor_dict.get(key, ''))).strip() - return merged_dict - - def process_misc_info_txt(framework_target_files_temp_dir, vendor_target_files_temp_dir, output_target_files_temp_dir, @@ -503,7 +445,7 @@ def process_misc_info_txt(framework_target_files_temp_dir, # Merge misc info keys used for Dynamic Partitions. if (merged_dict.get('use_dynamic_partitions') == 'true') and ( framework_dict.get('use_dynamic_partitions') == 'true'): - merged_dynamic_partitions_dict = merge_dynamic_partition_info_dicts( + merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts( framework_dict=framework_dict, vendor_dict=merged_dict, size_prefix='super_', @@ -566,7 +508,7 @@ def process_dynamic_partitions_info_txt(framework_target_files_dir, vendor_dynamic_partitions_dict = common.LoadDictionaryFromFile( os.path.join(vendor_target_files_dir, *dynamic_partitions_info_path)) - merged_dynamic_partitions_dict = merge_dynamic_partition_info_dicts( + merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts( framework_dict=framework_dynamic_partitions_dict, vendor_dict=vendor_dynamic_partitions_dict, # META/dynamic_partitions_info.txt does not use dynamic_partition_list. diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py index 3a2198ceb..bcfb1c104 100644 --- a/tools/releasetools/test_common.py +++ b/tools/releasetools/test_common.py @@ -1074,6 +1074,69 @@ class CommonUtilsTest(test_utils.ReleaseToolsTestCase): self.assertRaises( AssertionError, common.LoadInfoDict, target_files_zip, True) + def test_MergeDynamicPartitionInfoDicts_ReturnsMergedDict(self): + framework_dict = { + 'super_partition_groups': 'group_a', + 'dynamic_partition_list': 'system', + 'super_group_a_list': 'system', + } + vendor_dict = { + 'super_partition_groups': 'group_a group_b', + 'dynamic_partition_list': 'vendor product', + 'super_group_a_list': 'vendor', + 'super_group_a_size': '1000', + 'super_group_b_list': 'product', + 'super_group_b_size': '2000', + } + merged_dict = common.MergeDynamicPartitionInfoDicts( + framework_dict=framework_dict, + vendor_dict=vendor_dict, + size_prefix='super_', + size_suffix='_size', + list_prefix='super_', + list_suffix='_list') + expected_merged_dict = { + 'super_partition_groups': 'group_a group_b', + 'dynamic_partition_list': 'system vendor product', + 'super_group_a_list': 'system vendor', + 'super_group_a_size': '1000', + 'super_group_b_list': 'product', + 'super_group_b_size': '2000', + } + self.assertEqual(merged_dict, expected_merged_dict) + + def test_MergeDynamicPartitionInfoDicts_IgnoringFrameworkGroupSize(self): + framework_dict = { + 'super_partition_groups': 'group_a', + 'dynamic_partition_list': 'system', + 'super_group_a_list': 'system', + 'super_group_a_size': '5000', + } + vendor_dict = { + 'super_partition_groups': 'group_a group_b', + 'dynamic_partition_list': 'vendor product', + 'super_group_a_list': 'vendor', + 'super_group_a_size': '1000', + 'super_group_b_list': 'product', + 'super_group_b_size': '2000', + } + merged_dict = common.MergeDynamicPartitionInfoDicts( + framework_dict=framework_dict, + vendor_dict=vendor_dict, + size_prefix='super_', + size_suffix='_size', + list_prefix='super_', + list_suffix='_list') + expected_merged_dict = { + 'super_partition_groups': 'group_a group_b', + 'dynamic_partition_list': 'system vendor product', + 'super_group_a_list': 'system vendor', + 'super_group_a_size': '1000', + 'super_group_b_list': 'product', + 'super_group_b_size': '2000', + } + self.assertEqual(merged_dict, expected_merged_dict) + class InstallRecoveryScriptFormatTest(test_utils.ReleaseToolsTestCase): """Checks the format of install-recovery.sh. diff --git a/tools/releasetools/test_merge_target_files.py b/tools/releasetools/test_merge_target_files.py index 1b1c7259a..1abe83c7c 100644 --- a/tools/releasetools/test_merge_target_files.py +++ b/tools/releasetools/test_merge_target_files.py @@ -22,7 +22,6 @@ from merge_target_files import (validate_config_lists, DEFAULT_FRAMEWORK_ITEM_LIST, DEFAULT_VENDOR_ITEM_LIST, DEFAULT_FRAMEWORK_MISC_INFO_KEYS, copy_items, - merge_dynamic_partition_info_dicts, process_apex_keys_apk_certs_common) @@ -126,69 +125,6 @@ class MergeTargetFilesTest(test_utils.ReleaseToolsTestCase): framework_misc_info_keys, DEFAULT_VENDOR_ITEM_LIST)) - def test_merge_dynamic_partition_info_dicts_ReturnsMergedDict(self): - framework_dict = { - 'super_partition_groups': 'group_a', - 'dynamic_partition_list': 'system', - 'super_group_a_list': 'system', - } - vendor_dict = { - 'super_partition_groups': 'group_a group_b', - 'dynamic_partition_list': 'vendor product', - 'super_group_a_list': 'vendor', - 'super_group_a_size': '1000', - 'super_group_b_list': 'product', - 'super_group_b_size': '2000', - } - merged_dict = merge_dynamic_partition_info_dicts( - framework_dict=framework_dict, - vendor_dict=vendor_dict, - size_prefix='super_', - size_suffix='_size', - list_prefix='super_', - list_suffix='_list') - expected_merged_dict = { - 'super_partition_groups': 'group_a group_b', - 'dynamic_partition_list': 'system vendor product', - 'super_group_a_list': 'system vendor', - 'super_group_a_size': '1000', - 'super_group_b_list': 'product', - 'super_group_b_size': '2000', - } - self.assertEqual(merged_dict, expected_merged_dict) - - def test_merge_dynamic_partition_info_dicts_IgnoringFrameworkGroupSize(self): - framework_dict = { - 'super_partition_groups': 'group_a', - 'dynamic_partition_list': 'system', - 'super_group_a_list': 'system', - 'super_group_a_size': '5000', - } - vendor_dict = { - 'super_partition_groups': 'group_a group_b', - 'dynamic_partition_list': 'vendor product', - 'super_group_a_list': 'vendor', - 'super_group_a_size': '1000', - 'super_group_b_list': 'product', - 'super_group_b_size': '2000', - } - merged_dict = merge_dynamic_partition_info_dicts( - framework_dict=framework_dict, - vendor_dict=vendor_dict, - size_prefix='super_', - size_suffix='_size', - list_prefix='super_', - list_suffix='_list') - expected_merged_dict = { - 'super_partition_groups': 'group_a group_b', - 'dynamic_partition_list': 'system vendor product', - 'super_group_a_list': 'system vendor', - 'super_group_a_size': '1000', - 'super_group_b_list': 'product', - 'super_group_b_size': '2000', - } - self.assertEqual(merged_dict, expected_merged_dict) - def test_process_apex_keys_apk_certs_ReturnsTrueIfNoConflicts(self): output_dir = common.MakeTempDir() os.makedirs(os.path.join(output_dir, 'META'))