| # Copyright (C) 2018 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Send notification email if new version is found. |
| |
| Example usage: |
| external_updater_notifier \ |
| --history ~/updater/history \ |
| --generate_change \ |
| --recipients [email protected] \ |
| googletest |
| """ |
| |
| from datetime import timedelta, datetime |
| import argparse |
| import json |
| import os |
| import re |
| import subprocess |
| import time |
| |
| import git_utils |
| |
| def parse_args(): |
| """Parses commandline arguments.""" |
| |
| parser = argparse.ArgumentParser( |
| description='Check updates for third party projects in external/.') |
| parser.add_argument( |
| '--history', |
| help='Path of history file. If doesn' |
| 't exist, a new one will be created.') |
| parser.add_argument( |
| '--recipients', |
| help='Comma separated recipients of notification email.') |
| parser.add_argument( |
| '--generate_change', |
| help='If set, an upgrade change will be uploaded to Gerrit.', |
| action='store_true', required=False) |
| parser.add_argument( |
| 'paths', nargs='*', |
| help='Paths of the project.') |
| parser.add_argument( |
| '--all', action='store_true', |
| help='Checks all projects.') |
| |
| return parser.parse_args() |
| |
| |
| def _get_android_top(): |
| return os.environ['ANDROID_BUILD_TOP'] |
| |
| |
| CHANGE_URL_PATTERN = r'(https:\/\/[^\s]*android-review[^\s]*) Upgrade' |
| CHANGE_URL_RE = re.compile(CHANGE_URL_PATTERN) |
| |
| |
| def _read_owner_file(proj): |
| owner_file = os.path.join(_get_android_top(), 'external', proj, 'OWNERS') |
| if not os.path.isfile(owner_file): |
| return None |
| with open(owner_file, 'r') as f: |
| return f.read().strip() |
| |
| |
| def _send_email(proj, latest_ver, recipient, upgrade_log): |
| print('Sending email for {}: {}'.format(proj, latest_ver)) |
| msg = "New version: {}".format(latest_ver) |
| match = CHANGE_URL_RE.search(upgrade_log) |
| if match is not None: |
| msg += '\n\nAn upgrade change is generated at:\n{}'.format( |
| match.group(1)) |
| |
| owners = _read_owner_file(proj) |
| if owners: |
| msg += '\n\nOWNERS file: \n' |
| msg += owners |
| |
| msg += '\n\n' |
| msg += upgrade_log |
| |
| subprocess.run(['sendgmr', '--to=' + recipient, |
| '--subject=' + proj], check=True, |
| stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| input=msg, encoding='ascii') |
| |
| |
| NOTIFIED_TIME_KEY_NAME = 'latest_notified_time' |
| |
| |
| def _should_notify(latest_ver, proj_history): |
| if latest_ver in proj_history: |
| # Processed this version before. |
| return False |
| |
| timestamp = proj_history.get(NOTIFIED_TIME_KEY_NAME, 0) |
| time_diff = datetime.today() - datetime.fromtimestamp(timestamp) |
| if git_utils.is_commit(latest_ver) and time_diff <= timedelta(days=30): |
| return False |
| |
| return True |
| |
| |
| def _process_results(args, history, results): |
| for proj, res in results.items(): |
| if 'latest' not in res: |
| continue |
| latest_ver = res['latest'] |
| current_ver = res['current'] |
| if latest_ver == current_ver: |
| continue |
| proj_history = history.setdefault(proj, {}) |
| if _should_notify(latest_ver, proj_history): |
| upgrade_log = _upgrade(proj) if args.generate_change else "" |
| try: |
| _send_email(proj, latest_ver, args.recipients, upgrade_log) |
| proj_history[latest_ver] = int(time.time()) |
| proj_history[NOTIFIED_TIME_KEY_NAME] = int(time.time()) |
| except subprocess.CalledProcessError as err: |
| msg = """Failed to send email for {} ({}). |
| stdout: {} |
| stderr: {}""".format(proj, latest_ver, err.stdout, err.stderr) |
| print(msg) |
| |
| |
| RESULT_FILE_PATH = '/tmp/update_check_result.json' |
| |
| |
| def send_notification(args): |
| """Compare results and send notification.""" |
| results = {} |
| with open(RESULT_FILE_PATH, 'r') as f: |
| results = json.load(f) |
| history = {} |
| try: |
| with open(args.history, 'r') as f: |
| history = json.load(f) |
| except FileNotFoundError: |
| pass |
| |
| _process_results(args, history, results) |
| |
| with open(args.history, 'w') as f: |
| json.dump(history, f, sort_keys=True, indent=4) |
| |
| |
| def _upgrade(proj): |
| out = subprocess.run(['out/soong/host/linux-x86/bin/external_updater', |
| 'update', '--branch_and_commit', '--push_change', |
| proj], |
| stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| cwd=_get_android_top()) |
| stdout = out.stdout.decode('utf-8') |
| stderr = out.stderr.decode('utf-8') |
| return """ |
| ==================== |
| | Debug Info | |
| ==================== |
| -=-=-=-=stdout=-=-=-=- |
| {} |
| |
| -=-=-=-=stderr=-=-=-=- |
| {} |
| """.format(stdout, stderr) |
| |
| |
| def _check_updates(args): |
| params = ['out/soong/host/linux-x86/bin/external_updater', |
| 'check', '--json_output', RESULT_FILE_PATH, |
| '--delay', '30'] |
| if args.all: |
| params.append('--all') |
| else: |
| params += args.paths |
| |
| print(_get_android_top()) |
| subprocess.run(params, cwd=_get_android_top()) |
| |
| |
| def main(): |
| """The main entry.""" |
| |
| args = parse_args() |
| _check_updates(args) |
| send_notification(args) |
| |
| |
| if __name__ == '__main__': |
| main() |