diff options
author | Martin Liska <mliska@suse.cz> | 2020-05-19 11:02:03 +0200 |
---|---|---|
committer | Martin Liska <mliska@suse.cz> | 2020-05-19 11:39:58 +0200 |
commit | e576ed7a9dd71049e0af015137f75a9aa34a4844 (patch) | |
tree | 2d02195912b65da03f8d0b2649f26a542b9763dc /contrib/legacy/mklog | |
parent | 8389e89bd9990f392383908a232488f4986af7f7 (diff) | |
download | gcc-e576ed7a9dd71049e0af015137f75a9aa34a4844.zip gcc-e576ed7a9dd71049e0af015137f75a9aa34a4844.tar.gz gcc-e576ed7a9dd71049e0af015137f75a9aa34a4844.tar.bz2 |
Move 2 mklog scripts to legacy subfolder.
contrib/ChangeLog:
* legacy/mklog: Moved from mklog.
* legacy/mklog.pl: Moved from mklog.pl.
Diffstat (limited to 'contrib/legacy/mklog')
-rwxr-xr-x | contrib/legacy/mklog | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/contrib/legacy/mklog b/contrib/legacy/mklog new file mode 100755 index 0000000..71fd427 --- /dev/null +++ b/contrib/legacy/mklog @@ -0,0 +1,466 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2017-2019 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 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# 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. +# +# This is a straightforward adaptation of original Perl script. +# +# Author: Yury Gribov <tetra2005@gmail.com> + +import argparse +import sys +import re +import os.path +import os +import tempfile +import time +import shutil +from subprocess import Popen, PIPE + +me = os.path.basename(sys.argv[0]) + +pr_regex = re.compile('\+(\/(\/|\*)|[Cc*!])\s+(PR [a-z+-]+\/[0-9]+)') + +def error(msg): + sys.stderr.write("%s: error: %s\n" % (me, msg)) + sys.exit(1) + +def warn(msg): + sys.stderr.write("%s: warning: %s\n" % (me, msg)) + +class RegexCache(object): + """Simple trick to Perl-like combined match-and-bind.""" + + def __init__(self): + self.last_match = None + + def match(self, p, s): + self.last_match = re.match(p, s) if isinstance(p, str) else p.match(s) + return self.last_match + + def search(self, p, s): + self.last_match = re.search(p, s) if isinstance(p, str) else p.search(s) + return self.last_match + + def group(self, n): + return self.last_match.group(n) + +cache = RegexCache() + +def run(cmd, die_on_error): + """Simple wrapper for Popen.""" + proc = Popen(cmd.split(' '), stderr = PIPE, stdout = PIPE) + (out, err) = proc.communicate() + if die_on_error and proc.returncode != 0: + error("`%s` failed:\n" % (cmd, proc.stderr)) + return proc.returncode, out.decode(), err + +def read_user_info(): + dot_mklog_format_msg = """\ +The .mklog format is: +NAME = ... +EMAIL = ... +""" + + # First try to read .mklog config + mklog_conf = os.path.expanduser('~/.mklog') + if os.path.exists(mklog_conf): + attrs = {} + f = open(mklog_conf) + for s in f: + if cache.match(r'^\s*([a-zA-Z0-9_]+)\s*=\s*(.*?)\s*$', s): + attrs[cache.group(1)] = cache.group(2) + f.close() + if 'NAME' not in attrs: + error("'NAME' not present in .mklog") + if 'EMAIL' not in attrs: + error("'EMAIL' not present in .mklog") + return attrs['NAME'], attrs['EMAIL'] + + # Otherwise go with git + + rc1, name, _ = run('git config user.name', False) + name = name.rstrip() + rc2, email, _ = run('git config user.email', False) + email = email.rstrip() + + if rc1 != 0 or rc2 != 0: + error("""\ +Could not read git user.name and user.email settings. +Please add missing git settings, or create a %s. +""" % mklog_conf) + + return name, email + +def get_parent_changelog (s): + """See which ChangeLog this file change should go to.""" + + if s.find('\\') == -1 and s.find('/') == -1: + return "ChangeLog", s + + gcc_root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + + d = s + while d: + clname = d + "/ChangeLog" + if os.path.exists(gcc_root + '/' + clname) or os.path.exists(clname): + relname = s[len(d)+1:] + return clname, relname + d, _ = os.path.split(d) + + return "Unknown ChangeLog", s + +class FileDiff: + """Class to represent changes in a single file.""" + + def __init__(self, filename): + self.filename = filename + self.hunks = [] + self.clname, self.relname = get_parent_changelog(filename); + + def dump(self): + print("Diff for %s:\n ChangeLog = %s\n rel name = %s\n" % (self.filename, self.clname, self.relname)) + for i, h in enumerate(self.hunks): + print("Next hunk %d:" % i) + h.dump() + +class Hunk: + """Class to represent a single hunk of changes.""" + + def __init__(self, hdr): + self.hdr = hdr + self.lines = [] + self.ctx_diff = is_ctx_hunk_start(hdr) + + def dump(self): + print('%s' % self.hdr) + print('%s' % '\n'.join(self.lines)) + + def is_file_addition(self): + """Does hunk describe addition of file?""" + if self.ctx_diff: + for line in self.lines: + if re.match(r'^\*\*\* 0 \*\*\*\*', line): + return True + else: + return re.match(r'^@@ -0,0 \+1.* @@', self.hdr) + + def is_file_removal(self): + """Does hunk describe removal of file?""" + if self.ctx_diff: + for line in self.lines: + if re.match(r'^--- 0 ----', line): + return True + else: + return re.match(r'^@@ -1.* \+0,0 @@', self.hdr) + +def is_file_diff_start(s): + # Don't be fooled by context diff line markers: + # *** 385,391 **** + return ((s.startswith('*** ') and not s.endswith('***')) + or (s.startswith('--- ') and not s.endswith('---'))) + +def is_ctx_hunk_start(s): + return re.match(r'^\*\*\*\*\*\**', s) + +def is_uni_hunk_start(s): + return re.match(r'^@@ .* @@', s) + +def is_hunk_start(s): + return is_ctx_hunk_start(s) or is_uni_hunk_start(s) + +def remove_suffixes(s): + if s.startswith('a/') or s.startswith('b/'): + s = s[2:] + if s.endswith('.jj'): + s = s[:-3] + return s + +def find_changed_funs(hunk): + """Find all functions touched by hunk. We don't try too hard + to find good matches. This should return a superset + of the actual set of functions in the .diff file. + """ + + fns = [] + fn = None + + if (cache.match(r'^\*\*\*\*\*\** ([a-zA-Z0-9_].*)', hunk.hdr) + or cache.match(r'^@@ .* @@ ([a-zA-Z0-9_].*)', hunk.hdr)): + fn = cache.group(1) + + for i, line in enumerate(hunk.lines): + # Context diffs have extra whitespace after first char; + # remove it to make matching easier. + if hunk.ctx_diff: + line = re.sub(r'^([-+! ]) ', r'\1', line) + + # Remember most recent identifier in hunk + # that might be a function name. + if cache.match(r'^[-+! ]([a-zA-Z0-9_#].*)', line): + fn = cache.group(1) + + change = line and re.match(r'^[-+!][^-]', line) + + # Top-level comment cannot belong to function + if re.match(r'^[-+! ]\/\*', line): + fn = None + + if change and fn: + if cache.match(r'^((class|struct|union|enum)\s+[a-zA-Z0-9_]+)', fn): + # Struct declaration + fn = cache.group(1) + elif cache.search(r'#\s*define\s+([a-zA-Z0-9_]+)', fn): + # Macro definition + fn = cache.group(1) + elif cache.match('^DEF[A-Z0-9_]+\s*\(([a-zA-Z0-9_]+)', fn): + # Supermacro + fn = cache.group(1) + elif cache.search(r'([a-zA-Z_][^()\s]*)\s*\([^*]', fn): + # Discard template and function parameters. + fn = cache.group(1) + fn = re.sub(r'<[^<>]*>', '', fn) + fn = fn.rstrip() + else: + fn = None + + if fn and fn not in fns: # Avoid dups + fns.append(fn) + + fn = None + + return fns + +def parse_patch(contents): + """Parse patch contents to a sequence of FileDiffs.""" + + diffs = [] + + lines = contents.split('\n') + + i = 0 + while i < len(lines): + line = lines[i] + + # Diff headers look like + # --- a/gcc/tree.c + # +++ b/gcc/tree.c + # or + # *** gcc/cfgexpand.c 2013-12-25 20:07:24.800350058 +0400 + # --- gcc/cfgexpand.c 2013-12-25 20:06:30.612350178 +0400 + + if is_file_diff_start(line): + left = re.split(r'\s+', line)[1] + else: + i += 1 + continue + + left = remove_suffixes(left); + + i += 1 + line = lines[i] + + if not cache.match(r'^[+-][+-][+-] +(\S+)', line): + error("expected filename in line %d" % i) + right = remove_suffixes(cache.group(1)); + + # Extract real file name from left and right names. + filename = None + if left == right: + filename = left + elif left == '/dev/null': + filename = right; + elif right == '/dev/null': + filename = left; + else: + comps = [] + while left and right: + left, l = os.path.split(left) + right, r = os.path.split(right) + if l != r: + break + comps.append(l) + + if not comps: + error("failed to extract common name for %s and %s" % (left, right)) + + comps.reverse() + filename = '/'.join(comps) + + d = FileDiff(filename) + diffs.append(d) + + # Collect hunks for current file. + hunk = None + i += 1 + while i < len(lines): + line = lines[i] + + # Create new hunk when we see hunk header + if is_hunk_start(line): + if hunk is not None: + d.hunks.append(hunk) + hunk = Hunk(line) + i += 1 + continue + + # Stop when we reach next diff + if (is_file_diff_start(line) + or line.startswith('diff ') + or line.startswith('Index: ')): + i -= 1 + break + + if hunk is not None: + hunk.lines.append(line) + i += 1 + + d.hunks.append(hunk) + + return diffs + + +def get_pr_from_testcase(line): + r = pr_regex.search(line) + if r != None: + return r.group(3) + else: + return None + +def main(): + name, email = read_user_info() + + help_message = """\ +Generate ChangeLog template for PATCH. +PATCH must be generated using diff(1)'s -up or -cp options +(or their equivalent in Subversion/git). +""" + + inline_message = """\ +Prepends ChangeLog to PATCH. +If PATCH is not stdin, modifies PATCH in-place, +otherwise writes to stdout.' +""" + + parser = argparse.ArgumentParser(description = help_message) + parser.add_argument('-v', '--verbose', action = 'store_true', help = 'Verbose messages') + parser.add_argument('-i', '--inline', action = 'store_true', help = inline_message) + parser.add_argument('input', nargs = '?', help = 'Patch file (or missing, read standard input)') + args = parser.parse_args() + if args.input == '-': + args.input = None + input = open(args.input) if args.input else sys.stdin + contents = input.read() + diffs = parse_patch(contents) + + if args.verbose: + print("Parse results:") + for d in diffs: + d.dump() + + # Generate template ChangeLog. + + logs = {} + prs = [] + for d in diffs: + log_name = d.clname + + logs.setdefault(log_name, '') + logs[log_name] += '\t* %s' % d.relname + + change_msg = '' + + # Check if file was removed or added. + # Two patterns for context and unified diff. + if len(d.hunks) == 1: + hunk0 = d.hunks[0] + if hunk0.is_file_addition(): + if re.search(r'testsuite.*(?<!\.exp)$', d.filename): + change_msg = ': New test.\n' + pr = get_pr_from_testcase(hunk0.lines[0]) + if pr and pr not in prs: + prs.append(pr) + else: + change_msg = ": New file.\n" + elif hunk0.is_file_removal(): + change_msg = ": Remove.\n" + + _, ext = os.path.splitext(d.filename) + if (not change_msg and ext in ['.c', '.cpp', '.C', '.cc', '.h', '.inc', '.def'] + and not 'testsuite' in d.filename): + fns = [] + for hunk in d.hunks: + for fn in find_changed_funs(hunk): + if fn not in fns: + fns.append(fn) + + for fn in fns: + if change_msg: + change_msg += "\t(%s):\n" % fn + else: + change_msg = " (%s):\n" % fn + + logs[log_name] += change_msg if change_msg else ":\n" + + if args.inline and args.input: + # Get a temp filename, rather than an open filehandle, because we use + # the open to truncate. + fd, tmp = tempfile.mkstemp("tmp.XXXXXXXX") + os.close(fd) + + # Copy permissions to temp file + # (old Pythons do not support shutil.copymode) + shutil.copymode(args.input, tmp) + + # Open the temp file, clearing contents. + out = open(tmp, 'w') + else: + tmp = None + out = sys.stdout + + # Print log + date = time.strftime('%Y-%m-%d') + bugmsg = '' + if len(prs): + bugmsg = '\n'.join(['\t' + pr for pr in prs]) + '\n' + + for log_name, msg in sorted(logs.items()): + out.write("""\ +%s: + +%s %s <%s> + +%s%s\n""" % (log_name, date, name, email, bugmsg, msg)) + + if args.inline: + # Append patch body + out.write(contents) + + if args.input: + # Write new contents atomically + out.close() + shutil.move(tmp, args.input) + +if __name__ == '__main__': + main() |