]> git.proxmox.com Git - mirror_edk2.git/blob - BaseTools/Scripts/GetMaintainer.py
BaseTools: remove -DNO_MSABI_VA_FUNCS option in CLANGPDB tool chain
[mirror_edk2.git] / BaseTools / Scripts / GetMaintainer.py
1 ## @file
2 # Retrieves the people to request review from on submission of a commit.
3 #
4 # Copyright (c) 2019, Linaro Ltd. All rights reserved.<BR>
5 #
6 # SPDX-License-Identifier: BSD-2-Clause-Patent
7 #
8
9 from __future__ import print_function
10 from collections import defaultdict
11 from collections import OrderedDict
12 import argparse
13 import os
14 import re
15 import SetupGit
16
17 EXPRESSIONS = {
18 'exclude': re.compile(r'^X:\s*(?P<exclude>.*?)\r*$'),
19 'file': re.compile(r'^F:\s*(?P<file>.*?)\r*$'),
20 'list': re.compile(r'^L:\s*(?P<list>.*?)\r*$'),
21 'maintainer': re.compile(r'^M:\s*(?P<maintainer>.*<.*?>)\r*$'),
22 'reviewer': re.compile(r'^R:\s*(?P<reviewer>.*?)\r*$'),
23 'status': re.compile(r'^S:\s*(?P<status>.*?)\r*$'),
24 'tree': re.compile(r'^T:\s*(?P<tree>.*?)\r*$'),
25 'webpage': re.compile(r'^W:\s*(?P<webpage>.*?)\r*$')
26 }
27
28 def printsection(section):
29 """Prints out the dictionary describing a Maintainers.txt section."""
30 print('===')
31 for key in section.keys():
32 print("Key: %s" % key)
33 for item in section[key]:
34 print(' %s' % item)
35
36 def pattern_to_regex(pattern):
37 """Takes a string containing regular UNIX path wildcards
38 and returns a string suitable for matching with regex."""
39
40 pattern = pattern.replace('.', r'\.')
41 pattern = pattern.replace('?', r'.')
42 pattern = pattern.replace('*', r'.*')
43
44 if pattern.endswith('/'):
45 pattern += r'.*'
46 elif pattern.endswith('.*'):
47 pattern = pattern[:-2]
48 pattern += r'(?!.*?/.*?)'
49
50 return pattern
51
52 def path_in_section(path, section):
53 """Returns True of False indicating whether the path is covered by
54 the current section."""
55 if not 'file' in section:
56 return False
57
58 for pattern in section['file']:
59 regex = pattern_to_regex(pattern)
60
61 match = re.match(regex, path)
62 if match:
63 # Check if there is an exclude pattern that applies
64 for pattern in section['exclude']:
65 regex = pattern_to_regex(pattern)
66
67 match = re.match(regex, path)
68 if match:
69 return False
70
71 return True
72
73 return False
74
75 def get_section_maintainers(path, section):
76 """Returns a list with email addresses to any M: and R: entries
77 matching the provided path in the provided section."""
78 maintainers = []
79 lists = []
80
81 if path_in_section(path, section):
82 for address in section['maintainer'], section['reviewer']:
83 # Convert to list if necessary
84 if isinstance(address, list):
85 maintainers += address
86 else:
87 lists += [address]
88 for address in section['list']:
89 # Convert to list if necessary
90 if isinstance(address, list):
91 lists += address
92 else:
93 lists += [address]
94
95 return maintainers, lists
96
97 def get_maintainers(path, sections, level=0):
98 """For 'path', iterates over all sections, returning maintainers
99 for matching ones."""
100 maintainers = []
101 lists = []
102 for section in sections:
103 tmp_maint, tmp_lists = get_section_maintainers(path, section)
104 if tmp_maint:
105 maintainers += tmp_maint
106 if tmp_lists:
107 lists += tmp_lists
108
109 if not maintainers:
110 # If no match found, look for match for (nonexistent) file
111 # REPO.working_dir/<default>
112 print('"%s": no maintainers found, looking for default' % path)
113 if level == 0:
114 maintainers = get_maintainers('<default>', sections, level=level + 1)
115 else:
116 print("No <default> maintainers set for project.")
117 if not maintainers:
118 return None
119
120 return maintainers + lists
121
122 def parse_maintainers_line(line):
123 """Parse one line of Maintainers.txt, returning any match group and its key."""
124 for key, expression in EXPRESSIONS.items():
125 match = expression.match(line)
126 if match:
127 return key, match.group(key)
128 return None, None
129
130 def parse_maintainers_file(filename):
131 """Parse the Maintainers.txt from top-level of repo and
132 return a list containing dictionaries of all sections."""
133 with open(filename, 'r') as text:
134 line = text.readline()
135 sectionlist = []
136 section = defaultdict(list)
137 while line:
138 key, value = parse_maintainers_line(line)
139 if key and value:
140 section[key].append(value)
141
142 line = text.readline()
143 # If end of section (end of file, or non-tag line encountered)...
144 if not key or not value or not line:
145 # ...if non-empty, append section to list.
146 if section:
147 sectionlist.append(section.copy())
148 section.clear()
149
150 return sectionlist
151
152 def get_modified_files(repo, args):
153 """Returns a list of the files modified by the commit specified in 'args'."""
154 commit = repo.commit(args.commit)
155 return commit.stats.files
156
157 if __name__ == '__main__':
158 PARSER = argparse.ArgumentParser(
159 description='Retrieves information on who to cc for review on a given commit')
160 PARSER.add_argument('commit',
161 action="store",
162 help='git revision to examine (default: HEAD)',
163 nargs='?',
164 default='HEAD')
165 PARSER.add_argument('-l', '--lookup',
166 help='Find section matches for path LOOKUP',
167 required=False)
168 ARGS = PARSER.parse_args()
169
170 REPO = SetupGit.locate_repo()
171
172 CONFIG_FILE = os.path.join(REPO.working_dir, 'Maintainers.txt')
173
174 SECTIONS = parse_maintainers_file(CONFIG_FILE)
175
176 if ARGS.lookup:
177 FILES = [ARGS.lookup]
178 else:
179 FILES = get_modified_files(REPO, ARGS)
180
181 ADDRESSES = []
182
183 for file in FILES:
184 print(file)
185 addresslist = get_maintainers(file, SECTIONS)
186 if addresslist:
187 ADDRESSES += addresslist
188
189 for address in list(OrderedDict.fromkeys(ADDRESSES)):
190 print(' %s' % address)