comparison contrib/mklog.py @ 19:2b5abeee2509 default tip

update gcc11
author anatofuz
date Mon, 25 May 2020 07:50:57 +0900
parents
children
comparison
equal deleted inserted replaced
18:1830386684a0 19:2b5abeee2509
1 #!/usr/bin/env python3
2
3 # Copyright (C) 2020 Free Software Foundation, Inc.
4 #
5 # This file is part of GCC.
6 #
7 # GCC is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3, or (at your option)
10 # any later version.
11 #
12 # GCC is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with GCC; see the file COPYING. If not, write to
19 # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
20 # Boston, MA 02110-1301, USA.
21
22 # This script parses a .diff file generated with 'diff -up' or 'diff -cp'
23 # and adds a skeleton ChangeLog file to the file. It does not try to be
24 # too smart when parsing function names, but it produces a reasonable
25 # approximation.
26 #
27 # Author: Martin Liska <mliska@suse.cz>
28
29 import argparse
30 import os
31 import re
32 import sys
33
34 import requests
35
36 from unidiff import PatchSet
37
38 pr_regex = re.compile(r'(\/(\/|\*)|[Cc*!])\s+(?P<pr>PR [a-z+-]+\/[0-9]+)')
39 dr_regex = re.compile(r'(\/(\/|\*)|[Cc*!])\s+(?P<dr>DR [0-9]+)')
40 identifier_regex = re.compile(r'^([a-zA-Z0-9_#].*)')
41 comment_regex = re.compile(r'^\/\*')
42 struct_regex = re.compile(r'^(class|struct|union|enum)\s+'
43 r'(GTY\(.*\)\s+)?([a-zA-Z0-9_]+)')
44 macro_regex = re.compile(r'#\s*(define|undef)\s+([a-zA-Z0-9_]+)')
45 super_macro_regex = re.compile(r'^DEF[A-Z0-9_]+\s*\(([a-zA-Z0-9_]+)')
46 fn_regex = re.compile(r'([a-zA-Z_][^()\s]*)\s*\([^*]')
47 template_and_param_regex = re.compile(r'<[^<>]*>')
48 bugzilla_url = 'https://gcc.gnu.org/bugzilla/rest.cgi/bug?id=%s&' \
49 'include_fields=summary'
50
51 function_extensions = set(['.c', '.cpp', '.C', '.cc', '.h', '.inc', '.def'])
52
53 help_message = """\
54 Generate ChangeLog template for PATCH.
55 PATCH must be generated using diff(1)'s -up or -cp options
56 (or their equivalent in git).
57 """
58
59 script_folder = os.path.realpath(__file__)
60 gcc_root = os.path.dirname(os.path.dirname(script_folder))
61
62
63 def find_changelog(path):
64 folder = os.path.split(path)[0]
65 while True:
66 if os.path.exists(os.path.join(gcc_root, folder, 'ChangeLog')):
67 return folder
68 folder = os.path.dirname(folder)
69 if folder == '':
70 return folder
71 raise AssertionError()
72
73
74 def extract_function_name(line):
75 if comment_regex.match(line):
76 return None
77 m = struct_regex.search(line)
78 if m:
79 # Struct declaration
80 return m.group(1) + ' ' + m.group(3)
81 m = macro_regex.search(line)
82 if m:
83 # Macro definition
84 return m.group(2)
85 m = super_macro_regex.search(line)
86 if m:
87 # Supermacro
88 return m.group(1)
89 m = fn_regex.search(line)
90 if m:
91 # Discard template and function parameters.
92 fn = m.group(1)
93 fn = re.sub(template_and_param_regex, '', fn)
94 return fn.rstrip()
95 return None
96
97
98 def try_add_function(functions, line):
99 fn = extract_function_name(line)
100 if fn and fn not in functions:
101 functions.append(fn)
102 return bool(fn)
103
104
105 def sort_changelog_files(changed_file):
106 return (changed_file.is_added_file, changed_file.is_removed_file)
107
108
109 def get_pr_titles(prs):
110 output = ''
111 for pr in prs:
112 id = pr.split('/')[-1]
113 r = requests.get(bugzilla_url % id)
114 bugs = r.json()['bugs']
115 if len(bugs) == 1:
116 output += '%s - %s\n' % (pr, bugs[0]['summary'])
117 print(output)
118 if output:
119 output += '\n'
120 return output
121
122
123 def generate_changelog(data, no_functions=False, fill_pr_titles=False):
124 changelogs = {}
125 changelog_list = []
126 prs = []
127 out = ''
128 diff = PatchSet(data)
129
130 for file in diff:
131 changelog = find_changelog(file.path)
132 if changelog not in changelogs:
133 changelogs[changelog] = []
134 changelog_list.append(changelog)
135 changelogs[changelog].append(file)
136
137 # Extract PR entries from newly added tests
138 if 'testsuite' in file.path and file.is_added_file:
139 for line in list(file)[0]:
140 m = pr_regex.search(line.value)
141 if m:
142 pr = m.group('pr')
143 if pr not in prs:
144 prs.append(pr)
145 else:
146 m = dr_regex.search(line.value)
147 if m:
148 dr = m.group('dr')
149 if dr not in prs:
150 prs.append(dr)
151 else:
152 break
153
154 if fill_pr_titles:
155 out += get_pr_titles(prs)
156
157 # sort ChangeLog so that 'testsuite' is at the end
158 for changelog in sorted(changelog_list, key=lambda x: 'testsuite' in x):
159 files = changelogs[changelog]
160 out += '%s:\n' % os.path.join(changelog, 'ChangeLog')
161 out += '\n'
162 for pr in prs:
163 out += '\t%s\n' % pr
164 # new and deleted files should be at the end
165 for file in sorted(files, key=sort_changelog_files):
166 assert file.path.startswith(changelog)
167 in_tests = 'testsuite' in changelog or 'testsuite' in file.path
168 relative_path = file.path[len(changelog):].lstrip('/')
169 functions = []
170 if file.is_added_file:
171 msg = 'New test' if in_tests else 'New file'
172 out += '\t* %s: %s.\n' % (relative_path, msg)
173 elif file.is_removed_file:
174 out += '\t* %s: Removed.\n' % (relative_path)
175 else:
176 if not no_functions:
177 for hunk in file:
178 # Do not add function names for testsuite files
179 extension = os.path.splitext(relative_path)[1]
180 if not in_tests and extension in function_extensions:
181 last_fn = None
182 modified_visited = False
183 success = False
184 for line in hunk:
185 m = identifier_regex.match(line.value)
186 if line.is_added or line.is_removed:
187 if not line.value.strip():
188 continue
189 modified_visited = True
190 if m and try_add_function(functions,
191 m.group(1)):
192 last_fn = None
193 success = True
194 elif line.is_context:
195 if last_fn and modified_visited:
196 try_add_function(functions, last_fn)
197 last_fn = None
198 modified_visited = False
199 success = True
200 elif m:
201 last_fn = m.group(1)
202 modified_visited = False
203 if not success:
204 try_add_function(functions,
205 hunk.section_header)
206 if functions:
207 out += '\t* %s (%s):\n' % (relative_path, functions[0])
208 for fn in functions[1:]:
209 out += '\t(%s):\n' % fn
210 else:
211 out += '\t* %s:\n' % relative_path
212 out += '\n'
213 return out
214
215
216 if __name__ == '__main__':
217 parser = argparse.ArgumentParser(description=help_message)
218 parser.add_argument('input', nargs='?',
219 help='Patch file (or missing, read standard input)')
220 parser.add_argument('-s', '--no-functions', action='store_true',
221 help='Do not generate function names in ChangeLogs')
222 parser.add_argument('-p', '--fill-up-bug-titles', action='store_true',
223 help='Download title of mentioned PRs')
224 args = parser.parse_args()
225 if args.input == '-':
226 args.input = None
227
228 input = open(args.input) if args.input else sys.stdin
229 data = input.read()
230 output = generate_changelog(data, args.no_functions,
231 args.fill_up_bug_titles)
232 print(output, end='')