• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright (C) 2022 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 input package contains framework files, and the other contains vendor files.
20
21This script produces a complete, merged target files package:
22  - This package can be used to generate a flashable IMG package.
23    See --output-img.
24  - This package can be used to generate an OTA package. See --output-ota.
25  - The merged package is checked for compatibility between the two inputs.
26
27Usage: merge_target_files [args]
28
29  --framework-target-files framework-target-files-package
30      The input target files package containing framework bits. This is a zip
31      archive or a directory.
32
33  --framework-item-list framework-item-list-file
34      The optional path to a newline-separated config file of items that
35      are extracted as-is from the framework target files package.
36
37  --framework-misc-info-keys framework-misc-info-keys-file
38      The optional path to a newline-separated config file of keys to
39      extract from the framework META/misc_info.txt file.
40
41  --vendor-target-files vendor-target-files-package
42      The input target files package containing vendor bits. This is a zip
43      archive or a directory.
44
45  --vendor-item-list vendor-item-list-file
46      The optional path to a newline-separated config file of items that
47      are extracted as-is from the vendor target files package.
48
49  --output-target-files output-target-files-package
50      If provided, the output merged target files package. Also a zip archive.
51
52  --output-dir output-directory
53      If provided, the destination directory for saving merged files. Requires
54      the --output-item-list flag.
55      Can be provided alongside --output-target-files, or by itself.
56
57  --output-item-list output-item-list-file.
58      The optional path to a newline-separated config file that specifies the
59      file patterns to copy into the --output-dir. Required if providing
60      the --output-dir flag.
61
62  --output-ota output-ota-package
63      The output ota package. This is a zip archive. Use of this flag may
64      require passing the --path common flag; see common.py.
65
66  --output-img output-img-package
67      The output img package, suitable for use with 'fastboot update'. Use of
68      this flag may require passing the --path common flag; see common.py.
69
70  --output-super-empty output-super-empty-image
71      If provided, creates a super_empty.img file from the merged target
72      files package and saves it at this path.
73
74  --rebuild_recovery
75      Copy the recovery image used by non-A/B devices, used when
76      regenerating vendor images with --rebuild-sepolicy.
77
78  --allow-duplicate-apkapex-keys
79      If provided, duplicate APK/APEX keys are ignored and the value from the
80      framework is used.
81
82  --rebuild-sepolicy
83      If provided, rebuilds odm.img or vendor.img to include merged sepolicy
84      files. If odm is present then odm is preferred.
85
86  --vendor-otatools otatools.zip
87      If provided, use this otatools.zip when recompiling the odm or vendor
88      image to include sepolicy.
89
90  --keep-tmp
91      Keep tempoary files for debugging purposes.
92
93  The following only apply when using the VSDK to perform dexopt on vendor apps:
94
95  --framework-dexpreopt-config
96      If provided, the location of framwework's dexpreopt_config.zip.
97
98  --framework-dexpreopt-tools
99      if provided, the location of framework's dexpreopt_tools.zip.
100
101  --vendor-dexpreopt-config
102      If provided, the location of vendor's dexpreopt_config.zip.
103"""
104
105import logging
106import os
107import shutil
108import subprocess
109import sys
110import zipfile
111
112import add_img_to_target_files
113import build_image
114import build_super_image
115import common
116import img_from_target_files
117import merge_compatibility_checks
118import merge_dexopt
119import merge_meta
120import merge_utils
121import ota_from_target_files
122
123from common import ExternalError
124
125logger = logging.getLogger(__name__)
126
127OPTIONS = common.OPTIONS
128# Always turn on verbose logging.
129OPTIONS.verbose = True
130OPTIONS.framework_target_files = None
131OPTIONS.framework_item_list = []
132OPTIONS.framework_misc_info_keys = []
133OPTIONS.vendor_target_files = None
134OPTIONS.vendor_item_list = []
135OPTIONS.output_target_files = None
136OPTIONS.output_dir = None
137OPTIONS.output_item_list = []
138OPTIONS.output_ota = None
139OPTIONS.output_img = None
140OPTIONS.output_super_empty = None
141OPTIONS.rebuild_recovery = False
142# TODO(b/150582573): Remove this option.
143OPTIONS.allow_duplicate_apkapex_keys = False
144OPTIONS.vendor_otatools = None
145OPTIONS.rebuild_sepolicy = False
146OPTIONS.keep_tmp = False
147OPTIONS.framework_dexpreopt_config = None
148OPTIONS.framework_dexpreopt_tools = None
149OPTIONS.vendor_dexpreopt_config = None
150
151
152def move_only_exists(source, destination):
153  """Judge whether the file exists and then move the file."""
154
155  if os.path.exists(source):
156    shutil.move(source, destination)
157
158
159def remove_file_if_exists(file_name):
160  """Remove the file if it exists and skip otherwise."""
161
162  try:
163    os.remove(file_name)
164  except FileNotFoundError:
165    pass
166
167
168def include_meta_in_list(item_list):
169  """Include all `META/*` files in the item list.
170
171  To ensure that `AddImagesToTargetFiles` can still be used with vendor item
172  list that do not specify all of the required META/ files, those files should
173  be included by default. This preserves the backward compatibility of
174  `rebuild_image_with_sepolicy`.
175  """
176  if not item_list:
177    return None
178  return list(item_list) + ['META/*']
179
180
181def create_merged_package(temp_dir):
182  """Merges two target files packages into one target files structure.
183
184  Returns:
185    Path to merged package under temp directory.
186  """
187  # Extract "as is" items from the input framework and vendor partial target
188  # files packages directly into the output temporary directory, since these
189  # items do not need special case processing.
190
191  output_target_files_temp_dir = os.path.join(temp_dir, 'output')
192  merge_utils.CollectTargetFiles(
193      input_zipfile_or_dir=OPTIONS.framework_target_files,
194      output_dir=output_target_files_temp_dir,
195      item_list=OPTIONS.framework_item_list)
196  merge_utils.CollectTargetFiles(
197      input_zipfile_or_dir=OPTIONS.vendor_target_files,
198      output_dir=output_target_files_temp_dir,
199      item_list=OPTIONS.vendor_item_list)
200
201  # Perform special case processing on META/* items.
202  # After this function completes successfully, all the files we need to create
203  # the output target files package are in place.
204  merge_meta.MergeMetaFiles(
205      temp_dir=temp_dir, merged_dir=output_target_files_temp_dir)
206
207  merge_dexopt.MergeDexopt(
208      temp_dir=temp_dir, output_target_files_dir=output_target_files_temp_dir)
209
210  return output_target_files_temp_dir
211
212
213def generate_missing_images(target_files_dir):
214  """Generate any missing images from target files."""
215
216  # Regenerate IMAGES in the target directory.
217
218  add_img_args = [
219      '--verbose',
220      '--add_missing',
221  ]
222  if OPTIONS.rebuild_recovery:
223    add_img_args.append('--rebuild_recovery')
224  add_img_args.append(target_files_dir)
225
226  add_img_to_target_files.main(add_img_args)
227
228
229def rebuild_image_with_sepolicy(target_files_dir):
230  """Rebuilds odm.img or vendor.img to include merged sepolicy files.
231
232  If odm is present then odm is preferred -- otherwise vendor is used.
233  """
234  partition = 'vendor'
235  if os.path.exists(os.path.join(target_files_dir, 'ODM')):
236    partition = 'odm'
237  partition_img = '{}.img'.format(partition)
238  partition_map = '{}.map'.format(partition)
239
240  logger.info('Recompiling %s using the merged sepolicy files.', partition_img)
241
242  # Copy the combined SEPolicy file and framework hashes to the image that is
243  # being rebuilt.
244  def copy_selinux_file(input_path, output_filename):
245    input_filename = os.path.join(target_files_dir, input_path)
246    if not os.path.exists(input_filename):
247      input_filename = input_filename.replace('SYSTEM_EXT/',
248                                              'SYSTEM/system_ext/') \
249          .replace('PRODUCT/', 'SYSTEM/product/')
250      if not os.path.exists(input_filename):
251        logger.info('Skipping copy_selinux_file for %s', input_filename)
252        return
253    shutil.copy(
254        input_filename,
255        os.path.join(target_files_dir, partition.upper(), 'etc/selinux',
256                     output_filename))
257
258  copy_selinux_file('META/combined_sepolicy', 'precompiled_sepolicy')
259  copy_selinux_file('SYSTEM/etc/selinux/plat_sepolicy_and_mapping.sha256',
260                    'precompiled_sepolicy.plat_sepolicy_and_mapping.sha256')
261  copy_selinux_file(
262      'SYSTEM_EXT/etc/selinux/system_ext_sepolicy_and_mapping.sha256',
263      'precompiled_sepolicy.system_ext_sepolicy_and_mapping.sha256')
264  copy_selinux_file('PRODUCT/etc/selinux/product_sepolicy_and_mapping.sha256',
265                    'precompiled_sepolicy.product_sepolicy_and_mapping.sha256')
266
267  if not OPTIONS.vendor_otatools:
268    # Remove the partition from the merged target-files archive. It will be
269    # rebuilt later automatically by generate_missing_images().
270    remove_file_if_exists(
271        os.path.join(target_files_dir, 'IMAGES', partition_img))
272    return
273
274  # TODO(b/192253131): Remove the need for vendor_otatools by fixing
275  # backwards-compatibility issues when compiling images across releases.
276  if not OPTIONS.vendor_target_files:
277    raise ValueError(
278        'Expected vendor_target_files if vendor_otatools is not None.')
279  logger.info(
280      '%s recompilation will be performed using the vendor otatools.zip',
281      partition_img)
282
283  # Unzip the vendor build's otatools.zip and target-files archive.
284  vendor_otatools_dir = common.MakeTempDir(
285      prefix='merge_target_files_vendor_otatools_')
286  vendor_target_files_dir = common.MakeTempDir(
287      prefix='merge_target_files_vendor_target_files_')
288  common.UnzipToDir(OPTIONS.vendor_otatools, vendor_otatools_dir)
289  merge_utils.CollectTargetFiles(
290      input_zipfile_or_dir=OPTIONS.vendor_target_files,
291      output_dir=vendor_target_files_dir,
292      item_list=include_meta_in_list(OPTIONS.vendor_item_list))
293
294  # Copy the partition contents from the merged target-files archive to the
295  # vendor target-files archive.
296  shutil.rmtree(os.path.join(vendor_target_files_dir, partition.upper()))
297  shutil.copytree(
298      os.path.join(target_files_dir, partition.upper()),
299      os.path.join(vendor_target_files_dir, partition.upper()),
300      symlinks=True)
301
302  # Delete then rebuild the partition.
303  remove_file_if_exists(
304      os.path.join(vendor_target_files_dir, 'IMAGES', partition_img))
305  rebuild_partition_command = [
306      os.path.join(vendor_otatools_dir, 'bin', 'add_img_to_target_files'),
307      '--verbose',
308      '--add_missing',
309  ]
310  if OPTIONS.rebuild_recovery:
311    rebuild_partition_command.append('--rebuild_recovery')
312  rebuild_partition_command.append(vendor_target_files_dir)
313  logger.info('Recompiling %s: %s', partition_img,
314              ' '.join(rebuild_partition_command))
315  common.RunAndCheckOutput(rebuild_partition_command, verbose=True)
316
317  # Move the newly-created image to the merged target files dir.
318  if not os.path.exists(os.path.join(target_files_dir, 'IMAGES')):
319    os.makedirs(os.path.join(target_files_dir, 'IMAGES'))
320  shutil.move(
321      os.path.join(vendor_target_files_dir, 'IMAGES', partition_img),
322      os.path.join(target_files_dir, 'IMAGES', partition_img))
323  move_only_exists(
324      os.path.join(vendor_target_files_dir, 'IMAGES', partition_map),
325      os.path.join(target_files_dir, 'IMAGES', partition_map))
326
327  def copy_recovery_file(filename):
328    for subdir in ('VENDOR', 'SYSTEM/vendor'):
329      source = os.path.join(vendor_target_files_dir, subdir, filename)
330      if os.path.exists(source):
331        dest = os.path.join(target_files_dir, subdir, filename)
332        shutil.copy(source, dest)
333        return
334    logger.info('Skipping copy_recovery_file for %s, file not found', filename)
335
336  if OPTIONS.rebuild_recovery:
337    copy_recovery_file('etc/recovery.img')
338    copy_recovery_file('bin/install-recovery.sh')
339    copy_recovery_file('recovery-from-boot.p')
340
341
342def generate_super_empty_image(target_dir, output_super_empty):
343  """Generates super_empty image from target package.
344
345  Args:
346    target_dir: Path to the target file package which contains misc_info.txt for
347      detailed information for super image.
348    output_super_empty: If provided, copies a super_empty.img file from the
349      target files package to this path.
350  """
351  # Create super_empty.img using the merged misc_info.txt.
352
353  misc_info_txt = os.path.join(target_dir, 'META', 'misc_info.txt')
354
355  use_dynamic_partitions = common.LoadDictionaryFromFile(misc_info_txt).get(
356      'use_dynamic_partitions')
357
358  if use_dynamic_partitions != 'true' and output_super_empty:
359    raise ValueError(
360        'Building super_empty.img requires use_dynamic_partitions=true.')
361  elif use_dynamic_partitions == 'true':
362    super_empty_img = os.path.join(target_dir, 'IMAGES', 'super_empty.img')
363    build_super_image_args = [
364        misc_info_txt,
365        super_empty_img,
366    ]
367    build_super_image.main(build_super_image_args)
368
369    # Copy super_empty.img to the user-provided output_super_empty location.
370    if output_super_empty:
371      shutil.copyfile(super_empty_img, output_super_empty)
372
373
374def create_target_files_archive(output_zip, source_dir, temp_dir):
375  """Creates a target_files zip archive from the input source dir.
376
377  Args:
378    output_zip: The name of the zip archive target files package.
379    source_dir: The target directory contains package to be archived.
380    temp_dir: Path to temporary directory for any intermediate files.
381  """
382  output_target_files_list = os.path.join(temp_dir, 'output.list')
383  output_target_files_meta_dir = os.path.join(source_dir, 'META')
384
385  def files_from_path(target_path, extra_args=None):
386    """Gets files under the given path and return a sorted list."""
387    find_command = ['find', target_path] + (extra_args or [])
388    find_process = common.Run(
389        find_command, stdout=subprocess.PIPE, verbose=False)
390    return common.RunAndCheckOutput(['sort'],
391                                    stdin=find_process.stdout,
392                                    verbose=False)
393
394  # META content appears first in the zip. This is done by the
395  # standard build system for optimized extraction of those files,
396  # so we do the same step for merged target_files.zips here too.
397  meta_content = files_from_path(output_target_files_meta_dir)
398  other_content = files_from_path(
399      source_dir,
400      ['-path', output_target_files_meta_dir, '-prune', '-o', '-print'])
401
402  with open(output_target_files_list, 'w') as f:
403    f.write(meta_content)
404    f.write(other_content)
405
406  command = [
407      'soong_zip',
408      '-d',
409      '-o',
410      os.path.abspath(output_zip),
411      '-C',
412      source_dir,
413      '-r',
414      output_target_files_list,
415  ]
416
417  logger.info('creating %s', output_zip)
418  common.RunAndCheckOutput(command, verbose=True)
419  logger.info('finished creating %s', output_zip)
420
421
422def merge_target_files(temp_dir):
423  """Merges two target files packages together.
424
425  This function uses framework and vendor target files packages as input,
426  performs various file extractions, special case processing, and finally
427  creates a merged zip archive as output.
428
429  Args:
430    temp_dir: The name of a directory we use when we extract items from the
431      input target files packages, and also a scratch directory that we use for
432      temporary files.
433  """
434
435  logger.info('starting: merge framework %s and vendor %s into output %s',
436              OPTIONS.framework_target_files, OPTIONS.vendor_target_files,
437              OPTIONS.output_target_files)
438
439  output_target_files_temp_dir = create_merged_package(temp_dir)
440
441  partition_map = common.PartitionMapFromTargetFiles(
442      output_target_files_temp_dir)
443
444  compatibility_errors = merge_compatibility_checks.CheckCompatibility(
445      target_files_dir=output_target_files_temp_dir,
446      partition_map=partition_map)
447  if compatibility_errors:
448    for error in compatibility_errors:
449      logger.error(error)
450    raise ExternalError(
451        'Found incompatibilities in the merged target files package.')
452
453  # Include the compiled policy in an image if requested.
454  if OPTIONS.rebuild_sepolicy:
455    rebuild_image_with_sepolicy(output_target_files_temp_dir)
456
457  generate_missing_images(output_target_files_temp_dir)
458
459  generate_super_empty_image(output_target_files_temp_dir,
460                             OPTIONS.output_super_empty)
461
462  # Finally, create the output target files zip archive and/or copy the
463  # output items to the output target files directory.
464
465  if OPTIONS.output_dir:
466    merge_utils.CopyItems(output_target_files_temp_dir, OPTIONS.output_dir,
467                          OPTIONS.output_item_list)
468
469  if not OPTIONS.output_target_files:
470    return
471
472  create_target_files_archive(OPTIONS.output_target_files,
473                              output_target_files_temp_dir, temp_dir)
474
475  # Create the IMG package from the merged target files package.
476  if OPTIONS.output_img:
477    img_from_target_files.main(
478        [OPTIONS.output_target_files, OPTIONS.output_img])
479
480  # Create the OTA package from the merged target files package.
481
482  if OPTIONS.output_ota:
483    ota_from_target_files.main(
484        [OPTIONS.output_target_files, OPTIONS.output_ota])
485
486
487def main():
488  """The main function.
489
490  Process command line arguments, then call merge_target_files to
491  perform the heavy lifting.
492  """
493
494  common.InitLogging()
495
496  def option_handler(o, a):
497    if o == '--system-target-files':
498      logger.warning(
499          '--system-target-files has been renamed to --framework-target-files')
500      OPTIONS.framework_target_files = a
501    elif o == '--framework-target-files':
502      OPTIONS.framework_target_files = a
503    elif o == '--system-item-list':
504      logger.warning(
505          '--system-item-list has been renamed to --framework-item-list')
506      OPTIONS.framework_item_list = a
507    elif o == '--framework-item-list':
508      OPTIONS.framework_item_list = a
509    elif o == '--system-misc-info-keys':
510      logger.warning('--system-misc-info-keys has been renamed to '
511                     '--framework-misc-info-keys')
512      OPTIONS.framework_misc_info_keys = a
513    elif o == '--framework-misc-info-keys':
514      OPTIONS.framework_misc_info_keys = a
515    elif o == '--other-target-files':
516      logger.warning(
517          '--other-target-files has been renamed to --vendor-target-files')
518      OPTIONS.vendor_target_files = a
519    elif o == '--vendor-target-files':
520      OPTIONS.vendor_target_files = a
521    elif o == '--other-item-list':
522      logger.warning('--other-item-list has been renamed to --vendor-item-list')
523      OPTIONS.vendor_item_list = a
524    elif o == '--vendor-item-list':
525      OPTIONS.vendor_item_list = a
526    elif o == '--output-target-files':
527      OPTIONS.output_target_files = a
528    elif o == '--output-dir':
529      OPTIONS.output_dir = a
530    elif o == '--output-item-list':
531      OPTIONS.output_item_list = a
532    elif o == '--output-ota':
533      OPTIONS.output_ota = a
534    elif o == '--output-img':
535      OPTIONS.output_img = a
536    elif o == '--output-super-empty':
537      OPTIONS.output_super_empty = a
538    elif o == '--rebuild_recovery' or o == '--rebuild-recovery':
539      OPTIONS.rebuild_recovery = True
540    elif o == '--allow-duplicate-apkapex-keys':
541      OPTIONS.allow_duplicate_apkapex_keys = True
542    elif o == '--vendor-otatools':
543      OPTIONS.vendor_otatools = a
544    elif o == '--rebuild-sepolicy':
545      OPTIONS.rebuild_sepolicy = True
546    elif o == '--keep-tmp':
547      OPTIONS.keep_tmp = True
548    elif o == '--framework-dexpreopt-config':
549      OPTIONS.framework_dexpreopt_config = a
550    elif o == '--framework-dexpreopt-tools':
551      OPTIONS.framework_dexpreopt_tools = a
552    elif o == '--vendor-dexpreopt-config':
553      OPTIONS.vendor_dexpreopt_config = a
554    else:
555      return False
556    return True
557
558  args = common.ParseOptions(
559      sys.argv[1:],
560      __doc__,
561      extra_long_opts=[
562          'system-target-files=',
563          'framework-target-files=',
564          'system-item-list=',
565          'framework-item-list=',
566          'system-misc-info-keys=',
567          'framework-misc-info-keys=',
568          'other-target-files=',
569          'vendor-target-files=',
570          'other-item-list=',
571          'vendor-item-list=',
572          'output-target-files=',
573          'output-dir=',
574          'output-item-list=',
575          'output-ota=',
576          'output-img=',
577          'output-super-empty=',
578          'framework-dexpreopt-config=',
579          'framework-dexpreopt-tools=',
580          'vendor-dexpreopt-config=',
581          'rebuild_recovery',
582          'rebuild-recovery',
583          'allow-duplicate-apkapex-keys',
584          'vendor-otatools=',
585          'rebuild-sepolicy',
586          'keep-tmp',
587      ],
588      extra_option_handler=option_handler)
589
590  # pylint: disable=too-many-boolean-expressions
591  if (args or OPTIONS.framework_target_files is None or
592      OPTIONS.vendor_target_files is None or
593      (OPTIONS.output_target_files is None and OPTIONS.output_dir is None) or
594      (OPTIONS.output_dir is not None and not OPTIONS.output_item_list) or
595      (OPTIONS.rebuild_recovery and not OPTIONS.rebuild_sepolicy)):
596    common.Usage(__doc__)
597    sys.exit(1)
598
599  framework_namelist = merge_utils.GetTargetFilesItems(
600      OPTIONS.framework_target_files)
601  vendor_namelist = merge_utils.GetTargetFilesItems(
602      OPTIONS.vendor_target_files)
603
604  if OPTIONS.framework_item_list:
605    OPTIONS.framework_item_list = common.LoadListFromFile(
606        OPTIONS.framework_item_list)
607  else:
608    OPTIONS.framework_item_list = merge_utils.InferItemList(
609        input_namelist=framework_namelist, framework=True)
610  OPTIONS.framework_partition_set = merge_utils.ItemListToPartitionSet(
611      OPTIONS.framework_item_list)
612
613  if OPTIONS.framework_misc_info_keys:
614    OPTIONS.framework_misc_info_keys = common.LoadListFromFile(
615        OPTIONS.framework_misc_info_keys)
616  else:
617    OPTIONS.framework_misc_info_keys = merge_utils.InferFrameworkMiscInfoKeys(
618        input_namelist=framework_namelist)
619
620  if OPTIONS.vendor_item_list:
621    OPTIONS.vendor_item_list = common.LoadListFromFile(OPTIONS.vendor_item_list)
622  else:
623    OPTIONS.vendor_item_list = merge_utils.InferItemList(
624        input_namelist=vendor_namelist, framework=False)
625  OPTIONS.vendor_partition_set = merge_utils.ItemListToPartitionSet(
626      OPTIONS.vendor_item_list)
627
628  if OPTIONS.output_item_list:
629    OPTIONS.output_item_list = common.LoadListFromFile(OPTIONS.output_item_list)
630
631  if not merge_utils.ValidateConfigLists():
632    sys.exit(1)
633
634  temp_dir = common.MakeTempDir(prefix='merge_target_files_')
635  try:
636    merge_target_files(temp_dir)
637  finally:
638    if OPTIONS.keep_tmp:
639      logger.info('Keeping temp_dir %s', temp_dir)
640    else:
641      common.Cleanup()
642
643
644if __name__ == '__main__':
645  main()
646