generate_release_doc.py (5763B)
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 documentation (RST file) based on version number. 8 9 Usage: python3 generate_release_doc.py <version> <previous_version> [output_file] 10 11 Example: 12 python3 generate_release_doc.py 3.118 3.117 13 python3 generate_release_doc.py 3.118.1 3.118 doc/rst/releases/nss_3_118_1.rst 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_rst_content(version, nspr_version, bug_lines, release_date): 85 """Generate the RST content for the release notes.""" 86 version_underscore = version_string_to_underscore(version) 87 changes_text = "\n".join([f" - {line}" for line in bug_lines]) 88 89 rst_content = f""".. _mozilla_projects_nss_nss_{version_underscore}_release_notes: 90 91 NSS {version} release notes 92 ======================== 93 94 `Introduction <#introduction>`__ 95 -------------------------------- 96 97 .. container:: 98 99 Network Security Services (NSS) {version} was released on *{release_date}**. 100 101 `Distribution Information <#distribution_information>`__ 102 -------------------------------------------------------- 103 104 .. container:: 105 106 The HG tag is NSS_{version_underscore}_RTM. NSS {version} requires NSPR {nspr_version} or newer. 107 108 NSS {version} source distributions are available on ftp.mozilla.org for secure HTTPS download: 109 110 - Source tarballs: 111 https://ftp.mozilla.org/pub/mozilla.org/security/nss/releases/NSS_{version_underscore}_RTM/src/ 112 113 Other releases are available :ref:`mozilla_projects_nss_releases`. 114 115 .. _changes_in_nss_{version}: 116 117 `Changes in NSS {version} <#changes_in_nss_{version}>`__ 118 ------------------------------------------------------------------ 119 120 .. container:: 121 122 {changes_text} 123 124 """ 125 return rst_content 126 127 128 def main(): 129 if len(sys.argv) < 3: 130 print(__doc__) 131 sys.exit(1) 132 133 version = sys.argv[1].strip() 134 previous_version = sys.argv[2].strip() 135 136 # Determine output file 137 if len(sys.argv) >= 4: 138 output_file = sys.argv[3].strip() 139 else: 140 version_underscore = version_string_to_underscore(version) 141 output_file = f"doc/rst/releases/nss_{version_underscore}.rst" 142 143 # Get current date 144 current_date = datetime.now().strftime("%-d %B %Y") 145 146 # Get NSPR version 147 nspr_version = get_nspr_version() 148 149 # Convert versions to tags 150 current_tag = version_string_to_RTM_tag(version) 151 previous_tag = version_string_to_RTM_tag(previous_version) 152 153 print(f"Generating release documentation for NSS {version}") 154 print(f"Previous version: {previous_version}") 155 print(f"Current tag: {current_tag}") 156 print(f"Previous tag: {previous_tag}") 157 print(f"NSPR version: {nspr_version}") 158 print(f"Release date: {current_date}") 159 print() 160 161 # Get changes from Mercurial 162 print("Extracting changes from Mercurial...") 163 bug_lines = get_changes_from_hg(current_tag, previous_tag) 164 print(f"Found {len(bug_lines)} bug entries") 165 print() 166 167 # Generate RST content 168 rst_content = generate_rst_content(version, nspr_version, bug_lines, current_date) 169 170 # Write to file 171 os.makedirs(os.path.dirname(output_file), exist_ok=True) 172 with open(output_file, 'w') as f: 173 f.write(rst_content) 174 175 print(f"Release documentation written to: {output_file}") 176 print() 177 print("=" * 70) 178 print("Preview:") 179 print("=" * 70) 180 print(rst_content) 181 182 183 if __name__ == "__main__": 184 main()