blob: 9e9129cb3857b7270fbfd6358a3ad135ea336428 [file] [log] [blame]
Dave Rodgman8ae92572020-08-25 14:33:15 +01001#!/usr/bin/env python3
2"""This script generates the MbedTLS release notes in markdown format.
3
4It does this by calling assemble_changelog.py to generate the bulk of
5content, and also inserting other content such as a brief description,
6hashes for the tar and zip files containing the release, etc.
7
8Returns 0 on success, 1 on failure.
9
10Note: must be run from Mbed TLS root."""
11
12# Copyright (c) 2020, Arm Limited, All Rights Reserved
13# SPDX-License-Identifier: Apache-2.0
14#
15# Licensed under the Apache License, Version 2.0 (the "License"); you may
16# not use this file except in compliance with the License.
17# You may obtain a copy of the License at
18#
19# http://www.apache.org/licenses/LICENSE-2.0
20#
21# Unless required by applicable law or agreed to in writing, software
22# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
23# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24# See the License for the specific language governing permissions and
25# limitations under the License.
26#
27# This file is part of Mbed TLS (https://tls.mbed.org)
28
29import re
30import sys
31import os.path
32import hashlib
33import argparse
34import tempfile
35import subprocess
36
37TEMPLATE = """## Description
38
39These are the release notes for MbedTLS version {version}.
40
41{description}
42
43{changelog}
44
45## Who should update
46
47{whoshouldupdate}
48
49## Checksum
50
51The SHA256 hashes for the archives are:
52
53```
54{tarhash} mbedtls-{version}.tar.gz
55{ziphash} mbedtls-{version}.zip
56```
57"""
58
59WHO_SHOULD_UPDATE_DEFAULT = 'We recommend all affected users should \
60update to take advantage of the bug fixes contained in this release at \
61an appropriate point in their development lifecycle.'
62
63
64CHECKLIST = '''Please review the release notes to ensure that all of the \
65following are documented (if needed):
66- Missing functionality
67- Changes in functionality
68- Known issues
69'''
70
71
72CUSTOM_WORDS = 'Hellman API APIs gz lifecycle Bugfix CMake inlined Crypto endian SHA xxx'
73
74
75def sha256_digest(filename):
76 """Read given file and return a SHA256 digest"""
77 h = hashlib.sha256()
78 with open(filename, 'rb') as f:
79 h.update(f.read())
80 return h.hexdigest()
81
82
83def error(text):
84 """Display error message and exit"""
85 print(f'ERROR: {text}')
86 sys.exit(1)
87
88
89def warn(text):
90 """Display warning message"""
91 print(f'WARNING: {text}')
92
93
94def generate_content(args):
95 """Return template populated with given content"""
96 for field in ('version', 'tarhash', 'ziphash', 'changelog',
97 'description', 'whoshouldupdate'):
98 if not field in args:
99 error(f'{field} not specified')
100 return TEMPLATE.format(**args)
101
102
103def run_cmd(cmd, capture=True):
104 """Run given command in a shell and return the command output"""
105 # Note: [:-1] strips the trailing newline introduced by the shell.
106 if capture:
107 return subprocess.check_output(cmd, shell=True, input=None,
108 universal_newlines=True)[:-1]
109 else:
110 subprocess.call(cmd, shell=True)
111
112
113def parse_args(args):
114 """Parse command line arguments and return cleaned up args"""
115 parser = argparse.ArgumentParser(description=__doc__)
116 parser.add_argument('-o', '--output', default='ReleaseNotes.md',
117 help='Output file (defaults to ReleaseNotes.md)')
118 parser.add_argument('-t', '--tar', action='store',
119 help='Optional tar containing release (to generate hash)')
120 parser.add_argument('-z', '--zip', action='store',
121 help='Optional zip containing release (to generate hash)')
122 parser.add_argument('-d', '--description', action='store', required=True,
123 help='Short description of release (or name of file containing this)')
124 parser.add_argument('-w', '--who', action='store', default=WHO_SHOULD_UPDATE_DEFAULT,
125 help='Optional short description of who should \
126 update (or name of file containing this)')
127 args = parser.parse_args(args)
128
129 # If these exist as files, interpret as files containing
130 # desired content rather than literal content.
131 for field in ('description', 'who'):
132 if os.path.exists(getattr(args, field)):
133 with open(getattr(args, field), 'r') as f:
134 setattr(args, field, f.read())
135
136 return args
137
138
139def spellcheck(text):
140 with tempfile.NamedTemporaryFile() as temp_file:
141 with open(temp_file.name, 'w') as f:
142 f.write(text)
143 result = run_cmd(f'ispell -d american -w _- -a < {temp_file.name}')
144 input_lines = text.splitlines()
145 ispell_re = re.compile(r'& (\S+) \d+ \d+:.*')
146 bad_words = set()
147 bad_lines = set()
148 line_no = 1
149 for l in result.splitlines():
150 if l.strip() == '':
151 line_no += 1
152 elif l.startswith('&'):
153 m = ispell_re.fullmatch(l)
154 word = m.group(1)
155 if word.isupper():
156 # ignore all-uppercase words
157 pass
158 elif "_" in word:
159 # part of a non-English 'word' like PSA_CRYPTO_ECC
160 pass
161 elif word.startswith('-'):
162 # ignore flags
163 pass
164 elif word in CUSTOM_WORDS:
165 # accept known-good words
166 pass
167 else:
168 bad_words.add(word)
169 bad_lines.add(line_no)
170 if bad_words:
171 bad_lines = '\n'.join(' ' + input_lines[n] for n in sorted(bad_lines))
172 bad_words = ', '.join(bad_words)
173 warn('Release notes contain the following mis-spelled ' \
174 f'words: {bad_words}:\n{bad_lines}\n')
175
176
177def gen_rel_notes(args):
178 """Return release note content from given command line args"""
179 # Get version by parsing version.h. Assumption is that bump_version
180 # has been run and this contains the correct version number.
181 version = run_cmd('cat include/mbedtls/version.h | \
182 clang -Iinclude -dM -E - | grep "MBEDTLS_VERSION_STRING "')
183 version = version.split()[-1][1:-1]
184
185 # Get main changelog content.
186 assemble_path = os.path.join(os.getcwd(), 'scripts', 'assemble_changelog.py')
187 with tempfile.NamedTemporaryFile() as temp_file:
188 run_cmd(f'{assemble_path} -o {temp_file.name} --latest-only')
189 with open(temp_file.name) as f:
190 changelog = f.read()
191
192 arg_hash = {
193 'version': version,
194 'tarhash': '',
195 'ziphash': '',
196 'changelog': changelog.strip(),
197 'description': args.description.strip(),
198 'whoshouldupdate': args.who.strip()
199 }
200
201 spellcheck(generate_content(arg_hash))
202
203 arg_hash['tarhash'] = sha256_digest(args.tar) if args.tar else "x" * 64
204 arg_hash['ziphash'] = sha256_digest(args.zip) if args.zip else "x" * 64
205 return generate_content(arg_hash)
206
207
208def main():
209 # Very basic check to see if we are in the root.
210 path = os.path.join(os.getcwd(), 'scripts', 'generate_release_notes.py')
211 if not os.path.exists(path):
212 error(f'{sys.argv[0]} must be run from the mbedtls root')
213
214 args = parse_args(sys.argv[1:])
215
216 content = gen_rel_notes(args)
217 with open(args.output, 'w') as f:
218 f.write(content)
219
220 print(CHECKLIST)
221
222
223if __name__ == '__main__':
224 main()