Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 1 | # Copyright (C) 2018 The Android Open Source Project |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | """Send notification email if new version is found. |
| 15 | |
| 16 | Example usage: |
| 17 | external_updater_notifier \ |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 18 | --history ~/updater/history \ |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 19 | --generate_change \ |
| 20 | --recipients xxx@xxx.xxx \ |
| 21 | googletest |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 22 | """ |
| 23 | |
Haibo Huang | 11c4a75 | 2019-01-31 15:07:03 -0800 | [diff] [blame] | 24 | from datetime import timedelta, datetime |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 25 | import argparse |
| 26 | import json |
| 27 | import os |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 28 | import re |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 29 | import subprocess |
| 30 | import time |
| 31 | |
Thiébaud Weksteen | 4ac289b | 2020-09-28 15:23:29 +0200 | [diff] [blame] | 32 | # pylint: disable=invalid-name |
Haibo Huang | a08fb60 | 2020-05-29 16:24:13 -0700 | [diff] [blame] | 33 | |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 34 | def parse_args(): |
| 35 | """Parses commandline arguments.""" |
| 36 | |
| 37 | parser = argparse.ArgumentParser( |
| 38 | description='Check updates for third party projects in external/.') |
Haibo Huang | a08fb60 | 2020-05-29 16:24:13 -0700 | [diff] [blame] | 39 | parser.add_argument('--history', |
| 40 | help='Path of history file. If doesn' |
| 41 | 't exist, a new one will be created.') |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 42 | parser.add_argument( |
| 43 | '--recipients', |
| 44 | help='Comma separated recipients of notification email.') |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 45 | parser.add_argument( |
| 46 | '--generate_change', |
| 47 | help='If set, an upgrade change will be uploaded to Gerrit.', |
Haibo Huang | a08fb60 | 2020-05-29 16:24:13 -0700 | [diff] [blame] | 48 | action='store_true', |
| 49 | required=False) |
| 50 | parser.add_argument('paths', nargs='*', help='Paths of the project.') |
| 51 | parser.add_argument('--all', |
| 52 | action='store_true', |
| 53 | help='Checks all projects.') |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 54 | |
| 55 | return parser.parse_args() |
| 56 | |
| 57 | |
Haibo Huang | bdc8cde | 2019-11-08 16:29:39 -0800 | [diff] [blame] | 58 | def _get_android_top(): |
Haibo Huang | 081300e | 2019-11-11 14:50:17 -0800 | [diff] [blame] | 59 | return os.environ['ANDROID_BUILD_TOP'] |
Haibo Huang | bdc8cde | 2019-11-08 16:29:39 -0800 | [diff] [blame] | 60 | |
| 61 | |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 62 | CHANGE_URL_PATTERN = r'(https:\/\/[^\s]*android-review[^\s]*) Upgrade' |
| 63 | CHANGE_URL_RE = re.compile(CHANGE_URL_PATTERN) |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 64 | |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 65 | |
Haibo Huang | bdc8cde | 2019-11-08 16:29:39 -0800 | [diff] [blame] | 66 | def _read_owner_file(proj): |
| 67 | owner_file = os.path.join(_get_android_top(), 'external', proj, 'OWNERS') |
| 68 | if not os.path.isfile(owner_file): |
| 69 | return None |
| 70 | with open(owner_file, 'r') as f: |
| 71 | return f.read().strip() |
| 72 | |
| 73 | |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 74 | def _send_email(proj, latest_ver, recipient, upgrade_log): |
| 75 | print('Sending email for {}: {}'.format(proj, latest_ver)) |
Haibo Huang | d4eb2ca | 2021-02-18 12:32:10 -0800 | [diff] [blame] | 76 | msg = "" |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 77 | match = CHANGE_URL_RE.search(upgrade_log) |
| 78 | if match is not None: |
Haibo Huang | d4eb2ca | 2021-02-18 12:32:10 -0800 | [diff] [blame] | 79 | subject = "[Succeeded]" |
| 80 | msg = 'An upgrade change is generated at:\n{}'.format( |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 81 | match.group(1)) |
Haibo Huang | 8997d48 | 2021-02-17 12:13:04 -0800 | [diff] [blame] | 82 | else: |
Haibo Huang | d4eb2ca | 2021-02-18 12:32:10 -0800 | [diff] [blame] | 83 | subject = "[Failed]" |
| 84 | msg = 'Failed to generate upgrade change. See logs below for details.' |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 85 | |
Haibo Huang | d4eb2ca | 2021-02-18 12:32:10 -0800 | [diff] [blame] | 86 | subject += f" {proj} {latest_ver}" |
Haibo Huang | bdc8cde | 2019-11-08 16:29:39 -0800 | [diff] [blame] | 87 | owners = _read_owner_file(proj) |
| 88 | if owners: |
| 89 | msg += '\n\nOWNERS file: \n' |
| 90 | msg += owners |
| 91 | |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 92 | msg += '\n\n' |
| 93 | msg += upgrade_log |
| 94 | |
Haibo Huang | a1c747b | 2021-02-22 10:04:22 -0800 | [diff] [blame] | 95 | cc_recipient = '' |
| 96 | for line in owners.splitlines(): |
| 97 | line = line.strip() |
| 98 | if line.endswith('@google.com'): |
| 99 | cc_recipient += line |
| 100 | cc_recipient += ',' |
| 101 | |
| 102 | subprocess.run(['sendgmr', |
| 103 | f'--to={recipient}', |
| 104 | f'--cc={cc_recipient}', |
| 105 | f'--subject={subject}'], |
Haibo Huang | a08fb60 | 2020-05-29 16:24:13 -0700 | [diff] [blame] | 106 | check=True, |
| 107 | stdout=subprocess.PIPE, |
| 108 | stderr=subprocess.PIPE, |
| 109 | input=msg, |
| 110 | encoding='ascii') |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 111 | |
| 112 | |
Haibo Huang | 806c62b | 2020-09-25 15:32:36 -0700 | [diff] [blame] | 113 | COMMIT_PATTERN = r'^[a-f0-9]{40}$' |
| 114 | COMMIT_RE = re.compile(COMMIT_PATTERN) |
| 115 | |
| 116 | |
| 117 | def is_commit(commit: str) -> bool: |
| 118 | """Whether a string looks like a SHA1 hash.""" |
| 119 | return bool(COMMIT_RE.match(commit)) |
| 120 | |
| 121 | |
Haibo Huang | 11c4a75 | 2019-01-31 15:07:03 -0800 | [diff] [blame] | 122 | NOTIFIED_TIME_KEY_NAME = 'latest_notified_time' |
| 123 | |
| 124 | |
| 125 | def _should_notify(latest_ver, proj_history): |
| 126 | if latest_ver in proj_history: |
| 127 | # Processed this version before. |
| 128 | return False |
| 129 | |
| 130 | timestamp = proj_history.get(NOTIFIED_TIME_KEY_NAME, 0) |
| 131 | time_diff = datetime.today() - datetime.fromtimestamp(timestamp) |
Haibo Huang | 806c62b | 2020-09-25 15:32:36 -0700 | [diff] [blame] | 132 | if is_commit(latest_ver) and time_diff <= timedelta(days=30): |
Haibo Huang | 11c4a75 | 2019-01-31 15:07:03 -0800 | [diff] [blame] | 133 | return False |
| 134 | |
| 135 | return True |
| 136 | |
| 137 | |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 138 | def _process_results(args, history, results): |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 139 | for proj, res in results.items(): |
| 140 | if 'latest' not in res: |
| 141 | continue |
| 142 | latest_ver = res['latest'] |
| 143 | current_ver = res['current'] |
| 144 | if latest_ver == current_ver: |
| 145 | continue |
| 146 | proj_history = history.setdefault(proj, {}) |
Haibo Huang | 11c4a75 | 2019-01-31 15:07:03 -0800 | [diff] [blame] | 147 | if _should_notify(latest_ver, proj_history): |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 148 | upgrade_log = _upgrade(proj) if args.generate_change else "" |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 149 | try: |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 150 | _send_email(proj, latest_ver, args.recipients, upgrade_log) |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 151 | proj_history[latest_ver] = int(time.time()) |
Haibo Huang | 11c4a75 | 2019-01-31 15:07:03 -0800 | [diff] [blame] | 152 | proj_history[NOTIFIED_TIME_KEY_NAME] = int(time.time()) |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 153 | except subprocess.CalledProcessError as err: |
| 154 | msg = """Failed to send email for {} ({}). |
| 155 | stdout: {} |
| 156 | stderr: {}""".format(proj, latest_ver, err.stdout, err.stderr) |
| 157 | print(msg) |
| 158 | |
| 159 | |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 160 | RESULT_FILE_PATH = '/tmp/update_check_result.json' |
| 161 | |
| 162 | |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 163 | def send_notification(args): |
| 164 | """Compare results and send notification.""" |
| 165 | results = {} |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 166 | with open(RESULT_FILE_PATH, 'r') as f: |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 167 | results = json.load(f) |
| 168 | history = {} |
| 169 | try: |
| 170 | with open(args.history, 'r') as f: |
| 171 | history = json.load(f) |
Haibo Huang | 68c7a49 | 2021-02-17 15:18:27 -0800 | [diff] [blame] | 172 | except (FileNotFoundError, json.decoder.JSONDecodeError): |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 173 | pass |
| 174 | |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 175 | _process_results(args, history, results) |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 176 | |
| 177 | with open(args.history, 'w') as f: |
Haibo Huang | 38dda43 | 2019-02-01 15:45:35 -0800 | [diff] [blame] | 178 | json.dump(history, f, sort_keys=True, indent=4) |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 179 | |
| 180 | |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 181 | def _upgrade(proj): |
Thiébaud Weksteen | 4ac289b | 2020-09-28 15:23:29 +0200 | [diff] [blame] | 182 | # pylint: disable=subprocess-run-check |
Haibo Huang | a08fb60 | 2020-05-29 16:24:13 -0700 | [diff] [blame] | 183 | out = subprocess.run([ |
| 184 | 'out/soong/host/linux-x86/bin/external_updater', 'update', |
| 185 | '--branch_and_commit', '--push_change', proj |
| 186 | ], |
| 187 | stdout=subprocess.PIPE, |
| 188 | stderr=subprocess.PIPE, |
Haibo Huang | bdc8cde | 2019-11-08 16:29:39 -0800 | [diff] [blame] | 189 | cwd=_get_android_top()) |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 190 | stdout = out.stdout.decode('utf-8') |
| 191 | stderr = out.stderr.decode('utf-8') |
| 192 | return """ |
| 193 | ==================== |
| 194 | | Debug Info | |
| 195 | ==================== |
| 196 | -=-=-=-=stdout=-=-=-=- |
| 197 | {} |
| 198 | |
| 199 | -=-=-=-=stderr=-=-=-=- |
| 200 | {} |
| 201 | """.format(stdout, stderr) |
| 202 | |
| 203 | |
| 204 | def _check_updates(args): |
Haibo Huang | a08fb60 | 2020-05-29 16:24:13 -0700 | [diff] [blame] | 205 | params = [ |
| 206 | 'out/soong/host/linux-x86/bin/external_updater', 'check', |
| 207 | '--json_output', RESULT_FILE_PATH, '--delay', '30' |
| 208 | ] |
Haibo Huang | 1c7284e | 2019-02-01 11:34:21 -0800 | [diff] [blame] | 209 | if args.all: |
| 210 | params.append('--all') |
| 211 | else: |
| 212 | params += args.paths |
| 213 | |
Haibo Huang | 081300e | 2019-11-11 14:50:17 -0800 | [diff] [blame] | 214 | print(_get_android_top()) |
Thiébaud Weksteen | 4ac289b | 2020-09-28 15:23:29 +0200 | [diff] [blame] | 215 | # pylint: disable=subprocess-run-check |
Haibo Huang | bdc8cde | 2019-11-08 16:29:39 -0800 | [diff] [blame] | 216 | subprocess.run(params, cwd=_get_android_top()) |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 217 | |
| 218 | |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 219 | def main(): |
| 220 | """The main entry.""" |
| 221 | |
| 222 | args = parse_args() |
Haibo Huang | 39287b1 | 2019-01-30 15:48:27 -0800 | [diff] [blame] | 223 | _check_updates(args) |
Haibo Huang | 39aaab6 | 2019-01-25 12:23:03 -0800 | [diff] [blame] | 224 | send_notification(args) |
| 225 | |
| 226 | |
| 227 | if __name__ == '__main__': |
| 228 | main() |