1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2019 The Chromium OS Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""A manager for patches.""" 8 9from __future__ import print_function 10 11import argparse 12import json 13import os 14import subprocess 15import sys 16from collections import namedtuple 17 18import get_llvm_hash 19from failure_modes import FailureModes 20from subprocess_helpers import check_call 21from subprocess_helpers import check_output 22 23 24def is_directory(dir_path): 25 """Validates that the argument passed into 'argparse' is a directory.""" 26 27 if not os.path.isdir(dir_path): 28 raise ValueError('Path is not a directory: %s' % dir_path) 29 30 return dir_path 31 32 33def is_patch_metadata_file(patch_metadata_file): 34 """Valides the argument into 'argparse' is a patch file.""" 35 36 if not os.path.isfile(patch_metadata_file): 37 raise ValueError( 38 'Invalid patch metadata file provided: %s' % patch_metadata_file) 39 40 if not patch_metadata_file.endswith('.json'): 41 raise ValueError( 42 'Patch metadata file does not end in ".json": %s' % patch_metadata_file) 43 44 return patch_metadata_file 45 46 47def is_valid_failure_mode(failure_mode): 48 """Validates that the failure mode passed in is correct.""" 49 50 cur_failure_modes = [mode.value for mode in FailureModes] 51 52 if failure_mode not in cur_failure_modes: 53 raise ValueError('Invalid failure mode provided: %s' % failure_mode) 54 55 return failure_mode 56 57 58def EnsureBisectModeAndSvnVersionAreSpecifiedTogether(failure_mode, 59 good_svn_version): 60 """Validates that 'good_svn_version' is passed in only for bisection.""" 61 62 if failure_mode != FailureModes.BISECT_PATCHES.value and good_svn_version: 63 raise ValueError('"good_svn_version" is only available for bisection.') 64 elif failure_mode == FailureModes.BISECT_PATCHES.value and \ 65 not good_svn_version: 66 raise ValueError('A good SVN version is required for bisection (used by' 67 '"git bisect start".') 68 69 70def GetCommandLineArgs(): 71 """Get the required arguments from the command line.""" 72 73 # Create parser and add optional command-line arguments. 74 parser = argparse.ArgumentParser(description='A manager for patches.') 75 76 # Add argument for the last good SVN version which is required by 77 # `git bisect start` (only valid for bisection mode). 78 parser.add_argument( 79 '--good_svn_version', 80 type=int, 81 help='INTERNAL USE ONLY... (used for bisection.)') 82 83 # Add argument for the number of patches it iterate. Only used when performing 84 # `git bisect run`. 85 parser.add_argument( 86 '--num_patches_to_iterate', type=int, help=argparse.SUPPRESS) 87 88 # Add argument for whether bisection should continue. Only used for 89 # 'bisect_patches.' 90 parser.add_argument( 91 '--continue_bisection', 92 type=bool, 93 default=False, 94 help='Determines whether bisection should continue after successfully ' 95 'bisecting a patch (default: %(default)s) - only used for ' 96 '"bisect_patches"') 97 98 # Trust src_path HEAD and svn_version. 99 parser.add_argument( 100 '--use_src_head', 101 action='store_true', 102 help='Use the HEAD of src_path directory as is, not necessarily the same ' 103 'as the svn_version of upstream.') 104 105 # Add argument for the LLVM version to use for patch management. 106 parser.add_argument( 107 '--svn_version', 108 type=int, 109 required=True, 110 help='the LLVM svn version to use for patch management (determines ' 111 'whether a patch is applicable)') 112 113 # Add argument for the patch metadata file that is in $FILESDIR. 114 parser.add_argument( 115 '--patch_metadata_file', 116 required=True, 117 type=is_patch_metadata_file, 118 help='the absolute path to the .json file in "$FILESDIR/" of the ' 119 'package which has all the patches and their metadata if applicable') 120 121 # Add argument for the absolute path to the ebuild's $FILESDIR path. 122 # Example: '.../sys-devel/llvm/files/'. 123 parser.add_argument( 124 '--filesdir_path', 125 required=True, 126 type=is_directory, 127 help='the absolute path to the ebuild "files/" directory') 128 129 # Add argument for the absolute path to the unpacked sources. 130 parser.add_argument( 131 '--src_path', 132 required=True, 133 type=is_directory, 134 help='the absolute path to the unpacked LLVM sources') 135 136 # Add argument for the mode of the patch manager when handling failing 137 # applicable patches. 138 parser.add_argument( 139 '--failure_mode', 140 default=FailureModes.FAIL.value, 141 type=is_valid_failure_mode, 142 help='the mode of the patch manager when handling failed patches ' \ 143 '(default: %(default)s)') 144 145 # Parse the command line. 146 args_output = parser.parse_args() 147 148 EnsureBisectModeAndSvnVersionAreSpecifiedTogether( 149 args_output.failure_mode, args_output.good_svn_version) 150 151 return args_output 152 153 154def GetHEADSVNVersion(src_path): 155 """Gets the SVN version of HEAD in the src tree.""" 156 157 cmd = ['git', '-C', src_path, 'rev-parse', 'HEAD'] 158 159 git_hash = check_output(cmd) 160 161 version = get_llvm_hash.GetVersionFrom(src_path, git_hash.rstrip()) 162 163 return version 164 165 166def VerifyHEADIsTheSameAsSVNVersion(src_path, svn_version): 167 """Verifies that HEAD's SVN version matches 'svn_version'.""" 168 169 head_svn_version = GetHEADSVNVersion(src_path) 170 171 if head_svn_version != svn_version: 172 raise ValueError('HEAD\'s SVN version %d does not match "svn_version"' 173 ' %d, please move HEAD to "svn_version"s\' git hash.' % 174 (head_svn_version, svn_version)) 175 176 177def GetPathToPatch(filesdir_path, rel_patch_path): 178 """Gets the absolute path to a patch in $FILESDIR. 179 180 Args: 181 filesdir_path: The absolute path to $FILESDIR. 182 rel_patch_path: The relative path to the patch in '$FILESDIR/'. 183 184 Returns: 185 The absolute path to the patch in $FILESDIR. 186 187 Raises: 188 ValueError: Unable to find the path to the patch in $FILESDIR. 189 """ 190 191 if not os.path.isdir(filesdir_path): 192 raise ValueError('Invalid path to $FILESDIR provided: %s' % filesdir_path) 193 194 # Combine $FILESDIR + relative path of patch to $FILESDIR. 195 patch_path = os.path.join(filesdir_path, rel_patch_path) 196 197 if not os.path.isfile(patch_path): 198 raise ValueError('The absolute path %s to the patch %s does not exist' % 199 (patch_path, rel_patch_path)) 200 201 return patch_path 202 203 204def GetPatchMetadata(patch_dict): 205 """Gets the patch's metadata. 206 207 Args: 208 patch_dict: A dictionary that has the patch metadata. 209 210 Returns: 211 A tuple that contains the metadata values. 212 """ 213 214 # Get the metadata values of a patch if possible. 215 # FIXME(b/221489531): Remove start_version & end_version 216 if 'version_range' in patch_dict: 217 start_version = patch_dict['version_range'].get('from', 0) 218 end_version = patch_dict['version_range'].get('until', None) 219 else: 220 start_version = patch_dict.get('start_version', 0) 221 end_version = patch_dict.get('end_version', None) 222 is_critical = patch_dict.get('is_critical', False) 223 224 return start_version, end_version, is_critical 225 226 227def ApplyPatch(src_path, patch_path): 228 """Attempts to apply the patch. 229 230 Args: 231 src_path: The absolute path to the unpacked sources of the package. 232 patch_path: The absolute path to the patch in $FILESDIR/ 233 234 Returns: 235 A boolean where 'True' means that the patch applied fine or 'False' means 236 that the patch failed to apply. 237 """ 238 239 if not os.path.isdir(src_path): 240 raise ValueError('Invalid src path provided: %s' % src_path) 241 242 if not os.path.isfile(patch_path): 243 raise ValueError('Invalid patch file provided: %s' % patch_path) 244 245 # Test the patch with '--dry-run' before actually applying the patch. 246 test_patch_cmd = [ 247 'patch', '--dry-run', '-d', src_path, '-f', '-p1', '-E', 248 '--no-backup-if-mismatch', '-i', patch_path 249 ] 250 251 # Cmd to apply a patch in the src unpack path. 252 apply_patch_cmd = [ 253 'patch', '-d', src_path, '-f', '-p1', '-E', '--no-backup-if-mismatch', 254 '-i', patch_path 255 ] 256 257 try: 258 check_output(test_patch_cmd) 259 260 # If the mode is 'continue', then catching the exception makes sure that 261 # the program does not exit on the first failed applicable patch. 262 except subprocess.CalledProcessError: 263 # Test run on the patch failed to apply. 264 return False 265 266 # Test run succeeded on the patch. 267 check_output(apply_patch_cmd) 268 269 return True 270 271 272def UpdatePatchMetadataFile(patch_metadata_file, patches): 273 """Updates the .json file with unchanged and at least one changed patch. 274 275 Args: 276 patch_metadata_file: The absolute path to the .json file that has all the 277 patches and its metadata. 278 patches: A list of patches whose metadata were or were not updated. 279 280 Raises: 281 ValueError: The patch metadata file does not have the correct extension. 282 """ 283 284 if not patch_metadata_file.endswith('.json'): 285 raise ValueError('File does not end in ".json": %s' % patch_metadata_file) 286 287 with open(patch_metadata_file, 'w') as patch_file: 288 json.dump(patches, patch_file, indent=4, separators=(',', ': ')) 289 290 291def GetCommitHashesForBisection(src_path, good_svn_version, bad_svn_version): 292 """Gets the good and bad commit hashes required by `git bisect start`.""" 293 294 bad_commit_hash = get_llvm_hash.GetGitHashFrom(src_path, bad_svn_version) 295 296 good_commit_hash = get_llvm_hash.GetGitHashFrom(src_path, good_svn_version) 297 298 return good_commit_hash, bad_commit_hash 299 300 301def PerformBisection(src_path, good_commit, bad_commit, svn_version, 302 patch_metadata_file, filesdir_path, num_patches): 303 """Performs bisection to determine where a patch stops applying.""" 304 305 start_cmd = [ 306 'git', '-C', src_path, 'bisect', 'start', bad_commit, good_commit 307 ] 308 309 check_output(start_cmd) 310 311 run_cmd = [ 312 'git', '-C', src_path, 'bisect', 'run', 313 os.path.abspath(__file__), '--svn_version', 314 '%d' % svn_version, '--patch_metadata_file', patch_metadata_file, 315 '--filesdir_path', filesdir_path, '--src_path', src_path, 316 '--failure_mode', 'internal_bisection', '--num_patches_to_iterate', 317 '%d' % num_patches 318 ] 319 320 check_call(run_cmd) 321 322 # Successfully bisected the patch, so retrieve the SVN version from the 323 # commit message. 324 get_bad_commit_hash_cmd = [ 325 'git', '-C', src_path, 'rev-parse', 'refs/bisect/bad' 326 ] 327 328 git_hash = check_output(get_bad_commit_hash_cmd) 329 330 end_cmd = ['git', '-C', src_path, 'bisect', 'reset'] 331 332 check_output(end_cmd) 333 334 # `git bisect run` returns the bad commit hash and the commit message. 335 version = get_llvm_hash.GetVersionFrom(src_path, git_hash.rstrip()) 336 337 return version 338 339 340def CleanSrcTree(src_path): 341 """Cleans the source tree of the changes made in 'src_path'.""" 342 343 reset_src_tree_cmd = ['git', '-C', src_path, 'reset', 'HEAD', '--hard'] 344 345 check_output(reset_src_tree_cmd) 346 347 clean_src_tree_cmd = ['git', '-C', src_path, 'clean', '-fd'] 348 349 check_output(clean_src_tree_cmd) 350 351 352def SaveSrcTreeState(src_path): 353 """Stashes the changes made so far to the source tree.""" 354 355 save_src_tree_cmd = ['git', '-C', src_path, 'stash', '-a'] 356 357 check_output(save_src_tree_cmd) 358 359 360def RestoreSrcTreeState(src_path, bad_commit_hash): 361 """Restores the changes made to the source tree.""" 362 363 checkout_cmd = ['git', '-C', src_path, 'checkout', bad_commit_hash] 364 365 check_output(checkout_cmd) 366 367 get_changes_cmd = ['git', '-C', src_path, 'stash', 'pop'] 368 369 check_output(get_changes_cmd) 370 371 372def HandlePatches(svn_version, 373 patch_metadata_file, 374 filesdir_path, 375 src_path, 376 mode, 377 good_svn_version=None, 378 num_patches_to_iterate=None, 379 continue_bisection=False): 380 """Handles the patches in the .json file for the package. 381 382 Args: 383 svn_version: The LLVM version to use for patch management. 384 patch_metadata_file: The absolute path to the .json file in '$FILESDIR/' 385 that has all the patches and their metadata. 386 filesdir_path: The absolute path to $FILESDIR. 387 src_path: The absolute path to the unpacked destination of the package. 388 mode: The action to take when an applicable patch failed to apply. 389 Ex: 'FailureModes.FAIL' 390 good_svn_version: Only used by 'bisect_patches' which tells 391 `git bisect start` the good version. 392 num_patches_to_iterate: The number of patches to iterate in the .JSON file 393 (internal use). Only used by `git bisect run`. 394 continue_bisection: Only used for 'bisect_patches' mode. If flag is set, 395 then bisection will continue to the next patch when successfully bisected a 396 patch. 397 398 Returns: 399 Depending on the mode, 'None' would be returned if everything went well or 400 the .json file was not updated. Otherwise, a list or multiple lists would 401 be returned that indicates what has changed. 402 403 Raises: 404 ValueError: The patch metadata file does not exist or does not end with 405 '.json' or the absolute path to $FILESDIR does not exist or the unpacked 406 path does not exist or if the mode is 'fail', then an applicable patch 407 failed to apply. 408 """ 409 410 # A flag for whether the mode specified would possible modify the patches. 411 can_modify_patches = False 412 413 # 'fail' or 'continue' mode would not modify a patch's metadata, so the .json 414 # file would stay the same. 415 if mode != FailureModes.FAIL and mode != FailureModes.CONTINUE: 416 can_modify_patches = True 417 418 # A flag that determines whether at least one patch's metadata was 419 # updated due to the mode that is passed in. 420 updated_patch = False 421 422 # A list of patches that will be in the updated .json file. 423 applicable_patches = [] 424 425 # A list of patches that successfully applied. 426 applied_patches = [] 427 428 # A list of patches that were disabled. 429 disabled_patches = [] 430 431 # A list of bisected patches. 432 bisected_patches = [] 433 434 # A list of non applicable patches. 435 non_applicable_patches = [] 436 437 # A list of patches that will not be included in the updated .json file 438 removed_patches = [] 439 440 # Whether the patch metadata file was modified where 'None' means that the 441 # patch metadata file was not modified otherwise the absolute path to the 442 # patch metadata file is stored. 443 modified_metadata = None 444 445 # A list of patches that failed to apply. 446 failed_patches = [] 447 448 with open(patch_metadata_file) as patch_file: 449 patch_file_contents = json.load(patch_file) 450 451 if mode == FailureModes.BISECT_PATCHES: 452 # A good and bad commit are required by `git bisect start`. 453 good_commit, bad_commit = GetCommitHashesForBisection( 454 src_path, good_svn_version, svn_version) 455 456 # Patch format: 457 # { 458 # "rel_patch_path" : "[REL_PATCH_PATH_FROM_$FILESDIR]" 459 # [PATCH_METADATA] if available. 460 # } 461 # 462 # For each patch, find the path to it in $FILESDIR and get its metadata if 463 # available, then check if the patch is applicable. 464 for patch_dict_index, cur_patch_dict in enumerate(patch_file_contents): 465 # Used by the internal bisection. All the patches in the interval [0, N] 466 # have been iterated. 467 if num_patches_to_iterate and \ 468 (patch_dict_index + 1) > num_patches_to_iterate: 469 break 470 471 # Get the absolute path to the patch in $FILESDIR. 472 path_to_patch = GetPathToPatch(filesdir_path, 473 cur_patch_dict['rel_patch_path']) 474 475 # Get the patch's metadata. 476 # 477 # Index information of 'patch_metadata': 478 # [0]: start_version 479 # [1]: end_version 480 # [2]: is_critical 481 patch_metadata = GetPatchMetadata(cur_patch_dict) 482 483 if not patch_metadata[1]: 484 # Patch does not have an 'end_version' value which implies 'end_version' 485 # == 'inf' ('svn_version' will always be less than 'end_version'), so 486 # the patch is applicable if 'svn_version' >= 'start_version'. 487 patch_applicable = svn_version >= patch_metadata[0] 488 else: 489 # Patch is applicable if 'svn_version' >= 'start_version' && 490 # "svn_version" < "end_version". 491 patch_applicable = (svn_version >= patch_metadata[0] and \ 492 svn_version < patch_metadata[1]) 493 494 if can_modify_patches: 495 # Add to the list only if the mode can potentially modify a patch. 496 # 497 # If the mode is 'remove_patches', then all patches that are 498 # applicable or are from the future will be added to the updated .json 499 # file and all patches that are not applicable will be added to the 500 # remove patches list which will not be included in the updated .json 501 # file. 502 if patch_applicable or svn_version < patch_metadata[0] or \ 503 mode != FailureModes.REMOVE_PATCHES: 504 applicable_patches.append(cur_patch_dict) 505 elif mode == FailureModes.REMOVE_PATCHES: 506 removed_patches.append(path_to_patch) 507 508 if not modified_metadata: 509 # At least one patch will be removed from the .json file. 510 modified_metadata = patch_metadata_file 511 512 if not patch_applicable: 513 non_applicable_patches.append(os.path.basename(path_to_patch)) 514 515 # There is no need to apply patches in 'remove_patches' mode because the 516 # mode removes patches that do not apply anymore based off of 517 # 'svn_version.' 518 if patch_applicable and mode != FailureModes.REMOVE_PATCHES: 519 patch_applied = ApplyPatch(src_path, path_to_patch) 520 521 if not patch_applied: # Failed to apply patch. 522 failed_patches.append(os.path.basename(path_to_patch)) 523 524 # Check the mode to determine what action to take on the failing 525 # patch. 526 if mode == FailureModes.DISABLE_PATCHES: 527 # Set the patch's 'end_version' to 'svn_version' so the patch 528 # would not be applicable anymore (i.e. the patch's 'end_version' 529 # would not be greater than 'svn_version'). 530 531 # Last element in 'applicable_patches' is the current patch. 532 applicable_patches[-1]['end_version'] = svn_version 533 534 disabled_patches.append(os.path.basename(path_to_patch)) 535 536 if not updated_patch: 537 # At least one patch has been modified, so the .json file 538 # will be updated with the new patch metadata. 539 updated_patch = True 540 541 modified_metadata = patch_metadata_file 542 elif mode == FailureModes.BISECT_PATCHES: 543 # Figure out where the patch's stops applying and set the patch's 544 # 'end_version' to that version. 545 546 # Do not want to overwrite the changes to the current progress of 547 # 'bisect_patches' on the source tree. 548 SaveSrcTreeState(src_path) 549 550 # Need a clean source tree for `git bisect run` to avoid unnecessary 551 # fails for patches. 552 CleanSrcTree(src_path) 553 554 print('\nStarting to bisect patch %s for SVN version %d:\n' % 555 (os.path.basename(cur_patch_dict['rel_patch_path']), 556 svn_version)) 557 558 # Performs the bisection: calls `git bisect start` and 559 # `git bisect run`, where `git bisect run` is going to call this 560 # script as many times as needed with specific arguments. 561 bad_svn_version = PerformBisection( 562 src_path, good_commit, bad_commit, svn_version, 563 patch_metadata_file, filesdir_path, patch_dict_index + 1) 564 565 print('\nSuccessfully bisected patch %s, starts to fail to apply ' 566 'at %d\n' % (os.path.basename( 567 cur_patch_dict['rel_patch_path']), bad_svn_version)) 568 569 # Overwrite the .JSON file with the new 'end_version' for the 570 # current failed patch so that if there are other patches that 571 # fail to apply, then the 'end_version' for the current patch could 572 # be applicable when `git bisect run` is performed on the next 573 # failed patch because the same .JSON file is used for `git bisect 574 # run`. 575 patch_file_contents[patch_dict_index][ 576 'end_version'] = bad_svn_version 577 UpdatePatchMetadataFile(patch_metadata_file, patch_file_contents) 578 579 # Clear the changes made to the source tree by `git bisect run`. 580 CleanSrcTree(src_path) 581 582 if not continue_bisection: 583 # Exiting program early because 'continue_bisection' is not set. 584 sys.exit(0) 585 586 bisected_patches.append( 587 '%s starts to fail to apply at %d' % (os.path.basename( 588 cur_patch_dict['rel_patch_path']), bad_svn_version)) 589 590 # Continue where 'bisect_patches' left off. 591 RestoreSrcTreeState(src_path, bad_commit) 592 593 if not modified_metadata: 594 # At least one patch's 'end_version' has been updated. 595 modified_metadata = patch_metadata_file 596 597 elif mode == FailureModes.FAIL: 598 if applied_patches: 599 print('The following patches applied successfully up to the ' 600 'failed patch:') 601 print('\n'.join(applied_patches)) 602 603 # Throw an exception on the first patch that failed to apply. 604 raise ValueError( 605 'Failed to apply patch: %s' % os.path.basename(path_to_patch)) 606 elif mode == FailureModes.INTERNAL_BISECTION: 607 # Determine the exit status for `git bisect run` because of the 608 # failed patch in the interval [0, N]. 609 # 610 # NOTE: `git bisect run` exit codes are as follows: 611 # 130: Terminates the bisection. 612 # 1: Similar as `git bisect bad`. 613 614 # Some patch in the interval [0, N) failed, so terminate bisection 615 # (the patch stack is broken). 616 if (patch_dict_index + 1) != num_patches_to_iterate: 617 print('\nTerminating bisection due to patch %s failed to apply ' 618 'on SVN version %d.\n' % (os.path.basename( 619 cur_patch_dict['rel_patch_path']), svn_version)) 620 621 # Man page for `git bisect run` states that any value over 127 622 # terminates it. 623 sys.exit(130) 624 625 # Changes to the source tree need to be removed, otherwise some 626 # patches may fail when applying the patch to the source tree when 627 # `git bisect run` calls this script again. 628 CleanSrcTree(src_path) 629 630 # The last patch in the interval [0, N] failed to apply, so let 631 # `git bisect run` know that the last patch (the patch that failed 632 # originally which led to `git bisect run` to be invoked) is bad 633 # with exit code 1. 634 sys.exit(1) 635 else: # Successfully applied patch 636 applied_patches.append(os.path.basename(path_to_patch)) 637 638 # All patches in the interval [0, N] applied successfully, so let 639 # `git bisect run` know that the program exited with exit code 0 (good). 640 if mode == FailureModes.INTERNAL_BISECTION: 641 # Changes to the source tree need to be removed, otherwise some 642 # patches may fail when applying the patch to the source tree when 643 # `git bisect run` calls this script again. 644 # 645 # Also, if `git bisect run` will NOT call this script again (terminated) and 646 # if the source tree changes are not removed, `git bisect reset` will 647 # complain that the changes would need to be 'stashed' or 'removed' in 648 # order to reset HEAD back to the bad commit's git hash, so HEAD will remain 649 # on the last git hash used by `git bisect run`. 650 CleanSrcTree(src_path) 651 652 # NOTE: Exit code 0 is similar to `git bisect good`. 653 sys.exit(0) 654 655 # Create a namedtuple of the patch results. 656 PatchInfo = namedtuple('PatchInfo', [ 657 'applied_patches', 'failed_patches', 'non_applicable_patches', 658 'disabled_patches', 'removed_patches', 'modified_metadata' 659 ]) 660 661 patch_info = PatchInfo( 662 applied_patches=applied_patches, 663 failed_patches=failed_patches, 664 non_applicable_patches=non_applicable_patches, 665 disabled_patches=disabled_patches, 666 removed_patches=removed_patches, 667 modified_metadata=modified_metadata) 668 669 # Determine post actions after iterating through the patches. 670 if mode == FailureModes.REMOVE_PATCHES: 671 if removed_patches: 672 UpdatePatchMetadataFile(patch_metadata_file, applicable_patches) 673 elif mode == FailureModes.DISABLE_PATCHES: 674 if updated_patch: 675 UpdatePatchMetadataFile(patch_metadata_file, applicable_patches) 676 elif mode == FailureModes.BISECT_PATCHES: 677 PrintPatchResults(patch_info) 678 if modified_metadata: 679 print('\nThe following patches have been bisected:') 680 print('\n'.join(bisected_patches)) 681 682 # Exiting early because 'bisect_patches' will not be called from other 683 # scripts, only this script uses 'bisect_patches'. The intent is to provide 684 # bisection information on the patches and aid in the bisection process. 685 sys.exit(0) 686 687 return patch_info 688 689 690def PrintPatchResults(patch_info): 691 """Prints the results of handling the patches of a package. 692 693 Args: 694 patch_info: A namedtuple that has information on the patches. 695 """ 696 697 if patch_info.applied_patches: 698 print('\nThe following patches applied successfully:') 699 print('\n'.join(patch_info.applied_patches)) 700 701 if patch_info.failed_patches: 702 print('\nThe following patches failed to apply:') 703 print('\n'.join(patch_info.failed_patches)) 704 705 if patch_info.non_applicable_patches: 706 print('\nThe following patches were not applicable:') 707 print('\n'.join(patch_info.non_applicable_patches)) 708 709 if patch_info.modified_metadata: 710 print('\nThe patch metadata file %s has been modified' % os.path.basename( 711 patch_info.modified_metadata)) 712 713 if patch_info.disabled_patches: 714 print('\nThe following patches were disabled:') 715 print('\n'.join(patch_info.disabled_patches)) 716 717 if patch_info.removed_patches: 718 print('\nThe following patches were removed from the patch metadata file:') 719 for cur_patch_path in patch_info.removed_patches: 720 print('%s' % os.path.basename(cur_patch_path)) 721 722 723def main(): 724 """Applies patches to the source tree and takes action on a failed patch.""" 725 726 args_output = GetCommandLineArgs() 727 728 if args_output.failure_mode != FailureModes.INTERNAL_BISECTION.value: 729 # If the SVN version of HEAD is not the same as 'svn_version', then some 730 # patches that fail to apply could successfully apply if HEAD's SVN version 731 # was the same as 'svn_version'. In other words, HEAD's git hash should be 732 # what is being updated to (e.g. LLVM_NEXT_HASH). 733 if not args_output.use_src_head: 734 VerifyHEADIsTheSameAsSVNVersion(args_output.src_path, 735 args_output.svn_version) 736 else: 737 # `git bisect run` called this script. 738 # 739 # `git bisect run` moves HEAD each time it invokes this script, so set the 740 # 'svn_version' to be current HEAD's SVN version so that the previous 741 # SVN version is not used in determining whether a patch is applicable. 742 args_output.svn_version = GetHEADSVNVersion(args_output.src_path) 743 744 # Get the results of handling the patches of the package. 745 patch_info = HandlePatches( 746 args_output.svn_version, args_output.patch_metadata_file, 747 args_output.filesdir_path, args_output.src_path, 748 FailureModes(args_output.failure_mode), args_output.good_svn_version, 749 args_output.num_patches_to_iterate, args_output.continue_bisection) 750 751 PrintPatchResults(patch_info) 752 753 754if __name__ == '__main__': 755 main() 756