Blame bin/gen_release_notes.py

Packit Service 5cb53b
#! /usr/libexec/platform-python
Packit 8f2243
# Copyright © 2019-2020 Intel Corporation
Packit 8f2243
Packit 8f2243
# Permission is hereby granted, free of charge, to any person obtaining a copy
Packit 8f2243
# of this software and associated documentation files (the "Software"), to deal
Packit 8f2243
# in the Software without restriction, including without limitation the rights
Packit 8f2243
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
Packit 8f2243
# copies of the Software, and to permit persons to whom the Software is
Packit 8f2243
# furnished to do so, subject to the following conditions:
Packit 8f2243
Packit 8f2243
# The above copyright notice and this permission notice shall be included in
Packit 8f2243
# all copies or substantial portions of the Software.
Packit 8f2243
Packit 8f2243
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
Packit 8f2243
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
Packit 8f2243
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Packit 8f2243
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
Packit 8f2243
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
Packit 8f2243
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
Packit 8f2243
# SOFTWARE.
Packit 8f2243
Packit 8f2243
"""Generates release notes for a given version of mesa."""
Packit 8f2243
Packit 8f2243
import asyncio
Packit 8f2243
import datetime
Packit 8f2243
import os
Packit 8f2243
import pathlib
Packit Service 0f8cfe
import re
Packit Service 0f8cfe
import subprocess
Packit 8f2243
import sys
Packit 8f2243
import textwrap
Packit 8f2243
import typing
Packit 8f2243
import urllib.parse
Packit 8f2243
Packit 8f2243
import aiohttp
Packit 8f2243
from mako.template import Template
Packit 8f2243
from mako import exceptions
Packit 8f2243
Packit 8f2243
Packit 8f2243
CURRENT_GL_VERSION = '4.6'
Packit 8f2243
CURRENT_VK_VERSION = '1.2'
Packit 8f2243
Packit 8f2243
TEMPLATE = Template(textwrap.dedent("""\
Packit Service 0f8cfe
    ${header}
Packit Service 0f8cfe
    ${header_underline}
Packit Service 0f8cfe
Packit 8f2243
    %if not bugfix:
Packit Service 0f8cfe
    Mesa ${this_version} is a new development release. People who are concerned
Packit Service 0f8cfe
    with stability and reliability should stick with a previous release or
Packit Service 0f8cfe
    wait for Mesa ${this_version[:-1]}1.
Packit 8f2243
    %else:
Packit Service 0f8cfe
    Mesa ${this_version} is a bug fix release which fixes bugs found since the ${previous_version} release.
Packit 8f2243
    %endif
Packit Service 0f8cfe
Packit 8f2243
    Mesa ${this_version} implements the OpenGL ${gl_version} API, but the version reported by
Packit 8f2243
    glGetString(GL_VERSION) or glGetIntegerv(GL_MAJOR_VERSION) /
Packit 8f2243
    glGetIntegerv(GL_MINOR_VERSION) depends on the particular driver being used.
Packit 8f2243
    Some drivers don't support all the features required in OpenGL ${gl_version}. OpenGL
Packit Service 0f8cfe
    ${gl_version} is **only** available if requested at context creation.
Packit 8f2243
    Compatibility contexts may report a lower version depending on each driver.
Packit Service 0f8cfe
Packit 8f2243
    Mesa ${this_version} implements the Vulkan ${vk_version} API, but the version reported by
Packit 8f2243
    the apiVersion property of the VkPhysicalDeviceProperties struct
Packit 8f2243
    depends on the particular driver being used.
Packit 8f2243
Packit Service 0f8cfe
    SHA256 checksum
Packit Service 0f8cfe
    ---------------
Packit Service 0f8cfe
Packit Service 0f8cfe
    ::
Packit 8f2243
Packit Service 0f8cfe
        TBD.
Packit 8f2243
Packit 8f2243
Packit Service 0f8cfe
    New features
Packit Service 0f8cfe
    ------------
Packit Service 0f8cfe
Packit 8f2243
    %for f in features:
Packit Service 0f8cfe
    - ${rst_escape(f)}
Packit 8f2243
    %endfor
Packit 8f2243
Packit 8f2243
Packit Service 0f8cfe
    Bug fixes
Packit Service 0f8cfe
    ---------
Packit Service 0f8cfe
Packit 8f2243
    %for b in bugs:
Packit Service 0f8cfe
    - ${rst_escape(b)}
Packit 8f2243
    %endfor
Packit 8f2243
Packit 8f2243
Packit Service 0f8cfe
    Changes
Packit Service 0f8cfe
    -------
Packit Service 0f8cfe
    %for c, author_line in changes:
Packit Service 0f8cfe
      %if author_line:
Packit Service 0f8cfe
Packit Service 0f8cfe
    ${rst_escape(c)}
Packit Service 0f8cfe
Packit 8f2243
      %else:
Packit Service 0f8cfe
    - ${rst_escape(c)}
Packit 8f2243
      %endif
Packit 8f2243
    %endfor
Packit 8f2243
    """))
