aboutsummaryrefslogtreecommitdiff
path: root/llvm_tools/llvm_bisection.py
diff options
context:
space:
mode:
Diffstat (limited to 'llvm_tools/llvm_bisection.py')
-rwxr-xr-xllvm_tools/llvm_bisection.py757
1 files changed, 423 insertions, 334 deletions
diff --git a/llvm_tools/llvm_bisection.py b/llvm_tools/llvm_bisection.py
index 0148efd2..0b851ebe 100755
--- a/llvm_tools/llvm_bisection.py
+++ b/llvm_tools/llvm_bisection.py
@@ -1,12 +1,11 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Copyright 2019 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Performs bisection on LLVM based off a .JSON file."""
-from __future__ import print_function
import argparse
import enum
@@ -25,352 +24,442 @@ import update_tryjob_status
class BisectionExitStatus(enum.Enum):
- """Exit code when performing bisection."""
+ """Exit code when performing bisection."""
- # Means that there are no more revisions available to bisect.
- BISECTION_COMPLETE = 126
+ # Means that there are no more revisions available to bisect.
+ BISECTION_COMPLETE = 126
def GetCommandLineArgs():
- """Parses the command line for the command line arguments."""
-
- # Default path to the chroot if a path is not specified.
- cros_root = os.path.expanduser('~')
- cros_root = os.path.join(cros_root, 'chromiumos')
-
- # Create parser and add optional command-line arguments.
- parser = argparse.ArgumentParser(
- description='Bisects LLVM via tracking a JSON file.')
-
- # Add argument for other change lists that want to run alongside the tryjob
- # which has a change list of updating a package's git hash.
- parser.add_argument(
- '--parallel',
- type=int,
- default=3,
- help='How many tryjobs to create between the last good version and '
- 'the first bad version (default: %(default)s)')
-
- # Add argument for the good LLVM revision for bisection.
- parser.add_argument('--start_rev',
- required=True,
- type=int,
- help='The good revision for the bisection.')
-
- # Add argument for the bad LLVM revision for bisection.
- parser.add_argument('--end_rev',
- required=True,
- type=int,
- help='The bad revision for the bisection.')
-
- # Add argument for the absolute path to the file that contains information on
- # the previous tested svn version.
- parser.add_argument(
- '--last_tested',
- required=True,
- help='the absolute path to the file that contains the tryjobs')
-
- # Add argument for the absolute path to the LLVM source tree.
- parser.add_argument(
- '--src_path',
- help='the path to the LLVM source tree to use (used for retrieving the '
- 'git hash of each version between the last good version and first bad '
- 'version)')
-
- # Add argument for other change lists that want to run alongside the tryjob
- # which has a change list of updating a package's git hash.
- parser.add_argument(
- '--extra_change_lists',
- type=int,
- nargs='+',
- help='change lists that would like to be run alongside the change list '
- 'of updating the packages')
-
- # Add argument for custom options for the tryjob.
- parser.add_argument('--options',
- required=False,
- nargs='+',
- help='options to use for the tryjob testing')
-
- # Add argument for the builder to use for the tryjob.
- parser.add_argument('--builder',
- required=True,
- help='builder to use for the tryjob testing')
-
- # Add argument for the description of the tryjob.
- parser.add_argument('--description',
- required=False,
- nargs='+',
- help='the description of the tryjob')
-
- # Add argument for a specific chroot path.
- parser.add_argument('--chroot_path',
- default=cros_root,
- help='the path to the chroot (default: %(default)s)')
-
- # Add argument for whether to display command contents to `stdout`.
- parser.add_argument('--verbose',
- action='store_true',
- help='display contents of a command to the terminal '
- '(default: %(default)s)')
-
- # Add argument for whether to display command contents to `stdout`.
- parser.add_argument('--nocleanup',
- action='store_false',
- dest='cleanup',
- help='Abandon CLs created for bisectoin')
-
- args_output = parser.parse_args()
-
- assert args_output.start_rev < args_output.end_rev, (
- 'Start revision %d is >= end revision %d' %
- (args_output.start_rev, args_output.end_rev))
-
- if args_output.last_tested and not args_output.last_tested.endswith('.json'):
- raise ValueError('Filed provided %s does not end in ".json"' %
- args_output.last_tested)
-
- return args_output
+ """Parses the command line for the command line arguments."""
+
+ # Default path to the chroot if a path is not specified.
+ cros_root = os.path.expanduser("~")
+ cros_root = os.path.join(cros_root, "chromiumos")
+
+ # Create parser and add optional command-line arguments.
+ parser = argparse.ArgumentParser(
+ description="Bisects LLVM via tracking a JSON file."
+ )
+
+ # Add argument for other change lists that want to run alongside the tryjob
+ # which has a change list of updating a package's git hash.
+ parser.add_argument(
+ "--parallel",
+ type=int,
+ default=3,
+ help="How many tryjobs to create between the last good version and "
+ "the first bad version (default: %(default)s)",
+ )
+
+ # Add argument for the good LLVM revision for bisection.
+ parser.add_argument(
+ "--start_rev",
+ required=True,
+ type=int,
+ help="The good revision for the bisection.",
+ )
+
+ # Add argument for the bad LLVM revision for bisection.
+ parser.add_argument(
+ "--end_rev",
+ required=True,
+ type=int,
+ help="The bad revision for the bisection.",
+ )
+
+ # Add argument for the absolute path to the file that contains information on
+ # the previous tested svn version.
+ parser.add_argument(
+ "--last_tested",
+ required=True,
+ help="the absolute path to the file that contains the tryjobs",
+ )
+
+ # Add argument for the absolute path to the LLVM source tree.
+ parser.add_argument(
+ "--src_path",
+ help="the path to the LLVM source tree to use (used for retrieving the "
+ "git hash of each version between the last good version and first bad "
+ "version)",
+ )
+
+ # Add argument for other change lists that want to run alongside the tryjob
+ # which has a change list of updating a package's git hash.
+ parser.add_argument(
+ "--extra_change_lists",
+ type=int,
+ nargs="+",
+ help="change lists that would like to be run alongside the change list "
+ "of updating the packages",
+ )
+
+ # Add argument for custom options for the tryjob.
+ parser.add_argument(
+ "--options",
+ required=False,
+ nargs="+",
+ help="options to use for the tryjob testing",
+ )
+
+ # Add argument for the builder to use for the tryjob.
+ parser.add_argument(
+ "--builder", required=True, help="builder to use for the tryjob testing"
+ )
+
+ # Add argument for the description of the tryjob.
+ parser.add_argument(
+ "--description",
+ required=False,
+ nargs="+",
+ help="the description of the tryjob",
+ )
+
+ # Add argument for a specific chroot path.
+ parser.add_argument(
+ "--chroot_path",
+ default=cros_root,
+ help="the path to the chroot (default: %(default)s)",
+ )
+
+ # Add argument for whether to display command contents to `stdout`.
+ parser.add_argument(
+ "--verbose",
+ action="store_true",
+ help="display contents of a command to the terminal "
+ "(default: %(default)s)",
+ )
+
+ # Add argument for whether to display command contents to `stdout`.
+ parser.add_argument(
+ "--nocleanup",
+ action="store_false",
+ dest="cleanup",
+ help="Abandon CLs created for bisectoin",
+ )
+
+ args_output = parser.parse_args()
+
+ assert (
+ args_output.start_rev < args_output.end_rev
+ ), "Start revision %d is >= end revision %d" % (
+ args_output.start_rev,
+ args_output.end_rev,
+ )
+
+ if args_output.last_tested and not args_output.last_tested.endswith(
+ ".json"
+ ):
+ raise ValueError(
+ 'Filed provided %s does not end in ".json"'
+ % args_output.last_tested
+ )
+
+ return args_output
def GetRemainingRange(start, end, tryjobs):
- """Gets the start and end intervals in 'json_file'.
-
- Args:
- start: The start version of the bisection provided via the command line.
- end: The end version of the bisection provided via the command line.
- tryjobs: A list of tryjobs where each element is in the following format:
- [
- {[TRYJOB_INFORMATION]},
- {[TRYJOB_INFORMATION]},
- ...,
- {[TRYJOB_INFORMATION]}
- ]
-
- Returns:
- The new start version and end version for bisection, a set of revisions
- that are 'pending' and a set of revisions that are to be skipped.
-
- Raises:
- ValueError: The value for 'status' is missing or there is a mismatch
- between 'start' and 'end' compared to the 'start' and 'end' in the JSON
- file.
- AssertionError: The new start version is >= than the new end version.
- """
-
- if not tryjobs:
- return start, end, {}, {}
-
- # Verify that each tryjob has a value for the 'status' key.
- for cur_tryjob_dict in tryjobs:
- if not cur_tryjob_dict.get('status', None):
- raise ValueError('"status" is missing or has no value, please '
- 'go to %s and update it' % cur_tryjob_dict['link'])
-
- all_bad_revisions = [end]
- all_bad_revisions.extend(
- cur_tryjob['rev'] for cur_tryjob in tryjobs
- if cur_tryjob['status'] == update_tryjob_status.TryjobStatus.BAD.value)
-
- # The minimum value for the 'bad' field in the tryjobs is the new end
- # version.
- bad_rev = min(all_bad_revisions)
-
- all_good_revisions = [start]
- all_good_revisions.extend(
- cur_tryjob['rev'] for cur_tryjob in tryjobs
- if cur_tryjob['status'] == update_tryjob_status.TryjobStatus.GOOD.value)
-
- # The maximum value for the 'good' field in the tryjobs is the new start
- # version.
- good_rev = max(all_good_revisions)
-
- # The good version should always be strictly less than the bad version;
- # otherwise, bisection is broken.
- assert good_rev < bad_rev, ('Bisection is broken because %d (good) is >= '
- '%d (bad)' % (good_rev, bad_rev))
-
- # Find all revisions that are 'pending' within 'good_rev' and 'bad_rev'.
- #
- # NOTE: The intent is to not launch tryjobs between 'good_rev' and 'bad_rev'
- # that have already been launched (this set is used when constructing the
- # list of revisions to launch tryjobs for).
- pending_revisions = {
- tryjob['rev']
- for tryjob in tryjobs
- if tryjob['status'] == update_tryjob_status.TryjobStatus.PENDING.value
- and good_rev < tryjob['rev'] < bad_rev
- }
-
- # Find all revisions that are to be skipped within 'good_rev' and 'bad_rev'.
- #
- # NOTE: The intent is to not launch tryjobs between 'good_rev' and 'bad_rev'
- # that have already been marked as 'skip' (this set is used when constructing
- # the list of revisions to launch tryjobs for).
- skip_revisions = {
- tryjob['rev']
- for tryjob in tryjobs
- if tryjob['status'] == update_tryjob_status.TryjobStatus.SKIP.value
- and good_rev < tryjob['rev'] < bad_rev
- }
-
- return good_rev, bad_rev, pending_revisions, skip_revisions
-
-
-def GetCommitsBetween(start, end, parallel, src_path, pending_revisions,
- skip_revisions):
- """Determines the revisions between start and end."""
-
- with get_llvm_hash.LLVMHash().CreateTempDirectory() as temp_dir:
- # We have guaranteed contiguous revision numbers after this,
- # and that guarnatee simplifies things considerably, so we don't
- # support anything before it.
- assert start >= git_llvm_rev.base_llvm_revision, f'{start} was too long ago'
-
- with get_llvm_hash.CreateTempLLVMRepo(temp_dir) as new_repo:
- if not src_path:
- src_path = new_repo
- index_step = (end - (start + 1)) // (parallel + 1)
- if not index_step:
- index_step = 1
- revisions = [
- rev for rev in range(start + 1, end, index_step)
- if rev not in pending_revisions and rev not in skip_revisions
+ """Gets the start and end intervals in 'json_file'.
+
+ Args:
+ start: The start version of the bisection provided via the command line.
+ end: The end version of the bisection provided via the command line.
+ tryjobs: A list of tryjobs where each element is in the following format:
+ [
+ {[TRYJOB_INFORMATION]},
+ {[TRYJOB_INFORMATION]},
+ ...,
+ {[TRYJOB_INFORMATION]}
]
- git_hashes = [
- get_llvm_hash.GetGitHashFrom(src_path, rev) for rev in revisions
- ]
- return revisions, git_hashes
-
-
-def Bisect(revisions, git_hashes, bisect_state, last_tested, update_packages,
- chroot_path, patch_metadata_file, extra_change_lists, options,
- builder, verbose):
- """Adds tryjobs and updates the status file with the new tryjobs."""
- try:
- for svn_revision, git_hash in zip(revisions, git_hashes):
- tryjob_dict = modify_a_tryjob.AddTryjob(update_packages, git_hash,
- svn_revision, chroot_path,
- patch_metadata_file,
- extra_change_lists, options,
- builder, verbose, svn_revision)
-
- bisect_state['jobs'].append(tryjob_dict)
- finally:
- # Do not want to lose progress if there is an exception.
- if last_tested:
- new_file = '%s.new' % last_tested
- with open(new_file, 'w') as json_file:
- json.dump(bisect_state, json_file, indent=4, separators=(',', ': '))
-
- os.rename(new_file, last_tested)
+ Returns:
+ The new start version and end version for bisection, a set of revisions
+ that are 'pending' and a set of revisions that are to be skipped.
+
+ Raises:
+ ValueError: The value for 'status' is missing or there is a mismatch
+ between 'start' and 'end' compared to the 'start' and 'end' in the JSON
+ file.
+ AssertionError: The new start version is >= than the new end version.
+ """
+
+ if not tryjobs:
+ return start, end, {}, {}
+
+ # Verify that each tryjob has a value for the 'status' key.
+ for cur_tryjob_dict in tryjobs:
+ if not cur_tryjob_dict.get("status", None):
+ raise ValueError(
+ '"status" is missing or has no value, please '
+ "go to %s and update it" % cur_tryjob_dict["link"]
+ )
+
+ all_bad_revisions = [end]
+ all_bad_revisions.extend(
+ cur_tryjob["rev"]
+ for cur_tryjob in tryjobs
+ if cur_tryjob["status"] == update_tryjob_status.TryjobStatus.BAD.value
+ )
+
+ # The minimum value for the 'bad' field in the tryjobs is the new end
+ # version.
+ bad_rev = min(all_bad_revisions)
+
+ all_good_revisions = [start]
+ all_good_revisions.extend(
+ cur_tryjob["rev"]
+ for cur_tryjob in tryjobs
+ if cur_tryjob["status"] == update_tryjob_status.TryjobStatus.GOOD.value
+ )
+
+ # The maximum value for the 'good' field in the tryjobs is the new start
+ # version.
+ good_rev = max(all_good_revisions)
+
+ # The good version should always be strictly less than the bad version;
+ # otherwise, bisection is broken.
+ assert (
+ good_rev < bad_rev
+ ), "Bisection is broken because %d (good) is >= " "%d (bad)" % (
+ good_rev,
+ bad_rev,
+ )
+
+ # Find all revisions that are 'pending' within 'good_rev' and 'bad_rev'.
+ #
+ # NOTE: The intent is to not launch tryjobs between 'good_rev' and 'bad_rev'
+ # that have already been launched (this set is used when constructing the
+ # list of revisions to launch tryjobs for).
+ pending_revisions = {
+ tryjob["rev"]
+ for tryjob in tryjobs
+ if tryjob["status"] == update_tryjob_status.TryjobStatus.PENDING.value
+ and good_rev < tryjob["rev"] < bad_rev
+ }
+
+ # Find all revisions that are to be skipped within 'good_rev' and 'bad_rev'.
+ #
+ # NOTE: The intent is to not launch tryjobs between 'good_rev' and 'bad_rev'
+ # that have already been marked as 'skip' (this set is used when constructing
+ # the list of revisions to launch tryjobs for).
+ skip_revisions = {
+ tryjob["rev"]
+ for tryjob in tryjobs
+ if tryjob["status"] == update_tryjob_status.TryjobStatus.SKIP.value
+ and good_rev < tryjob["rev"] < bad_rev
+ }
+
+ return good_rev, bad_rev, pending_revisions, skip_revisions
+
+
+def GetCommitsBetween(
+ start, end, parallel, src_path, pending_revisions, skip_revisions
+):
+ """Determines the revisions between start and end."""
+
+ with get_llvm_hash.LLVMHash().CreateTempDirectory() as temp_dir:
+ # We have guaranteed contiguous revision numbers after this,
+ # and that guarnatee simplifies things considerably, so we don't
+ # support anything before it.
+ assert (
+ start >= git_llvm_rev.base_llvm_revision
+ ), f"{start} was too long ago"
+
+ with get_llvm_hash.CreateTempLLVMRepo(temp_dir) as new_repo:
+ if not src_path:
+ src_path = new_repo
+ index_step = (end - (start + 1)) // (parallel + 1)
+ if not index_step:
+ index_step = 1
+ revisions = [
+ rev
+ for rev in range(start + 1, end, index_step)
+ if rev not in pending_revisions and rev not in skip_revisions
+ ]
+ git_hashes = [
+ get_llvm_hash.GetGitHashFrom(src_path, rev) for rev in revisions
+ ]
+ return revisions, git_hashes
+
+
+def Bisect(
+ revisions,
+ git_hashes,
+ bisect_state,
+ last_tested,
+ update_packages,
+ chroot_path,
+ patch_metadata_file,
+ extra_change_lists,
+ options,
+ builder,
+ verbose,
+):
+ """Adds tryjobs and updates the status file with the new tryjobs."""
+
+ try:
+ for svn_revision, git_hash in zip(revisions, git_hashes):
+ tryjob_dict = modify_a_tryjob.AddTryjob(
+ update_packages,
+ git_hash,
+ svn_revision,
+ chroot_path,
+ patch_metadata_file,
+ extra_change_lists,
+ options,
+ builder,
+ verbose,
+ svn_revision,
+ )
+
+ bisect_state["jobs"].append(tryjob_dict)
+ finally:
+ # Do not want to lose progress if there is an exception.
+ if last_tested:
+ new_file = "%s.new" % last_tested
+ with open(new_file, "w") as json_file:
+ json.dump(
+ bisect_state, json_file, indent=4, separators=(",", ": ")
+ )
+
+ os.rename(new_file, last_tested)
def LoadStatusFile(last_tested, start, end):
- """Loads the status file for bisection."""
-
- try:
- with open(last_tested) as f:
- return json.load(f)
- except IOError as err:
- if err.errno != errno.ENOENT:
- raise
+ """Loads the status file for bisection."""
- return {'start': start, 'end': end, 'jobs': []}
-
-
-def main(args_output):
- """Bisects LLVM commits.
-
- Raises:
- AssertionError: The script was run inside the chroot.
- """
-
- chroot.VerifyOutsideChroot()
- patch_metadata_file = 'PATCHES.json'
- start = args_output.start_rev
- end = args_output.end_rev
-
- bisect_state = LoadStatusFile(args_output.last_tested, start, end)
- if start != bisect_state['start'] or end != bisect_state['end']:
- raise ValueError(
- f'The start {start} or the end {end} version provided is '
- f'different than "start" {bisect_state["start"]} or "end" '
- f'{bisect_state["end"]} in the .JSON file')
-
- # Pending and skipped revisions are between 'start_rev' and 'end_rev'.
- start_rev, end_rev, pending_revs, skip_revs = GetRemainingRange(
- start, end, bisect_state['jobs'])
-
- revisions, git_hashes = GetCommitsBetween(start_rev, end_rev,
- args_output.parallel,
- args_output.src_path, pending_revs,
- skip_revs)
-
- # No more revisions between 'start_rev' and 'end_rev', so
- # bisection is complete.
- #
- # This is determined by finding all valid revisions between 'start_rev'
- # and 'end_rev' and that are NOT in the 'pending' and 'skipped' set.
- if not revisions:
- if pending_revs:
- # Some tryjobs are not finished which may change the actual bad
- # commit/revision when those tryjobs are finished.
- no_revisions_message = (f'No revisions between start {start_rev} '
- f'and end {end_rev} to create tryjobs\n')
-
- if pending_revs:
- no_revisions_message += ('The following tryjobs are pending:\n' +
- '\n'.join(str(rev)
- for rev in pending_revs) + '\n')
-
- if skip_revs:
- no_revisions_message += ('The following tryjobs were skipped:\n' +
- '\n'.join(str(rev)
- for rev in skip_revs) + '\n')
-
- raise ValueError(no_revisions_message)
-
- print(f'Finished bisecting for {args_output.last_tested}')
- if args_output.src_path:
- bad_llvm_hash = get_llvm_hash.GetGitHashFrom(args_output.src_path,
- end_rev)
- else:
- bad_llvm_hash = get_llvm_hash.LLVMHash().GetLLVMHash(end_rev)
- print(f'The bad revision is {end_rev} and its commit hash is '
- f'{bad_llvm_hash}')
- if skip_revs:
- skip_revs_message = ('\nThe following revisions were skipped:\n' +
- '\n'.join(str(rev) for rev in skip_revs))
- print(skip_revs_message)
-
- if args_output.cleanup:
- # Abandon all the CLs created for bisection
- gerrit = os.path.join(args_output.chroot_path, 'chromite/bin/gerrit')
- for build in bisect_state['jobs']:
- try:
- subprocess.check_output(
- [gerrit, 'abandon', str(build['cl'])],
- stderr=subprocess.STDOUT,
- encoding='utf-8')
- except subprocess.CalledProcessError as err:
- # the CL may have been abandoned
- if 'chromite.lib.gob_util.GOBError' not in err.output:
+ try:
+ with open(last_tested) as f:
+ return json.load(f)
+ except IOError as err:
+ if err.errno != errno.ENOENT:
raise
- return BisectionExitStatus.BISECTION_COMPLETE.value
+ return {"start": start, "end": end, "jobs": []}
- for rev in revisions:
- if update_tryjob_status.FindTryjobIndex(rev,
- bisect_state['jobs']) is not None:
- raise ValueError(f'Revision {rev} exists already in "jobs"')
- Bisect(revisions, git_hashes, bisect_state, args_output.last_tested,
- update_chromeos_llvm_hash.DEFAULT_PACKAGES, args_output.chroot_path,
- patch_metadata_file, args_output.extra_change_lists,
- args_output.options, args_output.builder, args_output.verbose)
-
-
-if __name__ == '__main__':
- sys.exit(main(GetCommandLineArgs()))
+def main(args_output):
+ """Bisects LLVM commits.
+
+ Raises:
+ AssertionError: The script was run inside the chroot.
+ """
+
+ chroot.VerifyOutsideChroot()
+ patch_metadata_file = "PATCHES.json"
+ start = args_output.start_rev
+ end = args_output.end_rev
+
+ bisect_state = LoadStatusFile(args_output.last_tested, start, end)
+ if start != bisect_state["start"] or end != bisect_state["end"]:
+ raise ValueError(
+ f"The start {start} or the end {end} version provided is "
+ f'different than "start" {bisect_state["start"]} or "end" '
+ f'{bisect_state["end"]} in the .JSON file'
+ )
+
+ # Pending and skipped revisions are between 'start_rev' and 'end_rev'.
+ start_rev, end_rev, pending_revs, skip_revs = GetRemainingRange(
+ start, end, bisect_state["jobs"]
+ )
+
+ revisions, git_hashes = GetCommitsBetween(
+ start_rev,
+ end_rev,
+ args_output.parallel,
+ args_output.src_path,
+ pending_revs,
+ skip_revs,
+ )
+
+ # No more revisions between 'start_rev' and 'end_rev', so
+ # bisection is complete.
+ #
+ # This is determined by finding all valid revisions between 'start_rev'
+ # and 'end_rev' and that are NOT in the 'pending' and 'skipped' set.
+ if not revisions:
+ if pending_revs:
+ # Some tryjobs are not finished which may change the actual bad
+ # commit/revision when those tryjobs are finished.
+ no_revisions_message = (
+ f"No revisions between start {start_rev} "
+ f"and end {end_rev} to create tryjobs\n"
+ )
+
+ if pending_revs:
+ no_revisions_message += (
+ "The following tryjobs are pending:\n"
+ + "\n".join(str(rev) for rev in pending_revs)
+ + "\n"
+ )
+
+ if skip_revs:
+ no_revisions_message += (
+ "The following tryjobs were skipped:\n"
+ + "\n".join(str(rev) for rev in skip_revs)
+ + "\n"
+ )
+
+ raise ValueError(no_revisions_message)
+
+ print(f"Finished bisecting for {args_output.last_tested}")
+ if args_output.src_path:
+ bad_llvm_hash = get_llvm_hash.GetGitHashFrom(
+ args_output.src_path, end_rev
+ )
+ else:
+ bad_llvm_hash = get_llvm_hash.LLVMHash().GetLLVMHash(end_rev)
+ print(
+ f"The bad revision is {end_rev} and its commit hash is "
+ f"{bad_llvm_hash}"
+ )
+ if skip_revs:
+ skip_revs_message = (
+ "\nThe following revisions were skipped:\n"
+ + "\n".join(str(rev) for rev in skip_revs)
+ )
+ print(skip_revs_message)
+
+ if args_output.cleanup:
+ # Abandon all the CLs created for bisection
+ gerrit = os.path.join(
+ args_output.chroot_path, "chromite/bin/gerrit"
+ )
+ for build in bisect_state["jobs"]:
+ try:
+ subprocess.check_output(
+ [gerrit, "abandon", str(build["cl"])],
+ stderr=subprocess.STDOUT,
+ encoding="utf-8",
+ )
+ except subprocess.CalledProcessError as err:
+ # the CL may have been abandoned
+ if "chromite.lib.gob_util.GOBError" not in err.output:
+ raise
+
+ return BisectionExitStatus.BISECTION_COMPLETE.value
+
+ for rev in revisions:
+ if (
+ update_tryjob_status.FindTryjobIndex(rev, bisect_state["jobs"])
+ is not None
+ ):
+ raise ValueError(f'Revision {rev} exists already in "jobs"')
+
+ Bisect(
+ revisions,
+ git_hashes,
+ bisect_state,
+ args_output.last_tested,
+ update_chromeos_llvm_hash.DEFAULT_PACKAGES,
+ args_output.chroot_path,
+ patch_metadata_file,
+ args_output.extra_change_lists,
+ args_output.options,
+ args_output.builder,
+ args_output.verbose,
+ )
+
+
+if __name__ == "__main__":
+ sys.exit(main(GetCommandLineArgs()))