blob: dcff0b30a5dc47fea9d381fdf0d93825946a12e6 [file] [log] [blame]
Haibo Huang39aaab62019-01-25 12:23:03 -08001# 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
16Example usage:
17external_updater_notifier \
Haibo Huang39aaab62019-01-25 12:23:03 -080018 --history ~/updater/history \
Haibo Huang39287b12019-01-30 15:48:27 -080019 --generate_change \
20 --recipients xxx@xxx.xxx \
21 googletest
Haibo Huang39aaab62019-01-25 12:23:03 -080022"""
23
Haibo Huang11c4a752019-01-31 15:07:03 -080024from datetime import timedelta, datetime
Haibo Huang39aaab62019-01-25 12:23:03 -080025import argparse
26import json
27import os
Haibo Huang39287b12019-01-30 15:48:27 -080028import re
Haibo Huang39aaab62019-01-25 12:23:03 -080029import subprocess
30import time
31
Thiébaud Weksteen4ac289b2020-09-28 15:23:29 +020032# pylint: disable=invalid-name
Haibo Huanga08fb602020-05-29 16:24:13 -070033
Haibo Huang39aaab62019-01-25 12:23:03 -080034def parse_args():
35 """Parses commandline arguments."""
36
37 parser = argparse.ArgumentParser(
38 description='Check updates for third party projects in external/.')
Haibo Huanga08fb602020-05-29 16:24:13 -070039 parser.add_argument('--history',
40 help='Path of history file. If doesn'
41 't exist, a new one will be created.')
Haibo Huang39aaab62019-01-25 12:23:03 -080042 parser.add_argument(
43 '--recipients',
44 help='Comma separated recipients of notification email.')
Haibo Huang39287b12019-01-30 15:48:27 -080045 parser.add_argument(
46 '--generate_change',
47 help='If set, an upgrade change will be uploaded to Gerrit.',
Haibo Huanga08fb602020-05-29 16:24:13 -070048 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 Huang39aaab62019-01-25 12:23:03 -080054
55 return parser.parse_args()
56
57
Haibo Huangbdc8cde2019-11-08 16:29:39 -080058def _get_android_top():
Haibo Huang081300e2019-11-11 14:50:17 -080059 return os.environ['ANDROID_BUILD_TOP']
Haibo Huangbdc8cde2019-11-08 16:29:39 -080060
61
Haibo Huang39287b12019-01-30 15:48:27 -080062CHANGE_URL_PATTERN = r'(https:\/\/[^\s]*android-review[^\s]*) Upgrade'
63CHANGE_URL_RE = re.compile(CHANGE_URL_PATTERN)
Haibo Huang39aaab62019-01-25 12:23:03 -080064
Haibo Huang39287b12019-01-30 15:48:27 -080065
Haibo Huangbdc8cde2019-11-08 16:29:39 -080066def _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 Huang39287b12019-01-30 15:48:27 -080074def _send_email(proj, latest_ver, recipient, upgrade_log):
75 print('Sending email for {}: {}'.format(proj, latest_ver))
Haibo Huangd4eb2ca2021-02-18 12:32:10 -080076 msg = ""
Haibo Huang39287b12019-01-30 15:48:27 -080077 match = CHANGE_URL_RE.search(upgrade_log)
78 if match is not None:
Haibo Huangd4eb2ca2021-02-18 12:32:10 -080079 subject = "[Succeeded]"
80 msg = 'An upgrade change is generated at:\n{}'.format(
Haibo Huang39287b12019-01-30 15:48:27 -080081 match.group(1))
Haibo Huang8997d482021-02-17 12:13:04 -080082 else:
Haibo Huangd4eb2ca2021-02-18 12:32:10 -080083 subject = "[Failed]"
84 msg = 'Failed to generate upgrade change. See logs below for details.'
Haibo Huang39287b12019-01-30 15:48:27 -080085
Haibo Huangd4eb2ca2021-02-18 12:32:10 -080086 subject += f" {proj} {latest_ver}"
Haibo Huangbdc8cde2019-11-08 16:29:39 -080087 owners = _read_owner_file(proj)
88 if owners:
89 msg += '\n\nOWNERS file: \n'
90 msg += owners
91
Haibo Huang39287b12019-01-30 15:48:27 -080092 msg += '\n\n'
93 msg += upgrade_log
94
Haibo Huanga1c747b2021-02-22 10:04:22 -080095 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 Huanga08fb602020-05-29 16:24:13 -0700106 check=True,
107 stdout=subprocess.PIPE,
108 stderr=subprocess.PIPE,
109 input=msg,
110 encoding='ascii')
Haibo Huang39aaab62019-01-25 12:23:03 -0800111
112
Haibo Huang806c62b2020-09-25 15:32:36 -0700113COMMIT_PATTERN = r'^[a-f0-9]{40}$'
114COMMIT_RE = re.compile(COMMIT_PATTERN)
115
116
117def is_commit(commit: str) -> bool:
118 """Whether a string looks like a SHA1 hash."""
119 return bool(COMMIT_RE.match(commit))
120
121
Haibo Huang11c4a752019-01-31 15:07:03 -0800122NOTIFIED_TIME_KEY_NAME = 'latest_notified_time'
123
124
125def _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 Huang806c62b2020-09-25 15:32:36 -0700132 if is_commit(latest_ver) and time_diff <= timedelta(days=30):
Haibo Huang11c4a752019-01-31 15:07:03 -0800133 return False
134
135 return True
136
137
Haibo Huang39287b12019-01-30 15:48:27 -0800138def _process_results(args, history, results):
Haibo Huang39aaab62019-01-25 12:23:03 -0800139 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 Huang11c4a752019-01-31 15:07:03 -0800147 if _should_notify(latest_ver, proj_history):
Haibo Huang39287b12019-01-30 15:48:27 -0800148 upgrade_log = _upgrade(proj) if args.generate_change else ""
Haibo Huang39aaab62019-01-25 12:23:03 -0800149 try:
Haibo Huang39287b12019-01-30 15:48:27 -0800150 _send_email(proj, latest_ver, args.recipients, upgrade_log)
Haibo Huang39aaab62019-01-25 12:23:03 -0800151 proj_history[latest_ver] = int(time.time())
Haibo Huang11c4a752019-01-31 15:07:03 -0800152 proj_history[NOTIFIED_TIME_KEY_NAME] = int(time.time())
Haibo Huang39aaab62019-01-25 12:23:03 -0800153 except subprocess.CalledProcessError as err:
154 msg = """Failed to send email for {} ({}).
155stdout: {}
156stderr: {}""".format(proj, latest_ver, err.stdout, err.stderr)
157 print(msg)
158
159
Haibo Huang39287b12019-01-30 15:48:27 -0800160RESULT_FILE_PATH = '/tmp/update_check_result.json'
161
162
Haibo Huang39aaab62019-01-25 12:23:03 -0800163def send_notification(args):
164 """Compare results and send notification."""
165 results = {}
Haibo Huang39287b12019-01-30 15:48:27 -0800166 with open(RESULT_FILE_PATH, 'r') as f:
Haibo Huang39aaab62019-01-25 12:23:03 -0800167 results = json.load(f)
168 history = {}
169 try:
170 with open(args.history, 'r') as f:
171 history = json.load(f)
Haibo Huang68c7a492021-02-17 15:18:27 -0800172 except (FileNotFoundError, json.decoder.JSONDecodeError):
Haibo Huang39aaab62019-01-25 12:23:03 -0800173 pass
174
Haibo Huang39287b12019-01-30 15:48:27 -0800175 _process_results(args, history, results)
Haibo Huang39aaab62019-01-25 12:23:03 -0800176
177 with open(args.history, 'w') as f:
Haibo Huang38dda432019-02-01 15:45:35 -0800178 json.dump(history, f, sort_keys=True, indent=4)
Haibo Huang39aaab62019-01-25 12:23:03 -0800179
180
Haibo Huang39287b12019-01-30 15:48:27 -0800181def _upgrade(proj):
Thiébaud Weksteen4ac289b2020-09-28 15:23:29 +0200182 # pylint: disable=subprocess-run-check
Haibo Huanga08fb602020-05-29 16:24:13 -0700183 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 Huangbdc8cde2019-11-08 16:29:39 -0800189 cwd=_get_android_top())
Haibo Huang39287b12019-01-30 15:48:27 -0800190 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
204def _check_updates(args):
Haibo Huanga08fb602020-05-29 16:24:13 -0700205 params = [
206 'out/soong/host/linux-x86/bin/external_updater', 'check',
207 '--json_output', RESULT_FILE_PATH, '--delay', '30'
208 ]
Haibo Huang1c7284e2019-02-01 11:34:21 -0800209 if args.all:
210 params.append('--all')
211 else:
212 params += args.paths
213
Haibo Huang081300e2019-11-11 14:50:17 -0800214 print(_get_android_top())
Thiébaud Weksteen4ac289b2020-09-28 15:23:29 +0200215 # pylint: disable=subprocess-run-check
Haibo Huangbdc8cde2019-11-08 16:29:39 -0800216 subprocess.run(params, cwd=_get_android_top())
Haibo Huang39287b12019-01-30 15:48:27 -0800217
218
Haibo Huang39aaab62019-01-25 12:23:03 -0800219def main():
220 """The main entry."""
221
222 args = parse_args()
Haibo Huang39287b12019-01-30 15:48:27 -0800223 _check_updates(args)
Haibo Huang39aaab62019-01-25 12:23:03 -0800224 send_notification(args)
225
226
227if __name__ == '__main__':
228 main()