aboutsummaryrefslogtreecommitdiff
path: root/llvm_tools/nightly_revert_checker.py
diff options
context:
space:
mode:
Diffstat (limited to 'llvm_tools/nightly_revert_checker.py')
-rwxr-xr-xllvm_tools/nightly_revert_checker.py280
1 files changed, 188 insertions, 92 deletions
diff --git a/llvm_tools/nightly_revert_checker.py b/llvm_tools/nightly_revert_checker.py
index 3a23890a..89485088 100755
--- a/llvm_tools/nightly_revert_checker.py
+++ b/llvm_tools/nightly_revert_checker.py
@@ -6,12 +6,10 @@
"""Checks for new reverts in LLVM on a nightly basis.
-If any reverts are found that were previously unknown, this fires off an email.
-All LLVM SHAs to monitor are autodetected.
+If any reverts are found that were previously unknown, this cherry-picks them or
+fires off an email. All LLVM SHAs to monitor are autodetected.
"""
-# pylint: disable=cros-logging-import
-
from __future__ import print_function
import argparse
@@ -26,38 +24,50 @@ import typing as t
import cros_utils.email_sender as email_sender
import cros_utils.tiny_render as tiny_render
+
import get_llvm_hash
+import get_upstream_patch
import git_llvm_rev
import revert_checker
State = t.Any
-def _find_interesting_android_shas(
- android_llvm_toolchain_dir: str) -> t.List[t.Tuple[str]]:
+def _find_interesting_android_shas(android_llvm_toolchain_dir: str
+ ) -> t.List[t.Tuple[str, str]]:
llvm_project = os.path.join(android_llvm_toolchain_dir,
'toolchain/llvm-project')
def get_llvm_merge_base(branch: str) -> str:
- return subprocess.check_output(
- ['git', 'merge-base', branch, 'aosp/upstream-master'],
+ head_sha = subprocess.check_output(
+ ['git', 'rev-parse', branch],
cwd=llvm_project,
encoding='utf-8',
).strip()
+ merge_base = subprocess.check_output(
+ ['git', 'merge-base', branch, 'aosp/upstream-main'],
+ cwd=llvm_project,
+ encoding='utf-8',
+ ).strip()
+ logging.info('Merge-base for %s (HEAD == %s) and upstream-main is %s',
+ branch, head_sha, merge_base)
+ return merge_base
- main_legacy = get_llvm_merge_base('aosp/master-legacy')
+ main_legacy = get_llvm_merge_base('aosp/master-legacy') # nocheck
testing_upstream = get_llvm_merge_base('aosp/testing-upstream')
result = [('main-legacy', main_legacy)]
# If these are the same SHA, there's no point in tracking both.
if main_legacy != testing_upstream:
result.append(('testing-upstream', testing_upstream))
+ else:
+ logging.info('main-legacy and testing-upstream are identical; ignoring '
+ 'the latter.')
return result
-def _parse_llvm_ebuild_for_shas(
- ebuild_file: io.TextIOWrapper) -> t.List[t.Tuple[str]]:
-
+def _parse_llvm_ebuild_for_shas(ebuild_file: io.TextIOWrapper
+ ) -> t.List[t.Tuple[str, str]]:
def parse_ebuild_assignment(line: str) -> str:
no_comments = line.split('#')[0]
no_assign = no_comments.split('=', 1)[1].strip()
@@ -84,12 +94,12 @@ def _parse_llvm_ebuild_for_shas(
return results
-def _find_interesting_chromeos_shas(chromeos_base: str) -> t.List[t.Tuple[str]]:
+def _find_interesting_chromeos_shas(chromeos_base: str
+ ) -> t.List[t.Tuple[str, str]]:
llvm_dir = os.path.join(chromeos_base,
'src/third_party/chromiumos-overlay/sys-devel/llvm')
candidate_ebuilds = [
- os.path.join(llvm_dir, x)
- for x in os.listdir(llvm_dir)
+ os.path.join(llvm_dir, x) for x in os.listdir(llvm_dir)
if '_pre' in x and not os.path.islink(os.path.join(llvm_dir, x))
]
@@ -193,25 +203,132 @@ def _read_state(state_file: str) -> State:
return {}
-def main(argv: t.List[str]) -> None:
+def find_shas(llvm_dir: str, interesting_shas: t.List[t.Tuple[str, str]],
+ state: State, new_state: State):
+ for friendly_name, sha in interesting_shas:
+ logging.info('Finding reverts across %s (%s)', friendly_name, sha)
+ all_reverts = revert_checker.find_reverts(llvm_dir,
+ sha,
+ root='origin/' +
+ git_llvm_rev.MAIN_BRANCH)
+ logging.info('Detected the following revert(s) across %s:\n%s',
+ friendly_name, pprint.pformat(all_reverts))
+
+ new_state[sha] = [r.sha for r in all_reverts]
+
+ if sha not in state:
+ logging.info('SHA %s is new to me', sha)
+ existing_reverts = set()
+ else:
+ existing_reverts = set(state[sha])
+
+ new_reverts = [r for r in all_reverts if r.sha not in existing_reverts]
+ if not new_reverts:
+ logging.info('...All of which have been reported.')
+ continue
+
+ yield (friendly_name, sha, new_reverts)
+
+
+def do_cherrypick(chroot_path: str, llvm_dir: str,
+ interesting_shas: t.List[t.Tuple[str, str]], state: State,
+ reviewers: t.List[str], cc: t.List[str]) -> State:
+ new_state: State = {}
+ seen: t.Set[str] = set()
+ for friendly_name, _sha, reverts in find_shas(llvm_dir, interesting_shas,
+ state, new_state):
+ if friendly_name in seen:
+ continue
+ seen.add(friendly_name)
+ for sha, reverted_sha in reverts:
+ try:
+ # We upload reverts for all platforms by default, since there's no
+ # real reason for them to be CrOS-specific.
+ get_upstream_patch.get_from_upstream(chroot_path=chroot_path,
+ create_cl=True,
+ start_sha=reverted_sha,
+ patches=[sha],
+ reviewers=reviewers,
+ cc=cc,
+ platforms=())
+ except get_upstream_patch.CherrypickError as e:
+ logging.info('%s, skipping...', str(e))
+ return new_state
+
+
+def do_email(is_dry_run: bool, llvm_dir: str, repository: str,
+ interesting_shas: t.List[t.Tuple[str, str]], state: State,
+ recipients: _EmailRecipients) -> State:
+ def prettify_sha(sha: str) -> tiny_render.Piece:
+ rev = get_llvm_hash.GetVersionFrom(llvm_dir, sha)
+
+ # 12 is arbitrary, but should be unambiguous enough.
+ short_sha = sha[:12]
+ return tiny_render.Switch(
+ text=f'r{rev} ({short_sha})',
+ html=tiny_render.Link(href='https://reviews.llvm.org/rG' + sha,
+ inner='r' + str(rev)),
+ )
+
+ def get_sha_description(sha: str) -> tiny_render.Piece:
+ return subprocess.check_output(
+ ['git', 'log', '-n1', '--format=%s', sha],
+ cwd=llvm_dir,
+ encoding='utf-8',
+ ).strip()
+
+ new_state: State = {}
+ for friendly_name, sha, new_reverts in find_shas(llvm_dir, interesting_shas,
+ state, new_state):
+ email = _generate_revert_email(repository, friendly_name, sha,
+ prettify_sha, get_sha_description,
+ new_reverts)
+ if is_dry_run:
+ logging.info('Would send email:\nSubject: %s\nBody:\n%s\n',
+ email.subject, tiny_render.render_text_pieces(email.body))
+ else:
+ logging.info('Sending email with subject %r...', email.subject)
+ _send_revert_email(recipients, email)
+ logging.info('Email sent.')
+ return new_state
+
+
+def parse_args(argv: t.List[str]) -> t.Any:
parser = argparse.ArgumentParser(
- description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
- '--state_file', required=True, help='File to store persistent state in.')
+ 'action',
+ choices=['cherry-pick', 'email', 'dry-run'],
+ help='Automatically cherry-pick upstream reverts, send an email, or '
+ 'write to stdout.')
+ parser.add_argument('--state_file',
+ required=True,
+ help='File to store persistent state in.')
+ parser.add_argument('--llvm_dir',
+ required=True,
+ help='Up-to-date LLVM directory to use.')
+ parser.add_argument('--debug', action='store_true')
parser.add_argument(
- '--llvm_dir', required=True, help='Up-to-date LLVM directory to use.')
+ '--reviewers',
+ type=str,
+ nargs='*',
+ help='Requests reviews from REVIEWERS. All REVIEWERS must have existing '
+ 'accounts.')
parser.add_argument(
- '--dry_run',
- action='store_true',
- help='Print email contents, rather than sending them.')
- parser.add_argument('--debug', action='store_true')
+ '--cc',
+ type=str,
+ nargs='*',
+ help='CCs the CL to the recipients. All recipients must have existing '
+ 'accounts.')
subparsers = parser.add_subparsers(dest='repository')
subparsers.required = True
chromeos_subparser = subparsers.add_parser('chromeos')
- chromeos_subparser.add_argument(
- '--chromeos_dir', required=True, help='Up-to-date CrOS directory to use.')
+ chromeos_subparser.add_argument('--chromeos_dir',
+ required=True,
+ help='Up-to-date CrOS directory to use.')
android_subparser = subparsers.add_parser('android')
android_subparser.add_argument(
@@ -219,91 +336,70 @@ def main(argv: t.List[str]) -> None:
required=True,
help='Up-to-date android-llvm-toolchain directory to use.')
- opts = parser.parse_args(argv)
+ return parser.parse_args(argv)
+
+
+def find_chroot(opts: t.Any, reviewers: t.List[str], cc: t.List[str]
+ ) -> t.Tuple[str, t.List[t.Tuple[str, str]], _EmailRecipients]:
+ recipients = reviewers + cc
+ if opts.repository == 'chromeos':
+ chroot_path = opts.chromeos_dir
+ return (chroot_path, _find_interesting_chromeos_shas(chroot_path),
+ _EmailRecipients(well_known=['mage'], direct=recipients))
+ elif opts.repository == 'android':
+ if opts.action == 'cherry-pick':
+ raise RuntimeError(
+ "android doesn't currently support automatic cherry-picking.")
+
+ chroot_path = opts.android_llvm_toolchain_dir
+ return (chroot_path, _find_interesting_android_shas(chroot_path),
+ _EmailRecipients(well_known=[],
+ direct=['android-llvm-dev@google.com'] +
+ recipients))
+ else:
+ raise ValueError(f'Unknown repository {opts.repository}')
+
+
+def main(argv: t.List[str]) -> int:
+ opts = parse_args(argv)
logging.basicConfig(
format='%(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: %(message)s',
level=logging.DEBUG if opts.debug else logging.INFO,
)
- dry_run = opts.dry_run
+ action = opts.action
llvm_dir = opts.llvm_dir
repository = opts.repository
state_file = opts.state_file
+ reviewers = opts.reviewers if opts.reviewers else []
+ cc = opts.cc if opts.cc else []
- if repository == 'chromeos':
- interesting_shas = _find_interesting_chromeos_shas(opts.chromeos_dir)
- recipients = _EmailRecipients(well_known=['mage'], direct=[])
- elif repository == 'android':
- interesting_shas = _find_interesting_android_shas(
- opts.android_llvm_toolchain_dir)
- recipients = _EmailRecipients(
- well_known=[], direct=['android-llvm-dev@google.com'])
- else:
- raise ValueError('Unknown repository %s' % opts.repository)
-
+ chroot_path, interesting_shas, recipients = find_chroot(opts, reviewers, cc)
logging.info('Interesting SHAs were %r', interesting_shas)
state = _read_state(state_file)
logging.info('Loaded state\n%s', pprint.pformat(state))
- def prettify_sha(sha: str) -> tiny_render.Piece:
- rev = get_llvm_hash.GetVersionFrom(llvm_dir, sha)
-
- # 12 is arbitrary, but should be unambiguous enough.
- short_sha = sha[:12]
- return tiny_render.Switch(
- text='r%s (%s)' % (rev, short_sha),
- html=tiny_render.Link(
- href='https://reviews.llvm.org/rG' + sha, inner='r' + str(rev)),
- )
-
- def get_sha_description(sha: str) -> tiny_render.Piece:
- return subprocess.check_output(
- ['git', 'log', '-n1', '--format=%s', sha],
- cwd=llvm_dir,
- encoding='utf-8',
- ).strip()
-
- new_state: State = {}
- revert_emails_to_send: t.List[t.Tuple[str, t.List[revert_checker
- .Revert]]] = []
- for friendly_name, sha in interesting_shas:
- logging.info('Finding reverts across %s (%s)', friendly_name, sha)
- all_reverts = revert_checker.find_reverts(
- llvm_dir, sha, root='origin/' + git_llvm_rev.MAIN_BRANCH)
- logging.info('Detected the following revert(s) across %s:\n%s',
- friendly_name, pprint.pformat(all_reverts))
-
- new_state[sha] = [r.sha for r in all_reverts]
-
- if sha not in state:
- logging.info('SHA %s is new to me', sha)
- existing_reverts = set()
- else:
- existing_reverts = set(state[sha])
-
- new_reverts = [r for r in all_reverts if r.sha not in existing_reverts]
- if not new_reverts:
- logging.info('...All of which have been reported.')
- continue
-
- revert_emails_to_send.append(
- _generate_revert_email(repository, friendly_name, sha, prettify_sha,
- get_sha_description, new_reverts))
-
# We want to be as free of obvious side-effects as possible in case something
- # above breaks. Hence, send the email as late as possible.
- for email in revert_emails_to_send:
- if dry_run:
- logging.info('Would send email:\nSubject: %s\nBody:\n%s\n', email.subject,
- tiny_render.render_text_pieces(email.body))
- else:
- logging.info('Sending email with subject %r...', email.subject)
- _send_revert_email(recipients, email)
- logging.info('Email sent.')
+ # above breaks. Hence, action as late as possible.
+ if action == 'cherry-pick':
+ new_state = do_cherrypick(chroot_path=chroot_path,
+ llvm_dir=llvm_dir,
+ interesting_shas=interesting_shas,
+ state=state,
+ reviewers=reviewers,
+ cc=cc)
+ else:
+ new_state = do_email(is_dry_run=action == 'dry-run',
+ llvm_dir=llvm_dir,
+ repository=repository,
+ interesting_shas=interesting_shas,
+ state=state,
+ recipients=recipients)
_write_state(state_file, new_state)
+ return 0
if __name__ == '__main__':