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