blob: 4c847fe88fd4cacd7df36915715f11c7822f089b [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass26132882012-01-14 15:12:45 +00002# Copyright (c) 2011 The Chromium OS Authors.
3#
Simon Glass26132882012-01-14 15:12:45 +00004
Paul Burton352f1102016-09-27 16:03:52 +01005try:
6 import configparser as ConfigParser
7except:
8 import ConfigParser
9
Simon Glass9bb0a8d2020-11-03 13:54:13 -070010import argparse
Simon Glass26132882012-01-14 15:12:45 +000011import os
12import re
13
Simon Glassa997ea52020-04-17 18:09:04 -060014from patman import command
Simon Glassa997ea52020-04-17 18:09:04 -060015from patman import tools
Simon Glass26132882012-01-14 15:12:45 +000016
Doug Anderson31ffd7f2012-12-03 14:43:18 +000017"""Default settings per-project.
18
19These are used by _ProjectConfigParser. Settings names should match
20the "dest" of the option parser from patman.py.
21"""
22_default_settings = {
23 "u-boot": {},
24 "linux": {
25 "process_tags": "False",
Philipp Tomsich98373d42020-11-24 18:14:53 +010026 },
27 "gcc": {
28 "process_tags": "False",
29 "add_signoff": "False",
30 "check_patch": "False",
31 },
Doug Anderson31ffd7f2012-12-03 14:43:18 +000032}
33
34class _ProjectConfigParser(ConfigParser.SafeConfigParser):
35 """ConfigParser that handles projects.
36
37 There are two main goals of this class:
38 - Load project-specific default settings.
39 - Merge general default settings/aliases with project-specific ones.
40
41 # Sample config used for tests below...
Simon Glass945328b2020-04-17 18:08:55 -060042 >>> from io import StringIO
Doug Anderson31ffd7f2012-12-03 14:43:18 +000043 >>> sample_config = '''
44 ... [alias]
45 ... me: Peter P. <likesspiders@example.com>
46 ... enemies: Evil <evil@example.com>
47 ...
48 ... [sm_alias]
49 ... enemies: Green G. <ugly@example.com>
50 ...
51 ... [sm2_alias]
52 ... enemies: Doc O. <pus@example.com>
53 ...
54 ... [settings]
55 ... am_hero: True
56 ... '''
57
58 # Check to make sure that bogus project gets general alias.
59 >>> config = _ProjectConfigParser("zzz")
Paul Burton44fc9ba2016-09-27 16:03:55 +010060 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060061 >>> str(config.get("alias", "enemies"))
62 'Evil <evil@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000063
64 # Check to make sure that alias gets overridden by project.
65 >>> config = _ProjectConfigParser("sm")
Paul Burton44fc9ba2016-09-27 16:03:55 +010066 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060067 >>> str(config.get("alias", "enemies"))
68 'Green G. <ugly@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000069
70 # Check to make sure that settings get merged with project.
71 >>> config = _ProjectConfigParser("linux")
Paul Burton44fc9ba2016-09-27 16:03:55 +010072 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060073 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
74 [('am_hero', 'True'), ('process_tags', 'False')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000075
76 # Check to make sure that settings works with unknown project.
77 >>> config = _ProjectConfigParser("unknown")
Paul Burton44fc9ba2016-09-27 16:03:55 +010078 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060079 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
80 [('am_hero', 'True')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000081 """
82 def __init__(self, project_name):
83 """Construct _ProjectConfigParser.
84
85 In addition to standard SafeConfigParser initialization, this also loads
86 project defaults.
87
88 Args:
89 project_name: The name of the project.
90 """
91 self._project_name = project_name
92 ConfigParser.SafeConfigParser.__init__(self)
93
94 # Update the project settings in the config based on
95 # the _default_settings global.
96 project_settings = "%s_settings" % project_name
97 if not self.has_section(project_settings):
98 self.add_section(project_settings)
99 project_defaults = _default_settings.get(project_name, {})
Paul Burton4466f1a2016-09-27 16:03:54 +0100100 for setting_name, setting_value in project_defaults.items():
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000101 self.set(project_settings, setting_name, setting_value)
102
103 def get(self, section, option, *args, **kwargs):
104 """Extend SafeConfigParser to try project_section before section.
105
106 Args:
107 See SafeConfigParser.
108 Returns:
109 See SafeConfigParser.
110 """
111 try:
Simon Glass26f8b202018-10-01 21:12:33 -0600112 val = ConfigParser.SafeConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000113 self, "%s_%s" % (self._project_name, section), option,
114 *args, **kwargs
115 )
116 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
Simon Glass26f8b202018-10-01 21:12:33 -0600117 val = ConfigParser.SafeConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000118 self, section, option, *args, **kwargs
119 )
Simon Glass9dfb3112020-11-08 20:36:18 -0700120 return val
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000121
122 def items(self, section, *args, **kwargs):
123 """Extend SafeConfigParser to add project_section to section.
124
125 Args:
126 See SafeConfigParser.
127 Returns:
128 See SafeConfigParser.
129 """
130 project_items = []
131 has_project_section = False
132 top_items = []
133
134 # Get items from the project section
135 try:
136 project_items = ConfigParser.SafeConfigParser.items(
137 self, "%s_%s" % (self._project_name, section), *args, **kwargs
138 )
139 has_project_section = True
140 except ConfigParser.NoSectionError:
141 pass
142
143 # Get top-level items
144 try:
145 top_items = ConfigParser.SafeConfigParser.items(
146 self, section, *args, **kwargs
147 )
148 except ConfigParser.NoSectionError:
149 # If neither section exists raise the error on...
150 if not has_project_section:
151 raise
152
153 item_dict = dict(top_items)
154 item_dict.update(project_items)
Simon Glass9dfb3112020-11-08 20:36:18 -0700155 return {(item, val) for item, val in item_dict.items()}
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000156
Simon Glass26132882012-01-14 15:12:45 +0000157def ReadGitAliases(fname):
158 """Read a git alias file. This is in the form used by git:
159
160 alias uboot u-boot@lists.denx.de
161 alias wd Wolfgang Denk <wd@denx.de>
162
163 Args:
164 fname: Filename to read
165 """
166 try:
Simon Glassf544a2d2019-10-31 07:42:51 -0600167 fd = open(fname, 'r', encoding='utf-8')
Simon Glass26132882012-01-14 15:12:45 +0000168 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100169 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass26132882012-01-14 15:12:45 +0000170 return
171
172 re_line = re.compile('alias\s+(\S+)\s+(.*)')
173 for line in fd.readlines():
174 line = line.strip()
175 if not line or line[0] == '#':
176 continue
177
178 m = re_line.match(line)
179 if not m:
Paul Burtonc3931342016-09-27 16:03:50 +0100180 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass26132882012-01-14 15:12:45 +0000181 continue
182
183 list = alias.get(m.group(1), [])
184 for item in m.group(2).split(','):
185 item = item.strip()
186 if item:
187 list.append(item)
188 alias[m.group(1)] = list
189
190 fd.close()
191
Simon Glassff15dcd2020-06-07 06:45:49 -0600192def CreatePatmanConfigFile(gitutil, config_fname):
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000193 """Creates a config file under $(HOME)/.patman if it can't find one.
194
195 Args:
196 config_fname: Default config filename i.e., $(HOME)/.patman
197
198 Returns:
199 None
200 """
Simon Glass761648b2022-01-29 14:14:11 -0700201 name = gitutil.get_default_user_name()
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000202 if name == None:
Simon Glass547cba62022-02-11 13:23:18 -0700203 name = input("Enter name: ")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000204
Simon Glass761648b2022-01-29 14:14:11 -0700205 email = gitutil.get_default_user_email()
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000206
207 if email == None:
Simon Glass547cba62022-02-11 13:23:18 -0700208 email = input("Enter email: ")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000209
210 try:
211 f = open(config_fname, 'w')
212 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100213 print("Couldn't create patman config file\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000214 raise
215
Simon Glass52a08642017-09-12 20:30:28 -0600216 print('''[alias]
217me: %s <%s>
218
219[bounces]
220nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
221''' % (name, email), file=f)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000222 f.close();
223
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700224def _UpdateDefaults(main_parser, config):
Doug Anderson3d3077c2012-12-03 14:43:17 +0000225 """Update the given OptionParser defaults based on config.
226
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700227 We'll walk through all of the settings from all parsers.
Doug Anderson3d3077c2012-12-03 14:43:17 +0000228 For each setting we'll look for a default in the option parser.
229 If it's found we'll update the option parser default.
230
231 The idea here is that the .patman file should be able to update
232 defaults but that command line flags should still have the final
233 say.
234
235 Args:
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700236 parser: An instance of an ArgumentParser whose defaults will be
Doug Anderson3d3077c2012-12-03 14:43:17 +0000237 updated.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000238 config: An instance of _ProjectConfigParser that we will query
Doug Anderson3d3077c2012-12-03 14:43:17 +0000239 for settings.
240 """
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700241 # Find all the parsers and subparsers
242 parsers = [main_parser]
243 parsers += [subparser for action in main_parser._actions
244 if isinstance(action, argparse._SubParsersAction)
245 for _, subparser in action.choices.items()]
246
247 # Collect the defaults from each parser
248 defaults = {}
Sean Anderson461df862022-04-29 10:53:34 -0400249 parser_defaults = []
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700250 for parser in parsers:
251 pdefs = parser.parse_known_args()[0]
Sean Anderson461df862022-04-29 10:53:34 -0400252 parser_defaults.append(pdefs)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700253 defaults.update(vars(pdefs))
254
255 # Go through the settings and collect defaults
Doug Anderson3d3077c2012-12-03 14:43:17 +0000256 for name, val in config.items('settings'):
Simon Glasseb101ac2020-07-05 21:41:53 -0600257 if name in defaults:
258 default_val = defaults[name]
Doug Anderson3d3077c2012-12-03 14:43:17 +0000259 if isinstance(default_val, bool):
260 val = config.getboolean('settings', name)
261 elif isinstance(default_val, int):
262 val = config.getint('settings', name)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700263 elif isinstance(default_val, str):
264 val = config.get('settings', name)
Simon Glasseb101ac2020-07-05 21:41:53 -0600265 defaults[name] = val
Doug Anderson3d3077c2012-12-03 14:43:17 +0000266 else:
Paul Burtonc3931342016-09-27 16:03:50 +0100267 print("WARNING: Unknown setting %s" % name)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700268
Sean Anderson461df862022-04-29 10:53:34 -0400269 # Set all the defaults and manually propagate them to subparsers
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700270 main_parser.set_defaults(**defaults)
Sean Anderson461df862022-04-29 10:53:34 -0400271 for parser, pdefs in zip(parsers, parser_defaults):
272 parser.set_defaults(**{ k: v for k, v in defaults.items()
273 if k in pdefs })
Doug Anderson3d3077c2012-12-03 14:43:17 +0000274
Simon Glass33382692015-01-29 11:35:17 -0700275def _ReadAliasFile(fname):
276 """Read in the U-Boot git alias file if it exists.
277
278 Args:
279 fname: Filename to read.
280 """
281 if os.path.exists(fname):
282 bad_line = None
Simon Glassf544a2d2019-10-31 07:42:51 -0600283 with open(fname, encoding='utf-8') as fd:
Simon Glass33382692015-01-29 11:35:17 -0700284 linenum = 0
285 for line in fd:
286 linenum += 1
287 line = line.strip()
288 if not line or line.startswith('#'):
289 continue
Adam Sampson02651322018-06-27 14:38:58 +0100290 words = line.split(None, 2)
Simon Glass33382692015-01-29 11:35:17 -0700291 if len(words) < 3 or words[0] != 'alias':
292 if not bad_line:
293 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
294 line)
295 continue
296 alias[words[1]] = [s.strip() for s in words[2].split(',')]
297 if bad_line:
Paul Burtonc3931342016-09-27 16:03:50 +0100298 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700299
Chris Packhame8d2a122017-09-01 20:57:53 +1200300def _ReadBouncesFile(fname):
301 """Read in the bounces file if it exists
302
303 Args:
304 fname: Filename to read.
305 """
306 if os.path.exists(fname):
307 with open(fname) as fd:
308 for line in fd:
309 if line.startswith('#'):
310 continue
311 bounces.add(line.strip())
312
Simon Glass52a08642017-09-12 20:30:28 -0600313def GetItems(config, section):
314 """Get the items from a section of the config.
315
316 Args:
317 config: _ProjectConfigParser object containing settings
318 section: name of section to retrieve
319
320 Returns:
321 List of (name, value) tuples for the section
322 """
323 try:
324 return config.items(section)
325 except ConfigParser.NoSectionError as e:
326 return []
327 except:
328 raise
329
Simon Glassff15dcd2020-06-07 06:45:49 -0600330def Setup(gitutil, parser, project_name, config_fname=''):
Simon Glass26132882012-01-14 15:12:45 +0000331 """Set up the settings module by reading config files.
332
333 Args:
Doug Anderson3d3077c2012-12-03 14:43:17 +0000334 parser: The parser to update
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000335 project_name: Name of project that we're working on; we'll look
336 for sections named "project_section" as well.
Simon Glass26132882012-01-14 15:12:45 +0000337 config_fname: Config filename to read ('' for default)
338 """
Simon Glass33382692015-01-29 11:35:17 -0700339 # First read the git alias file if available
340 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000341 config = _ProjectConfigParser(project_name)
Simon Glass26132882012-01-14 15:12:45 +0000342 if config_fname == '':
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000343 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000344
345 if not os.path.exists(config_fname):
Paul Burtonc3931342016-09-27 16:03:50 +0100346 print("No config file found ~/.patman\nCreating one...\n")
Simon Glassff15dcd2020-06-07 06:45:49 -0600347 CreatePatmanConfigFile(gitutil, config_fname)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000348
Doug Anderson3d3077c2012-12-03 14:43:17 +0000349 config.read(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000350
Simon Glass52a08642017-09-12 20:30:28 -0600351 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000352 alias[name] = value.split(',')
353
Chris Packhame8d2a122017-09-01 20:57:53 +1200354 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600355 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200356 bounces.add(value)
357
Doug Anderson3d3077c2012-12-03 14:43:17 +0000358 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000359
360# These are the aliases we understand, indexed by alias. Each member is a list.
361alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200362bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000363
364if __name__ == "__main__":
365 import doctest
366
367 doctest.testmod()