#!/usr/bin/env python3 import json import os import sys import subprocess import errno from typing import List, Dict, Any ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ZULIP_PATH) log_dir = "var/log/python_dependency_upgrade" try: os.makedirs(log_dir) except OSError as e: if e.errno != errno.EEXIST: raise outdated_packages = [] # type: List[Dict[str, str]] packages_json = subprocess.check_output(["pip", "list", "--outdated", "--format=json"]) outdated_packages = json.loads(packages_json.decode("utf-8")) unupgradable_packages = {} # type: Dict[str, Any] with open("requirements/unupgradable.json") as f: unupgradable_packages = json.load(f) def delete_locked_requirements(locked_requirement_files: List[str]) -> None: for lock_file in locked_requirement_files: try: os.remove("requirements/{}".format(lock_file)) except OSError: pass def prepare_for_commit() -> None: subprocess.check_output(["./tools/update-locked-requirements"], stderr=subprocess.STDOUT) subprocess.check_output(["./tools/provision"], stderr=subprocess.STDOUT) subprocess.check_output(["./tools/test-backend"], stderr=subprocess.STDOUT) subprocess.check_call(["git", "add", "requirements"]) def commit_and_push(commit_msg: str) -> None: subprocess.check_call(["git", "commit", "-m", commit_msg]) subprocess.check_call(["git", "push", "origin", "HEAD", "--force"]) def do_upgrade(requirement_file: str, locked_requirement_files: List[str]) -> None: if len(locked_requirement_files) != 0: delete_locked_requirements(locked_requirement_files) try: prepare_for_commit() diff = subprocess.check_output(["git", "diff", "requirements/"]) if diff: locked_files_str = ", ".join(locked_requirement_files) commit_msg = "requirements: Upgrade indirect dependencies in {}.".format(locked_files_str) commit_and_push(commit_msg) except subprocess.CalledProcessError as e: print("Upgrading indirect dependencies in {} failed".format(requirement_file)) log_file = "{}/{}".format(log_dir, requirement_file) with open(log_file, "w") as f: f.write(e.output.decode("utf-8")) for package in outdated_packages: pkg_name = package["name"] version = package["version"] latest_version = package["latest_version"] log_file = "{}/{}".format(log_dir, pkg_name) if pkg_name in unupgradable_packages: continue sed_string_template = "s/{name}=={version}/{name}=={latest_version}/g" sed_string = sed_string_template.format(name=pkg_name, version=version, latest_version=latest_version) files_updated = False subprocess.check_call(["git", "stash"]) file_path = "requirements/{}".format(requirement_file) subprocess.check_call(["sed", "-i", sed_string, file_path]) files_updated = subprocess.check_output(["git", "diff", file_path]) if not files_updated: continue if pkg_name == "stripe": with open(log_file, "w") as f: url = ("https://zulip.readthedocs.io/en/stable/subsystems/billing.html" "#upgrading-stripe-api-versions") f.write("Stripe upgrade available. Upgrade manually. Make sure to update fixtures as well.") f.write(url) continue try: print("Trying to upgrade {}".format(pkg_name)) prepare_for_commit() commit_msg = "requirements: Upgrade {} from {} to {}.".format(pkg_name, version, latest_version) commit_and_push(commit_msg) except subprocess.CalledProcessError as e: print("{} upgrade failed".format(pkg_name)) with open(log_file, "w") as f: f.write(e.output.decode("utf-8")) do_upgrade("common.in", ["dev.txt", "prod.txt"]) do_upgrade("dev.in", []) do_upgrade("prod.in", []) do_upgrade("docs.in", ["docs.txt"]) do_upgrade("thumbor.in", ["thumbor.txt"]) do_upgrade("pip.txt", [])