generate_release_email.py (5751B)
1 #!/usr/bin/env python3 2 # This Source Code Form is subject to the terms of the Mozilla Public 3 # License, v. 2.0. If a copy of the MPL was not distributed with this 4 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 6 """ 7 Generate NSS release email text based on version number. 8 9 Usage: python3 generate_release_email.py <version> <previous_version> [output_file] 10 11 Example: 12 python3 generate_release_email.py 3.118 3.117 13 python3 generate_release_email.py 3.118.1 3.118 release_email_3.118.1.txt 14 """ 15 16 import os 17 import re 18 import sys 19 from datetime import datetime 20 from subprocess import check_output 21 22 23 def exit_with_failure(message): 24 """Exit the script with an error message.""" 25 print(f"ERROR: {message}", file=sys.stderr) 26 sys.exit(1) 27 28 29 def version_string_to_underscore(version_string): 30 """Convert version string like '3.118' to '3_118'.""" 31 return version_string.replace('.', '_') 32 33 34 def version_string_to_RTM_tag(version_string): 35 """Convert version string like '3.118' to 'NSS_3_118_RTM'.""" 36 parts = version_string.split('.') 37 return "NSS_" + "_".join(parts) + "_RTM" 38 39 40 def get_nspr_version(): 41 """Read the NSPR version from automation/release/nspr-version.txt.""" 42 nspr_version_file = "automation/release/nspr-version.txt" 43 try: 44 with open(nspr_version_file, 'r') as f: 45 return f.readline().strip() 46 except FileNotFoundError: 47 exit_with_failure(f"Could not find {nspr_version_file}. Are you running from the NSS root directory?") 48 49 50 def get_changes_from_hg(current_tag, previous_tag): 51 """Extract bug changes from Mercurial log between two tags.""" 52 try: 53 # Get log entries between previous tag and current tag 54 command = ["hg", "log", "-r", f"{previous_tag}:{current_tag}", "--template", "{desc|firstline}\\n"] 55 log_output = check_output(command).decode('utf-8') 56 except Exception as e: 57 exit_with_failure(f"Failed to get hg log: {e}") 58 59 # Extract bug numbers and descriptions 60 bug_lines = [] 61 for line in reversed(log_output.split('\n')): 62 if 'Bug' in line or 'bug' in line: 63 line = line.strip() 64 # Remove reviewer information 65 line = line.split("r=")[0].strip() 66 67 # Match patterns like "Bug 1234567 Something" and convert to "Bug 1234567 - Something" 68 line = re.sub(r'(Bug\s+\d+)\s+([^-])', r'\1 - \2', line, flags=re.IGNORECASE) 69 70 # Clean up punctuation 71 if line: 72 line = line.rstrip(',') 73 74 # Add a full stop at the end if there isn't one 75 if line and not line.endswith('.'): 76 line = line + '.' 77 78 if line and line not in bug_lines: 79 bug_lines.append(line) 80 81 return bug_lines 82 83 84 def generate_email_content(version, nspr_version, bug_lines, release_date): 85 """Generate the email content for the release announcement.""" 86 version_underscore = version_string_to_underscore(version) 87 changes_text = "\n\n".join([f" {line}" for line in bug_lines]) 88 89 email_content = f"""Network Security Services (NSS) {version} was released on {release_date}. 90 91 92 93 The HG tag is NSS_{version_underscore}_RTM. This version of NSS requires NSPR {nspr_version} or newer. The latest version of NSPR is {nspr_version}. 94 95 NSS {version} source distributions are available on ftp.mozilla.org for secure HTTPS download: 96 97 <https://ftp.mozilla.org/pub/security/nss/releases/NSS_{version_underscore}_RTM/src/> 98 99 Changes: 100 101 {changes_text} 102 103 NSS {version} shared libraries are backwards-compatible with all older NSS 3.x shared libraries. A program linked with older NSS 3.x shared libraries will work with this new version of the shared libraries without recompiling or relinking. Furthermore, applications that restrict their use of NSS APIs to the functions listed in NSS Public Functions will remain compatible with future versions of the NSS shared libraries. 104 105 Bugs discovered should be reported by filing a bug report at <https://bugzilla.mozilla.org/enter_bug.cgi?product=NSS> 106 107 Release notes are available at <https://firefox-source-docs.mozilla.org/security/nss/releases/index.html>. 108 """ 109 return email_content 110 111 112 def main(): 113 if len(sys.argv) < 3: 114 print(__doc__) 115 sys.exit(1) 116 117 version = sys.argv[1].strip() 118 previous_version = sys.argv[2].strip() 119 120 # Determine output file (optional) 121 output_file = None 122 if len(sys.argv) >= 4: 123 output_file = sys.argv[3].strip() 124 125 # Get current date 126 current_date = datetime.now().strftime("%-d %B %Y") 127 128 # Get NSPR version 129 nspr_version = get_nspr_version() 130 131 # Convert versions to tags 132 current_tag = version_string_to_RTM_tag(version) 133 previous_tag = version_string_to_RTM_tag(previous_version) 134 135 print(f"Generating release email for NSS {version}") 136 print(f"Previous version: {previous_version}") 137 print(f"Current tag: {current_tag}") 138 print(f"Previous tag: {previous_tag}") 139 print(f"NSPR version: {nspr_version}") 140 print(f"Release date: {current_date}") 141 print() 142 143 # Get changes from Mercurial 144 print("Extracting changes from Mercurial...") 145 bug_lines = get_changes_from_hg(current_tag, previous_tag) 146 print(f"Found {len(bug_lines)} bug entries") 147 print() 148 149 # Generate email content 150 email_content = generate_email_content(version, nspr_version, bug_lines, current_date) 151 152 # Write to file if specified, otherwise print to stdout 153 if output_file: 154 with open(output_file, 'w') as f: 155 f.write(email_content) 156 print(f"Release email written to: {output_file}") 157 print() 158 159 print("=" * 70) 160 print("Email Content:") 161 print("=" * 70) 162 print(email_content) 163 164 165 if __name__ == "__main__": 166 main()