view contrib/ @ 19:2b5abeee2509 default tip

update gcc11
author anatofuz
date Mon, 25 May 2020 07:50:57 +0900
line wrap: on
line source

#!/usr/bin/env python3

# Copyright (C) 2020 Free Software Foundation, Inc.
# This file is part of GCC.
# GCC is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
# GCC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with GCC; see the file COPYING.  If not, write to
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.

# This script parses a .diff file generated with 'diff -up' or 'diff -cp'
# and adds a skeleton ChangeLog file to the file. It does not try to be
# too smart when parsing function names, but it produces a reasonable
# approximation.
# Author: Martin Liska <>

import argparse
import os
import re
import sys

import requests

from unidiff import PatchSet

pr_regex = re.compile(r'(\/(\/|\*)|[Cc*!])\s+(?P<pr>PR [a-z+-]+\/[0-9]+)')
dr_regex = re.compile(r'(\/(\/|\*)|[Cc*!])\s+(?P<dr>DR [0-9]+)')
identifier_regex = re.compile(r'^([a-zA-Z0-9_#].*)')
comment_regex = re.compile(r'^\/\*')
struct_regex = re.compile(r'^(class|struct|union|enum)\s+'
macro_regex = re.compile(r'#\s*(define|undef)\s+([a-zA-Z0-9_]+)')
super_macro_regex = re.compile(r'^DEF[A-Z0-9_]+\s*\(([a-zA-Z0-9_]+)')
fn_regex = re.compile(r'([a-zA-Z_][^()\s]*)\s*\([^*]')
template_and_param_regex = re.compile(r'<[^<>]*>')
bugzilla_url = '' \

function_extensions = set(['.c', '.cpp', '.C', '.cc', '.h', '.inc', '.def'])

help_message = """\
Generate ChangeLog template for PATCH.
PATCH must be generated using diff(1)'s -up or -cp options
(or their equivalent in git).

script_folder = os.path.realpath(__file__)
gcc_root = os.path.dirname(os.path.dirname(script_folder))

def find_changelog(path):
    folder = os.path.split(path)[0]
    while True:
        if os.path.exists(os.path.join(gcc_root, folder, 'ChangeLog')):
            return folder
        folder = os.path.dirname(folder)
        if folder == '':
            return folder
    raise AssertionError()

def extract_function_name(line):
    if comment_regex.match(line):
        return None
    m =
    if m:
        # Struct declaration
        return + ' ' +
    m =
    if m:
        # Macro definition
    m =
    if m:
        # Supermacro
    m =
    if m:
        # Discard template and function parameters.
        fn =
        fn = re.sub(template_and_param_regex, '', fn)
        return fn.rstrip()
    return None

def try_add_function(functions, line):
    fn = extract_function_name(line)
    if fn and fn not in functions:
    return bool(fn)

def sort_changelog_files(changed_file):
    return (changed_file.is_added_file, changed_file.is_removed_file)

def get_pr_titles(prs):
    output = ''
    for pr in prs:
        id = pr.split('/')[-1]
        r = requests.get(bugzilla_url % id)
        bugs = r.json()['bugs']
        if len(bugs) == 1:
            output += '%s - %s\n' % (pr, bugs[0]['summary'])
    if output:
        output += '\n'
    return output

def generate_changelog(data, no_functions=False, fill_pr_titles=False):
    changelogs = {}
    changelog_list = []
    prs = []
    out = ''
    diff = PatchSet(data)

    for file in diff:
        changelog = find_changelog(file.path)
        if changelog not in changelogs:
            changelogs[changelog] = []

        # Extract PR entries from newly added tests
        if 'testsuite' in file.path and file.is_added_file:
            for line in list(file)[0]:
                m =
                if m:
                    pr ='pr')
                    if pr not in prs:
                    m =
                    if m:
                        dr ='dr')
                        if dr not in prs:

    if fill_pr_titles:
        out += get_pr_titles(prs)

    # sort ChangeLog so that 'testsuite' is at the end
    for changelog in sorted(changelog_list, key=lambda x: 'testsuite' in x):
        files = changelogs[changelog]
        out += '%s:\n' % os.path.join(changelog, 'ChangeLog')
        out += '\n'
        for pr in prs:
            out += '\t%s\n' % pr
        # new and deleted files should be at the end
        for file in sorted(files, key=sort_changelog_files):
            assert file.path.startswith(changelog)
            in_tests = 'testsuite' in changelog or 'testsuite' in file.path
            relative_path = file.path[len(changelog):].lstrip('/')
            functions = []
            if file.is_added_file:
                msg = 'New test' if in_tests else 'New file'
                out += '\t* %s: %s.\n' % (relative_path, msg)
            elif file.is_removed_file:
                out += '\t* %s: Removed.\n' % (relative_path)
                if not no_functions:
                    for hunk in file:
                        # Do not add function names for testsuite files
                        extension = os.path.splitext(relative_path)[1]
                        if not in_tests and extension in function_extensions:
                            last_fn = None
                            modified_visited = False
                            success = False
                            for line in hunk:
                                m = identifier_regex.match(line.value)
                                if line.is_added or line.is_removed:
                                    if not line.value.strip():
                                    modified_visited = True
                                    if m and try_add_function(functions,
                                        last_fn = None
                                        success = True
                                elif line.is_context:
                                    if last_fn and modified_visited:
                                        try_add_function(functions, last_fn)
                                        last_fn = None
                                        modified_visited = False
                                        success = True
                                    elif m:
                                        last_fn =
                                        modified_visited = False
                            if not success:
                if functions:
                    out += '\t* %s (%s):\n' % (relative_path, functions[0])
                    for fn in functions[1:]:
                        out += '\t(%s):\n' % fn
                    out += '\t* %s:\n' % relative_path
        out += '\n'
    return out

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=help_message)
    parser.add_argument('input', nargs='?',
                        help='Patch file (or missing, read standard input)')
    parser.add_argument('-s', '--no-functions', action='store_true',
                        help='Do not generate function names in ChangeLogs')
    parser.add_argument('-p', '--fill-up-bug-titles', action='store_true',
                        help='Download title of mentioned PRs')
    args = parser.parse_args()
    if args.input == '-':
        args.input = None

    input = open(args.input) if args.input else sys.stdin
    data =
    output = generate_changelog(data, args.no_functions,
    print(output, end='')