blob: 8b846799dfa26c7c63a76665af372ef3ffa8709e [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
Maxim Cournoyer97f4d762022-12-20 00:38:36 -05007except Exception:
Paul Burton352f1102016-09-27 16:03:52 +01008 import ConfigParser
9
Simon Glass9bb0a8d2020-11-03 13:54:13 -070010import argparse
Simon Glass26132882012-01-14 15:12:45 +000011import os
12import re
13
Maxim Cournoyera81a41f2022-12-20 00:38:38 -050014from patman import gitutil
15
Doug Anderson31ffd7f2012-12-03 14:43:18 +000016"""Default settings per-project.
17
18These are used by _ProjectConfigParser. Settings names should match
19the "dest" of the option parser from patman.py.
20"""
21_default_settings = {
22 "u-boot": {},
23 "linux": {
24 "process_tags": "False",
Douglas Andersonbe4f2712022-07-19 14:56:27 -070025 "check_patch_use_tree": "True",
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
Maxim Cournoyer97f4d762022-12-20 00:38:36 -050034
Maxim Cournoyercc38d352022-12-20 00:38:37 -050035class _ProjectConfigParser(ConfigParser.ConfigParser):
Doug Anderson31ffd7f2012-12-03 14:43:18 +000036 """ConfigParser that handles projects.
37
38 There are two main goals of this class:
39 - Load project-specific default settings.
40 - Merge general default settings/aliases with project-specific ones.
41
42 # Sample config used for tests below...
Simon Glass945328b2020-04-17 18:08:55 -060043 >>> from io import StringIO
Doug Anderson31ffd7f2012-12-03 14:43:18 +000044 >>> sample_config = '''
45 ... [alias]
46 ... me: Peter P. <likesspiders@example.com>
47 ... enemies: Evil <evil@example.com>
48 ...
49 ... [sm_alias]
50 ... enemies: Green G. <ugly@example.com>
51 ...
52 ... [sm2_alias]
53 ... enemies: Doc O. <pus@example.com>
54 ...
55 ... [settings]
56 ... am_hero: True
57 ... '''
58
59 # Check to make sure that bogus project gets general alias.
60 >>> config = _ProjectConfigParser("zzz")
Paul Burton44fc9ba2016-09-27 16:03:55 +010061 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060062 >>> str(config.get("alias", "enemies"))
63 'Evil <evil@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000064
65 # Check to make sure that alias gets overridden by project.
66 >>> config = _ProjectConfigParser("sm")
Paul Burton44fc9ba2016-09-27 16:03:55 +010067 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060068 >>> str(config.get("alias", "enemies"))
69 'Green G. <ugly@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000070
71 # Check to make sure that settings get merged with project.
72 >>> config = _ProjectConfigParser("linux")
Paul Burton44fc9ba2016-09-27 16:03:55 +010073 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060074 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
Douglas Andersonbe4f2712022-07-19 14:56:27 -070075 [('am_hero', 'True'), ('check_patch_use_tree', 'True'), ('process_tags', 'False')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000076
77 # Check to make sure that settings works with unknown project.
78 >>> config = _ProjectConfigParser("unknown")
Paul Burton44fc9ba2016-09-27 16:03:55 +010079 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060080 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
81 [('am_hero', 'True')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000082 """
83 def __init__(self, project_name):
84 """Construct _ProjectConfigParser.
85
Maxim Cournoyercc38d352022-12-20 00:38:37 -050086 In addition to standard ConfigParser initialization, this also
87 loads project defaults.
Doug Anderson31ffd7f2012-12-03 14:43:18 +000088
89 Args:
90 project_name: The name of the project.
91 """
92 self._project_name = project_name
Maxim Cournoyercc38d352022-12-20 00:38:37 -050093 ConfigParser.ConfigParser.__init__(self)
Doug Anderson31ffd7f2012-12-03 14:43:18 +000094
95 # Update the project settings in the config based on
96 # the _default_settings global.
97 project_settings = "%s_settings" % project_name
98 if not self.has_section(project_settings):
99 self.add_section(project_settings)
100 project_defaults = _default_settings.get(project_name, {})
Paul Burton4466f1a2016-09-27 16:03:54 +0100101 for setting_name, setting_value in project_defaults.items():
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000102 self.set(project_settings, setting_name, setting_value)
103
104 def get(self, section, option, *args, **kwargs):
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500105 """Extend ConfigParser to try project_section before section.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000106
107 Args:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500108 See ConfigParser.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000109 Returns:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500110 See ConfigParser.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000111 """
112 try:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500113 val = ConfigParser.ConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000114 self, "%s_%s" % (self._project_name, section), option,
115 *args, **kwargs
116 )
117 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500118 val = ConfigParser.ConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000119 self, section, option, *args, **kwargs
120 )
Simon Glass9dfb3112020-11-08 20:36:18 -0700121 return val
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000122
123 def items(self, section, *args, **kwargs):
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500124 """Extend ConfigParser to add project_section to section.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000125
126 Args:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500127 See ConfigParser.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000128 Returns:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500129 See ConfigParser.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000130 """
131 project_items = []
132 has_project_section = False
133 top_items = []
134
135 # Get items from the project section
136 try:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500137 project_items = ConfigParser.ConfigParser.items(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000138 self, "%s_%s" % (self._project_name, section), *args, **kwargs
139 )
140 has_project_section = True
141 except ConfigParser.NoSectionError:
142 pass
143
144 # Get top-level items
145 try:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500146 top_items = ConfigParser.ConfigParser.items(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000147 self, section, *args, **kwargs
148 )
149 except ConfigParser.NoSectionError:
150 # If neither section exists raise the error on...
151 if not has_project_section:
152 raise
153
154 item_dict = dict(top_items)
155 item_dict.update(project_items)
Simon Glass9dfb3112020-11-08 20:36:18 -0700156 return {(item, val) for item, val in item_dict.items()}
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000157
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500158
Simon Glass26132882012-01-14 15:12:45 +0000159def ReadGitAliases(fname):
160 """Read a git alias file. This is in the form used by git:
161
162 alias uboot u-boot@lists.denx.de
163 alias wd Wolfgang Denk <wd@denx.de>
164
165 Args:
166 fname: Filename to read
167 """
168 try:
Simon Glassf544a2d2019-10-31 07:42:51 -0600169 fd = open(fname, 'r', encoding='utf-8')
Simon Glass26132882012-01-14 15:12:45 +0000170 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100171 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass26132882012-01-14 15:12:45 +0000172 return
173
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500174 re_line = re.compile(r'alias\s+(\S+)\s+(.*)')
Simon Glass26132882012-01-14 15:12:45 +0000175 for line in fd.readlines():
176 line = line.strip()
177 if not line or line[0] == '#':
178 continue
179
180 m = re_line.match(line)
181 if not m:
Paul Burtonc3931342016-09-27 16:03:50 +0100182 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass26132882012-01-14 15:12:45 +0000183 continue
184
185 list = alias.get(m.group(1), [])
186 for item in m.group(2).split(','):
187 item = item.strip()
188 if item:
189 list.append(item)
190 alias[m.group(1)] = list
191
192 fd.close()
193
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500194
Maxim Cournoyera81a41f2022-12-20 00:38:38 -0500195def CreatePatmanConfigFile(config_fname):
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000196 """Creates a config file under $(HOME)/.patman if it can't find one.
197
198 Args:
199 config_fname: Default config filename i.e., $(HOME)/.patman
200
201 Returns:
202 None
203 """
Simon Glass761648b2022-01-29 14:14:11 -0700204 name = gitutil.get_default_user_name()
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500205 if name is None:
Simon Glass547cba62022-02-11 13:23:18 -0700206 name = input("Enter name: ")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000207
Simon Glass761648b2022-01-29 14:14:11 -0700208 email = gitutil.get_default_user_email()
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000209
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500210 if email is None:
Simon Glass547cba62022-02-11 13:23:18 -0700211 email = input("Enter email: ")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000212
213 try:
214 f = open(config_fname, 'w')
215 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100216 print("Couldn't create patman config file\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000217 raise
218
Simon Glass52a08642017-09-12 20:30:28 -0600219 print('''[alias]
220me: %s <%s>
221
222[bounces]
223nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
224''' % (name, email), file=f)
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500225 f.close()
226
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000227
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700228def _UpdateDefaults(main_parser, config):
Doug Anderson3d3077c2012-12-03 14:43:17 +0000229 """Update the given OptionParser defaults based on config.
230
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700231 We'll walk through all of the settings from all parsers.
Doug Anderson3d3077c2012-12-03 14:43:17 +0000232 For each setting we'll look for a default in the option parser.
233 If it's found we'll update the option parser default.
234
235 The idea here is that the .patman file should be able to update
236 defaults but that command line flags should still have the final
237 say.
238
239 Args:
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700240 parser: An instance of an ArgumentParser whose defaults will be
Doug Anderson3d3077c2012-12-03 14:43:17 +0000241 updated.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000242 config: An instance of _ProjectConfigParser that we will query
Doug Anderson3d3077c2012-12-03 14:43:17 +0000243 for settings.
244 """
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700245 # Find all the parsers and subparsers
246 parsers = [main_parser]
247 parsers += [subparser for action in main_parser._actions
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500248 if isinstance(action, argparse._SubParsersAction)
249 for _, subparser in action.choices.items()]
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700250
251 # Collect the defaults from each parser
252 defaults = {}
Sean Anderson461df862022-04-29 10:53:34 -0400253 parser_defaults = []
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700254 for parser in parsers:
255 pdefs = parser.parse_known_args()[0]
Sean Anderson461df862022-04-29 10:53:34 -0400256 parser_defaults.append(pdefs)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700257 defaults.update(vars(pdefs))
258
259 # Go through the settings and collect defaults
Doug Anderson3d3077c2012-12-03 14:43:17 +0000260 for name, val in config.items('settings'):
Simon Glasseb101ac2020-07-05 21:41:53 -0600261 if name in defaults:
262 default_val = defaults[name]
Doug Anderson3d3077c2012-12-03 14:43:17 +0000263 if isinstance(default_val, bool):
264 val = config.getboolean('settings', name)
265 elif isinstance(default_val, int):
266 val = config.getint('settings', name)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700267 elif isinstance(default_val, str):
268 val = config.get('settings', name)
Simon Glasseb101ac2020-07-05 21:41:53 -0600269 defaults[name] = val
Doug Anderson3d3077c2012-12-03 14:43:17 +0000270 else:
Paul Burtonc3931342016-09-27 16:03:50 +0100271 print("WARNING: Unknown setting %s" % name)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700272
Sean Anderson461df862022-04-29 10:53:34 -0400273 # Set all the defaults and manually propagate them to subparsers
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700274 main_parser.set_defaults(**defaults)
Sean Anderson461df862022-04-29 10:53:34 -0400275 for parser, pdefs in zip(parsers, parser_defaults):
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500276 parser.set_defaults(**{k: v for k, v in defaults.items()
277 if k in pdefs})
278
Doug Anderson3d3077c2012-12-03 14:43:17 +0000279
Simon Glass33382692015-01-29 11:35:17 -0700280def _ReadAliasFile(fname):
281 """Read in the U-Boot git alias file if it exists.
282
283 Args:
284 fname: Filename to read.
285 """
286 if os.path.exists(fname):
287 bad_line = None
Simon Glassf544a2d2019-10-31 07:42:51 -0600288 with open(fname, encoding='utf-8') as fd:
Simon Glass33382692015-01-29 11:35:17 -0700289 linenum = 0
290 for line in fd:
291 linenum += 1
292 line = line.strip()
293 if not line or line.startswith('#'):
294 continue
Adam Sampson02651322018-06-27 14:38:58 +0100295 words = line.split(None, 2)
Simon Glass33382692015-01-29 11:35:17 -0700296 if len(words) < 3 or words[0] != 'alias':
297 if not bad_line:
298 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
299 line)
300 continue
301 alias[words[1]] = [s.strip() for s in words[2].split(',')]
302 if bad_line:
Paul Burtonc3931342016-09-27 16:03:50 +0100303 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700304
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500305
Chris Packhame8d2a122017-09-01 20:57:53 +1200306def _ReadBouncesFile(fname):
307 """Read in the bounces file if it exists
308
309 Args:
310 fname: Filename to read.
311 """
312 if os.path.exists(fname):
313 with open(fname) as fd:
314 for line in fd:
315 if line.startswith('#'):
316 continue
317 bounces.add(line.strip())
318
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500319
Simon Glass52a08642017-09-12 20:30:28 -0600320def GetItems(config, section):
321 """Get the items from a section of the config.
322
323 Args:
324 config: _ProjectConfigParser object containing settings
325 section: name of section to retrieve
326
327 Returns:
328 List of (name, value) tuples for the section
329 """
330 try:
331 return config.items(section)
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500332 except ConfigParser.NoSectionError:
Simon Glass52a08642017-09-12 20:30:28 -0600333 return []
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500334
Simon Glass52a08642017-09-12 20:30:28 -0600335
Maxim Cournoyerda6f7cf2022-12-20 00:38:39 -0500336def Setup(parser, project_name, config_fname=None):
Simon Glass26132882012-01-14 15:12:45 +0000337 """Set up the settings module by reading config files.
338
339 Args:
Maxim Cournoyerda6f7cf2022-12-20 00:38:39 -0500340 parser: The parser to update.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000341 project_name: Name of project that we're working on; we'll look
342 for sections named "project_section" as well.
Maxim Cournoyerda6f7cf2022-12-20 00:38:39 -0500343 config_fname: Config filename to read.
Simon Glass26132882012-01-14 15:12:45 +0000344 """
Simon Glass33382692015-01-29 11:35:17 -0700345 # First read the git alias file if available
346 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000347 config = _ProjectConfigParser(project_name)
Maxim Cournoyerda6f7cf2022-12-20 00:38:39 -0500348
349 if not config_fname:
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000350 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000351
352 if not os.path.exists(config_fname):
Paul Burtonc3931342016-09-27 16:03:50 +0100353 print("No config file found ~/.patman\nCreating one...\n")
Maxim Cournoyera81a41f2022-12-20 00:38:38 -0500354 CreatePatmanConfigFile(config_fname)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000355
Doug Anderson3d3077c2012-12-03 14:43:17 +0000356 config.read(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000357
Simon Glass52a08642017-09-12 20:30:28 -0600358 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000359 alias[name] = value.split(',')
360
Chris Packhame8d2a122017-09-01 20:57:53 +1200361 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600362 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200363 bounces.add(value)
364
Doug Anderson3d3077c2012-12-03 14:43:17 +0000365 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000366
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500367
Simon Glass26132882012-01-14 15:12:45 +0000368# These are the aliases we understand, indexed by alias. Each member is a list.
369alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200370bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000371
372if __name__ == "__main__":
373 import doctest
374
375 doctest.testmod()