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