Packit 8f2243
Packit 8f2243
Packit Service 0f8cfe
def rst_escape(unsafe_str: str) -> str:
Packit Service 0f8cfe
    "Escape rST special chars when they follow or preceed a whitespace"
Packit Service 0f8cfe
    special = re.escape(r'`<>*_#[]|')
Packit Service 0f8cfe
    unsafe_str = re.sub(r'(^|\s)([' + special + r'])',
Packit Service 0f8cfe
                        r'\1\\\2',
Packit Service 0f8cfe
                        unsafe_str)
Packit Service 0f8cfe
    unsafe_str = re.sub(r'([' + special + r'])(\s|$)',
Packit Service 0f8cfe
                        r'\\\1\2',
Packit Service 0f8cfe
                        unsafe_str)
Packit Service 0f8cfe
    return unsafe_str
Packit Service 0f8cfe
Packit Service 0f8cfe
Packit 8f2243
async def gather_commits(version: str) -> str:
Packit 8f2243
    p = await asyncio.create_subprocess_exec(
Packit 8f2243
        'git', 'log', '--oneline', f'mesa-{version}..', '--grep', r'Closes: \(https\|#\).*',
Packit 8f2243
        stdout=asyncio.subprocess.PIPE)
Packit 8f2243
    out, _ = await p.communicate()
Packit 8f2243
    assert p.returncode == 0, f"git log didn't work: {version}"
Packit 8f2243
    return out.decode().strip()
Packit 8f2243
Packit 8f2243
Packit 8f2243
async def gather_bugs(version: str) -> typing.List[str]:
Packit 8f2243
    commits = await gather_commits(version)
Packit 8f2243
Packit 8f2243
    issues: typing.List[str] = []
Packit 8f2243
    for commit in commits.split('\n'):
Packit 8f2243
        sha, message = commit.split(maxsplit=1)
Packit 8f2243
        p = await asyncio.create_subprocess_exec(
Packit 8f2243
            'git', 'log', '--max-count', '1', r'--format=%b', sha,
Packit 8f2243
            stdout=asyncio.subprocess.PIPE)
Packit 8f2243
        _out, _ = await p.communicate()
Packit 8f2243
        out = _out.decode().split('\n')
Packit 8f2243
        for line in reversed(out):
Packit 8f2243
            if line.startswith('Closes:'):
Packit 8f2243
                bug = line.lstrip('Closes:').strip()
Packit 8f2243
                break
Packit 8f2243
        else:
Packit 8f2243
            raise Exception('No closes found?')
Packit 8f2243
        if bug.startswith('h'):
Packit 8f2243
            # This means we have a bug in the form "Closes: https://..."
Packit 8f2243
            issues.append(os.path.basename(urllib.parse.urlparse(bug).path))
Packit 8f2243
        else:
Packit 8f2243
            issues.append(bug.lstrip('#'))
Packit 8f2243
Packit 8f2243
    loop = asyncio.get_event_loop()
Packit 8f2243
    async with aiohttp.ClientSession(loop=loop) as session:
Packit 8f2243
        results = await asyncio.gather(*[get_bug(session, i) for i in issues])
Packit 8f2243
    typing.cast(typing.Tuple[str, ...], results)
Packit Service 0f8cfe
    bugs = list(results)
Packit Service 0f8cfe
    if not bugs:
Packit Service 0f8cfe
        bugs = ['None']
Packit Service 0f8cfe
    return bugs
Packit 8f2243
Packit 8f2243
Packit 8f2243
async def get_bug(session: aiohttp.ClientSession, bug_id: str) -> str:
Packit 8f2243
    """Query gitlab to get the name of the issue that was closed."""
Packit 8f2243
    # Mesa's gitlab id is 176,
Packit 8f2243
    url = 'https://gitlab.freedesktop.org/api/v4/projects/176/issues'
Packit 8f2243
    params = {'iids[]': bug_id}
Packit 8f2243
    async with session.get(url, params=params) as response:
Packit 8f2243
        content = await response.json()
Packit 8f2243
    return content[0]['title']
Packit 8f2243
Packit 8f2243
Packit 8f2243
async def get_shortlog(version: str) -> str:
Packit 8f2243
    """Call git shortlog."""
Packit 8f2243
    p = await asyncio.create_subprocess_exec('git', 'shortlog', f'mesa-{version}..',
Packit 8f2243
                                             stdout=asyncio.subprocess.PIPE)
Packit 8f2243
    out, _ = await p.communicate()
Packit 8f2243
    assert p.returncode == 0, 'error getting shortlog'
Packit 8f2243
    assert out is not None, 'just for mypy'
Packit 8f2243
    return out.decode()
Packit 8f2243
Packit 8f2243
Packit 8f2243
def walk_shortlog(log: str) -> typing.Generator[typing.Tuple[str, bool], None, None]:
Packit 8f2243
    for l in log.split('\n'):
Packit 8f2243
        if l.startswith(' '): # this means we have a patch description
Packit Service 0f8cfe
            yield l.lstrip(), False
Packit Service 0f8cfe
        elif l.strip():
Packit 8f2243
            yield l, True
