Home | History | Annotate | Download | only in releasetools
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2019 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
      6 # use this file except in compliance with the License. You may obtain a copy of
      7 # the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     14 # License for the specific language governing permissions and limitations under
     15 # the License.
     16 
     17 """
     18 This script merges two partial target files packages (one of which contains
     19 system files, and the other contains non-system files) together, producing a
     20 complete target files package that can be used to generate an OTA package.
     21 
     22 Usage: merge_target_files.py [args]
     23 
     24   --system-target-files system-target-files-zip-archive
     25       The input target files package containing system bits. This is a zip
     26       archive.
     27 
     28   --system-item-list system-item-list-file
     29       The optional path to a newline-separated config file that replaces the
     30       contents of default_system_item_list if provided.
     31 
     32   --system-misc-info-keys system-misc-info-keys-file
     33       The optional path to a newline-separated config file that replaces the
     34       contents of default_system_misc_info_keys if provided.
     35 
     36   --other-target-files other-target-files-zip-archive
     37       The input target files package containing other bits. This is a zip
     38       archive.
     39 
     40   --other-item-list other-item-list-file
     41       The optional path to a newline-separated config file that replaces the
     42       contents of default_other_item_list if provided.
     43 
     44   --output-target-files output-target-files-package
     45       The output merged target files package. Also a zip archive.
     46 
     47   --rebuild_recovery
     48       Rebuild the recovery patch used by non-A/B devices and write it to the
     49       system image.
     50 
     51   --keep-tmp
     52       Keep tempoary files for debugging purposes.
     53 """
     54 
     55 from __future__ import print_function
     56 
     57 import fnmatch
     58 import logging
     59 import os
     60 import sys
     61 import zipfile
     62 
     63 import common
     64 import add_img_to_target_files
     65 
     66 logger = logging.getLogger(__name__)
     67 OPTIONS = common.OPTIONS
     68 OPTIONS.verbose = True
     69 OPTIONS.system_target_files = None
     70 OPTIONS.system_item_list = None
     71 OPTIONS.system_misc_info_keys = None
     72 OPTIONS.other_target_files = None
     73 OPTIONS.other_item_list = None
     74 OPTIONS.output_target_files = None
     75 OPTIONS.rebuild_recovery = False
     76 OPTIONS.keep_tmp = False
     77 
     78 # default_system_item_list is a list of items to extract from the partial
     79 # system target files package as is, meaning these items will land in the
     80 # output target files package exactly as they appear in the input partial
     81 # system target files package.
     82 
     83 default_system_item_list = [
     84     'META/apkcerts.txt',
     85     'META/filesystem_config.txt',
     86     'META/root_filesystem_config.txt',
     87     'META/system_manifest.xml',
     88     'META/system_matrix.xml',
     89     'META/update_engine_config.txt',
     90     'PRODUCT/*',
     91     'ROOT/*',
     92     'SYSTEM/*',
     93 ]
     94 
     95 # system_extract_special_item_list is a list of items to extract from the
     96 # partial system target files package that need some special processing, such
     97 # as some sort of combination with items from the partial other target files
     98 # package.
     99 
    100 system_extract_special_item_list = [
    101     'META/*',
    102 ]
    103 
    104 # default_system_misc_info_keys is a list of keys to obtain from the system instance of
    105 # META/misc_info.txt. The remaining keys from the other instance.
    106 
    107 default_system_misc_info_keys = [
    108     'avb_system_hashtree_enable',
    109     'avb_system_add_hashtree_footer_args',
    110     'avb_system_key_path',
    111     'avb_system_algorithm',
    112     'avb_system_rollback_index_location',
    113     'avb_product_hashtree_enable',
    114     'avb_product_add_hashtree_footer_args',
    115     'avb_product_services_hashtree_enable',
    116     'avb_product_services_add_hashtree_footer_args',
    117     'system_root_image',
    118     'root_dir',
    119     'ab_update',
    120     'default_system_dev_certificate',
    121     'system_size',
    122 ]
    123 
    124 # default_other_item_list is a list of items to extract from the partial
    125 # other target files package as is, meaning these items will land in the output
    126 # target files package exactly as they appear in the input partial other target
    127 # files package.
    128 
    129 default_other_item_list = [
    130     'META/boot_filesystem_config.txt',
    131     'META/otakeys.txt',
    132     'META/releasetools.py',
    133     'META/vendor_filesystem_config.txt',
    134     'META/vendor_manifest.xml',
    135     'META/vendor_matrix.xml',
    136     'BOOT/*',
    137     'DATA/*',
    138     'ODM/*',
    139     'OTA/android-info.txt',
    140     'PREBUILT_IMAGES/*',
    141     'RADIO/*',
    142     'VENDOR/*',
    143 ]
    144 
    145 # other_extract_special_item_list is a list of items to extract from the
    146 # partial other target files package that need some special processing, such as
    147 # some sort of combination with items from the partial system target files
    148 # package.
    149 
    150 other_extract_special_item_list = [
    151     'META/*',
    152 ]
    153 
    154 
    155 def extract_items(target_files, target_files_temp_dir, extract_item_list):
    156   """Extract items from target files to temporary directory.
    157 
    158   This function extracts from the specified target files zip archive into the
    159   specified temporary directory, the items specified in the extract item list.
    160 
    161   Args:
    162     target_files: The target files zip archive from which to extract items.
    163 
    164     target_files_temp_dir: The temporary directory where the extracted items
    165     will land.
    166 
    167     extract_item_list: A list of items to extract.
    168   """
    169 
    170   logger.info('extracting from %s', target_files)
    171 
    172   # Filter the extract_item_list to remove any items that do not exist in the
    173   # zip file. Otherwise, the extraction step will fail.
    174 
    175   with zipfile.ZipFile(
    176       target_files,
    177       'r',
    178       allowZip64=True) as target_files_zipfile:
    179     target_files_namelist = target_files_zipfile.namelist()
    180 
    181   filtered_extract_item_list = []
    182   for pattern in extract_item_list:
    183     matching_namelist = fnmatch.filter(target_files_namelist, pattern)
    184     if not matching_namelist:
    185       logger.warning('no match for %s', pattern)
    186     else:
    187       filtered_extract_item_list.append(pattern)
    188 
    189   # Extract from target_files into target_files_temp_dir the
    190   # filtered_extract_item_list.
    191 
    192   common.UnzipToDir(
    193       target_files,
    194       target_files_temp_dir,
    195       filtered_extract_item_list)
    196 
    197 
    198 def read_config_list(config_file_path):
    199   """Reads a config file into a list of strings.
    200 
    201   Expects the file to be newline-separated.
    202 
    203   Args:
    204     config_file_path: The path to the config file to open and read.
    205   """
    206   with open(config_file_path) as config_file:
    207     return config_file.read().splitlines()
    208 
    209 
    210 def validate_config_lists(
    211     system_item_list,
    212     system_misc_info_keys,
    213     other_item_list):
    214   """Performs validations on the merge config lists.
    215 
    216   Args:
    217     system_item_list: The list of items to extract from the partial
    218     system target files package as is.
    219 
    220     system_misc_info_keys: A list of keys to obtain from the system instance
    221     of META/misc_info.txt. The remaining keys from the other instance.
    222 
    223     other_item_list: The list of items to extract from the partial
    224     other target files package as is.
    225 
    226   Returns:
    227     False if a validation fails, otherwise true.
    228   """
    229   default_combined_item_set = set(default_system_item_list)
    230   default_combined_item_set.update(default_other_item_list)
    231 
    232   combined_item_set = set(system_item_list)
    233   combined_item_set.update(other_item_list)
    234 
    235   # Check that the merge config lists are not missing any item specified
    236   # by the default config lists.
    237   difference = default_combined_item_set.difference(combined_item_set)
    238   if difference:
    239     logger.error('Missing merge config items: %s' % list(difference))
    240     logger.error('Please ensure missing items are in either the '
    241                  'system-item-list or other-item-list files provided to '
    242                  'this script.')
    243     return False
    244 
    245   if ('dynamic_partition_list' in system_misc_info_keys) or (
    246       'super_partition_groups' in system_misc_info_keys):
    247     logger.error('Dynamic partition misc info keys should come from '
    248                  'the other instance of META/misc_info.txt.')
    249     return False
    250 
    251   return True
    252 
    253 
    254 def process_ab_partitions_txt(
    255     system_target_files_temp_dir,
    256     other_target_files_temp_dir,
    257     output_target_files_temp_dir):
    258   """Perform special processing for META/ab_partitions.txt
    259 
    260   This function merges the contents of the META/ab_partitions.txt files from
    261   the system directory and the other directory, placing the merged result in
    262   the output directory. The precondition in that the files are already
    263   extracted. The post condition is that the output META/ab_partitions.txt
    264   contains the merged content. The format for each ab_partitions.txt a one
    265   partition name per line. The output file contains the union of the parition
    266   names.
    267 
    268   Args:
    269     system_target_files_temp_dir: The name of a directory containing the
    270     special items extracted from the system target files package.
    271 
    272     other_target_files_temp_dir: The name of a directory containing the
    273     special items extracted from the other target files package.
    274 
    275     output_target_files_temp_dir: The name of a directory that will be used
    276     to create the output target files package after all the special cases
    277     are processed.
    278   """
    279 
    280   system_ab_partitions_txt = os.path.join(
    281       system_target_files_temp_dir, 'META', 'ab_partitions.txt')
    282 
    283   other_ab_partitions_txt = os.path.join(
    284       other_target_files_temp_dir, 'META', 'ab_partitions.txt')
    285 
    286   with open(system_ab_partitions_txt) as f:
    287     system_ab_partitions = f.read().splitlines()
    288 
    289   with open(other_ab_partitions_txt) as f:
    290     other_ab_partitions = f.read().splitlines()
    291 
    292   output_ab_partitions = set(system_ab_partitions + other_ab_partitions)
    293 
    294   output_ab_partitions_txt = os.path.join(
    295       output_target_files_temp_dir, 'META', 'ab_partitions.txt')
    296 
    297   with open(output_ab_partitions_txt, 'w') as output:
    298     for partition in sorted(output_ab_partitions):
    299       output.write('%s\n' % partition)
    300 
    301 
    302 def append_recovery_to_filesystem_config(output_target_files_temp_dir):
    303   """Perform special processing for META/filesystem_config.txt
    304 
    305   This function appends recovery information to META/filesystem_config.txt
    306   so that recovery patch regeneration will succeed.
    307 
    308   Args:
    309     output_target_files_temp_dir: The name of a directory that will be used
    310     to create the output target files package after all the special cases
    311     are processed. We find filesystem_config.txt here.
    312   """
    313 
    314   filesystem_config_txt = os.path.join(
    315       output_target_files_temp_dir,
    316       'META',
    317       'filesystem_config.txt')
    318 
    319   with open(filesystem_config_txt, 'a') as f:
    320     # TODO(bpeckham) this data is hard coded. It should be generated
    321     # programmatically.
    322     f.write(
    323         'system/bin/install-recovery.sh 0 0 750 '
    324         'selabel=u:object_r:install_recovery_exec:s0 capabilities=0x0\n')
    325     f.write(
    326         'system/recovery-from-boot.p 0 0 644 '
    327         'selabel=u:object_r:system_file:s0 capabilities=0x0\n')
    328     f.write(
    329         'system/etc/recovery.img 0 0 440 '
    330         'selabel=u:object_r:install_recovery_exec:s0 capabilities=0x0\n')
    331 
    332 
    333 def process_misc_info_txt(
    334     system_target_files_temp_dir,
    335     other_target_files_temp_dir,
    336     output_target_files_temp_dir,
    337     system_misc_info_keys):
    338   """Perform special processing for META/misc_info.txt
    339 
    340   This function merges the contents of the META/misc_info.txt files from the
    341   system directory and the other directory, placing the merged result in the
    342   output directory. The precondition in that the files are already extracted.
    343   The post condition is that the output META/misc_info.txt contains the merged
    344   content.
    345 
    346   Args:
    347     system_target_files_temp_dir: The name of a directory containing the
    348     special items extracted from the system target files package.
    349 
    350     other_target_files_temp_dir: The name of a directory containing the
    351     special items extracted from the other target files package.
    352 
    353     output_target_files_temp_dir: The name of a directory that will be used
    354     to create the output target files package after all the special cases
    355     are processed.
    356 
    357     system_misc_info_keys: A list of keys to obtain from the system instance
    358     of META/misc_info.txt. The remaining keys from the other instance.
    359   """
    360 
    361   def read_helper(d):
    362     misc_info_txt = os.path.join(d, 'META', 'misc_info.txt')
    363     with open(misc_info_txt) as f:
    364       return list(f.read().splitlines())
    365 
    366   system_info_dict = common.LoadDictionaryFromLines(
    367       read_helper(system_target_files_temp_dir))
    368 
    369   # We take most of the misc info from the other target files.
    370 
    371   merged_info_dict = common.LoadDictionaryFromLines(
    372       read_helper(other_target_files_temp_dir))
    373 
    374   # Replace certain values in merged_info_dict with values from
    375   # system_info_dict.
    376 
    377   for key in system_misc_info_keys:
    378     merged_info_dict[key] = system_info_dict[key]
    379 
    380   # Merge misc info keys used for Dynamic Partitions.
    381   if (merged_info_dict.get('use_dynamic_partitions') == 'true') and (
    382       system_info_dict.get('use_dynamic_partitions') == 'true'):
    383     merged_info_dict['dynamic_partition_list'] = '%s %s' % (
    384         system_info_dict.get('dynamic_partition_list', ''),
    385         merged_info_dict.get('dynamic_partition_list', ''))
    386     # Partition groups and group sizes are defined by the other (non-system)
    387     # misc info file because these values may vary for each board that uses
    388     # a shared system image.
    389     for partition_group in merged_info_dict['super_partition_groups'].split(' '):
    390       if ('super_%s_group_size' % partition_group) not in merged_info_dict:
    391         raise common.ExternalError(
    392             'Other META/misc_info.txt does not contain required key '
    393             'super_%s_group_size.' % partition_group)
    394       key = 'super_%s_partition_list' % partition_group
    395       merged_info_dict[key] = '%s %s' % (
    396         system_info_dict.get(key, ''),
    397         merged_info_dict.get(key, ''))
    398 
    399   output_misc_info_txt = os.path.join(
    400       output_target_files_temp_dir,
    401       'META', 'misc_info.txt')
    402 
    403   sorted_keys = sorted(merged_info_dict.keys())
    404 
    405   with open(output_misc_info_txt, 'w') as output:
    406     for key in sorted_keys:
    407       output.write('{}={}\n'.format(key, merged_info_dict[key]))
    408 
    409 
    410 def process_file_contexts_bin(temp_dir, output_target_files_temp_dir):
    411   """Perform special processing for META/file_contexts.bin.
    412 
    413   This function combines plat_file_contexts and vendor_file_contexts, which are
    414   expected to already be extracted in temp_dir, to produce a merged
    415   file_contexts.bin that will land in temp_dir at META/file_contexts.bin.
    416 
    417   Args:
    418     temp_dir: The name of a scratch directory that this function can use for
    419     intermediate files generated during processing.
    420 
    421     output_target_files_temp_dir: The name of the working directory that must
    422     already contain plat_file_contexts and vendor_file_contexts (in the
    423     appropriate sub directories), and to which META/file_contexts.bin will be
    424     written.
    425   """
    426 
    427   # To create a merged file_contexts.bin file, we use the system and vendor
    428   # file contexts files as input, the m4 tool to combine them, the sorting tool
    429   # to sort, and finally the sefcontext_compile tool to generate the final
    430   # output. We currently omit a checkfc step since the files had been checked
    431   # as part of the build.
    432 
    433   # The m4 step concatenates the two input files contexts files. Since m4
    434   # writes to stdout, we receive that into an array of bytes, and then write it
    435   # to a file.
    436 
    437   # Collect the file contexts that we're going to combine from SYSTEM, VENDOR,
    438   # PRODUCT, and ODM. We require SYSTEM and VENDOR, but others are optional.
    439 
    440   file_contexts_list = []
    441 
    442   for partition in ['SYSTEM', 'VENDOR', 'PRODUCT', 'ODM']:
    443     prefix = 'plat' if partition == 'SYSTEM' else partition.lower()
    444 
    445     file_contexts = os.path.join(
    446         output_target_files_temp_dir,
    447         partition, 'etc', 'selinux', prefix + '_file_contexts')
    448 
    449     mandatory = partition in ['SYSTEM', 'VENDOR']
    450 
    451     if mandatory or os.path.isfile(file_contexts):
    452       file_contexts_list.append(file_contexts)
    453     else:
    454       logger.warning('file not found: %s', file_contexts)
    455 
    456   command = ['m4', '--fatal-warnings', '-s'] + file_contexts_list
    457 
    458   merged_content = common.RunAndCheckOutput(command, verbose=False)
    459 
    460   merged_file_contexts_txt = os.path.join(temp_dir, 'merged_file_contexts.txt')
    461 
    462   with open(merged_file_contexts_txt, 'wb') as f:
    463     f.write(merged_content)
    464 
    465   # The sort step sorts the concatenated file.
    466 
    467   sorted_file_contexts_txt = os.path.join(temp_dir, 'sorted_file_contexts.txt')
    468   command = ['fc_sort', merged_file_contexts_txt, sorted_file_contexts_txt]
    469   common.RunAndWait(command, verbose=True)
    470 
    471   # Finally, the compile step creates the final META/file_contexts.bin.
    472 
    473   file_contexts_bin = os.path.join(
    474       output_target_files_temp_dir,
    475       'META', 'file_contexts.bin')
    476 
    477   command = [
    478       'sefcontext_compile',
    479       '-o', file_contexts_bin,
    480       sorted_file_contexts_txt,
    481   ]
    482 
    483   common.RunAndWait(command, verbose=True)
    484 
    485 
    486 def process_special_cases(
    487     temp_dir,
    488     system_target_files_temp_dir,
    489     other_target_files_temp_dir,
    490     output_target_files_temp_dir,
    491     system_misc_info_keys,
    492     rebuild_recovery
    493 ):
    494   """Perform special-case processing for certain target files items.
    495 
    496   Certain files in the output target files package require special-case
    497   processing. This function performs all that special-case processing.
    498 
    499   Args:
    500     temp_dir: The name of a scratch directory that this function can use for
    501     intermediate files generated during processing.
    502 
    503     system_target_files_temp_dir: The name of a directory containing the
    504     special items extracted from the system target files package.
    505 
    506     other_target_files_temp_dir: The name of a directory containing the
    507     special items extracted from the other target files package.
    508 
    509     output_target_files_temp_dir: The name of a directory that will be used
    510     to create the output target files package after all the special cases
    511     are processed.
    512 
    513     system_misc_info_keys: A list of keys to obtain from the system instance
    514     of META/misc_info.txt. The remaining keys from the other instance.
    515 
    516     rebuild_recovery: If true, rebuild the recovery patch used by non-A/B
    517     devices and write it to the system image.
    518   """
    519 
    520   if 'ab_update' in system_misc_info_keys:
    521     process_ab_partitions_txt(
    522         system_target_files_temp_dir=system_target_files_temp_dir,
    523         other_target_files_temp_dir=other_target_files_temp_dir,
    524         output_target_files_temp_dir=output_target_files_temp_dir)
    525 
    526   if rebuild_recovery:
    527     append_recovery_to_filesystem_config(
    528         output_target_files_temp_dir=output_target_files_temp_dir)
    529 
    530   process_misc_info_txt(
    531       system_target_files_temp_dir=system_target_files_temp_dir,
    532       other_target_files_temp_dir=other_target_files_temp_dir,
    533       output_target_files_temp_dir=output_target_files_temp_dir,
    534       system_misc_info_keys=system_misc_info_keys)
    535 
    536   process_file_contexts_bin(
    537       temp_dir=temp_dir,
    538       output_target_files_temp_dir=output_target_files_temp_dir)
    539 
    540 
    541 def merge_target_files(
    542     temp_dir,
    543     system_target_files,
    544     system_item_list,
    545     system_misc_info_keys,
    546     other_target_files,
    547     other_item_list,
    548     output_target_files,
    549     rebuild_recovery):
    550   """Merge two target files packages together.
    551 
    552   This function takes system and other target files packages as input, performs
    553   various file extractions, special case processing, and finally creates a
    554   merged zip archive as output.
    555 
    556   Args:
    557     temp_dir: The name of a directory we use when we extract items from the
    558     input target files packages, and also a scratch directory that we use for
    559     temporary files.
    560 
    561     system_target_files: The name of the zip archive containing the system
    562     partial target files package.
    563 
    564     system_item_list: The list of items to extract from the partial system
    565     target files package as is, meaning these items will land in the output
    566     target files package exactly as they appear in the input partial system
    567     target files package.
    568 
    569     system_misc_info_keys: The list of keys to obtain from the system instance
    570     of META/misc_info.txt. The remaining keys from the other instance.
    571 
    572     other_target_files: The name of the zip archive containing the other
    573     partial target files package.
    574 
    575     other_item_list: The list of items to extract from the partial other
    576     target files package as is, meaning these items will land in the output
    577     target files package exactly as they appear in the input partial other
    578     target files package.
    579 
    580     output_target_files: The name of the output zip archive target files
    581     package created by merging system and other.
    582 
    583     rebuild_recovery: If true, rebuild the recovery patch used by non-A/B
    584     devices and write it to the system image.
    585   """
    586 
    587   logger.info(
    588       'starting: merge system %s and other %s into output %s',
    589       system_target_files,
    590       other_target_files,
    591       output_target_files)
    592 
    593   # Create directory names that we'll use when we extract files from system,
    594   # and other, and for zipping the final output.
    595 
    596   system_target_files_temp_dir = os.path.join(temp_dir, 'system')
    597   other_target_files_temp_dir = os.path.join(temp_dir, 'other')
    598   output_target_files_temp_dir = os.path.join(temp_dir, 'output')
    599 
    600   # Extract "as is" items from the input system partial target files package.
    601   # We extract them directly into the output temporary directory since the
    602   # items do not need special case processing.
    603 
    604   extract_items(
    605       target_files=system_target_files,
    606       target_files_temp_dir=output_target_files_temp_dir,
    607       extract_item_list=system_item_list)
    608 
    609   # Extract "as is" items from the input other partial target files package. We
    610   # extract them directly into the output temporary directory since the items
    611   # do not need special case processing.
    612 
    613   extract_items(
    614       target_files=other_target_files,
    615       target_files_temp_dir=output_target_files_temp_dir,
    616       extract_item_list=other_item_list)
    617 
    618   # Extract "special" items from the input system partial target files package.
    619   # We extract these items to different directory since they require special
    620   # processing before they will end up in the output directory.
    621 
    622   extract_items(
    623       target_files=system_target_files,
    624       target_files_temp_dir=system_target_files_temp_dir,
    625       extract_item_list=system_extract_special_item_list)
    626 
    627   # Extract "special" items from the input other partial target files package.
    628   # We extract these items to different directory since they require special
    629   # processing before they will end up in the output directory.
    630 
    631   extract_items(
    632       target_files=other_target_files,
    633       target_files_temp_dir=other_target_files_temp_dir,
    634       extract_item_list=other_extract_special_item_list)
    635 
    636   # Now that the temporary directories contain all the extracted files, perform
    637   # special case processing on any items that need it. After this function
    638   # completes successfully, all the files we need to create the output target
    639   # files package are in place.
    640 
    641   process_special_cases(
    642       temp_dir=temp_dir,
    643       system_target_files_temp_dir=system_target_files_temp_dir,
    644       other_target_files_temp_dir=other_target_files_temp_dir,
    645       output_target_files_temp_dir=output_target_files_temp_dir,
    646       system_misc_info_keys=system_misc_info_keys,
    647       rebuild_recovery=rebuild_recovery)
    648 
    649   # Regenerate IMAGES in the temporary directory.
    650 
    651   add_img_args = ['--verbose']
    652   if rebuild_recovery:
    653     add_img_args.append('--rebuild_recovery')
    654   add_img_args.append(output_target_files_temp_dir)
    655 
    656   add_img_to_target_files.main(add_img_args)
    657 
    658   # Finally, create the output target files zip archive.
    659 
    660   output_zip = os.path.abspath(output_target_files)
    661   output_target_files_list = os.path.join(temp_dir, 'output.list')
    662   output_target_files_meta_dir = os.path.join(
    663       output_target_files_temp_dir, 'META')
    664 
    665   command = [
    666       'find',
    667       output_target_files_meta_dir,
    668   ]
    669   # TODO(bpeckham): sort this to be more like build.
    670   meta_content = common.RunAndCheckOutput(command, verbose=False)
    671   command = [
    672       'find',
    673       output_target_files_temp_dir,
    674       '-path',
    675       output_target_files_meta_dir,
    676       '-prune',
    677       '-o',
    678       '-print'
    679   ]
    680   # TODO(bpeckham): sort this to be more like build.
    681   other_content = common.RunAndCheckOutput(command, verbose=False)
    682 
    683   with open(output_target_files_list, 'wb') as f:
    684     f.write(meta_content)
    685     f.write(other_content)
    686 
    687   command = [
    688       'soong_zip',
    689       '-d',
    690       '-o', output_zip,
    691       '-C', output_target_files_temp_dir,
    692       '-l', output_target_files_list,
    693   ]
    694   logger.info('creating %s', output_target_files)
    695   common.RunAndWait(command, verbose=True)
    696 
    697 
    698 def call_func_with_temp_dir(func, keep_tmp):
    699   """Manage the creation and cleanup of the temporary directory.
    700 
    701   This function calls the given function after first creating a temporary
    702   directory. It also cleans up the temporary directory.
    703 
    704   Args:
    705     func: The function to call. Should accept one parameter, the path to
    706     the temporary directory.
    707 
    708     keep_tmp: Keep the temporary directory after processing is complete.
    709   """
    710 
    711   # Create a temporary directory. This will serve as the parent of directories
    712   # we use when we extract items from the input target files packages, and also
    713   # a scratch directory that we use for temporary files.
    714 
    715   temp_dir = common.MakeTempDir(prefix='merge_target_files_')
    716 
    717   try:
    718     func(temp_dir)
    719   except:
    720     raise
    721   finally:
    722     if keep_tmp:
    723       logger.info('keeping %s', temp_dir)
    724     else:
    725       common.Cleanup()
    726 
    727 
    728 def main():
    729   """The main function.
    730 
    731   Process command line arguments, then call merge_target_files to
    732   perform the heavy lifting.
    733   """
    734 
    735   common.InitLogging()
    736 
    737   def option_handler(o, a):
    738     if o == '--system-target-files':
    739       OPTIONS.system_target_files = a
    740     elif o == '--system-item-list':
    741       OPTIONS.system_item_list = a
    742     elif o == '--system-misc-info-keys':
    743       OPTIONS.system_misc_info_keys = a
    744     elif o == '--other-target-files':
    745       OPTIONS.other_target_files = a
    746     elif o == '--other-item-list':
    747       OPTIONS.other_item_list = a
    748     elif o == '--output-target-files':
    749       OPTIONS.output_target_files = a
    750     elif o == '--rebuild_recovery':
    751       OPTIONS.rebuild_recovery = True
    752     elif o == '--keep-tmp':
    753       OPTIONS.keep_tmp = True
    754     else:
    755       return False
    756     return True
    757 
    758   args = common.ParseOptions(
    759       sys.argv[1:], __doc__,
    760       extra_long_opts=[
    761           'system-target-files=',
    762           'system-item-list=',
    763           'system-misc-info-keys=',
    764           'other-target-files=',
    765           'other-item-list=',
    766           'output-target-files=',
    767           'rebuild_recovery',
    768           'keep-tmp',
    769       ],
    770       extra_option_handler=option_handler)
    771 
    772   if (len(args) != 0 or
    773       OPTIONS.system_target_files is None or
    774       OPTIONS.other_target_files is None or
    775       OPTIONS.output_target_files is None):
    776     common.Usage(__doc__)
    777     sys.exit(1)
    778 
    779   if OPTIONS.system_item_list:
    780     system_item_list = read_config_list(OPTIONS.system_item_list)
    781   else:
    782     system_item_list = default_system_item_list
    783 
    784   if OPTIONS.system_misc_info_keys:
    785     system_misc_info_keys = read_config_list(OPTIONS.system_misc_info_keys)
    786   else:
    787     system_misc_info_keys = default_system_misc_info_keys
    788 
    789   if OPTIONS.other_item_list:
    790     other_item_list = read_config_list(OPTIONS.other_item_list)
    791   else:
    792     other_item_list = default_other_item_list
    793 
    794   if not validate_config_lists(
    795       system_item_list=system_item_list,
    796       system_misc_info_keys=system_misc_info_keys,
    797       other_item_list=other_item_list):
    798     sys.exit(1)
    799 
    800   call_func_with_temp_dir(
    801       lambda temp_dir: merge_target_files(
    802           temp_dir=temp_dir,
    803           system_target_files=OPTIONS.system_target_files,
    804           system_item_list=system_item_list,
    805           system_misc_info_keys=system_misc_info_keys,
    806           other_target_files=OPTIONS.other_target_files,
    807           other_item_list=other_item_list,
    808           output_target_files=OPTIONS.output_target_files,
    809           rebuild_recovery=OPTIONS.rebuild_recovery),
    810       OPTIONS.keep_tmp)
    811 
    812 
    813 if __name__ == '__main__':
    814   main()
    815