blob: 12daeb2d6bedfe92c9880b365fad09bbf8408c22 [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
Haibo Huang11c4a752019-01-31 15:07:03 -080032import git_utils
Haibo Huang39aaab62019-01-25 12:23:03 -080033
34def parse_args():
35 """Parses commandline arguments."""
36
37 parser = argparse.ArgumentParser(
38 description='Check updates for third party projects in external/.')
39 parser.add_argument(
Haibo Huang39aaab62019-01-25 12:23:03 -080040 '--history',
41 help='Path of history file. If doesn'
42 't exist, a new one will be created.')
43 parser.add_argument(
44 '--recipients',
45 help='Comma separated recipients of notification email.')
Haibo Huang39287b12019-01-30 15:48:27 -080046 parser.add_argument(
47 '--generate_change',
48 help='If set, an upgrade change will be uploaded to Gerrit.',
49 action='store_true', required=False)
50 parser.add_argument(
51 'paths', nargs='*',
52 help='Paths of the project.')
Haibo Huang1c7284e2019-02-01 11:34:21 -080053 parser.add_argument(
54 '--all', action='store_true',
55 help='Checks all projects.')
Haibo Huang39aaab62019-01-25 12:23:03 -080056
57 return parser.parse_args()
58
59
Haibo Huangbdc8cde2019-11-08 16:29:39 -080060def _get_android_top():
Haibo Huang081300e2019-11-11 14:50:17 -080061 return os.environ['ANDROID_BUILD_TOP']
Haibo Huangbdc8cde2019-11-08 16:29:39 -080062
63
Haibo Huang39287b12019-01-30 15:48:27 -080064CHANGE_URL_PATTERN = r'(https:\/\/[^\s]*android-review[^\s]*) Upgrade'
65CHANGE_URL_RE = re.compile(CHANGE_URL_PATTERN)
Haibo Huang39aaab62019-01-25 12:23:03 -080066
Haibo Huang39287b12019-01-30 15:48:27 -080067
Haibo Huangbdc8cde2019-11-08 16:29:39 -080068def _read_owner_file(proj):
69 owner_file = os.path.join(_get_android_top(), 'external', proj, 'OWNERS')
70 if not os.path.isfile(owner_file):
71 return None
72 with open(owner_file, 'r') as f:
73 return f.read().strip()
74
75
Haibo Huang39287b12019-01-30 15:48:27 -080076def _send_email(proj, latest_ver, recipient, upgrade_log):
77 print('Sending email for {}: {}'.format(proj, latest_ver))
78 msg = "New version: {}".format(latest_ver)
79 match = CHANGE_URL_RE.search(upgrade_log)
80 if match is not None:
81 msg += '\n\nAn upgrade change is generated at:\n{}'.format(
82 match.group(1))
83
Haibo Huangbdc8cde2019-11-08 16:29:39 -080084 owners = _read_owner_file(proj)
85 if owners:
86 msg += '\n\nOWNERS file: \n'
87 msg += owners
88
Haibo Huang39287b12019-01-30 15:48:27 -080089 msg += '\n\n'
90 msg += upgrade_log
91
Haibo Huang39aaab62019-01-25 12:23:03 -080092 subprocess.run(['sendgmr', '--to=' + recipient,
93 '--subject=' + proj], check=True,
94 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
95 input=msg, encoding='ascii')
96
97
Haibo Huang11c4a752019-01-31 15:07:03 -080098NOTIFIED_TIME_KEY_NAME = 'latest_notified_time'
99
100
101def _should_notify(latest_ver, proj_history):
102 if latest_ver in proj_history:
103 # Processed this version before.
104 return False
105
106 timestamp = proj_history.get(NOTIFIED_TIME_KEY_NAME, 0)
107 time_diff = datetime.today() - datetime.fromtimestamp(timestamp)
108 if git_utils.is_commit(latest_ver) and time_diff <= timedelta(days=30):
109 return False
110
111 return True
112
113
Haibo Huang39287b12019-01-30 15:48:27 -0800114def _process_results(args, history, results):
Haibo Huang39aaab62019-01-25 12:23:03 -0800115 for proj, res in results.items():
116 if 'latest' not in res:
117 continue
118 latest_ver = res['latest']
119 current_ver = res['current']
120 if latest_ver == current_ver:
121 continue
122 proj_history = history.setdefault(proj, {})
Haibo Huang11c4a752019-01-31 15:07:03 -0800123 if _should_notify(latest_ver, proj_history):
Haibo Huang39287b12019-01-30 15:48:27 -0800124 upgrade_log = _upgrade(proj) if args.generate_change else ""
Haibo Huang39aaab62019-01-25 12:23:03 -0800125 try:
Haibo Huang39287b12019-01-30 15:48:27 -0800126 _send_email(proj, latest_ver, args.recipients, upgrade_log)
Haibo Huang39aaab62019-01-25 12:23:03 -0800127 proj_history[latest_ver] = int(time.time())
Haibo Huang11c4a752019-01-31 15:07:03 -0800128 proj_history[NOTIFIED_TIME_KEY_NAME] = int(time.time())
Haibo Huang39aaab62019-01-25 12:23:03 -0800129 except subprocess.CalledProcessError as err:
130 msg = """Failed to send email for {} ({}).
131stdout: {}
132stderr: {}""".format(proj, latest_ver, err.stdout, err.stderr)
133 print(msg)
134
135
Haibo Huang39287b12019-01-30 15:48:27 -0800136RESULT_FILE_PATH = '/tmp/update_check_result.json'
137
138
Haibo Huang39aaab62019-01-25 12:23:03 -0800139def send_notification(args):
140 """Compare results and send notification."""
141 results = {}
Haibo Huang39287b12019-01-30 15:48:27 -0800142 with open(RESULT_FILE_PATH, 'r') as f:
Haibo Huang39aaab62019-01-25 12:23:03 -0800143 results = json.load(f)
144 history = {}
145 try:
146 with open(args.history, 'r') as f:
147 history = json.load(f)
148 except FileNotFoundError:
149 pass
150
Haibo Huang39287b12019-01-30 15:48:27 -0800151 _process_results(args, history, results)
Haibo Huang39aaab62019-01-25 12:23:03 -0800152
153 with open(args.history, 'w') as f:
Haibo Huang38dda432019-02-01 15:45:35 -0800154 json.dump(history, f, sort_keys=True, indent=4)
Haibo Huang39aaab62019-01-25 12:23:03 -0800155
156
Haibo Huang39287b12019-01-30 15:48:27 -0800157def _upgrade(proj):
158 out = subprocess.run(['out/soong/host/linux-x86/bin/external_updater',
159 'update', '--branch_and_commit', '--push_change',
160 proj],
161 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Haibo Huangbdc8cde2019-11-08 16:29:39 -0800162 cwd=_get_android_top())
Haibo Huang39287b12019-01-30 15:48:27 -0800163 stdout = out.stdout.decode('utf-8')
164 stderr = out.stderr.decode('utf-8')
165 return """
166====================
167| Debug Info |
168====================
169-=-=-=-=stdout=-=-=-=-
170{}
171
172-=-=-=-=stderr=-=-=-=-
173{}
174""".format(stdout, stderr)
175
176
177def _check_updates(args):
Haibo Huang1c7284e2019-02-01 11:34:21 -0800178 params = ['out/soong/host/linux-x86/bin/external_updater',
179 'check', '--json_output', RESULT_FILE_PATH,
Haibo Huang5c6c63e2019-02-01 11:44:48 -0800180 '--delay', '30']
Haibo Huang1c7284e2019-02-01 11:34:21 -0800181 if args.all:
182 params.append('--all')
183 else:
184 params += args.paths
185
Haibo Huang081300e2019-11-11 14:50:17 -0800186 print(_get_android_top())
Haibo Huangbdc8cde2019-11-08 16:29:39 -0800187 subprocess.run(params, cwd=_get_android_top())
Haibo Huang39287b12019-01-30 15:48:27 -0800188
189
Haibo Huang39aaab62019-01-25 12:23:03 -0800190def main():
191 """The main entry."""
192
193 args = parse_args()
Haibo Huang39287b12019-01-30 15:48:27 -0800194 _check_updates(args)
Haibo Huang39aaab62019-01-25 12:23:03 -0800195 send_notification(args)
196
197
198if __name__ == '__main__':
199 main()