Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mesa
Path: blob/21.2-virgl/bin/gen_release_notes.py
4545 views
1
#!/usr/bin/env python3
2
# Copyright © 2019-2020 Intel Corporation
3
4
# Permission is hereby granted, free of charge, to any person obtaining a copy
5
# of this software and associated documentation files (the "Software"), to deal
6
# in the Software without restriction, including without limitation the rights
7
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
# copies of the Software, and to permit persons to whom the Software is
9
# furnished to do so, subject to the following conditions:
10
11
# The above copyright notice and this permission notice shall be included in
12
# all copies or substantial portions of the Software.
13
14
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
# SOFTWARE.
21
22
"""Generates release notes for a given version of mesa."""
23
24
import asyncio
25
import datetime
26
import os
27
import pathlib
28
import re
29
import subprocess
30
import sys
31
import textwrap
32
import typing
33
import urllib.parse
34
35
import aiohttp
36
from mako.template import Template
37
from mako import exceptions
38
39
import docutils.utils
40
import docutils.parsers.rst.states as states
41
42
CURRENT_GL_VERSION = '4.6'
43
CURRENT_VK_VERSION = '1.2'
44
45
TEMPLATE = Template(textwrap.dedent("""\
46
${header}
47
${header_underline}
48
49
%if not bugfix:
50
Mesa ${this_version} is a new development release. People who are concerned
51
with stability and reliability should stick with a previous release or
52
wait for Mesa ${this_version[:-1]}1.
53
%else:
54
Mesa ${this_version} is a bug fix release which fixes bugs found since the ${previous_version} release.
55
%endif
56
57
Mesa ${this_version} implements the OpenGL ${gl_version} API, but the version reported by
58
glGetString(GL_VERSION) or glGetIntegerv(GL_MAJOR_VERSION) /
59
glGetIntegerv(GL_MINOR_VERSION) depends on the particular driver being used.
60
Some drivers don't support all the features required in OpenGL ${gl_version}. OpenGL
61
${gl_version} is **only** available if requested at context creation.
62
Compatibility contexts may report a lower version depending on each driver.
63
64
Mesa ${this_version} implements the Vulkan ${vk_version} API, but the version reported by
65
the apiVersion property of the VkPhysicalDeviceProperties struct
66
depends on the particular driver being used.
67
68
SHA256 checksum
69
---------------
70
71
::
72
73
TBD.
74
75
76
New features
77
------------
78
79
%for f in features:
80
- ${rst_escape(f)}
81
%endfor
82
83
84
Bug fixes
85
---------
86
87
%for b in bugs:
88
- ${rst_escape(b)}
89
%endfor
90
91
92
Changes
93
-------
94
%for c, author_line in changes:
95
%if author_line:
96
97
${rst_escape(c)}
98
99
%else:
100
- ${rst_escape(c)}
101
%endif
102
%endfor
103
"""))
104
105
106
# copied from https://docutils.sourceforge.io/sandbox/xml2rst/xml2rstlib/markup.py
107
class Inliner(states.Inliner):
108
"""
109
Recognizer for inline markup. Derive this from the original inline
110
markup parser for best results.
111
"""
112
113
# Copy static attributes from super class
114
vars().update(vars(states.Inliner))
115
116
def quoteInline(self, text):
117
"""
118
`text`: ``str``
119
Return `text` with inline markup quoted.
120
"""
121
# Method inspired by `states.Inliner.parse`
122
self.document = docutils.utils.new_document("<string>")
123
self.document.settings.trim_footnote_reference_space = False
124
self.document.settings.character_level_inline_markup = False
125
self.document.settings.pep_references = False
126
self.document.settings.rfc_references = False
127
128
self.init_customizations(self.document.settings)
129
130
self.reporter = self.document.reporter
131
self.reporter.stream = None
132
self.language = None
133
self.parent = self.document
134
remaining = docutils.utils.escape2null(text)
135
checked = ""
136
processed = []
137
unprocessed = []
138
messages = []
139
while remaining:
140
original = remaining
141
match = self.patterns.initial.search(remaining)
142
if match:
143
groups = match.groupdict()
144
method = self.dispatch[groups['start'] or groups['backquote']
145
or groups['refend'] or groups['fnend']]
146
before, inlines, remaining, sysmessages = method(self, match, 0)
147
checked += before
148
if inlines:
149
assert len(inlines) == 1, "More than one inline found"
150
inline = original[len(before)
151
:len(original) - len(remaining)]
152
rolePfx = re.search("^:" + self.simplename + ":(?=`)",
153
inline)
154
refSfx = re.search("_+$", inline)
155
if rolePfx:
156
# Prefixed roles need to be quoted in the middle
157
checked += (inline[:rolePfx.end()] + "\\"
158
+ inline[rolePfx.end():])
159
elif refSfx and not re.search("^`", inline):
160
# Pure reference markup needs to be quoted at the end
161
checked += (inline[:refSfx.start()] + "\\"
162
+ inline[refSfx.start():])
163
else:
164
# Quote other inlines by prefixing
165
checked += "\\" + inline
166
else:
167
checked += remaining
168
break
169
# Quote all original backslashes
170
checked = re.sub('\x00', "\\\x00", checked)
171
return docutils.utils.unescape(checked, 1)
172
173
inliner = Inliner();
174
175
176
async def gather_commits(version: str) -> str:
177
p = await asyncio.create_subprocess_exec(
178
'git', 'log', '--oneline', f'mesa-{version}..', '--grep', r'Closes: \(https\|#\).*',
179
stdout=asyncio.subprocess.PIPE)
180
out, _ = await p.communicate()
181
assert p.returncode == 0, f"git log didn't work: {version}"
182
return out.decode().strip()
183
184
185
async def parse_issues(commits: str) -> typing.List[str]:
186
issues: typing.List[str] = []
187
for commit in commits.split('\n'):
188
sha, message = commit.split(maxsplit=1)
189
p = await asyncio.create_subprocess_exec(
190
'git', 'log', '--max-count', '1', r'--format=%b', sha,
191
stdout=asyncio.subprocess.PIPE)
192
_out, _ = await p.communicate()
193
out = _out.decode().split('\n')
194
195
for line in reversed(out):
196
if line.startswith('Closes:'):
197
bug = line.lstrip('Closes:').strip()
198
if bug.startswith('https://gitlab.freedesktop.org/mesa/mesa'):
199
# This means we have a bug in the form "Closes: https://..."
200
issues.append(os.path.basename(urllib.parse.urlparse(bug).path))
201
elif ',' in bug:
202
issues.extend([b.strip().lstrip('#') for b in bug.split(',')])
203
elif bug.startswith('#'):
204
issues.append(bug.lstrip('#'))
205
206
return issues
207
208
209
async def gather_bugs(version: str) -> typing.List[str]:
210
commits = await gather_commits(version)
211
issues = await parse_issues(commits)
212
213
loop = asyncio.get_event_loop()
214
async with aiohttp.ClientSession(loop=loop) as session:
215
results = await asyncio.gather(*[get_bug(session, i) for i in issues])
216
typing.cast(typing.Tuple[str, ...], results)
217
bugs = list(results)
218
if not bugs:
219
bugs = ['None']
220
return bugs
221
222
223
async def get_bug(session: aiohttp.ClientSession, bug_id: str) -> str:
224
"""Query gitlab to get the name of the issue that was closed."""
225
# Mesa's gitlab id is 176,
226
url = 'https://gitlab.freedesktop.org/api/v4/projects/176/issues'
227
params = {'iids[]': bug_id}
228
async with session.get(url, params=params) as response:
229
content = await response.json()
230
return content[0]['title']
231
232
233
async def get_shortlog(version: str) -> str:
234
"""Call git shortlog."""
235
p = await asyncio.create_subprocess_exec('git', 'shortlog', f'mesa-{version}..',
236
stdout=asyncio.subprocess.PIPE)
237
out, _ = await p.communicate()
238
assert p.returncode == 0, 'error getting shortlog'
239
assert out is not None, 'just for mypy'
240
return out.decode()
241
242
243
def walk_shortlog(log: str) -> typing.Generator[typing.Tuple[str, bool], None, None]:
244
for l in log.split('\n'):
245
if l.startswith(' '): # this means we have a patch description
246
yield l.lstrip(), False
247
elif l.strip():
248
yield l, True
249
250
251
def calculate_next_version(version: str, is_point: bool) -> str:
252
"""Calculate the version about to be released."""
253
if '-' in version:
254
version = version.split('-')[0]
255
if is_point:
256
base = version.split('.')
257
base[2] = str(int(base[2]) + 1)
258
return '.'.join(base)
259
return version
260
261
262
def calculate_previous_version(version: str, is_point: bool) -> str:
263
"""Calculate the previous version to compare to.
264
265
In the case of -rc to final that verison is the previous .0 release,
266
(19.3.0 in the case of 20.0.0, for example). for point releases that is
267
the last point release. This value will be the same as the input value
268
for a point release, but different for a major release.
269
"""
270
if '-' in version:
271
version = version.split('-')[0]
272
if is_point:
273
return version
274
base = version.split('.')
275
if base[1] == '0':
276
base[0] = str(int(base[0]) - 1)
277
base[1] = '3'
278
else:
279
base[1] = str(int(base[1]) - 1)
280
return '.'.join(base)
281
282
283
def get_features(is_point_release: bool) -> typing.Generator[str, None, None]:
284
p = pathlib.Path(__file__).parent.parent / 'docs' / 'relnotes' / 'new_features.txt'
285
if p.exists():
286
if is_point_release:
287
print("WARNING: new features being introduced in a point release", file=sys.stderr)
288
with p.open('rt') as f:
289
for line in f:
290
yield line
291
else:
292
yield "None"
293
p.unlink()
294
else:
295
yield "None"
296
297
298
async def main() -> None:
299
v = pathlib.Path(__file__).parent.parent / 'VERSION'
300
with v.open('rt') as f:
301
raw_version = f.read().strip()
302
is_point_release = '-rc' not in raw_version
303
assert '-devel' not in raw_version, 'Do not run this script on -devel'
304
version = raw_version.split('-')[0]
305
previous_version = calculate_previous_version(version, is_point_release)
306
this_version = calculate_next_version(version, is_point_release)
307
today = datetime.date.today()
308
header = f'Mesa {this_version} Release Notes / {today}'
309
header_underline = '=' * len(header)
310
311
shortlog, bugs = await asyncio.gather(
312
get_shortlog(previous_version),
313
gather_bugs(previous_version),
314
)
315
316
final = pathlib.Path(__file__).parent.parent / 'docs' / 'relnotes' / f'{this_version}.rst'
317
with final.open('wt') as f:
318
try:
319
f.write(TEMPLATE.render(
320
bugfix=is_point_release,
321
bugs=bugs,
322
changes=walk_shortlog(shortlog),
323
features=get_features(is_point_release),
324
gl_version=CURRENT_GL_VERSION,
325
this_version=this_version,
326
header=header,
327
header_underline=header_underline,
328
previous_version=previous_version,
329
vk_version=CURRENT_VK_VERSION,
330
rst_escape=inliner.quoteInline,
331
))
332
except:
333
print(exceptions.text_error_template().render())
334
335
subprocess.run(['git', 'add', final])
336
subprocess.run(['git', 'commit', '-m',
337
f'docs: add release notes for {this_version}'])
338
339
340
if __name__ == "__main__":
341
loop = asyncio.get_event_loop()
342
loop.run_until_complete(main())
343
344