• 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-zip-archive
30      The input target files package containing framework bits. This is a zip
31      archive.
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-zip-archive
42      The input target files package containing vendor bits. This is a zip
43      archive.
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 create_merged_package(temp_dir):
153  """Merges two target files packages into one target files structure.
154
155  Returns:
156    Path to merged package under temp directory.
157  """
158  # Extract "as is" items from the input framework and vendor partial target
159  # files packages directly into the output temporary directory, since these items
160  # do not need special case processing.
161
162  output_target_files_temp_dir = os.path.join(temp_dir, 'output')
163  merge_utils.ExtractItems(
164      input_zip=OPTIONS.framework_target_files,
165      output_dir=output_target_files_temp_dir,
166      extract_item_list=OPTIONS.framework_item_list)
167  merge_utils.ExtractItems(
168      input_zip=OPTIONS.vendor_target_files,
169      output_dir=output_target_files_temp_dir,
170      extract_item_list=OPTIONS.vendor_item_list)
171
172  # Perform special case processing on META/* items.
173  # After this function completes successfully, all the files we need to create
174  # the output target files package are in place.
175  merge_meta.MergeMetaFiles(
176      temp_dir=temp_dir, merged_dir=output_target_files_temp_dir)
177
178  merge_dexopt.MergeDexopt(
179      temp_dir=temp_dir, output_target_files_dir=output_target_files_temp_dir)
180
181  return output_target_files_temp_dir
182
183
184def generate_missing_images(target_files_dir):
185  """Generate any missing images from target files."""
186
187  # Regenerate IMAGES in the target directory.
188
189  add_img_args = [
190      '--verbose',
191      '--add_missing',
192  ]
193  if OPTIONS.rebuild_recovery:
194    add_img_args.append('--rebuild_recovery')
195  add_img_args.append(target_files_dir)
196
197  add_img_to_target_files.main(add_img_args)
198
199
200def rebuild_image_with_sepolicy(target_files_dir):
201  """Rebuilds odm.img or vendor.img to include merged sepolicy files.
202
203  If odm is present then odm is preferred -- otherwise vendor is used.
204  """
205  partition = 'vendor'
206  if os.path.exists(os.path.join(target_files_dir, 'ODM')) or os.path.exists(
207      os.path.join(target_files_dir, 'IMAGES/odm.img')):
208    partition = 'odm'
209  partition_img = '{}.img'.format(partition)
210  partition_map = '{}.map'.format(partition)
211
212  logger.info('Recompiling %s using the merged sepolicy files.', partition_img)
213
214  # Copy the combined SEPolicy file and framework hashes to the image that is
215  # being rebuilt.
216  def copy_selinux_file(input_path, output_filename):
217    input_filename = os.path.join(target_files_dir, input_path)
218    if not os.path.exists(input_filename):
219      input_filename = input_filename.replace('SYSTEM_EXT/', 'SYSTEM/system_ext/') \
220          .replace('PRODUCT/', 'SYSTEM/product/')
221      if not os.path.exists(input_filename):
222        logger.info('Skipping copy_selinux_file for %s', input_filename)
223        return
224    shutil.copy(
225        input_filename,
226        os.path.join(target_files_dir, partition.upper(), 'etc/selinux',
227                     output_filename))
228
229  copy_selinux_file('META/combined_sepolicy', 'precompiled_sepolicy')
230  copy_selinux_file('SYSTEM/etc/selinux/plat_sepolicy_and_mapping.sha256',
231                    'precompiled_sepolicy.plat_sepolicy_and_mapping.sha256')
232  copy_selinux_file(
233      'SYSTEM_EXT/etc/selinux/system_ext_sepolicy_and_mapping.sha256',
234      'precompiled_sepolicy.system_ext_sepolicy_and_mapping.sha256')
235  copy_selinux_file('PRODUCT/etc/selinux/product_sepolicy_and_mapping.sha256',
236                    'precompiled_sepolicy.product_sepolicy_and_mapping.sha256')
237
238  if not OPTIONS.vendor_otatools:
239    # Remove the partition from the merged target-files archive. It will be
240    # rebuilt later automatically by generate_missing_images().
241    os.remove(os.path.join(target_files_dir, 'IMAGES', partition_img))
242    return
243
244  # TODO(b/192253131): Remove the need for vendor_otatools by fixing
245  # backwards-compatibility issues when compiling images across releases.
246  if not OPTIONS.vendor_target_files:
247    raise ValueError(
248        'Expected vendor_target_files if vendor_otatools is not None.')
249  logger.info(
250      '%s recompilation will be performed using the vendor otatools.zip',
251      partition_img)
252
253  # Unzip the vendor build's otatools.zip and target-files archive.
254  vendor_otatools_dir = common.MakeTempDir(
255      prefix='merge_target_files_vendor_otatools_')
256  vendor_target_files_dir = common.MakeTempDir(
257      prefix='merge_target_files_vendor_target_files_')
258  common.UnzipToDir(OPTIONS.vendor_otatools, vendor_otatools_dir)
259  common.UnzipToDir(OPTIONS.vendor_target_files, vendor_target_files_dir)
260
261  # Copy the partition contents from the merged target-files archive to the
262  # vendor target-files archive.
263  shutil.rmtree(os.path.join(vendor_target_files_dir, partition.upper()))
264  shutil.copytree(
265      os.path.join(target_files_dir, partition.upper()),
266      os.path.join(vendor_target_files_dir, partition.upper()),
267      symlinks=True)
268
269  # Delete then rebuild the partition.
270  os.remove(os.path.join(vendor_target_files_dir, 'IMAGES', partition_img))
271  rebuild_partition_command = [
272      os.path.join(vendor_otatools_dir, 'bin', 'add_img_to_target_files'),
273      '--verbose',
274      '--add_missing',
275  ]
276  if OPTIONS.rebuild_recovery:
277    rebuild_partition_command.append('--rebuild_recovery')
278  rebuild_partition_command.append(vendor_target_files_dir)
279  logger.info('Recompiling %s: %s', partition_img,
280              ' '.join(rebuild_partition_command))
281  common.RunAndCheckOutput(rebuild_partition_command, verbose=True)
282
283  # Move the newly-created image to the merged target files dir.
284  if not os.path.exists(os.path.join(target_files_dir, 'IMAGES')):
285    os.makedirs(os.path.join(target_files_dir, 'IMAGES'))
286  shutil.move(
287      os.path.join(vendor_target_files_dir, 'IMAGES', partition_img),
288      os.path.join(target_files_dir, 'IMAGES', partition_img))
289  shutil.move(
290      os.path.join(vendor_target_files_dir, 'IMAGES', partition_map),
291      os.path.join(target_files_dir, 'IMAGES', partition_map))
292
293  def copy_recovery_file(filename):
294    for subdir in ('VENDOR', 'SYSTEM/vendor'):
295      source = os.path.join(vendor_target_files_dir, subdir, filename)
296      if os.path.exists(source):
297        dest = os.path.join(target_files_dir, subdir, filename)
298        shutil.copy(source, dest)
299        return
300    logger.info('Skipping copy_recovery_file for %s, file not found', filename)
301
302  if OPTIONS.rebuild_recovery:
303    copy_recovery_file('etc/recovery.img')
304    copy_recovery_file('bin/install-recovery.sh')
305    copy_recovery_file('recovery-from-boot.p')
306
307
308def generate_super_empty_image(target_dir, output_super_empty):
309  """Generates super_empty image from target package.
310
311  Args:
312    target_dir: Path to the target file package which contains misc_info.txt for
313      detailed information for super image.
314    output_super_empty: If provided, copies a super_empty.img file from the
315      target files package to this path.
316  """
317  # Create super_empty.img using the merged misc_info.txt.
318
319  misc_info_txt = os.path.join(target_dir, 'META', 'misc_info.txt')
320
321  use_dynamic_partitions = common.LoadDictionaryFromFile(misc_info_txt).get(
322      'use_dynamic_partitions')
323
324  if use_dynamic_partitions != 'true' and output_super_empty:
325    raise ValueError(
326        'Building super_empty.img requires use_dynamic_partitions=true.')
327  elif use_dynamic_partitions == 'true':
328    super_empty_img = os.path.join(target_dir, 'IMAGES', 'super_empty.img')
329    build_super_image_args = [
330        misc_info_txt,
331        super_empty_img,
332    ]
333    build_super_image.main(build_super_image_args)
334
335    # Copy super_empty.img to the user-provided output_super_empty location.
336    if output_super_empty:
337      shutil.copyfile(super_empty_img, output_super_empty)
338
339
340def create_target_files_archive(output_zip, source_dir, temp_dir):
341  """Creates a target_files zip archive from the input source dir.
342
343  Args:
344    output_zip: The name of the zip archive target files package.
345    source_dir: The target directory contains package to be archived.
346    temp_dir: Path to temporary directory for any intermediate files.
347  """
348  output_target_files_list = os.path.join(temp_dir, 'output.list')
349  output_target_files_meta_dir = os.path.join(source_dir, 'META')
350
351  def files_from_path(target_path, extra_args=None):
352    """Gets files under the given path and return a sorted list."""
353    find_command = ['find', target_path] + (extra_args or [])
354    find_process = common.Run(
355        find_command, stdout=subprocess.PIPE, verbose=False)
356    return common.RunAndCheckOutput(['sort'],
357                                    stdin=find_process.stdout,
358                                    verbose=False)
359
360  # META content appears first in the zip. This is done by the
361  # standard build system for optimized extraction of those files,
362  # so we do the same step for merged target_files.zips here too.
363  meta_content = files_from_path(output_target_files_meta_dir)
364  other_content = files_from_path(
365      source_dir,
366      ['-path', output_target_files_meta_dir, '-prune', '-o', '-print'])
367
368  with open(output_target_files_list, 'w') as f:
369    f.write(meta_content)
370    f.write(other_content)
371
372  command = [
373      'soong_zip',
374      '-d',
375      '-o',
376      os.path.abspath(output_zip),
377      '-C',
378      source_dir,
379      '-r',
380      output_target_files_list,
381  ]
382
383  logger.info('creating %s', output_zip)
384  common.RunAndCheckOutput(command, verbose=True)
385  logger.info('finished creating %s', output_zip)
386
387
388def merge_target_files(temp_dir):
389  """Merges two target files packages together.
390
391  This function uses framework and vendor target files packages as input,
392  performs various file extractions, special case processing, and finally
393  creates a merged zip archive as output.
394
395  Args:
396    temp_dir: The name of a directory we use when we extract items from the
397      input target files packages, and also a scratch directory that we use for
398      temporary files.
399  """
400
401  logger.info('starting: merge framework %s and vendor %s into output %s',
402              OPTIONS.framework_target_files, OPTIONS.vendor_target_files,
403              OPTIONS.output_target_files)
404
405  output_target_files_temp_dir = create_merged_package(temp_dir)
406
407  partition_map = common.PartitionMapFromTargetFiles(
408      output_target_files_temp_dir)
409
410  compatibility_errors = merge_compatibility_checks.CheckCompatibility(
411      target_files_dir=output_target_files_temp_dir,
412      partition_map=partition_map)
413  if compatibility_errors:
414    for error in compatibility_errors:
415      logger.error(error)
416    raise ExternalError(
417        'Found incompatibilities in the merged target files package.')
418
419  # Include the compiled policy in an image if requested.
420  if OPTIONS.rebuild_sepolicy:
421    rebuild_image_with_sepolicy(output_target_files_temp_dir)
422
423  generate_missing_images(output_target_files_temp_dir)
424
425  generate_super_empty_image(output_target_files_temp_dir,
426                             OPTIONS.output_super_empty)
427
428  # Finally, create the output target files zip archive and/or copy the
429  # output items to the output target files directory.
430
431  if OPTIONS.output_dir:
432    merge_utils.CopyItems(output_target_files_temp_dir, OPTIONS.output_dir,
433                          OPTIONS.output_item_list)
434
435  if not OPTIONS.output_target_files:
436    return
437
438  create_target_files_archive(OPTIONS.output_target_files,
439                              output_target_files_temp_dir, temp_dir)
440
441  # Create the IMG package from the merged target files package.
442  if OPTIONS.output_img:
443    img_from_target_files.main(
444        [OPTIONS.output_target_files, OPTIONS.output_img])
445
446  # Create the OTA package from the merged target files package.
447
448  if OPTIONS.output_ota:
449    ota_from_target_files.main(
450        [OPTIONS.output_target_files, OPTIONS.output_ota])
451
452
453def main():
454  """The main function.
455
456  Process command line arguments, then call merge_target_files to
457  perform the heavy lifting.
458  """
459
460  common.InitLogging()
461
462  def option_handler(o, a):
463    if o == '--system-target-files':
464      logger.warning(
465          '--system-target-files has been renamed to --framework-target-files')
466      OPTIONS.framework_target_files = a
467    elif o == '--framework-target-files':
468      OPTIONS.framework_target_files = a
469    elif o == '--system-item-list':
470      logger.warning(
471          '--system-item-list has been renamed to --framework-item-list')
472      OPTIONS.framework_item_list = a
473    elif o == '--framework-item-list':
474      OPTIONS.framework_item_list = a
475    elif o == '--system-misc-info-keys':
476      logger.warning('--system-misc-info-keys has been renamed to '
477                     '--framework-misc-info-keys')
478      OPTIONS.framework_misc_info_keys = a
479    elif o == '--framework-misc-info-keys':
480      OPTIONS.framework_misc_info_keys = a
481    elif o == '--other-target-files':
482      logger.warning(
483          '--other-target-files has been renamed to --vendor-target-files')
484      OPTIONS.vendor_target_files = a
485    elif o == '--vendor-target-files':
486      OPTIONS.vendor_target_files = a
487    elif o == '--other-item-list':
488      logger.warning('--other-item-list has been renamed to --vendor-item-list')
489      OPTIONS.vendor_item_list = a
490    elif o == '--vendor-item-list':
491      OPTIONS.vendor_item_list = a
492    elif o == '--output-target-files':
493      OPTIONS.output_target_files = a
494    elif o == '--output-dir':
495      OPTIONS.output_dir = a
496    elif o == '--output-item-list':
497      OPTIONS.output_item_list = a
498    elif o == '--output-ota':
499      OPTIONS.output_ota = a
500    elif o == '--output-img':
501      OPTIONS.output_img = a
502    elif o == '--output-super-empty':
503      OPTIONS.output_super_empty = a
504    elif o == '--rebuild_recovery' or o == '--rebuild-recovery':
505      OPTIONS.rebuild_recovery = True
506    elif o == '--allow-duplicate-apkapex-keys':
507      OPTIONS.allow_duplicate_apkapex_keys = True
508    elif o == '--vendor-otatools':
509      OPTIONS.vendor_otatools = a
510    elif o == '--rebuild-sepolicy':
511      OPTIONS.rebuild_sepolicy = True
512    elif o == '--keep-tmp':
513      OPTIONS.keep_tmp = True
514    elif o == '--framework-dexpreopt-config':
515      OPTIONS.framework_dexpreopt_config = a
516    elif o == '--framework-dexpreopt-tools':
517      OPTIONS.framework_dexpreopt_tools = a
518    elif o == '--vendor-dexpreopt-config':
519      OPTIONS.vendor_dexpreopt_config = a
520    else:
521      return False
522    return True
523
524  args = common.ParseOptions(
525      sys.argv[1:],
526      __doc__,
527      extra_long_opts=[
528          'system-target-files=',
529          'framework-target-files=',
530          'system-item-list=',
531          'framework-item-list=',
532          'system-misc-info-keys=',
533          'framework-misc-info-keys=',
534          'other-target-files=',
535          'vendor-target-files=',
536          'other-item-list=',
537          'vendor-item-list=',
538          'output-target-files=',
539          'output-dir=',
540          'output-item-list=',
541          'output-ota=',
542          'output-img=',
543          'output-super-empty=',
544          'framework-dexpreopt-config=',
545          'framework-dexpreopt-tools=',
546          'vendor-dexpreopt-config=',
547          'rebuild_recovery',
548          'rebuild-recovery',
549          'allow-duplicate-apkapex-keys',
550          'vendor-otatools=',
551          'rebuild-sepolicy',
552          'keep-tmp',
553      ],
554      extra_option_handler=option_handler)
555
556  # pylint: disable=too-many-boolean-expressions
557  if (args or OPTIONS.framework_target_files is None or
558      OPTIONS.vendor_target_files is None or
559      (OPTIONS.output_target_files is None and OPTIONS.output_dir is None) or
560      (OPTIONS.output_dir is not None and not OPTIONS.output_item_list) or
561      (OPTIONS.rebuild_recovery and not OPTIONS.rebuild_sepolicy)):
562    common.Usage(__doc__)
563    sys.exit(1)
564
565  with zipfile.ZipFile(OPTIONS.framework_target_files, allowZip64=True) as fz:
566    framework_namelist = fz.namelist()
567  with zipfile.ZipFile(OPTIONS.vendor_target_files, allowZip64=True) as vz:
568    vendor_namelist = vz.namelist()
569
570  if OPTIONS.framework_item_list:
571    OPTIONS.framework_item_list = common.LoadListFromFile(
572        OPTIONS.framework_item_list)
573  else:
574    OPTIONS.framework_item_list = merge_utils.InferItemList(
575        input_namelist=framework_namelist, framework=True)
576  OPTIONS.framework_partition_set = merge_utils.ItemListToPartitionSet(
577      OPTIONS.framework_item_list)
578
579  if OPTIONS.framework_misc_info_keys:
580    OPTIONS.framework_misc_info_keys = common.LoadListFromFile(
581        OPTIONS.framework_misc_info_keys)
582  else:
583    OPTIONS.framework_misc_info_keys = merge_utils.InferFrameworkMiscInfoKeys(
584        input_namelist=framework_namelist)
585
586  if OPTIONS.vendor_item_list:
587    OPTIONS.vendor_item_list = common.LoadListFromFile(OPTIONS.vendor_item_list)
588  else:
589    OPTIONS.vendor_item_list = merge_utils.InferItemList(
590        input_namelist=vendor_namelist, framework=False)
591  OPTIONS.vendor_partition_set = merge_utils.ItemListToPartitionSet(
592      OPTIONS.vendor_item_list)
593
594  if OPTIONS.output_item_list:
595    OPTIONS.output_item_list = common.LoadListFromFile(OPTIONS.output_item_list)
596
597  if not merge_utils.ValidateConfigLists():
598    sys.exit(1)
599
600  temp_dir = common.MakeTempDir(prefix='merge_target_files_')
601  try:
602    merge_target_files(temp_dir)
603  finally:
604    if OPTIONS.keep_tmp:
605      logger.info('Keeping temp_dir %s', temp_dir)
606    else:
607      common.Cleanup()
608
609
610if __name__ == '__main__':
611  main()
612