blob: 636983e32da8a4afad9a265e152ffcd420d88b9d [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.
Maxim Cournoyer8f8d3f72022-12-20 00:38:41 -05003# Copyright (c) 2022 Maxim Cournoyer <maxim.cournoyer@savoirfairelinux.com>
Simon Glass0d24de92012-01-14 15:12:45 +00004#
Simon Glass0d24de92012-01-14 15:12:45 +00005
Paul Burton2ce7b212016-09-27 16:03:52 +01006try:
7 import configparser as ConfigParser
Maxim Cournoyer872f3a42022-12-20 00:38:36 -05008except Exception:
Paul Burton2ce7b212016-09-27 16:03:52 +01009 import ConfigParser
10
Simon Glass3145b632020-11-03 13:54:13 -070011import argparse
Simon Glass0d24de92012-01-14 15:12:45 +000012import os
13import re
14
Maxim Cournoyer57e3b032022-12-20 00:38:38 -050015from patman import gitutil
16
Doug Andersona1dcee82012-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",
Douglas Andersondce43222022-07-19 14:56:27 -070026 "check_patch_use_tree": "True",
Philipp Tomsichddc44c22020-11-24 18:14:53 +010027 },
28 "gcc": {
29 "process_tags": "False",
30 "add_signoff": "False",
31 "check_patch": "False",
32 },
Doug Andersona1dcee82012-12-03 14:43:18 +000033}
34
Maxim Cournoyer872f3a42022-12-20 00:38:36 -050035
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -050036class _ProjectConfigParser(ConfigParser.ConfigParser):
Doug Andersona1dcee82012-12-03 14:43:18 +000037 """ConfigParser that handles projects.
38
39 There are two main goals of this class:
40 - Load project-specific default settings.
41 - Merge general default settings/aliases with project-specific ones.
42
43 # Sample config used for tests below...
Simon Glassc3a13cc2020-04-17 18:08:55 -060044 >>> from io import StringIO
Doug Andersona1dcee82012-12-03 14:43:18 +000045 >>> sample_config = '''
46 ... [alias]
47 ... me: Peter P. <likesspiders@example.com>
48 ... enemies: Evil <evil@example.com>
49 ...
50 ... [sm_alias]
51 ... enemies: Green G. <ugly@example.com>
52 ...
53 ... [sm2_alias]
54 ... enemies: Doc O. <pus@example.com>
55 ...
56 ... [settings]
57 ... am_hero: True
58 ... '''
59
60 # Check to make sure that bogus project gets general alias.
61 >>> config = _ProjectConfigParser("zzz")
Paul Burtonf5d44b92016-09-27 16:03:55 +010062 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060063 >>> str(config.get("alias", "enemies"))
64 'Evil <evil@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000065
66 # Check to make sure that alias gets overridden by project.
67 >>> config = _ProjectConfigParser("sm")
Paul Burtonf5d44b92016-09-27 16:03:55 +010068 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060069 >>> str(config.get("alias", "enemies"))
70 'Green G. <ugly@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000071
72 # Check to make sure that settings get merged with project.
73 >>> config = _ProjectConfigParser("linux")
Paul Burtonf5d44b92016-09-27 16:03:55 +010074 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060075 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
Douglas Andersondce43222022-07-19 14:56:27 -070076 [('am_hero', 'True'), ('check_patch_use_tree', 'True'), ('process_tags', 'False')]
Doug Andersona1dcee82012-12-03 14:43:18 +000077
78 # Check to make sure that settings works with unknown project.
79 >>> config = _ProjectConfigParser("unknown")
Paul Burtonf5d44b92016-09-27 16:03:55 +010080 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060081 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
82 [('am_hero', 'True')]
Doug Andersona1dcee82012-12-03 14:43:18 +000083 """
84 def __init__(self, project_name):
85 """Construct _ProjectConfigParser.
86
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -050087 In addition to standard ConfigParser initialization, this also
88 loads project defaults.
Doug Andersona1dcee82012-12-03 14:43:18 +000089
90 Args:
91 project_name: The name of the project.
92 """
93 self._project_name = project_name
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -050094 ConfigParser.ConfigParser.__init__(self)
Doug Andersona1dcee82012-12-03 14:43:18 +000095
96 # Update the project settings in the config based on
97 # the _default_settings global.
98 project_settings = "%s_settings" % project_name
99 if not self.has_section(project_settings):
100 self.add_section(project_settings)
101 project_defaults = _default_settings.get(project_name, {})
Paul Burtonc9eac382016-09-27 16:03:54 +0100102 for setting_name, setting_value in project_defaults.items():
Doug Andersona1dcee82012-12-03 14:43:18 +0000103 self.set(project_settings, setting_name, setting_value)
104
105 def get(self, section, option, *args, **kwargs):
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500106 """Extend ConfigParser to try project_section before section.
Doug Andersona1dcee82012-12-03 14:43:18 +0000107
108 Args:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500109 See ConfigParser.
Doug Andersona1dcee82012-12-03 14:43:18 +0000110 Returns:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500111 See ConfigParser.
Doug Andersona1dcee82012-12-03 14:43:18 +0000112 """
113 try:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500114 val = ConfigParser.ConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000115 self, "%s_%s" % (self._project_name, section), option,
116 *args, **kwargs
117 )
118 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500119 val = ConfigParser.ConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000120 self, section, option, *args, **kwargs
121 )
Simon Glassfc0056e2020-11-08 20:36:18 -0700122 return val
Doug Andersona1dcee82012-12-03 14:43:18 +0000123
124 def items(self, section, *args, **kwargs):
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500125 """Extend ConfigParser to add project_section to section.
Doug Andersona1dcee82012-12-03 14:43:18 +0000126
127 Args:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500128 See ConfigParser.
Doug Andersona1dcee82012-12-03 14:43:18 +0000129 Returns:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500130 See ConfigParser.
Doug Andersona1dcee82012-12-03 14:43:18 +0000131 """
132 project_items = []
133 has_project_section = False
134 top_items = []
135
136 # Get items from the project section
137 try:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500138 project_items = ConfigParser.ConfigParser.items(
Doug Andersona1dcee82012-12-03 14:43:18 +0000139 self, "%s_%s" % (self._project_name, section), *args, **kwargs
140 )
141 has_project_section = True
142 except ConfigParser.NoSectionError:
143 pass
144
145 # Get top-level items
146 try:
Maxim Cournoyer8b73f9b2022-12-20 00:38:37 -0500147 top_items = ConfigParser.ConfigParser.items(
Doug Andersona1dcee82012-12-03 14:43:18 +0000148 self, section, *args, **kwargs
149 )
150 except ConfigParser.NoSectionError:
151 # If neither section exists raise the error on...
152 if not has_project_section:
153 raise
154
155 item_dict = dict(top_items)
156 item_dict.update(project_items)
Simon Glassfc0056e2020-11-08 20:36:18 -0700157 return {(item, val) for item, val in item_dict.items()}
Doug Andersona1dcee82012-12-03 14:43:18 +0000158
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500159
Simon Glass0d24de92012-01-14 15:12:45 +0000160def ReadGitAliases(fname):
161 """Read a git alias file. This is in the form used by git:
162
163 alias uboot u-boot@lists.denx.de
164 alias wd Wolfgang Denk <wd@denx.de>
165
166 Args:
167 fname: Filename to read
168 """
169 try:
Simon Glass272cd852019-10-31 07:42:51 -0600170 fd = open(fname, 'r', encoding='utf-8')
Simon Glass0d24de92012-01-14 15:12:45 +0000171 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100172 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000173 return
174
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500175 re_line = re.compile(r'alias\s+(\S+)\s+(.*)')
Simon Glass0d24de92012-01-14 15:12:45 +0000176 for line in fd.readlines():
177 line = line.strip()
178 if not line or line[0] == '#':
179 continue
180
181 m = re_line.match(line)
182 if not m:
Paul Burtona920a172016-09-27 16:03:50 +0100183 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass0d24de92012-01-14 15:12:45 +0000184 continue
185
186 list = alias.get(m.group(1), [])
187 for item in m.group(2).split(','):
188 item = item.strip()
189 if item:
190 list.append(item)
191 alias[m.group(1)] = list
192
193 fd.close()
194
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500195
Maxim Cournoyer57e3b032022-12-20 00:38:38 -0500196def CreatePatmanConfigFile(config_fname):
Vikram Narayanan87d65552012-05-23 09:01:06 +0000197 """Creates a config file under $(HOME)/.patman if it can't find one.
198
199 Args:
200 config_fname: Default config filename i.e., $(HOME)/.patman
201
202 Returns:
203 None
204 """
Simon Glass0157b182022-01-29 14:14:11 -0700205 name = gitutil.get_default_user_name()
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500206 if name is None:
Simon Glass32cc6ae2022-02-11 13:23:18 -0700207 name = input("Enter name: ")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000208
Simon Glass0157b182022-01-29 14:14:11 -0700209 email = gitutil.get_default_user_email()
Vikram Narayanan87d65552012-05-23 09:01:06 +0000210
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500211 if email is None:
Simon Glass32cc6ae2022-02-11 13:23:18 -0700212 email = input("Enter email: ")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000213
214 try:
215 f = open(config_fname, 'w')
216 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100217 print("Couldn't create patman config file\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000218 raise
219
Simon Glassad893142017-09-12 20:30:28 -0600220 print('''[alias]
221me: %s <%s>
222
223[bounces]
224nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
225''' % (name, email), file=f)
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500226 f.close()
227
Vikram Narayanan87d65552012-05-23 09:01:06 +0000228
Simon Glass3145b632020-11-03 13:54:13 -0700229def _UpdateDefaults(main_parser, config):
Doug Anderson8568bae2012-12-03 14:43:17 +0000230 """Update the given OptionParser defaults based on config.
231
Simon Glass3145b632020-11-03 13:54:13 -0700232 We'll walk through all of the settings from all parsers.
Doug Anderson8568bae2012-12-03 14:43:17 +0000233 For each setting we'll look for a default in the option parser.
234 If it's found we'll update the option parser default.
235
236 The idea here is that the .patman file should be able to update
237 defaults but that command line flags should still have the final
238 say.
239
240 Args:
Simon Glass3145b632020-11-03 13:54:13 -0700241 parser: An instance of an ArgumentParser whose defaults will be
Doug Anderson8568bae2012-12-03 14:43:17 +0000242 updated.
Doug Andersona1dcee82012-12-03 14:43:18 +0000243 config: An instance of _ProjectConfigParser that we will query
Doug Anderson8568bae2012-12-03 14:43:17 +0000244 for settings.
245 """
Simon Glass3145b632020-11-03 13:54:13 -0700246 # Find all the parsers and subparsers
247 parsers = [main_parser]
248 parsers += [subparser for action in main_parser._actions
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500249 if isinstance(action, argparse._SubParsersAction)
250 for _, subparser in action.choices.items()]
Simon Glass3145b632020-11-03 13:54:13 -0700251
252 # Collect the defaults from each parser
253 defaults = {}
Sean Anderson4780f7d2022-04-29 10:53:34 -0400254 parser_defaults = []
Simon Glass3145b632020-11-03 13:54:13 -0700255 for parser in parsers:
256 pdefs = parser.parse_known_args()[0]
Sean Anderson4780f7d2022-04-29 10:53:34 -0400257 parser_defaults.append(pdefs)
Simon Glass3145b632020-11-03 13:54:13 -0700258 defaults.update(vars(pdefs))
259
260 # Go through the settings and collect defaults
Doug Anderson8568bae2012-12-03 14:43:17 +0000261 for name, val in config.items('settings'):
Simon Glassfda1e372020-07-05 21:41:53 -0600262 if name in defaults:
263 default_val = defaults[name]
Doug Anderson8568bae2012-12-03 14:43:17 +0000264 if isinstance(default_val, bool):
265 val = config.getboolean('settings', name)
266 elif isinstance(default_val, int):
267 val = config.getint('settings', name)
Simon Glass3145b632020-11-03 13:54:13 -0700268 elif isinstance(default_val, str):
269 val = config.get('settings', name)
Simon Glassfda1e372020-07-05 21:41:53 -0600270 defaults[name] = val
Doug Anderson8568bae2012-12-03 14:43:17 +0000271 else:
Paul Burtona920a172016-09-27 16:03:50 +0100272 print("WARNING: Unknown setting %s" % name)
Simon Glass3145b632020-11-03 13:54:13 -0700273
Sean Anderson4780f7d2022-04-29 10:53:34 -0400274 # Set all the defaults and manually propagate them to subparsers
Simon Glass3145b632020-11-03 13:54:13 -0700275 main_parser.set_defaults(**defaults)
Sean Anderson4780f7d2022-04-29 10:53:34 -0400276 for parser, pdefs in zip(parsers, parser_defaults):
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500277 parser.set_defaults(**{k: v for k, v in defaults.items()
278 if k in pdefs})
279
Doug Anderson8568bae2012-12-03 14:43:17 +0000280
Simon Glass8895b3e2015-01-29 11:35:17 -0700281def _ReadAliasFile(fname):
282 """Read in the U-Boot git alias file if it exists.
283
284 Args:
285 fname: Filename to read.
286 """
287 if os.path.exists(fname):
288 bad_line = None
Simon Glass272cd852019-10-31 07:42:51 -0600289 with open(fname, encoding='utf-8') as fd:
Simon Glass8895b3e2015-01-29 11:35:17 -0700290 linenum = 0
291 for line in fd:
292 linenum += 1
293 line = line.strip()
294 if not line or line.startswith('#'):
295 continue
Adam Sampsonb8a48fb2018-06-27 14:38:58 +0100296 words = line.split(None, 2)
Simon Glass8895b3e2015-01-29 11:35:17 -0700297 if len(words) < 3 or words[0] != 'alias':
298 if not bad_line:
299 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
300 line)
301 continue
302 alias[words[1]] = [s.strip() for s in words[2].split(',')]
303 if bad_line:
Paul Burtona920a172016-09-27 16:03:50 +0100304 print(bad_line)
Simon Glass8895b3e2015-01-29 11:35:17 -0700305
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500306
Chris Packhame11aa602017-09-01 20:57:53 +1200307def _ReadBouncesFile(fname):
308 """Read in the bounces file if it exists
309
310 Args:
311 fname: Filename to read.
312 """
313 if os.path.exists(fname):
314 with open(fname) as fd:
315 for line in fd:
316 if line.startswith('#'):
317 continue
318 bounces.add(line.strip())
319
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500320
Simon Glassad893142017-09-12 20:30:28 -0600321def GetItems(config, section):
322 """Get the items from a section of the config.
323
324 Args:
325 config: _ProjectConfigParser object containing settings
326 section: name of section to retrieve
327
328 Returns:
329 List of (name, value) tuples for the section
330 """
331 try:
332 return config.items(section)
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500333 except ConfigParser.NoSectionError:
Simon Glassad893142017-09-12 20:30:28 -0600334 return []
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500335
Simon Glassad893142017-09-12 20:30:28 -0600336
Maxim Cournoyer2c58a5e2022-12-20 00:38:39 -0500337def Setup(parser, project_name, config_fname=None):
Simon Glass0d24de92012-01-14 15:12:45 +0000338 """Set up the settings module by reading config files.
339
Maxim Cournoyer8f8d3f72022-12-20 00:38:41 -0500340 Unless `config_fname` is specified, a `.patman` config file local
341 to the git repository is consulted, followed by the global
342 `$HOME/.patman`. If none exists, the later is created. Values
343 defined in the local config file take precedence over those
344 defined in the global one.
345
Simon Glass0d24de92012-01-14 15:12:45 +0000346 Args:
Maxim Cournoyer2c58a5e2022-12-20 00:38:39 -0500347 parser: The parser to update.
Doug Andersona1dcee82012-12-03 14:43:18 +0000348 project_name: Name of project that we're working on; we'll look
349 for sections named "project_section" as well.
Maxim Cournoyerdb16edd2022-12-20 00:38:40 -0500350 config_fname: Config filename to read. An error is raised if it
351 does not exist.
Simon Glass0d24de92012-01-14 15:12:45 +0000352 """
Simon Glass8895b3e2015-01-29 11:35:17 -0700353 # First read the git alias file if available
354 _ReadAliasFile('doc/git-mailrc')
Doug Andersona1dcee82012-12-03 14:43:18 +0000355 config = _ProjectConfigParser(project_name)
Maxim Cournoyer2c58a5e2022-12-20 00:38:39 -0500356
Maxim Cournoyerdb16edd2022-12-20 00:38:40 -0500357 if config_fname and not os.path.exists(config_fname):
358 raise Exception(f'provided {config_fname} does not exist')
359
Maxim Cournoyer2c58a5e2022-12-20 00:38:39 -0500360 if not config_fname:
Vikram Narayanan2b36c752012-05-23 08:58:58 +0000361 config_fname = '%s/.patman' % os.getenv('HOME')
Maxim Cournoyer8f8d3f72022-12-20 00:38:41 -0500362 has_config = os.path.exists(config_fname)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000363
Maxim Cournoyer8f8d3f72022-12-20 00:38:41 -0500364 git_local_config_fname = os.path.join(gitutil.get_top_level(), '.patman')
365 has_git_local_config = os.path.exists(git_local_config_fname)
366
367 # Read the git local config last, so that its values override
368 # those of the global config, if any.
369 if has_config:
370 config.read(config_fname)
371 if has_git_local_config:
372 config.read(git_local_config_fname)
373
374 if not (has_config or has_git_local_config):
375 print("No config file found.\nCreating ~/.patman...\n")
Maxim Cournoyer57e3b032022-12-20 00:38:38 -0500376 CreatePatmanConfigFile(config_fname)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000377
Simon Glassad893142017-09-12 20:30:28 -0600378 for name, value in GetItems(config, 'alias'):
Simon Glass0d24de92012-01-14 15:12:45 +0000379 alias[name] = value.split(',')
380
Chris Packhame11aa602017-09-01 20:57:53 +1200381 _ReadBouncesFile('doc/bounces')
Simon Glassad893142017-09-12 20:30:28 -0600382 for name, value in GetItems(config, 'bounces'):
Chris Packhame11aa602017-09-01 20:57:53 +1200383 bounces.add(value)
384
Doug Anderson8568bae2012-12-03 14:43:17 +0000385 _UpdateDefaults(parser, config)
Simon Glass0d24de92012-01-14 15:12:45 +0000386
Maxim Cournoyer872f3a42022-12-20 00:38:36 -0500387
Simon Glass0d24de92012-01-14 15:12:45 +0000388# These are the aliases we understand, indexed by alias. Each member is a list.
389alias = {}
Chris Packhame11aa602017-09-01 20:57:53 +1200390bounces = set()
Doug Andersona1dcee82012-12-03 14:43:18 +0000391
392if __name__ == "__main__":
393 import doctest
394
395 doctest.testmod()