blob: 7fb9d6d5a083005d1dd30d4c5f481fb83951c097 [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
Doug Andersona1dcee82012-12-03 14:43:18 +000014"""Default settings per-project.
15
16These are used by _ProjectConfigParser. Settings names should match
17the "dest" of the option parser from patman.py.
18"""
19_default_settings = {
20 "u-boot": {},
21 "linux": {
22 "process_tags": "False",
Douglas Andersondce43222022-07-19 14:56:27 -070023 "check_patch_use_tree": "True",
Philipp Tomsichddc44c22020-11-24 18:14:53 +010024 },
25 "gcc": {
26 "process_tags": "False",
27 "add_signoff": "False",
28 "check_patch": "False",
29 },
Doug Andersona1dcee82012-12-03 14:43:18 +000030}
31
Maxim Cournoyer872f3a42022-12-20 00:38:36 -050032
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -050033class _ProjectConfigParser(ConfigParser.ConfigParser):
Doug Andersona1dcee82012-12-03 14:43:18 +000034 """ConfigParser that handles projects.
35
36 There are two main goals of this class:
37 - Load project-specific default settings.
38 - Merge general default settings/aliases with project-specific ones.
39
40 # Sample config used for tests below...
Simon Glassc3a13cc2020-04-17 18:08:55 -060041 >>> from io import StringIO
Doug Andersona1dcee82012-12-03 14:43:18 +000042 >>> sample_config = '''
43 ... [alias]
44 ... me: Peter P. <likesspiders@example.com>
45 ... enemies: Evil <evil@example.com>
46 ...
47 ... [sm_alias]
48 ... enemies: Green G. <ugly@example.com>
49 ...
50 ... [sm2_alias]
51 ... enemies: Doc O. <pus@example.com>
52 ...
53 ... [settings]
54 ... am_hero: True
55 ... '''
56
57 # Check to make sure that bogus project gets general alias.
58 >>> config = _ProjectConfigParser("zzz")
Paul Burtonf5d44b92016-09-27 16:03:55 +010059 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060060 >>> str(config.get("alias", "enemies"))
61 'Evil <evil@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000062
63 # Check to make sure that alias gets overridden by project.
64 >>> config = _ProjectConfigParser("sm")
Paul Burtonf5d44b92016-09-27 16:03:55 +010065 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060066 >>> str(config.get("alias", "enemies"))
67 'Green G. <ugly@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000068
69 # Check to make sure that settings get merged with project.
70 >>> config = _ProjectConfigParser("linux")
Paul Burtonf5d44b92016-09-27 16:03:55 +010071 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060072 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
Douglas Andersondce43222022-07-19 14:56:27 -070073 [('am_hero', 'True'), ('check_patch_use_tree', 'True'), ('process_tags', 'False')]
Doug Andersona1dcee82012-12-03 14:43:18 +000074
75 # Check to make sure that settings works with unknown project.
76 >>> config = _ProjectConfigParser("unknown")
Paul Burtonf5d44b92016-09-27 16:03:55 +010077 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060078 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
79 [('am_hero', 'True')]
Doug Andersona1dcee82012-12-03 14:43:18 +000080 """
81 def __init__(self, project_name):
82 """Construct _ProjectConfigParser.
83
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -050084 In addition to standard ConfigParser initialization, this also
85 loads project defaults.
Doug Andersona1dcee82012-12-03 14:43:18 +000086
87 Args:
88 project_name: The name of the project.
89 """
90 self._project_name = project_name
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -050091 ConfigParser.ConfigParser.__init__(self)
Doug Andersona1dcee82012-12-03 14:43:18 +000092
93 # Update the project settings in the config based on
94 # the _default_settings global.
95 project_settings = "%s_settings" % project_name
96 if not self.has_section(project_settings):
97 self.add_section(project_settings)
98 project_defaults = _default_settings.get(project_name, {})
Paul Burtonc9eac382016-09-27 16:03:54 +010099 for setting_name, setting_value in project_defaults.items():
Doug Andersona1dcee82012-12-03 14:43:18 +0000100 self.set(project_settings, setting_name, setting_value)
101
102 def get(self, section, option, *args, **kwargs):
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500103 """Extend ConfigParser to try project_section before section.
Doug Andersona1dcee82012-12-03 14:43:18 +0000104
105 Args:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500106 See ConfigParser.
Doug Andersona1dcee82012-12-03 14:43:18 +0000107 Returns:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500108 See ConfigParser.
Doug Andersona1dcee82012-12-03 14:43:18 +0000109 """
110 try:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500111 val = ConfigParser.ConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000112 self, "%s_%s" % (self._project_name, section), option,
113 *args, **kwargs
114 )
115 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500116 val = ConfigParser.ConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000117 self, section, option, *args, **kwargs
118 )
Simon Glassfc0056e2020-11-08 20:36:18 -0700119 return val
Doug Andersona1dcee82012-12-03 14:43:18 +0000120
121 def items(self, section, *args, **kwargs):
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500122 """Extend ConfigParser to add project_section to section.
Doug Andersona1dcee82012-12-03 14:43:18 +0000123
124 Args:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500125 See ConfigParser.
Doug Andersona1dcee82012-12-03 14:43:18 +0000126 Returns:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500127 See ConfigParser.
Doug Andersona1dcee82012-12-03 14:43:18 +0000128 """
129 project_items = []
130 has_project_section = False
131 top_items = []
132
133 # Get items from the project section
134 try:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500135 project_items = ConfigParser.ConfigParser.items(
Doug Andersona1dcee82012-12-03 14:43:18 +0000136 self, "%s_%s" % (self._project_name, section), *args, **kwargs
137 )
138 has_project_section = True
139 except ConfigParser.NoSectionError:
140 pass
141
142 # Get top-level items
143 try:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500144 top_items = ConfigParser.ConfigParser.items(
Doug Andersona1dcee82012-12-03 14:43:18 +0000145 self, section, *args, **kwargs
146 )
147 except ConfigParser.NoSectionError:
148 # If neither section exists raise the error on...
149 if not has_project_section:
150 raise
151
152 item_dict = dict(top_items)
153 item_dict.update(project_items)
Simon Glassfc0056e2020-11-08 20:36:18 -0700154 return {(item, val) for item, val in item_dict.items()}
Doug Andersona1dcee82012-12-03 14:43:18 +0000155
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500156
Simon Glass0d24de92012-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 Glass272cd852019-10-31 07:42:51 -0600167 fd = open(fname, 'r', encoding='utf-8')
Simon Glass0d24de92012-01-14 15:12:45 +0000168 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100169 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000170 return
171
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500172 re_line = re.compile(r'alias\s+(\S+)\s+(.*)')
Simon Glass0d24de92012-01-14 15:12:45 +0000173 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 Burtona920a172016-09-27 16:03:50 +0100180 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass0d24de92012-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
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500192
Simon Glassdd3dac22020-06-07 06:45:49 -0600193def CreatePatmanConfigFile(gitutil, config_fname):
Vikram Narayanan87d65552012-05-23 09:01:06 +0000194 """Creates a config file under $(HOME)/.patman if it can't find one.
195
196 Args:
197 config_fname: Default config filename i.e., $(HOME)/.patman
198
199 Returns:
200 None
201 """
Simon Glass0157b182022-01-29 14:14:11 -0700202 name = gitutil.get_default_user_name()
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500203 if name is None:
Simon Glass32cc6ae2022-02-11 13:23:18 -0700204 name = input("Enter name: ")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000205
Simon Glass0157b182022-01-29 14:14:11 -0700206 email = gitutil.get_default_user_email()
Vikram Narayanan87d65552012-05-23 09:01:06 +0000207
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500208 if email is None:
Simon Glass32cc6ae2022-02-11 13:23:18 -0700209 email = input("Enter email: ")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000210
211 try:
212 f = open(config_fname, 'w')
213 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100214 print("Couldn't create patman config file\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000215 raise
216
Simon Glassad893142017-09-12 20:30:28 -0600217 print('''[alias]
218me: %s <%s>
219
220[bounces]
221nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
222''' % (name, email), file=f)
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500223 f.close()
224
Vikram Narayanan87d65552012-05-23 09:01:06 +0000225
Simon Glass3145b632020-11-03 13:54:13 -0700226def _UpdateDefaults(main_parser, config):
Doug Anderson8568bae2012-12-03 14:43:17 +0000227 """Update the given OptionParser defaults based on config.
228
Simon Glass3145b632020-11-03 13:54:13 -0700229 We'll walk through all of the settings from all parsers.
Doug Anderson8568bae2012-12-03 14:43:17 +0000230 For each setting we'll look for a default in the option parser.
231 If it's found we'll update the option parser default.
232
233 The idea here is that the .patman file should be able to update
234 defaults but that command line flags should still have the final
235 say.
236
237 Args:
Simon Glass3145b632020-11-03 13:54:13 -0700238 parser: An instance of an ArgumentParser whose defaults will be
Doug Anderson8568bae2012-12-03 14:43:17 +0000239 updated.
Doug Andersona1dcee82012-12-03 14:43:18 +0000240 config: An instance of _ProjectConfigParser that we will query
Doug Anderson8568bae2012-12-03 14:43:17 +0000241 for settings.
242 """
Simon Glass3145b632020-11-03 13:54:13 -0700243 # Find all the parsers and subparsers
244 parsers = [main_parser]
245 parsers += [subparser for action in main_parser._actions
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500246 if isinstance(action, argparse._SubParsersAction)
247 for _, subparser in action.choices.items()]
Simon Glass3145b632020-11-03 13:54:13 -0700248
249 # Collect the defaults from each parser
250 defaults = {}
Sean Anderson4780f7d2022-04-29 10:53:34 -0400251 parser_defaults = []
Simon Glass3145b632020-11-03 13:54:13 -0700252 for parser in parsers:
253 pdefs = parser.parse_known_args()[0]
Sean Anderson4780f7d2022-04-29 10:53:34 -0400254 parser_defaults.append(pdefs)
Simon Glass3145b632020-11-03 13:54:13 -0700255 defaults.update(vars(pdefs))
256
257 # Go through the settings and collect defaults
Doug Anderson8568bae2012-12-03 14:43:17 +0000258 for name, val in config.items('settings'):
Simon Glassfda1e372020-07-05 21:41:53 -0600259 if name in defaults:
260 default_val = defaults[name]
Doug Anderson8568bae2012-12-03 14:43:17 +0000261 if isinstance(default_val, bool):
262 val = config.getboolean('settings', name)
263 elif isinstance(default_val, int):
264 val = config.getint('settings', name)
Simon Glass3145b632020-11-03 13:54:13 -0700265 elif isinstance(default_val, str):
266 val = config.get('settings', name)
Simon Glassfda1e372020-07-05 21:41:53 -0600267 defaults[name] = val
Doug Anderson8568bae2012-12-03 14:43:17 +0000268 else:
Paul Burtona920a172016-09-27 16:03:50 +0100269 print("WARNING: Unknown setting %s" % name)
Simon Glass3145b632020-11-03 13:54:13 -0700270
Sean Anderson4780f7d2022-04-29 10:53:34 -0400271 # Set all the defaults and manually propagate them to subparsers
Simon Glass3145b632020-11-03 13:54:13 -0700272 main_parser.set_defaults(**defaults)
Sean Anderson4780f7d2022-04-29 10:53:34 -0400273 for parser, pdefs in zip(parsers, parser_defaults):
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500274 parser.set_defaults(**{k: v for k, v in defaults.items()
275 if k in pdefs})
276
Doug Anderson8568bae2012-12-03 14:43:17 +0000277
Simon Glass8895b3e2015-01-29 11:35:17 -0700278def _ReadAliasFile(fname):
279 """Read in the U-Boot git alias file if it exists.
280
281 Args:
282 fname: Filename to read.
283 """
284 if os.path.exists(fname):
285 bad_line = None
Simon Glass272cd852019-10-31 07:42:51 -0600286 with open(fname, encoding='utf-8') as fd:
Simon Glass8895b3e2015-01-29 11:35:17 -0700287 linenum = 0
288 for line in fd:
289 linenum += 1
290 line = line.strip()
291 if not line or line.startswith('#'):
292 continue
Adam Sampsonb8a48fb2018-06-27 14:38:58 +0100293 words = line.split(None, 2)
Simon Glass8895b3e2015-01-29 11:35:17 -0700294 if len(words) < 3 or words[0] != 'alias':
295 if not bad_line:
296 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
297 line)
298 continue
299 alias[words[1]] = [s.strip() for s in words[2].split(',')]
300 if bad_line:
Paul Burtona920a172016-09-27 16:03:50 +0100301 print(bad_line)
Simon Glass8895b3e2015-01-29 11:35:17 -0700302
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500303
Chris Packhame11aa602017-09-01 20:57:53 +1200304def _ReadBouncesFile(fname):
305 """Read in the bounces file if it exists
306
307 Args:
308 fname: Filename to read.
309 """
310 if os.path.exists(fname):
311 with open(fname) as fd:
312 for line in fd:
313 if line.startswith('#'):
314 continue
315 bounces.add(line.strip())
316
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500317
Simon Glassad893142017-09-12 20:30:28 -0600318def GetItems(config, section):
319 """Get the items from a section of the config.
320
321 Args:
322 config: _ProjectConfigParser object containing settings
323 section: name of section to retrieve
324
325 Returns:
326 List of (name, value) tuples for the section
327 """
328 try:
329 return config.items(section)
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500330 except ConfigParser.NoSectionError:
Simon Glassad893142017-09-12 20:30:28 -0600331 return []
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500332
Simon Glassad893142017-09-12 20:30:28 -0600333
Simon Glassdd3dac22020-06-07 06:45:49 -0600334def Setup(gitutil, parser, project_name, config_fname=''):
Simon Glass0d24de92012-01-14 15:12:45 +0000335 """Set up the settings module by reading config files.
336
337 Args:
Doug Anderson8568bae2012-12-03 14:43:17 +0000338 parser: The parser to update
Doug Andersona1dcee82012-12-03 14:43:18 +0000339 project_name: Name of project that we're working on; we'll look
340 for sections named "project_section" as well.
Simon Glass0d24de92012-01-14 15:12:45 +0000341 config_fname: Config filename to read ('' for default)
342 """
Simon Glass8895b3e2015-01-29 11:35:17 -0700343 # First read the git alias file if available
344 _ReadAliasFile('doc/git-mailrc')
Doug Andersona1dcee82012-12-03 14:43:18 +0000345 config = _ProjectConfigParser(project_name)
Simon Glass0d24de92012-01-14 15:12:45 +0000346 if config_fname == '':
Vikram Narayanan2b36c752012-05-23 08:58:58 +0000347 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan87d65552012-05-23 09:01:06 +0000348
349 if not os.path.exists(config_fname):
Paul Burtona920a172016-09-27 16:03:50 +0100350 print("No config file found ~/.patman\nCreating one...\n")
Simon Glassdd3dac22020-06-07 06:45:49 -0600351 CreatePatmanConfigFile(gitutil, config_fname)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000352
Doug Anderson8568bae2012-12-03 14:43:17 +0000353 config.read(config_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000354
Simon Glassad893142017-09-12 20:30:28 -0600355 for name, value in GetItems(config, 'alias'):
Simon Glass0d24de92012-01-14 15:12:45 +0000356 alias[name] = value.split(',')
357
Chris Packhame11aa602017-09-01 20:57:53 +1200358 _ReadBouncesFile('doc/bounces')
Simon Glassad893142017-09-12 20:30:28 -0600359 for name, value in GetItems(config, 'bounces'):
Chris Packhame11aa602017-09-01 20:57:53 +1200360 bounces.add(value)
361
Doug Anderson8568bae2012-12-03 14:43:17 +0000362 _UpdateDefaults(parser, config)
Simon Glass0d24de92012-01-14 15:12:45 +0000363
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500364
Simon Glass0d24de92012-01-14 15:12:45 +0000365# These are the aliases we understand, indexed by alias. Each member is a list.
366alias = {}
Chris Packhame11aa602017-09-01 20:57:53 +1200367bounces = set()
Doug Andersona1dcee82012-12-03 14:43:18 +0000368
369if __name__ == "__main__":
370 import doctest
371
372 doctest.testmod()