blob: 7fb9d6d5a083005d1dd30d4c5f481fb83951c097 [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
Doug Anderson31ffd7f2012-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 Andersonbe4f2712022-07-19 14:56:27 -070023 "check_patch_use_tree": "True",
Philipp Tomsich98373d42020-11-24 18:14:53 +010024 },
25 "gcc": {
26 "process_tags": "False",
27 "add_signoff": "False",
28 "check_patch": "False",
29 },
Doug Anderson31ffd7f2012-12-03 14:43:18 +000030}
31
Maxim Cournoyer97f4d762022-12-20 00:38:36 -050032
Maxim Cournoyercc38d352022-12-20 00:38:37 -050033class _ProjectConfigParser(ConfigParser.ConfigParser):
Doug Anderson31ffd7f2012-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 Glass945328b2020-04-17 18:08:55 -060041 >>> from io import StringIO
Doug Anderson31ffd7f2012-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 Burton44fc9ba2016-09-27 16:03:55 +010059 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060060 >>> str(config.get("alias", "enemies"))
61 'Evil <evil@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000062
63 # Check to make sure that alias gets overridden by project.
64 >>> config = _ProjectConfigParser("sm")
Paul Burton44fc9ba2016-09-27 16:03:55 +010065 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060066 >>> str(config.get("alias", "enemies"))
67 'Green G. <ugly@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000068
69 # Check to make sure that settings get merged with project.
70 >>> config = _ProjectConfigParser("linux")
Paul Burton44fc9ba2016-09-27 16:03:55 +010071 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060072 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
Douglas Andersonbe4f2712022-07-19 14:56:27 -070073 [('am_hero', 'True'), ('check_patch_use_tree', 'True'), ('process_tags', 'False')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000074
75 # Check to make sure that settings works with unknown project.
76 >>> config = _ProjectConfigParser("unknown")
Paul Burton44fc9ba2016-09-27 16:03:55 +010077 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060078 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
79 [('am_hero', 'True')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000080 """
81 def __init__(self, project_name):
82 """Construct _ProjectConfigParser.
83
Maxim Cournoyercc38d352022-12-20 00:38:37 -050084 In addition to standard ConfigParser initialization, this also
85 loads project defaults.
Doug Anderson31ffd7f2012-12-03 14:43:18 +000086
87 Args:
88 project_name: The name of the project.
89 """
90 self._project_name = project_name
Maxim Cournoyercc38d352022-12-20 00:38:37 -050091 ConfigParser.ConfigParser.__init__(self)
Doug Anderson31ffd7f2012-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 Burton4466f1a2016-09-27 16:03:54 +010099 for setting_name, setting_value in project_defaults.items():
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000100 self.set(project_settings, setting_name, setting_value)
101
102 def get(self, section, option, *args, **kwargs):
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500103 """Extend ConfigParser to try project_section before section.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000104
105 Args:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500106 See ConfigParser.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000107 Returns:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500108 See ConfigParser.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000109 """
110 try:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500111 val = ConfigParser.ConfigParser.get(
Doug Anderson31ffd7f2012-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 Cournoyercc38d352022-12-20 00:38:37 -0500116 val = ConfigParser.ConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000117 self, section, option, *args, **kwargs
118 )
Simon Glass9dfb3112020-11-08 20:36:18 -0700119 return val
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000120
121 def items(self, section, *args, **kwargs):
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500122 """Extend ConfigParser to add project_section to section.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000123
124 Args:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500125 See ConfigParser.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000126 Returns:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500127 See ConfigParser.
Doug Anderson31ffd7f2012-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 Cournoyercc38d352022-12-20 00:38:37 -0500135 project_items = ConfigParser.ConfigParser.items(
Doug Anderson31ffd7f2012-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 Cournoyercc38d352022-12-20 00:38:37 -0500144 top_items = ConfigParser.ConfigParser.items(
Doug Anderson31ffd7f2012-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 Glass9dfb3112020-11-08 20:36:18 -0700154 return {(item, val) for item, val in item_dict.items()}
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000155
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500156
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
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500172 re_line = re.compile(r'alias\s+(\S+)\s+(.*)')
Simon Glass26132882012-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 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
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500192
Simon Glassff15dcd2020-06-07 06:45:49 -0600193def CreatePatmanConfigFile(gitutil, config_fname):
Vikram Narayanan12fb29a2012-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 Glass761648b2022-01-29 14:14:11 -0700202 name = gitutil.get_default_user_name()
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500203 if name is None:
Simon Glass547cba62022-02-11 13:23:18 -0700204 name = input("Enter name: ")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000205
Simon Glass761648b2022-01-29 14:14:11 -0700206 email = gitutil.get_default_user_email()
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000207
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500208 if email is None:
Simon Glass547cba62022-02-11 13:23:18 -0700209 email = input("Enter email: ")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000210
211 try:
212 f = open(config_fname, 'w')
213 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100214 print("Couldn't create patman config file\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000215 raise
216
Simon Glass52a08642017-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 Cournoyer97f4d762022-12-20 00:38:36 -0500223 f.close()
224
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000225
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700226def _UpdateDefaults(main_parser, config):
Doug Anderson3d3077c2012-12-03 14:43:17 +0000227 """Update the given OptionParser defaults based on config.
228
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700229 We'll walk through all of the settings from all parsers.
Doug Anderson3d3077c2012-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 Glass9bb0a8d2020-11-03 13:54:13 -0700238 parser: An instance of an ArgumentParser whose defaults will be
Doug Anderson3d3077c2012-12-03 14:43:17 +0000239 updated.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000240 config: An instance of _ProjectConfigParser that we will query
Doug Anderson3d3077c2012-12-03 14:43:17 +0000241 for settings.
242 """
Simon Glass9bb0a8d2020-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 Cournoyer97f4d762022-12-20 00:38:36 -0500246 if isinstance(action, argparse._SubParsersAction)
247 for _, subparser in action.choices.items()]
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700248
249 # Collect the defaults from each parser
250 defaults = {}
Sean Anderson461df862022-04-29 10:53:34 -0400251 parser_defaults = []
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700252 for parser in parsers:
253 pdefs = parser.parse_known_args()[0]
Sean Anderson461df862022-04-29 10:53:34 -0400254 parser_defaults.append(pdefs)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700255 defaults.update(vars(pdefs))
256
257 # Go through the settings and collect defaults
Doug Anderson3d3077c2012-12-03 14:43:17 +0000258 for name, val in config.items('settings'):
Simon Glasseb101ac2020-07-05 21:41:53 -0600259 if name in defaults:
260 default_val = defaults[name]
Doug Anderson3d3077c2012-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 Glass9bb0a8d2020-11-03 13:54:13 -0700265 elif isinstance(default_val, str):
266 val = config.get('settings', name)
Simon Glasseb101ac2020-07-05 21:41:53 -0600267 defaults[name] = val
Doug Anderson3d3077c2012-12-03 14:43:17 +0000268 else:
Paul Burtonc3931342016-09-27 16:03:50 +0100269 print("WARNING: Unknown setting %s" % name)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700270
Sean Anderson461df862022-04-29 10:53:34 -0400271 # Set all the defaults and manually propagate them to subparsers
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700272 main_parser.set_defaults(**defaults)
Sean Anderson461df862022-04-29 10:53:34 -0400273 for parser, pdefs in zip(parsers, parser_defaults):
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500274 parser.set_defaults(**{k: v for k, v in defaults.items()
275 if k in pdefs})
276
Doug Anderson3d3077c2012-12-03 14:43:17 +0000277
Simon Glass33382692015-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 Glassf544a2d2019-10-31 07:42:51 -0600286 with open(fname, encoding='utf-8') as fd:
Simon Glass33382692015-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 Sampson02651322018-06-27 14:38:58 +0100293 words = line.split(None, 2)
Simon Glass33382692015-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 Burtonc3931342016-09-27 16:03:50 +0100301 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700302
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500303
Chris Packhame8d2a122017-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 Cournoyer97f4d762022-12-20 00:38:36 -0500317
Simon Glass52a08642017-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 Cournoyer97f4d762022-12-20 00:38:36 -0500330 except ConfigParser.NoSectionError:
Simon Glass52a08642017-09-12 20:30:28 -0600331 return []
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500332
Simon Glass52a08642017-09-12 20:30:28 -0600333
Simon Glassff15dcd2020-06-07 06:45:49 -0600334def Setup(gitutil, parser, project_name, config_fname=''):
Simon Glass26132882012-01-14 15:12:45 +0000335 """Set up the settings module by reading config files.
336
337 Args:
Doug Anderson3d3077c2012-12-03 14:43:17 +0000338 parser: The parser to update
Doug Anderson31ffd7f2012-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 Glass26132882012-01-14 15:12:45 +0000341 config_fname: Config filename to read ('' for default)
342 """
Simon Glass33382692015-01-29 11:35:17 -0700343 # First read the git alias file if available
344 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000345 config = _ProjectConfigParser(project_name)
Simon Glass26132882012-01-14 15:12:45 +0000346 if config_fname == '':
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000347 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000348
349 if not os.path.exists(config_fname):
Paul Burtonc3931342016-09-27 16:03:50 +0100350 print("No config file found ~/.patman\nCreating one...\n")
Simon Glassff15dcd2020-06-07 06:45:49 -0600351 CreatePatmanConfigFile(gitutil, config_fname)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000352
Doug Anderson3d3077c2012-12-03 14:43:17 +0000353 config.read(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000354
Simon Glass52a08642017-09-12 20:30:28 -0600355 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000356 alias[name] = value.split(',')
357
Chris Packhame8d2a122017-09-01 20:57:53 +1200358 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600359 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200360 bounces.add(value)
361
Doug Anderson3d3077c2012-12-03 14:43:17 +0000362 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000363
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500364
Simon Glass26132882012-01-14 15:12:45 +0000365# These are the aliases we understand, indexed by alias. Each member is a list.
366alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200367bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000368
369if __name__ == "__main__":
370 import doctest
371
372 doctest.testmod()