blob: c05efd2475ec8647f7c1de13ece83f25841dc50b [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass0d24de92012-01-14 15:12:45 +00002# Copyright (c) 2011 The Chromium OS Authors.
3#
Simon Glass0d24de92012-01-14 15:12:45 +00004
Paul Burton2ce7b212016-09-27 16:03:52 +01005try:
6 import configparser as ConfigParser
Maxim Cournoyer872f3a42022-12-20 00:38:36 -05007except Exception:
Paul Burton2ce7b212016-09-27 16:03:52 +01008 import ConfigParser
9
Simon Glass3145b632020-11-03 13:54:13 -070010import argparse
Simon Glass0d24de92012-01-14 15:12:45 +000011import os
12import re
13
Maxim Cournoyer57e3b032022-12-20 00:38:38 -050014from patman import gitutil
15
Doug Andersona1dcee82012-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 Andersondce43222022-07-19 14:56:27 -070025 "check_patch_use_tree": "True",
Philipp Tomsichddc44c22020-11-24 18:14:53 +010026 },
27 "gcc": {
28 "process_tags": "False",
29 "add_signoff": "False",
30 "check_patch": "False",
31 },
Doug Andersona1dcee82012-12-03 14:43:18 +000032}
33
Maxim Cournoyer872f3a42022-12-20 00:38:36 -050034
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -050035class _ProjectConfigParser(ConfigParser.ConfigParser):
Doug Andersona1dcee82012-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 Glassc3a13cc2020-04-17 18:08:55 -060043 >>> from io import StringIO
Doug Andersona1dcee82012-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 Burtonf5d44b92016-09-27 16:03:55 +010061 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060062 >>> str(config.get("alias", "enemies"))
63 'Evil <evil@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000064
65 # Check to make sure that alias gets overridden by project.
66 >>> config = _ProjectConfigParser("sm")
Paul Burtonf5d44b92016-09-27 16:03:55 +010067 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060068 >>> str(config.get("alias", "enemies"))
69 'Green G. <ugly@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000070
71 # Check to make sure that settings get merged with project.
72 >>> config = _ProjectConfigParser("linux")
Paul Burtonf5d44b92016-09-27 16:03:55 +010073 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060074 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
Douglas Andersondce43222022-07-19 14:56:27 -070075 [('am_hero', 'True'), ('check_patch_use_tree', 'True'), ('process_tags', 'False')]
Doug Andersona1dcee82012-12-03 14:43:18 +000076
77 # Check to make sure that settings works with unknown project.
78 >>> config = _ProjectConfigParser("unknown")
Paul Burtonf5d44b92016-09-27 16:03:55 +010079 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060080 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
81 [('am_hero', 'True')]
Doug Andersona1dcee82012-12-03 14:43:18 +000082 """
83 def __init__(self, project_name):
84 """Construct _ProjectConfigParser.
85
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -050086 In addition to standard ConfigParser initialization, this also
87 loads project defaults.
Doug Andersona1dcee82012-12-03 14:43:18 +000088
89 Args:
90 project_name: The name of the project.
91 """
92 self._project_name = project_name
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -050093 ConfigParser.ConfigParser.__init__(self)
Doug Andersona1dcee82012-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 Burtonc9eac382016-09-27 16:03:54 +0100101 for setting_name, setting_value in project_defaults.items():
Doug Andersona1dcee82012-12-03 14:43:18 +0000102 self.set(project_settings, setting_name, setting_value)
103
104 def get(self, section, option, *args, **kwargs):
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500105 """Extend ConfigParser to try project_section before section.
Doug Andersona1dcee82012-12-03 14:43:18 +0000106
107 Args:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500108 See ConfigParser.
Doug Andersona1dcee82012-12-03 14:43:18 +0000109 Returns:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500110 See ConfigParser.
Doug Andersona1dcee82012-12-03 14:43:18 +0000111 """
112 try:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500113 val = ConfigParser.ConfigParser.get(
Doug Andersona1dcee82012-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 Cournoyer8b73f9b2022-12-20 00:38:37 -0500118 val = ConfigParser.ConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000119 self, section, option, *args, **kwargs
120 )
Simon Glassfc0056e2020-11-08 20:36:18 -0700121 return val
Doug Andersona1dcee82012-12-03 14:43:18 +0000122
123 def items(self, section, *args, **kwargs):
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500124 """Extend ConfigParser to add project_section to section.
Doug Andersona1dcee82012-12-03 14:43:18 +0000125
126 Args:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500127 See ConfigParser.
Doug Andersona1dcee82012-12-03 14:43:18 +0000128 Returns:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500129 See ConfigParser.
Doug Andersona1dcee82012-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 Cournoyer8b73f9b2022-12-20 00:38:37 -0500137 project_items = ConfigParser.ConfigParser.items(
Doug Andersona1dcee82012-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 Cournoyer8b73f9b2022-12-20 00:38:37 -0500146 top_items = ConfigParser.ConfigParser.items(
Doug Andersona1dcee82012-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 Glassfc0056e2020-11-08 20:36:18 -0700156 return {(item, val) for item, val in item_dict.items()}
Doug Andersona1dcee82012-12-03 14:43:18 +0000157
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500158
Simon Glass0d24de92012-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 Glass272cd852019-10-31 07:42:51 -0600169 fd = open(fname, 'r', encoding='utf-8')
Simon Glass0d24de92012-01-14 15:12:45 +0000170 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100171 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000172 return
173
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500174 re_line = re.compile(r'alias\s+(\S+)\s+(.*)')
Simon Glass0d24de92012-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 Burtona920a172016-09-27 16:03:50 +0100182 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass0d24de92012-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 Cournoyer872f3a42022-12-20 00:38:36 -0500194
Maxim Cournoyer57e3b032022-12-20 00:38:38 -0500195def CreatePatmanConfigFile(config_fname):
Vikram Narayanan87d65552012-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 Glass0157b182022-01-29 14:14:11 -0700204 name = gitutil.get_default_user_name()
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500205 if name is None:
Simon Glass32cc6ae2022-02-11 13:23:18 -0700206 name = input("Enter name: ")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000207
Simon Glass0157b182022-01-29 14:14:11 -0700208 email = gitutil.get_default_user_email()
Vikram Narayanan87d65552012-05-23 09:01:06 +0000209
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500210 if email is None:
Simon Glass32cc6ae2022-02-11 13:23:18 -0700211 email = input("Enter email: ")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000212
213 try:
214 f = open(config_fname, 'w')
215 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100216 print("Couldn't create patman config file\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000217 raise
218
Simon Glassad893142017-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 Cournoyer872f3a42022-12-20 00:38:36 -0500225 f.close()
226
Vikram Narayanan87d65552012-05-23 09:01:06 +0000227
Simon Glass3145b632020-11-03 13:54:13 -0700228def _UpdateDefaults(main_parser, config):
Doug Anderson8568bae2012-12-03 14:43:17 +0000229 """Update the given OptionParser defaults based on config.
230
Simon Glass3145b632020-11-03 13:54:13 -0700231 We'll walk through all of the settings from all parsers.
Doug Anderson8568bae2012-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 Glass3145b632020-11-03 13:54:13 -0700240 parser: An instance of an ArgumentParser whose defaults will be
Doug Anderson8568bae2012-12-03 14:43:17 +0000241 updated.
Doug Andersona1dcee82012-12-03 14:43:18 +0000242 config: An instance of _ProjectConfigParser that we will query
Doug Anderson8568bae2012-12-03 14:43:17 +0000243 for settings.
244 """
Simon Glass3145b632020-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 Cournoyer872f3a42022-12-20 00:38:36 -0500248 if isinstance(action, argparse._SubParsersAction)
249 for _, subparser in action.choices.items()]
Simon Glass3145b632020-11-03 13:54:13 -0700250
251 # Collect the defaults from each parser
252 defaults = {}
Sean Anderson4780f7d2022-04-29 10:53:34 -0400253 parser_defaults = []
Simon Glass3145b632020-11-03 13:54:13 -0700254 for parser in parsers:
255 pdefs = parser.parse_known_args()[0]
Sean Anderson4780f7d2022-04-29 10:53:34 -0400256 parser_defaults.append(pdefs)
Simon Glass3145b632020-11-03 13:54:13 -0700257 defaults.update(vars(pdefs))
258
259 # Go through the settings and collect defaults
Doug Anderson8568bae2012-12-03 14:43:17 +0000260 for name, val in config.items('settings'):
Simon Glassfda1e372020-07-05 21:41:53 -0600261 if name in defaults:
262 default_val = defaults[name]
Doug Anderson8568bae2012-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 Glass3145b632020-11-03 13:54:13 -0700267 elif isinstance(default_val, str):
268 val = config.get('settings', name)
Simon Glassfda1e372020-07-05 21:41:53 -0600269 defaults[name] = val
Doug Anderson8568bae2012-12-03 14:43:17 +0000270 else:
Paul Burtona920a172016-09-27 16:03:50 +0100271 print("WARNING: Unknown setting %s" % name)
Simon Glass3145b632020-11-03 13:54:13 -0700272
Sean Anderson4780f7d2022-04-29 10:53:34 -0400273 # Set all the defaults and manually propagate them to subparsers
Simon Glass3145b632020-11-03 13:54:13 -0700274 main_parser.set_defaults(**defaults)
Sean Anderson4780f7d2022-04-29 10:53:34 -0400275 for parser, pdefs in zip(parsers, parser_defaults):
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500276 parser.set_defaults(**{k: v for k, v in defaults.items()
277 if k in pdefs})
278
Doug Anderson8568bae2012-12-03 14:43:17 +0000279
Simon Glass8895b3e2015-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 Glass272cd852019-10-31 07:42:51 -0600288 with open(fname, encoding='utf-8') as fd:
Simon Glass8895b3e2015-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 Sampsonb8a48fb2018-06-27 14:38:58 +0100295 words = line.split(None, 2)
Simon Glass8895b3e2015-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 Burtona920a172016-09-27 16:03:50 +0100303 print(bad_line)
Simon Glass8895b3e2015-01-29 11:35:17 -0700304
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500305
Chris Packhame11aa602017-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 Cournoyer872f3a42022-12-20 00:38:36 -0500319
Simon Glassad893142017-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 Cournoyer872f3a42022-12-20 00:38:36 -0500332 except ConfigParser.NoSectionError:
Simon Glassad893142017-09-12 20:30:28 -0600333 return []
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500334
Simon Glassad893142017-09-12 20:30:28 -0600335
Maxim Cournoyer2c58a5e2022-12-20 00:38:39 -0500336def Setup(parser, project_name, config_fname=None):
Simon Glass0d24de92012-01-14 15:12:45 +0000337 """Set up the settings module by reading config files.
338
339 Args:
Maxim Cournoyer2c58a5e2022-12-20 00:38:39 -0500340 parser: The parser to update.
Doug Andersona1dcee82012-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 Cournoyerdb16edd2022-12-20 00:38:40 -0500343 config_fname: Config filename to read. An error is raised if it
344 does not exist.
Simon Glass0d24de92012-01-14 15:12:45 +0000345 """
Simon Glass8895b3e2015-01-29 11:35:17 -0700346 # First read the git alias file if available
347 _ReadAliasFile('doc/git-mailrc')
Doug Andersona1dcee82012-12-03 14:43:18 +0000348 config = _ProjectConfigParser(project_name)
Maxim Cournoyer2c58a5e2022-12-20 00:38:39 -0500349
Maxim Cournoyerdb16edd2022-12-20 00:38:40 -0500350 if config_fname and not os.path.exists(config_fname):
351 raise Exception(f'provided {config_fname} does not exist')
352
Maxim Cournoyer2c58a5e2022-12-20 00:38:39 -0500353 if not config_fname:
Vikram Narayanan2b36c752012-05-23 08:58:58 +0000354 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan87d65552012-05-23 09:01:06 +0000355
356 if not os.path.exists(config_fname):
Paul Burtona920a172016-09-27 16:03:50 +0100357 print("No config file found ~/.patman\nCreating one...\n")
Maxim Cournoyer57e3b032022-12-20 00:38:38 -0500358 CreatePatmanConfigFile(config_fname)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000359
Doug Anderson8568bae2012-12-03 14:43:17 +0000360 config.read(config_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000361
Simon Glassad893142017-09-12 20:30:28 -0600362 for name, value in GetItems(config, 'alias'):
Simon Glass0d24de92012-01-14 15:12:45 +0000363 alias[name] = value.split(',')
364
Chris Packhame11aa602017-09-01 20:57:53 +1200365 _ReadBouncesFile('doc/bounces')
Simon Glassad893142017-09-12 20:30:28 -0600366 for name, value in GetItems(config, 'bounces'):
Chris Packhame11aa602017-09-01 20:57:53 +1200367 bounces.add(value)
368
Doug Anderson8568bae2012-12-03 14:43:17 +0000369 _UpdateDefaults(parser, config)
Simon Glass0d24de92012-01-14 15:12:45 +0000370
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500371
Simon Glass0d24de92012-01-14 15:12:45 +0000372# These are the aliases we understand, indexed by alias. Each member is a list.
373alias = {}
Chris Packhame11aa602017-09-01 20:57:53 +1200374bounces = set()
Doug Andersona1dcee82012-12-03 14:43:18 +0000375
376if __name__ == "__main__":
377 import doctest
378
379 doctest.testmod()