Packit 8f2243
Packit 8f2243
Packit 8f2243
def calculate_next_version(version: str, is_point: bool) -> str:
Packit 8f2243
    """Calculate the version about to be released."""
Packit 8f2243
    if '-' in version:
Packit 8f2243
        version = version.split('-')[0]
Packit 8f2243
    if is_point:
Packit 8f2243
        base = version.split('.')
Packit 8f2243
        base[2] = str(int(base[2]) + 1)
Packit 8f2243
        return '.'.join(base)
Packit 8f2243
    return version
Packit 8f2243
Packit 8f2243
Packit 8f2243
def calculate_previous_version(version: str, is_point: bool) -> str:
Packit 8f2243
    """Calculate the previous version to compare to.
Packit 8f2243
Packit 8f2243
    In the case of -rc to final that verison is the previous .0 release,
Packit 8f2243
    (19.3.0 in the case of 20.0.0, for example). for point releases that is
Packit 8f2243
    the last point release. This value will be the same as the input value
Packit 8f2243
    for a point release, but different for a major release.
Packit 8f2243
    """
Packit 8f2243
    if '-' in version:
Packit 8f2243
        version = version.split('-')[0]
Packit 8f2243
    if is_point:
Packit 8f2243
        return version
Packit 8f2243
    base = version.split('.')
Packit 8f2243
    if base[1] == '0':
Packit 8f2243
        base[0] = str(int(base[0]) - 1)
Packit 8f2243
        base[1] = '3'
Packit 8f2243
    else:
Packit 8f2243
        base[1] = str(int(base[1]) - 1)
Packit 8f2243
    return '.'.join(base)
Packit 8f2243
Packit 8f2243
Packit 8f2243
def get_features(is_point_release: bool) -> typing.Generator[str, None, None]:
Packit 8f2243
    p = pathlib.Path(__file__).parent.parent / 'docs' / 'relnotes' / 'new_features.txt'
Packit 8f2243
    if p.exists():
Packit 8f2243
        if is_point_release:
Packit 8f2243
            print("WARNING: new features being introduced in a point release", file=sys.stderr)
Packit 8f2243
        with p.open('rt') as f:
Packit 8f2243
            for line in f:
Packit 8f2243
                yield line
Packit Service 0f8cfe
            else:
Packit Service 0f8cfe
                yield "None"
Packit Service 0f8cfe
        p.unlink()
Packit 8f2243
    else:
Packit 8f2243
        yield "None"
Packit 8f2243
Packit 8f2243
Packit 8f2243
async def main() -> None:
Packit 8f2243
    v = pathlib.Path(__file__).parent.parent / 'VERSION'
Packit 8f2243
    with v.open('rt') as f:
Packit 8f2243
        raw_version = f.read().strip()
Packit 8f2243
    is_point_release = '-rc' not in raw_version
Packit 8f2243
    assert '-devel' not in raw_version, 'Do not run this script on -devel'
Packit 8f2243
    version = raw_version.split('-')[0]
Packit 8f2243
    previous_version = calculate_previous_version(version, is_point_release)
Packit 8f2243
    this_version = calculate_next_version(version, is_point_release)
Packit Service 0f8cfe
    today = datetime.date.today()
Packit Service 0f8cfe
    header = f'Mesa {this_version} Release Notes / {today}'
Packit Service 0f8cfe
    header_underline = '=' * len(header)
Packit 8f2243
Packit 8f2243
    shortlog, bugs = await asyncio.gather(
Packit 8f2243
        get_shortlog(previous_version),
Packit 8f2243
        gather_bugs(previous_version),
Packit 8f2243
    )
Packit 8f2243
Packit Service 0f8cfe
    final = pathlib.Path(__file__).parent.parent / 'docs' / 'relnotes' / f'{this_version}.rst'
Packit 8f2243
    with final.open('wt') as f:
Packit 8f2243
        try:
Packit 8f2243
            f.write(TEMPLATE.render(
Packit 8f2243
                bugfix=is_point_release,
Packit 8f2243
                bugs=bugs,
Packit 8f2243
                changes=walk_shortlog(shortlog),
Packit 8f2243
                features=get_features(is_point_release),
Packit 8f2243
                gl_version=CURRENT_GL_VERSION,
Packit 8f2243
                this_version=this_version,
Packit Service 0f8cfe
                header=header,
Packit Service 0f8cfe
                header_underline=header_underline,
Packit 8f2243
                previous_version=previous_version,
Packit 8f2243
                vk_version=CURRENT_VK_VERSION,
Packit Service 0f8cfe
                rst_escape=rst_escape,
Packit 8f2243
            ))
Packit 8f2243
        except:
Packit 8f2243
            print(exceptions.text_error_template().render())
Packit 8f2243
Packit Service 0f8cfe
    subprocess.run(['git', 'add', final])
Packit Service 0f8cfe
    subprocess.run(['git', 'commit', '-m',
Packit Service 0f8cfe
                    f'docs: add release notes for {this_version}'])
Packit Service 0f8cfe
Packit 8f2243
Packit 8f2243
if __name__ == "__main__":
Packit 8f2243
    loop = asyncio.get_event_loop()
Packit 8f2243
    loop.run_until_complete(main())