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"""This script merges two partial target files packages.
18
19One package contains framework files, and the other contains vendor files.
20It produces a complete target files package that can be used to generate an
21OTA package.
22
23Usage: merge_target_files.py [args]
24
25  --framework-target-files framework-target-files-zip-archive
26      The input target files package containing framework bits. This is a zip
27      archive.
28
29  --framework-item-list framework-item-list-file
30      The optional path to a newline-separated config file that replaces the
31      contents of DEFAULT_FRAMEWORK_ITEM_LIST if provided.
32
33  --framework-misc-info-keys framework-misc-info-keys-file
34      The optional path to a newline-separated config file that replaces the
35      contents of DEFAULT_FRAMEWORK_MISC_INFO_KEYS if provided.
36
37  --vendor-target-files vendor-target-files-zip-archive
38      The input target files package containing vendor bits. This is a zip
39      archive.
40
41  --vendor-item-list vendor-item-list-file
42      The optional path to a newline-separated config file that replaces the
43      contents of DEFAULT_VENDOR_ITEM_LIST if provided.
44
45  --output-target-files output-target-files-package
46      If provided, the output merged target files package. Also a zip archive.
47
48  --output-dir output-directory
49      If provided, the destination directory for saving merged files. Requires
50      the --output-item-list flag.
51      Can be provided alongside --output-target-files, or by itself.
52
53  --output-item-list output-item-list-file.
54      The optional path to a newline-separated config file that specifies the
55      file patterns to copy into the --output-dir. Required if providing
56      the --output-dir flag.
57
58  --output-ota output-ota-package
59      The output ota package. This is a zip archive. Use of this flag may
60      require passing the --path common flag; see common.py.
61
62  --output-img output-img-package
63      The output img package, suitable for use with 'fastboot update'. Use of
64      this flag may require passing the --path common flag; see common.py.
65
66  --output-super-empty output-super-empty-image
67      If provided, creates a super_empty.img file from the merged target
68      files package and saves it at this path.
69
70  --rebuild_recovery
71      Deprecated; does nothing.
72
73  --keep-tmp
74      Keep tempoary files for debugging purposes.
75"""
76
77from __future__ import print_function
78
79import fnmatch
80import logging
81import os
82import re
83import shutil
84import subprocess
85import sys
86import zipfile
87
88import add_img_to_target_files
89import build_super_image
90import check_target_files_vintf
91import common
92import img_from_target_files
93import ota_from_target_files
94
95logger = logging.getLogger(__name__)
96
97OPTIONS = common.OPTIONS
98# Always turn on verbose logging.
99OPTIONS.verbose = True
100OPTIONS.framework_target_files = None
101OPTIONS.framework_item_list = None
102OPTIONS.framework_misc_info_keys = None
103OPTIONS.vendor_target_files = None
104OPTIONS.vendor_item_list = None
105OPTIONS.output_target_files = None
106OPTIONS.output_dir = None
107OPTIONS.output_item_list = None
108OPTIONS.output_ota = None
109OPTIONS.output_img = None
110OPTIONS.output_super_empty = None
111# TODO(b/132730255): Remove this option.
112OPTIONS.rebuild_recovery = False
113OPTIONS.keep_tmp = False
114
115# In an item list (framework or vendor), we may see entries that select whole
116# partitions. Such an entry might look like this 'SYSTEM/*' (e.g., for the
117# system partition). The following regex matches this and extracts the
118# partition name.
119
120PARTITION_ITEM_PATTERN = re.compile(r'^([A-Z_]+)/\*$')
121
122# In apexkeys.txt or apkcerts.txt, we will find partition tags on each entry in
123# the file. We use these partition tags to filter the entries in those files
124# from the two different target files packages to produce a merged apexkeys.txt
125# or apkcerts.txt file. A partition tag (e.g., for the product partition) looks
126# like this: 'partition="product"'. We use the group syntax grab the value of
127# the tag. We use non-greedy matching in case there are other fields on the
128# same line.
129
130PARTITION_TAG_PATTERN = re.compile(r'partition="(.*?)"')
131
132# The sorting algorithm for apexkeys.txt and apkcerts.txt does not include the
133# ".apex" or ".apk" suffix, so we use the following pattern to extract a key.
134
135MODULE_KEY_PATTERN = re.compile(r'name="(.+)\.(apex|apk)"')
136
137# DEFAULT_FRAMEWORK_ITEM_LIST is a list of items to extract from the partial
138# framework target files package as is, meaning these items will land in the
139# output target files package exactly as they appear in the input partial
140# framework target files package.
141
142DEFAULT_FRAMEWORK_ITEM_LIST = (
143    'META/apkcerts.txt',
144    'META/filesystem_config.txt',
145    'META/root_filesystem_config.txt',
146    'META/update_engine_config.txt',
147    'PRODUCT/*',
148    'ROOT/*',
149    'SYSTEM/*',
150)
151
152# FRAMEWORK_EXTRACT_SPECIAL_ITEM_LIST is a list of items to extract from the
153# partial framework target files package that need some special processing, such
154# as some sort of combination with items from the partial vendor target files
155# package.
156
157FRAMEWORK_EXTRACT_SPECIAL_ITEM_LIST = ('META/*',)
158
159# DEFAULT_FRAMEWORK_MISC_INFO_KEYS is a list of keys to obtain from the
160# framework instance of META/misc_info.txt. The remaining keys from the
161# vendor instance.
162
163DEFAULT_FRAMEWORK_MISC_INFO_KEYS = (
164    'avb_system_hashtree_enable',
165    'avb_system_add_hashtree_footer_args',
166    'avb_system_key_path',
167    'avb_system_algorithm',
168    'avb_system_rollback_index_location',
169    'avb_product_hashtree_enable',
170    'avb_product_add_hashtree_footer_args',
171    'avb_system_ext_hashtree_enable',
172    'avb_system_ext_add_hashtree_footer_args',
173    'system_root_image',
174    'root_dir',
175    'ab_update',
176    'default_system_dev_certificate',
177    'system_size',
178    'building_system_image',
179    'building_system_ext_image',
180    'building_product_image',
181)
182
183# DEFAULT_VENDOR_ITEM_LIST is a list of items to extract from the partial
184# vendor target files package as is, meaning these items will land in the output
185# target files package exactly as they appear in the input partial vendor target
186# files package.
187
188DEFAULT_VENDOR_ITEM_LIST = (
189    'META/boot_filesystem_config.txt',
190    'META/otakeys.txt',
191    'META/releasetools.py',
192    'META/vendor_filesystem_config.txt',
193    'BOOT/*',
194    'DATA/*',
195    'ODM/*',
196    'OTA/android-info.txt',
197    'PREBUILT_IMAGES/*',
198    'RADIO/*',
199    'VENDOR/*',
200    'VENDOR_DLKM/*',
201    'ODM_DLKM/*',
202)
203
204# VENDOR_EXTRACT_SPECIAL_ITEM_LIST is a list of items to extract from the
205# partial vendor target files package that need some special processing, such as
206# some sort of combination with items from the partial framework target files
207# package.
208
209VENDOR_EXTRACT_SPECIAL_ITEM_LIST = ('META/*',)
210
211# The merge config lists should not attempt to extract items from both
212# builds for any of the following partitions. The partitions in
213# SINGLE_BUILD_PARTITIONS should come entirely from a single build (either
214# framework or vendor, but not both).
215
216SINGLE_BUILD_PARTITIONS = (
217    'BOOT/',
218    'DATA/',
219    'ODM/',
220    'PRODUCT/',
221    'SYSTEM_EXT/',
222    'RADIO/',
223    'RECOVERY/',
224    'ROOT/',
225    'SYSTEM/',
226    'SYSTEM_OTHER/',
227    'VENDOR/',
228    'VENDOR_DLKM/',
229    'ODM_DLKM/',
230)
231
232
233def write_sorted_data(data, path):
234  """Writes the sorted contents of either a list or dict to file.
235
236  This function sorts the contents of the list or dict and then writes the
237  resulting sorted contents to a file specified by path.
238
239  Args:
240    data: The list or dict to sort and write.
241    path: Path to the file to write the sorted values to. The file at path will
242      be overridden if it exists.
243  """
244  with open(path, 'w') as output:
245    for entry in sorted(data):
246      out_str = '{}={}\n'.format(entry, data[entry]) if isinstance(
247          data, dict) else '{}\n'.format(entry)
248      output.write(out_str)
249
250
251def extract_items(target_files, target_files_temp_dir, extract_item_list):
252  """Extracts items from target files to temporary directory.
253
254  This function extracts from the specified target files zip archive into the
255  specified temporary directory, the items specified in the extract item list.
256
257  Args:
258    target_files: The target files zip archive from which to extract items.
259    target_files_temp_dir: The temporary directory where the extracted items
260      will land.
261    extract_item_list: A list of items to extract.
262  """
263
264  logger.info('extracting from %s', target_files)
265
266  # Filter the extract_item_list to remove any items that do not exist in the
267  # zip file. Otherwise, the extraction step will fail.
268
269  with zipfile.ZipFile(target_files, allowZip64=True) as target_files_zipfile:
270    target_files_namelist = target_files_zipfile.namelist()
271
272  filtered_extract_item_list = []
273  for pattern in extract_item_list:
274    matching_namelist = fnmatch.filter(target_files_namelist, pattern)
275    if not matching_namelist:
276      logger.warning('no match for %s', pattern)
277    else:
278      filtered_extract_item_list.append(pattern)
279
280  # Extract from target_files into target_files_temp_dir the
281  # filtered_extract_item_list.
282
283  common.UnzipToDir(target_files, target_files_temp_dir,
284                    filtered_extract_item_list)
285
286
287def copy_items(from_dir, to_dir, patterns):
288  """Similar to extract_items() except uses an input dir instead of zip."""
289  file_paths = []
290  for dirpath, _, filenames in os.walk(from_dir):
291    file_paths.extend(
292        os.path.relpath(path=os.path.join(dirpath, filename), start=from_dir)
293        for filename in filenames)
294
295  filtered_file_paths = set()
296  for pattern in patterns:
297    filtered_file_paths.update(fnmatch.filter(file_paths, pattern))
298
299  for file_path in filtered_file_paths:
300    original_file_path = os.path.join(from_dir, file_path)
301    copied_file_path = os.path.join(to_dir, file_path)
302    copied_file_dir = os.path.dirname(copied_file_path)
303    if not os.path.exists(copied_file_dir):
304      os.makedirs(copied_file_dir)
305    if os.path.islink(original_file_path):
306      os.symlink(os.readlink(original_file_path), copied_file_path)
307    else:
308      shutil.copyfile(original_file_path, copied_file_path)
309
310
311def validate_config_lists(framework_item_list, framework_misc_info_keys,
312                          vendor_item_list):
313  """Performs validations on the merge config lists.
314
315  Args:
316    framework_item_list: The list of items to extract from the partial framework
317      target files package as is.
318    framework_misc_info_keys: A list of keys to obtain from the framework
319      instance of META/misc_info.txt. The remaining keys from the vendor
320      instance.
321    vendor_item_list: The list of items to extract from the partial vendor
322      target files package as is.
323
324  Returns:
325    False if a validation fails, otherwise true.
326  """
327  has_error = False
328
329  default_combined_item_set = set(DEFAULT_FRAMEWORK_ITEM_LIST)
330  default_combined_item_set.update(DEFAULT_VENDOR_ITEM_LIST)
331
332  combined_item_set = set(framework_item_list)
333  combined_item_set.update(vendor_item_list)
334
335  # Check that the merge config lists are not missing any item specified
336  # by the default config lists.
337  difference = default_combined_item_set.difference(combined_item_set)
338  if difference:
339    logger.error('Missing merge config items: %s', list(difference))
340    logger.error('Please ensure missing items are in either the '
341                 'framework-item-list or vendor-item-list files provided to '
342                 'this script.')
343    has_error = True
344
345  for partition in SINGLE_BUILD_PARTITIONS:
346    in_framework = any(
347        item.startswith(partition) for item in framework_item_list)
348    in_vendor = any(item.startswith(partition) for item in vendor_item_list)
349    if in_framework and in_vendor:
350      logger.error(
351          'Cannot extract items from %s for both the framework and vendor'
352          ' builds. Please ensure only one merge config item list'
353          ' includes %s.', partition, partition)
354      has_error = True
355
356  if ('dynamic_partition_list' in framework_misc_info_keys) or (
357      'super_partition_groups' in framework_misc_info_keys):
358    logger.error('Dynamic partition misc info keys should come from '
359                 'the vendor instance of META/misc_info.txt.')
360    has_error = True
361
362  return not has_error
363
364
365def process_ab_partitions_txt(framework_target_files_temp_dir,
366                              vendor_target_files_temp_dir,
367                              output_target_files_temp_dir):
368  """Performs special processing for META/ab_partitions.txt.
369
370  This function merges the contents of the META/ab_partitions.txt files from the
371  framework directory and the vendor directory, placing the merged result in the
372  output directory. The precondition in that the files are already extracted.
373  The post condition is that the output META/ab_partitions.txt contains the
374  merged content. The format for each ab_partitions.txt a one partition name per
375  line. The output file contains the union of the parition names.
376
377  Args:
378    framework_target_files_temp_dir: The name of a directory containing the
379      special items extracted from the framework target files package.
380    vendor_target_files_temp_dir: The name of a directory containing the special
381      items extracted from the vendor target files package.
382    output_target_files_temp_dir: The name of a directory that will be used to
383      create the output target files package after all the special cases are
384      processed.
385  """
386
387  framework_ab_partitions_txt = os.path.join(framework_target_files_temp_dir,
388                                             'META', 'ab_partitions.txt')
389
390  vendor_ab_partitions_txt = os.path.join(vendor_target_files_temp_dir, 'META',
391                                          'ab_partitions.txt')
392
393  with open(framework_ab_partitions_txt) as f:
394    framework_ab_partitions = f.read().splitlines()
395
396  with open(vendor_ab_partitions_txt) as f:
397    vendor_ab_partitions = f.read().splitlines()
398
399  output_ab_partitions = set(framework_ab_partitions + vendor_ab_partitions)
400
401  output_ab_partitions_txt = os.path.join(output_target_files_temp_dir, 'META',
402                                          'ab_partitions.txt')
403
404  write_sorted_data(data=output_ab_partitions, path=output_ab_partitions_txt)
405
406
407def process_misc_info_txt(framework_target_files_temp_dir,
408                          vendor_target_files_temp_dir,
409                          output_target_files_temp_dir,
410                          framework_misc_info_keys):
411  """Performs special processing for META/misc_info.txt.
412
413  This function merges the contents of the META/misc_info.txt files from the
414  framework directory and the vendor directory, placing the merged result in the
415  output directory. The precondition in that the files are already extracted.
416  The post condition is that the output META/misc_info.txt contains the merged
417  content.
418
419  Args:
420    framework_target_files_temp_dir: The name of a directory containing the
421      special items extracted from the framework target files package.
422    vendor_target_files_temp_dir: The name of a directory containing the special
423      items extracted from the vendor target files package.
424    output_target_files_temp_dir: The name of a directory that will be used to
425      create the output target files package after all the special cases are
426      processed.
427    framework_misc_info_keys: A list of keys to obtain from the framework
428      instance of META/misc_info.txt. The remaining keys from the vendor
429      instance.
430  """
431
432  misc_info_path = ['META', 'misc_info.txt']
433  framework_dict = common.LoadDictionaryFromFile(
434      os.path.join(framework_target_files_temp_dir, *misc_info_path))
435
436  # We take most of the misc info from the vendor target files.
437
438  merged_dict = common.LoadDictionaryFromFile(
439      os.path.join(vendor_target_files_temp_dir, *misc_info_path))
440
441  # Replace certain values in merged_dict with values from
442  # framework_dict.
443
444  for key in framework_misc_info_keys:
445    merged_dict[key] = framework_dict[key]
446
447  # Merge misc info keys used for Dynamic Partitions.
448  if (merged_dict.get('use_dynamic_partitions') == 'true') and (
449      framework_dict.get('use_dynamic_partitions') == 'true'):
450    merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts(
451        framework_dict=framework_dict, vendor_dict=merged_dict)
452    merged_dict.update(merged_dynamic_partitions_dict)
453    # Ensure that add_img_to_target_files rebuilds super split images for
454    # devices that retrofit dynamic partitions. This flag may have been set to
455    # false in the partial builds to prevent duplicate building of super.img.
456    merged_dict['build_super_partition'] = 'true'
457
458  # Replace <image>_selinux_fc values with framework or vendor file_contexts.bin
459  # depending on which dictionary the key came from.
460  # Only the file basename is required because all selinux_fc properties are
461  # replaced with the full path to the file under META/ when misc_info.txt is
462  # loaded from target files for repacking. See common.py LoadInfoDict().
463  for key in merged_dict:
464    if key.endswith('_selinux_fc'):
465      merged_dict[key] = 'vendor_file_contexts.bin'
466  for key in framework_dict:
467    if key.endswith('_selinux_fc'):
468      merged_dict[key] = 'framework_file_contexts.bin'
469
470  output_misc_info_txt = os.path.join(output_target_files_temp_dir, 'META',
471                                      'misc_info.txt')
472  write_sorted_data(data=merged_dict, path=output_misc_info_txt)
473
474
475def process_dynamic_partitions_info_txt(framework_target_files_dir,
476                                        vendor_target_files_dir,
477                                        output_target_files_dir):
478  """Performs special processing for META/dynamic_partitions_info.txt.
479
480  This function merges the contents of the META/dynamic_partitions_info.txt
481  files from the framework directory and the vendor directory, placing the
482  merged result in the output directory.
483
484  This function does nothing if META/dynamic_partitions_info.txt from the vendor
485  directory does not exist.
486
487  Args:
488    framework_target_files_dir: The name of a directory containing the special
489      items extracted from the framework target files package.
490    vendor_target_files_dir: The name of a directory containing the special
491      items extracted from the vendor target files package.
492    output_target_files_dir: The name of a directory that will be used to create
493      the output target files package after all the special cases are processed.
494  """
495
496  if not os.path.exists(
497      os.path.join(vendor_target_files_dir, 'META',
498                   'dynamic_partitions_info.txt')):
499    return
500
501  dynamic_partitions_info_path = ['META', 'dynamic_partitions_info.txt']
502
503  framework_dynamic_partitions_dict = common.LoadDictionaryFromFile(
504      os.path.join(framework_target_files_dir, *dynamic_partitions_info_path))
505  vendor_dynamic_partitions_dict = common.LoadDictionaryFromFile(
506      os.path.join(vendor_target_files_dir, *dynamic_partitions_info_path))
507
508  merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts(
509      framework_dict=framework_dynamic_partitions_dict,
510      vendor_dict=vendor_dynamic_partitions_dict)
511
512  output_dynamic_partitions_info_txt = os.path.join(
513      output_target_files_dir, 'META', 'dynamic_partitions_info.txt')
514  write_sorted_data(
515      data=merged_dynamic_partitions_dict,
516      path=output_dynamic_partitions_info_txt)
517
518
519def item_list_to_partition_set(item_list):
520  """Converts a target files item list to a partition set.
521
522  The item list contains items that might look like 'SYSTEM/*' or 'VENDOR/*' or
523  'OTA/android-info.txt'. Items that end in '/*' are assumed to match entire
524  directories where 'SYSTEM' or 'VENDOR' is a directory name that identifies the
525  contents of a partition of the same name. Other items in the list, such as the
526  'OTA' example contain metadata. This function iterates such a list, returning
527  a set that contains the partition entries.
528
529  Args:
530    item_list: A list of items in a target files package.
531  Returns:
532    A set of partitions extracted from the list of items.
533  """
534
535  partition_set = set()
536
537  for item in item_list:
538    match = PARTITION_ITEM_PATTERN.search(item.strip())
539    partition_tag = match.group(1).lower() if match else None
540
541    if partition_tag:
542      partition_set.add(partition_tag)
543
544  return partition_set
545
546
547def process_apex_keys_apk_certs_common(framework_target_files_dir,
548                                       vendor_target_files_dir,
549                                       output_target_files_dir,
550                                       framework_partition_set,
551                                       vendor_partition_set, file_name):
552
553  """Performs special processing for META/apexkeys.txt or META/apkcerts.txt.
554
555  This function merges the contents of the META/apexkeys.txt or
556  META/apkcerts.txt files from the framework directory and the vendor directory,
557  placing the merged result in the output directory. The precondition in that
558  the files are already extracted. The post condition is that the output
559  META/apexkeys.txt or META/apkcerts.txt contains the merged content.
560
561  Args:
562    framework_target_files_dir: The name of a directory containing the special
563      items extracted from the framework target files package.
564    vendor_target_files_dir: The name of a directory containing the special
565      items extracted from the vendor target files package.
566    output_target_files_dir: The name of a directory that will be used to create
567      the output target files package after all the special cases are processed.
568    framework_partition_set: Partitions that are considered framework
569      partitions. Used to filter apexkeys.txt and apkcerts.txt.
570    vendor_partition_set: Partitions that are considered vendor partitions. Used
571      to filter apexkeys.txt and apkcerts.txt.
572    file_name: The name of the file to merge. One of apkcerts.txt or
573      apexkeys.txt.
574  """
575
576  def read_helper(d):
577    temp = {}
578    file_path = os.path.join(d, 'META', file_name)
579    with open(file_path) as f:
580      for line in f:
581        if line.strip():
582          name = line.split()[0]
583          match = MODULE_KEY_PATTERN.search(name)
584          temp[match.group(1)] = line.strip()
585    return temp
586
587  framework_dict = read_helper(framework_target_files_dir)
588  vendor_dict = read_helper(vendor_target_files_dir)
589  merged_dict = {}
590
591  def filter_into_merged_dict(item_dict, partition_set):
592    for key, value in item_dict.items():
593      match = PARTITION_TAG_PATTERN.search(value)
594
595      if match is None:
596        raise ValueError('Entry missing partition tag: %s' % value)
597
598      partition_tag = match.group(1)
599
600      if partition_tag in partition_set:
601        if key in merged_dict:
602          raise ValueError('Duplicate key %s' % key)
603
604        merged_dict[key] = value
605
606  filter_into_merged_dict(framework_dict, framework_partition_set)
607  filter_into_merged_dict(vendor_dict, vendor_partition_set)
608
609  output_file = os.path.join(output_target_files_dir, 'META', file_name)
610
611  # The following code is similar to write_sorted_data, but different enough
612  # that we couldn't use that function. We need the output to be sorted by the
613  # basename of the apex/apk (without the ".apex" or ".apk" suffix). This
614  # allows the sort to be consistent with the framework/vendor input data and
615  # eases comparison of input data with merged data.
616  with open(output_file, 'w') as output:
617    for key in sorted(merged_dict.keys()):
618      out_str = merged_dict[key] + '\n'
619      output.write(out_str)
620
621
622def copy_file_contexts(framework_target_files_dir, vendor_target_files_dir,
623                       output_target_files_dir):
624  """Creates named copies of each build's file_contexts.bin in output META/."""
625  framework_fc_path = os.path.join(framework_target_files_dir, 'META',
626                                   'framework_file_contexts.bin')
627  if not os.path.exists(framework_fc_path):
628    framework_fc_path = os.path.join(framework_target_files_dir, 'META',
629                                     'file_contexts.bin')
630    if not os.path.exists(framework_fc_path):
631      raise ValueError('Missing framework file_contexts.bin.')
632  shutil.copyfile(
633      framework_fc_path,
634      os.path.join(output_target_files_dir, 'META',
635                   'framework_file_contexts.bin'))
636
637  vendor_fc_path = os.path.join(vendor_target_files_dir, 'META',
638                                'vendor_file_contexts.bin')
639  if not os.path.exists(vendor_fc_path):
640    vendor_fc_path = os.path.join(vendor_target_files_dir, 'META',
641                                  'file_contexts.bin')
642    if not os.path.exists(vendor_fc_path):
643      raise ValueError('Missing vendor file_contexts.bin.')
644  shutil.copyfile(
645      vendor_fc_path,
646      os.path.join(output_target_files_dir, 'META', 'vendor_file_contexts.bin'))
647
648
649def process_special_cases(framework_target_files_temp_dir,
650                          vendor_target_files_temp_dir,
651                          output_target_files_temp_dir,
652                          framework_misc_info_keys,
653                          framework_partition_set,
654                          vendor_partition_set):
655  """Performs special-case processing for certain target files items.
656
657  Certain files in the output target files package require special-case
658  processing. This function performs all that special-case processing.
659
660  Args:
661    framework_target_files_temp_dir: The name of a directory containing the
662      special items extracted from the framework target files package.
663    vendor_target_files_temp_dir: The name of a directory containing the special
664      items extracted from the vendor target files package.
665    output_target_files_temp_dir: The name of a directory that will be used to
666      create the output target files package after all the special cases are
667      processed.
668    framework_misc_info_keys: A list of keys to obtain from the framework
669      instance of META/misc_info.txt. The remaining keys from the vendor
670      instance.
671    framework_partition_set: Partitions that are considered framework
672      partitions. Used to filter apexkeys.txt and apkcerts.txt.
673    vendor_partition_set: Partitions that are considered vendor partitions. Used
674      to filter apexkeys.txt and apkcerts.txt.
675  """
676
677  if 'ab_update' in framework_misc_info_keys:
678    process_ab_partitions_txt(
679        framework_target_files_temp_dir=framework_target_files_temp_dir,
680        vendor_target_files_temp_dir=vendor_target_files_temp_dir,
681        output_target_files_temp_dir=output_target_files_temp_dir)
682
683  copy_file_contexts(
684      framework_target_files_dir=framework_target_files_temp_dir,
685      vendor_target_files_dir=vendor_target_files_temp_dir,
686      output_target_files_dir=output_target_files_temp_dir)
687
688  process_misc_info_txt(
689      framework_target_files_temp_dir=framework_target_files_temp_dir,
690      vendor_target_files_temp_dir=vendor_target_files_temp_dir,
691      output_target_files_temp_dir=output_target_files_temp_dir,
692      framework_misc_info_keys=framework_misc_info_keys)
693
694  process_dynamic_partitions_info_txt(
695      framework_target_files_dir=framework_target_files_temp_dir,
696      vendor_target_files_dir=vendor_target_files_temp_dir,
697      output_target_files_dir=output_target_files_temp_dir)
698
699  process_apex_keys_apk_certs_common(
700      framework_target_files_dir=framework_target_files_temp_dir,
701      vendor_target_files_dir=vendor_target_files_temp_dir,
702      output_target_files_dir=output_target_files_temp_dir,
703      framework_partition_set=framework_partition_set,
704      vendor_partition_set=vendor_partition_set,
705      file_name='apkcerts.txt')
706
707  process_apex_keys_apk_certs_common(
708      framework_target_files_dir=framework_target_files_temp_dir,
709      vendor_target_files_dir=vendor_target_files_temp_dir,
710      output_target_files_dir=output_target_files_temp_dir,
711      framework_partition_set=framework_partition_set,
712      vendor_partition_set=vendor_partition_set,
713      file_name='apexkeys.txt')
714
715
716def files_from_path(target_path, extra_args=None):
717  """Gets files under given path.
718
719  Get (sub)files from given target path and return sorted list.
720
721  Args:
722    target_path: Target path to get subfiles.
723    extra_args: List of extra argument for find command. Optional.
724
725  Returns:
726    Sorted files and directories list.
727  """
728
729  find_command = ['find', target_path] + (extra_args or [])
730  find_process = common.Run(find_command, stdout=subprocess.PIPE, verbose=False)
731  return common.RunAndCheckOutput(['sort'],
732                                  stdin=find_process.stdout,
733                                  verbose=False)
734
735
736def create_merged_package(temp_dir, framework_target_files, framework_item_list,
737                          vendor_target_files, vendor_item_list,
738                          framework_misc_info_keys, rebuild_recovery):
739  """Merges two target files packages into one target files structure.
740
741  Args:
742    temp_dir: The name of a directory we use when we extract items from the
743      input target files packages, and also a scratch directory that we use for
744      temporary files.
745    framework_target_files: The name of the zip archive containing the framework
746      partial target files package.
747    framework_item_list: The list of items to extract from the partial framework
748      target files package as is, meaning these items will land in the output
749      target files package exactly as they appear in the input partial framework
750      target files package.
751    vendor_target_files: The name of the zip archive containing the vendor
752      partial target files package.
753    vendor_item_list: The list of items to extract from the partial vendor
754      target files package as is, meaning these items will land in the output
755      target files package exactly as they appear in the input partial vendor
756      target files package.
757    framework_misc_info_keys: The list of keys to obtain from the framework
758      instance of META/misc_info.txt. The remaining keys from the vendor
759      instance.
760    rebuild_recovery: If true, rebuild the recovery patch used by non-A/B
761      devices and write it to the system image.
762
763  Returns:
764    Path to merged package under temp directory.
765  """
766
767  # Create directory names that we'll use when we extract files from framework,
768  # and vendor, and for zipping the final output.
769
770  framework_target_files_temp_dir = os.path.join(temp_dir, 'framework')
771  vendor_target_files_temp_dir = os.path.join(temp_dir, 'vendor')
772  output_target_files_temp_dir = os.path.join(temp_dir, 'output')
773
774  # Extract "as is" items from the input framework partial target files package.
775  # We extract them directly into the output temporary directory since the
776  # items do not need special case processing.
777
778  extract_items(
779      target_files=framework_target_files,
780      target_files_temp_dir=output_target_files_temp_dir,
781      extract_item_list=framework_item_list)
782
783  # Extract "as is" items from the input vendor partial target files package. We
784  # extract them directly into the output temporary directory since the items
785  # do not need special case processing.
786
787  extract_items(
788      target_files=vendor_target_files,
789      target_files_temp_dir=output_target_files_temp_dir,
790      extract_item_list=vendor_item_list)
791
792  # Extract "special" items from the input framework partial target files
793  # package. We extract these items to different directory since they require
794  # special processing before they will end up in the output directory.
795
796  extract_items(
797      target_files=framework_target_files,
798      target_files_temp_dir=framework_target_files_temp_dir,
799      extract_item_list=FRAMEWORK_EXTRACT_SPECIAL_ITEM_LIST)
800
801  # Extract "special" items from the input vendor partial target files package.
802  # We extract these items to different directory since they require special
803  # processing before they will end up in the output directory.
804
805  extract_items(
806      target_files=vendor_target_files,
807      target_files_temp_dir=vendor_target_files_temp_dir,
808      extract_item_list=VENDOR_EXTRACT_SPECIAL_ITEM_LIST)
809
810  # Now that the temporary directories contain all the extracted files, perform
811  # special case processing on any items that need it. After this function
812  # completes successfully, all the files we need to create the output target
813  # files package are in place.
814
815  process_special_cases(
816      framework_target_files_temp_dir=framework_target_files_temp_dir,
817      vendor_target_files_temp_dir=vendor_target_files_temp_dir,
818      output_target_files_temp_dir=output_target_files_temp_dir,
819      framework_misc_info_keys=framework_misc_info_keys,
820      framework_partition_set=item_list_to_partition_set(framework_item_list),
821      vendor_partition_set=item_list_to_partition_set(vendor_item_list))
822
823  return output_target_files_temp_dir
824
825
826def generate_images(target_files_dir, rebuild_recovery):
827  """Generate images from target files.
828
829  This function takes merged output temporary directory and create images
830  from it.
831
832  Args:
833    target_files_dir: Path to merged temp directory.
834    rebuild_recovery: If true, rebuild the recovery patch used by non-A/B
835      devices and write it to the system image.
836  """
837
838  # Regenerate IMAGES in the target directory.
839
840  add_img_args = ['--verbose']
841  add_img_args.append('--add_missing')
842  # TODO(b/132730255): Remove this if statement.
843  if rebuild_recovery:
844    add_img_args.append('--rebuild_recovery')
845  add_img_args.append(target_files_dir)
846
847  add_img_to_target_files.main(add_img_args)
848
849
850def generate_super_empty_image(target_dir, output_super_empty):
851  """Generates super_empty image from target package.
852
853  Args:
854    target_dir: Path to the target file package which contains misc_info.txt for
855      detailed information for super image.
856    output_super_empty: If provided, copies a super_empty.img file from the
857      target files package to this path.
858  """
859  # Create super_empty.img using the merged misc_info.txt.
860
861  misc_info_txt = os.path.join(target_dir, 'META', 'misc_info.txt')
862
863  use_dynamic_partitions = common.LoadDictionaryFromFile(misc_info_txt).get(
864      'use_dynamic_partitions')
865
866  if use_dynamic_partitions != 'true' and output_super_empty:
867    raise ValueError(
868        'Building super_empty.img requires use_dynamic_partitions=true.')
869  elif use_dynamic_partitions == 'true':
870    super_empty_img = os.path.join(target_dir, 'IMAGES', 'super_empty.img')
871    build_super_image_args = [
872        misc_info_txt,
873        super_empty_img,
874    ]
875    build_super_image.main(build_super_image_args)
876
877    # Copy super_empty.img to the user-provided output_super_empty location.
878    if output_super_empty:
879      shutil.copyfile(super_empty_img, output_super_empty)
880
881
882def create_target_files_archive(output_file, source_dir, temp_dir):
883  """Creates archive from target package.
884
885  Args:
886    output_file: The name of the zip archive target files package.
887    source_dir: The target directory contains package to be archived.
888    temp_dir: Path to temporary directory for any intermediate files.
889  """
890  output_target_files_list = os.path.join(temp_dir, 'output.list')
891  output_zip = os.path.abspath(output_file)
892  output_target_files_meta_dir = os.path.join(source_dir, 'META')
893
894  meta_content = files_from_path(output_target_files_meta_dir)
895  other_content = files_from_path(
896      source_dir,
897      ['-path', output_target_files_meta_dir, '-prune', '-o', '-print'])
898
899  with open(output_target_files_list, 'w') as f:
900    f.write(meta_content)
901    f.write(other_content)
902
903  command = [
904      'soong_zip',
905      '-d',
906      '-o',
907      output_zip,
908      '-C',
909      source_dir,
910      '-l',
911      output_target_files_list,
912  ]
913
914  logger.info('creating %s', output_file)
915  common.RunAndWait(command, verbose=True)
916  logger.info('finished creating %s', output_file)
917
918  return output_zip
919
920
921def merge_target_files(temp_dir, framework_target_files, framework_item_list,
922                       framework_misc_info_keys, vendor_target_files,
923                       vendor_item_list, output_target_files, output_dir,
924                       output_item_list, output_ota, output_img,
925                       output_super_empty, rebuild_recovery):
926  """Merges two target files packages together.
927
928  This function takes framework and vendor target files packages as input,
929  performs various file extractions, special case processing, and finally
930  creates a merged zip archive as output.
931
932  Args:
933    temp_dir: The name of a directory we use when we extract items from the
934      input target files packages, and also a scratch directory that we use for
935      temporary files.
936    framework_target_files: The name of the zip archive containing the framework
937      partial target files package.
938    framework_item_list: The list of items to extract from the partial framework
939      target files package as is, meaning these items will land in the output
940      target files package exactly as they appear in the input partial framework
941      target files package.
942    framework_misc_info_keys: The list of keys to obtain from the framework
943      instance of META/misc_info.txt. The remaining keys from the vendor
944      instance.
945    vendor_target_files: The name of the zip archive containing the vendor
946      partial target files package.
947    vendor_item_list: The list of items to extract from the partial vendor
948      target files package as is, meaning these items will land in the output
949      target files package exactly as they appear in the input partial vendor
950      target files package.
951    output_target_files: The name of the output zip archive target files package
952      created by merging framework and vendor.
953    output_dir: The destination directory for saving merged files.
954    output_item_list: The list of items to copy into the output_dir.
955    output_ota: The name of the output zip archive ota package.
956    output_img: The name of the output zip archive img package.
957    output_super_empty: If provided, creates a super_empty.img file from the
958      merged target files package and saves it at this path.
959    rebuild_recovery: If true, rebuild the recovery patch used by non-A/B
960      devices and write it to the system image.
961  """
962
963  logger.info('starting: merge framework %s and vendor %s into output %s',
964              framework_target_files, vendor_target_files, output_target_files)
965
966  output_target_files_temp_dir = create_merged_package(
967      temp_dir, framework_target_files, framework_item_list,
968      vendor_target_files, vendor_item_list, framework_misc_info_keys,
969      rebuild_recovery)
970
971  if not check_target_files_vintf.CheckVintf(output_target_files_temp_dir):
972    raise RuntimeError("Incompatible VINTF metadata")
973
974  generate_images(output_target_files_temp_dir, rebuild_recovery)
975
976  generate_super_empty_image(output_target_files_temp_dir, output_super_empty)
977
978  # Finally, create the output target files zip archive and/or copy the
979  # output items to the output target files directory.
980
981  if output_dir:
982    copy_items(output_target_files_temp_dir, output_dir, output_item_list)
983
984  if not output_target_files:
985    return
986
987  output_zip = create_target_files_archive(output_target_files,
988                                           output_target_files_temp_dir,
989                                           temp_dir)
990
991  # Create the IMG package from the merged target files package.
992
993  if output_img:
994    img_from_target_files.main([output_zip, output_img])
995
996  # Create the OTA package from the merged target files package.
997
998  if output_ota:
999    ota_from_target_files.main([output_zip, output_ota])
1000
1001
1002def call_func_with_temp_dir(func, keep_tmp):
1003  """Manages the creation and cleanup of the temporary directory.
1004
1005  This function calls the given function after first creating a temporary
1006  directory. It also cleans up the temporary directory.
1007
1008  Args:
1009    func: The function to call. Should accept one parameter, the path to the
1010      temporary directory.
1011    keep_tmp: Keep the temporary directory after processing is complete.
1012  """
1013
1014  # Create a temporary directory. This will serve as the parent of directories
1015  # we use when we extract items from the input target files packages, and also
1016  # a scratch directory that we use for temporary files.
1017
1018  temp_dir = common.MakeTempDir(prefix='merge_target_files_')
1019
1020  try:
1021    func(temp_dir)
1022  finally:
1023    if keep_tmp:
1024      logger.info('keeping %s', temp_dir)
1025    else:
1026      common.Cleanup()
1027
1028
1029def main():
1030  """The main function.
1031
1032  Process command line arguments, then call merge_target_files to
1033  perform the heavy lifting.
1034  """
1035
1036  common.InitLogging()
1037
1038  def option_handler(o, a):
1039    if o == '--system-target-files':
1040      logger.warning(
1041          '--system-target-files has been renamed to --framework-target-files')
1042      OPTIONS.framework_target_files = a
1043    elif o == '--framework-target-files':
1044      OPTIONS.framework_target_files = a
1045    elif o == '--system-item-list':
1046      logger.warning(
1047          '--system-item-list has been renamed to --framework-item-list')
1048      OPTIONS.framework_item_list = a
1049    elif o == '--framework-item-list':
1050      OPTIONS.framework_item_list = a
1051    elif o == '--system-misc-info-keys':
1052      logger.warning('--system-misc-info-keys has been renamed to '
1053                     '--framework-misc-info-keys')
1054      OPTIONS.framework_misc_info_keys = a
1055    elif o == '--framework-misc-info-keys':
1056      OPTIONS.framework_misc_info_keys = a
1057    elif o == '--other-target-files':
1058      logger.warning(
1059          '--other-target-files has been renamed to --vendor-target-files')
1060      OPTIONS.vendor_target_files = a
1061    elif o == '--vendor-target-files':
1062      OPTIONS.vendor_target_files = a
1063    elif o == '--other-item-list':
1064      logger.warning('--other-item-list has been renamed to --vendor-item-list')
1065      OPTIONS.vendor_item_list = a
1066    elif o == '--vendor-item-list':
1067      OPTIONS.vendor_item_list = a
1068    elif o == '--output-target-files':
1069      OPTIONS.output_target_files = a
1070    elif o == '--output-dir':
1071      OPTIONS.output_dir = a
1072    elif o == '--output-item-list':
1073      OPTIONS.output_item_list = a
1074    elif o == '--output-ota':
1075      OPTIONS.output_ota = a
1076    elif o == '--output-img':
1077      OPTIONS.output_img = a
1078    elif o == '--output-super-empty':
1079      OPTIONS.output_super_empty = a
1080    elif o == '--rebuild_recovery': # TODO(b/132730255): Warn
1081      OPTIONS.rebuild_recovery = True
1082    elif o == '--keep-tmp':
1083      OPTIONS.keep_tmp = True
1084    else:
1085      return False
1086    return True
1087
1088  args = common.ParseOptions(
1089      sys.argv[1:],
1090      __doc__,
1091      extra_long_opts=[
1092          'system-target-files=',
1093          'framework-target-files=',
1094          'system-item-list=',
1095          'framework-item-list=',
1096          'system-misc-info-keys=',
1097          'framework-misc-info-keys=',
1098          'other-target-files=',
1099          'vendor-target-files=',
1100          'other-item-list=',
1101          'vendor-item-list=',
1102          'output-target-files=',
1103          'output-dir=',
1104          'output-item-list=',
1105          'output-ota=',
1106          'output-img=',
1107          'output-super-empty=',
1108          'rebuild_recovery',
1109          'keep-tmp',
1110      ],
1111      extra_option_handler=option_handler)
1112
1113  # pylint: disable=too-many-boolean-expressions
1114  if (args or OPTIONS.framework_target_files is None or
1115      OPTIONS.vendor_target_files is None or
1116      (OPTIONS.output_target_files is None and OPTIONS.output_dir is None) or
1117      (OPTIONS.output_dir is not None and OPTIONS.output_item_list is None)):
1118    common.Usage(__doc__)
1119    sys.exit(1)
1120
1121  if OPTIONS.framework_item_list:
1122    framework_item_list = common.LoadListFromFile(OPTIONS.framework_item_list)
1123  else:
1124    framework_item_list = DEFAULT_FRAMEWORK_ITEM_LIST
1125
1126  if OPTIONS.framework_misc_info_keys:
1127    framework_misc_info_keys = common.LoadListFromFile(
1128        OPTIONS.framework_misc_info_keys)
1129  else:
1130    framework_misc_info_keys = DEFAULT_FRAMEWORK_MISC_INFO_KEYS
1131
1132  if OPTIONS.vendor_item_list:
1133    vendor_item_list = common.LoadListFromFile(OPTIONS.vendor_item_list)
1134  else:
1135    vendor_item_list = DEFAULT_VENDOR_ITEM_LIST
1136
1137  if OPTIONS.output_item_list:
1138    output_item_list = common.LoadListFromFile(OPTIONS.output_item_list)
1139  else:
1140    output_item_list = None
1141
1142  if not validate_config_lists(
1143      framework_item_list=framework_item_list,
1144      framework_misc_info_keys=framework_misc_info_keys,
1145      vendor_item_list=vendor_item_list):
1146    sys.exit(1)
1147
1148  call_func_with_temp_dir(
1149      lambda temp_dir: merge_target_files(
1150          temp_dir=temp_dir,
1151          framework_target_files=OPTIONS.framework_target_files,
1152          framework_item_list=framework_item_list,
1153          framework_misc_info_keys=framework_misc_info_keys,
1154          vendor_target_files=OPTIONS.vendor_target_files,
1155          vendor_item_list=vendor_item_list,
1156          output_target_files=OPTIONS.output_target_files,
1157          output_dir=OPTIONS.output_dir,
1158          output_item_list=output_item_list,
1159          output_ota=OPTIONS.output_ota,
1160          output_img=OPTIONS.output_img,
1161          output_super_empty=OPTIONS.output_super_empty,
1162          rebuild_recovery=OPTIONS.rebuild_recovery), OPTIONS.keep_tmp)
1163
1164
1165if __name__ == '__main__':
1166  main()
1167