#!/usr/bin/env python3 """ Update (or check) the contents of release_date.rs, in preparation for a software release. """ import argparse import calendar import os import sys import re import time from pathlib import Path def parse_date(s: str) -> time.struct_time: return time.strptime(s, "%Y-%m-%d") WARNING_TEXT = """\ // DO NOT EDIT MANUALLY. // This file was automatically generated by maint/update-release-date. """ SEC_PER_DAY = 86400 DATE_RE = re.compile(r"const ARTI_CLIENT_RELEASE_DATE: &str = \"([-\d]*)\";") def check_date(filename: Path, date: time.struct_time, tolerance: int): contents = filename.open("r").read() if not contents.startswith(WARNING_TEXT): print(f"{filename} does not start with our usual warning. Not proceeding.") sys.exit(1) m = DATE_RE.search(contents) if m is None: print(f"Didn't find a date string in {filename}") sys.exit(1) found_date_str = m.group(1) found_date = time.strptime(found_date_str, "%Y-%m-%d") released = calendar.timegm(found_date) now = calendar.timegm(date) if abs(now - released) > tolerance * SEC_PER_DAY: print(f"Release date of {found_date_str} is too far in the past or future!") print("Run maint/update-release-date to correct this.") sys.exit(1) else: print(f"Release date of {found_date_str} is okay.") def update_date(filename: Path, date: time.struct_time): contents = filename.open("r").read() if not contents.startswith(WARNING_TEXT): print(f"{filename} does not start with our usual warning. Not proceeding.") sys.exit(1) date_text = time.strftime("%Y-%m-%d", date) with filename.open("w") as f: f.write(f"""\ {WARNING_TEXT} //! Release date for arti-client. //! //! This module is automatically generated; do not edit by hand. /// The approximate release date for this version of arti-client. /// /// We use this release date in [`protostatus`](crate::protostatus) /// to decide whether a consensus is new enough /// that we should obey its recommendations. /// /// (We do not warn or shut down based on recommendations /// from consensuses older than our software: /// the fact that we released _this_ version /// indicates that a newer consensus probably permits this version.) pub(crate) const ARTI_CLIENT_RELEASE_DATE: &str = "{date_text}"; """) def run(): parser = argparse.ArgumentParser() parser.add_argument("--check", action="store_true") parser.add_argument( "--tolerance", default=2, help="Tolerance in days (used with --check)" ) parser.add_argument( "--date", help="The target release date, if not today. YYYY-MM-DD form" ) parser.add_argument("--filename", help="The rust module to check or update.") args = parser.parse_args() filename = (args.filename,) check_only = args.check tolerance = int(args.tolerance) if args.date is None: date = time.gmtime() else: date = parse_date(args.date) if args.filename is None: filename = ( Path(os.path.dirname(sys.argv[0])).parent.parent / "crates/arti-client/src/release_date.rs" ) else: filename = Path(args.filename) if not filename.is_file(): print(f"{filename} does not exist; not proceeding") sys.exit(1) if check_only: check_date(filename, date, tolerance) else: update_date(filename, date) if __name__ == "__main__": run()