blob: 7a0866cd370b36be9b7ca36ae81c08f06af8adab [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.
Maxim Cournoyercb8d3b12022-12-20 00:38:41 -05003# Copyright (c) 2022 Maxim Cournoyer <maxim.cournoyer@savoirfairelinux.com>
Simon Glass26132882012-01-14 15:12:45 +00004#
Simon Glass26132882012-01-14 15:12:45 +00005
Paul Burton352f1102016-09-27 16:03:52 +01006try:
7 import configparser as ConfigParser
Maxim Cournoyer97f4d762022-12-20 00:38:36 -05008except Exception:
Paul Burton352f1102016-09-27 16:03:52 +01009 import ConfigParser
10
Simon Glass9bb0a8d2020-11-03 13:54:13 -070011import argparse
Simon Glass26132882012-01-14 15:12:45 +000012import os
13import re
14
Simon Glassba1b3b92025-02-09 14:26:00 -070015from u_boot_pylib import gitutil
Maxim Cournoyera81a41f2022-12-20 00:38:38 -050016
Doug Anderson31ffd7f2012-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 Andersonbe4f2712022-07-19 14:56:27 -070026 "check_patch_use_tree": "True",
Philipp Tomsich98373d42020-11-24 18:14:53 +010027 },
28 "gcc": {
29 "process_tags": "False",
30 "add_signoff": "False",
31 "check_patch": "False",
32 },
Doug Anderson31ffd7f2012-12-03 14:43:18 +000033}
34
Maxim Cournoyer97f4d762022-12-20 00:38:36 -050035
Maxim Cournoyercc38d352022-12-20 00:38:37 -050036class _ProjectConfigParser(ConfigParser.ConfigParser):
Doug Anderson31ffd7f2012-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 Glass945328b2020-04-17 18:08:55 -060044 >>> from io import StringIO
Doug Anderson31ffd7f2012-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")
Brandon Maier9f070262024-06-04 16:16:07 +000062 >>> config.read_file(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060063 >>> str(config.get("alias", "enemies"))
64 'Evil <evil@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000065
66 # Check to make sure that alias gets overridden by project.
67 >>> config = _ProjectConfigParser("sm")
Brandon Maier9f070262024-06-04 16:16:07 +000068 >>> config.read_file(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060069 >>> str(config.get("alias", "enemies"))
70 'Green G. <ugly@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000071
72 # Check to make sure that settings get merged with project.
73 >>> config = _ProjectConfigParser("linux")
Brandon Maier9f070262024-06-04 16:16:07 +000074 >>> config.read_file(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060075 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
Douglas Andersonbe4f2712022-07-19 14:56:27 -070076 [('am_hero', 'True'), ('check_patch_use_tree', 'True'), ('process_tags', 'False')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000077
78 # Check to make sure that settings works with unknown project.
79 >>> config = _ProjectConfigParser("unknown")
Brandon Maier9f070262024-06-04 16:16:07 +000080 >>> config.read_file(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060081 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
82 [('am_hero', 'True')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000083 """
84 def __init__(self, project_name):
85 """Construct _ProjectConfigParser.
86
Maxim Cournoyercc38d352022-12-20 00:38:37 -050087 In addition to standard ConfigParser initialization, this also
88 loads project defaults.
Doug Anderson31ffd7f2012-12-03 14:43:18 +000089
90 Args:
91 project_name: The name of the project.
92 """
93 self._project_name = project_name
Maxim Cournoyercc38d352022-12-20 00:38:37 -050094 ConfigParser.ConfigParser.__init__(self)
Doug Anderson31ffd7f2012-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 Burton4466f1a2016-09-27 16:03:54 +0100102 for setting_name, setting_value in project_defaults.items():
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000103 self.set(project_settings, setting_name, setting_value)
104
105 def get(self, section, option, *args, **kwargs):
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500106 """Extend ConfigParser to try project_section before section.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000107
108 Args:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500109 See ConfigParser.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000110 Returns:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500111 See ConfigParser.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000112 """
113 try:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500114 val = ConfigParser.ConfigParser.get(
Doug Anderson31ffd7f2012-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 Cournoyercc38d352022-12-20 00:38:37 -0500119 val = ConfigParser.ConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000120 self, section, option, *args, **kwargs
121 )
Simon Glass9dfb3112020-11-08 20:36:18 -0700122 return val
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000123
124 def items(self, section, *args, **kwargs):
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500125 """Extend ConfigParser to add project_section to section.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000126
127 Args:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500128 See ConfigParser.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000129 Returns:
Maxim Cournoyercc38d352022-12-20 00:38:37 -0500130 See ConfigParser.
Doug Anderson31ffd7f2012-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 Cournoyercc38d352022-12-20 00:38:37 -0500138 project_items = ConfigParser.ConfigParser.items(
Doug Anderson31ffd7f2012-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 Cournoyercc38d352022-12-20 00:38:37 -0500147 top_items = ConfigParser.ConfigParser.items(
Doug Anderson31ffd7f2012-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 Glass9dfb3112020-11-08 20:36:18 -0700157 return {(item, val) for item, val in item_dict.items()}
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000158
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500159
Simon Glass26132882012-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 Glassf544a2d2019-10-31 07:42:51 -0600170 fd = open(fname, 'r', encoding='utf-8')
Simon Glass26132882012-01-14 15:12:45 +0000171 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100172 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass26132882012-01-14 15:12:45 +0000173 return
174
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500175 re_line = re.compile(r'alias\s+(\S+)\s+(.*)')
Simon Glass26132882012-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 Burtonc3931342016-09-27 16:03:50 +0100183 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass26132882012-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 Cournoyer97f4d762022-12-20 00:38:36 -0500195
Maxim Cournoyera81a41f2022-12-20 00:38:38 -0500196def CreatePatmanConfigFile(config_fname):
Vikram Narayanan12fb29a2012-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 Glass761648b2022-01-29 14:14:11 -0700205 name = gitutil.get_default_user_name()
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500206 if name is None:
Simon Glass547cba62022-02-11 13:23:18 -0700207 name = input("Enter name: ")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000208
Simon Glass761648b2022-01-29 14:14:11 -0700209 email = gitutil.get_default_user_email()
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000210
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500211 if email is None:
Simon Glass547cba62022-02-11 13:23:18 -0700212 email = input("Enter email: ")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000213
214 try:
215 f = open(config_fname, 'w')
216 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100217 print("Couldn't create patman config file\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000218 raise
219
Simon Glass52a08642017-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 Cournoyer97f4d762022-12-20 00:38:36 -0500226 f.close()
227
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000228
Simon Glass8b521cf2025-05-08 05:58:10 +0200229def _UpdateDefaults(main_parser, config, argv):
Doug Anderson3d3077c2012-12-03 14:43:17 +0000230 """Update the given OptionParser defaults based on config.
231
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700232 We'll walk through all of the settings from all parsers.
Doug Anderson3d3077c2012-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 Glass9bb0a8d2020-11-03 13:54:13 -0700241 parser: An instance of an ArgumentParser whose defaults will be
Doug Anderson3d3077c2012-12-03 14:43:17 +0000242 updated.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000243 config: An instance of _ProjectConfigParser that we will query
Doug Anderson3d3077c2012-12-03 14:43:17 +0000244 for settings.
Simon Glass8b521cf2025-05-08 05:58:10 +0200245 argv (list of str or None): Arguments to parse
Doug Anderson3d3077c2012-12-03 14:43:17 +0000246 """
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700247 # Find all the parsers and subparsers
248 parsers = [main_parser]
249 parsers += [subparser for action in main_parser._actions
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500250 if isinstance(action, argparse._SubParsersAction)
251 for _, subparser in action.choices.items()]
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700252
253 # Collect the defaults from each parser
254 defaults = {}
Sean Anderson461df862022-04-29 10:53:34 -0400255 parser_defaults = []
Simon Glass8b521cf2025-05-08 05:58:10 +0200256 argv = list(argv)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700257 for parser in parsers:
258 pdefs = parser.parse_known_args()[0]
Sean Anderson461df862022-04-29 10:53:34 -0400259 parser_defaults.append(pdefs)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700260 defaults.update(vars(pdefs))
261
262 # Go through the settings and collect defaults
Doug Anderson3d3077c2012-12-03 14:43:17 +0000263 for name, val in config.items('settings'):
Simon Glasseb101ac2020-07-05 21:41:53 -0600264 if name in defaults:
265 default_val = defaults[name]
Doug Anderson3d3077c2012-12-03 14:43:17 +0000266 if isinstance(default_val, bool):
267 val = config.getboolean('settings', name)
268 elif isinstance(default_val, int):
269 val = config.getint('settings', name)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700270 elif isinstance(default_val, str):
271 val = config.get('settings', name)
Simon Glasseb101ac2020-07-05 21:41:53 -0600272 defaults[name] = val
Doug Anderson3d3077c2012-12-03 14:43:17 +0000273 else:
Paul Burtonc3931342016-09-27 16:03:50 +0100274 print("WARNING: Unknown setting %s" % name)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700275
Sean Anderson461df862022-04-29 10:53:34 -0400276 # Set all the defaults and manually propagate them to subparsers
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700277 main_parser.set_defaults(**defaults)
Simon Glass8b521cf2025-05-08 05:58:10 +0200278 assert len(parsers) == len(parser_defaults)
Sean Anderson461df862022-04-29 10:53:34 -0400279 for parser, pdefs in zip(parsers, parser_defaults):
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500280 parser.set_defaults(**{k: v for k, v in defaults.items()
281 if k in pdefs})
Simon Glass8b521cf2025-05-08 05:58:10 +0200282 return defaults
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500283
Doug Anderson3d3077c2012-12-03 14:43:17 +0000284
Simon Glass33382692015-01-29 11:35:17 -0700285def _ReadAliasFile(fname):
286 """Read in the U-Boot git alias file if it exists.
287
288 Args:
289 fname: Filename to read.
290 """
291 if os.path.exists(fname):
292 bad_line = None
Simon Glassf544a2d2019-10-31 07:42:51 -0600293 with open(fname, encoding='utf-8') as fd:
Simon Glass33382692015-01-29 11:35:17 -0700294 linenum = 0
295 for line in fd:
296 linenum += 1
297 line = line.strip()
298 if not line or line.startswith('#'):
299 continue
Adam Sampson02651322018-06-27 14:38:58 +0100300 words = line.split(None, 2)
Simon Glass33382692015-01-29 11:35:17 -0700301 if len(words) < 3 or words[0] != 'alias':
302 if not bad_line:
303 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
304 line)
305 continue
306 alias[words[1]] = [s.strip() for s in words[2].split(',')]
307 if bad_line:
Paul Burtonc3931342016-09-27 16:03:50 +0100308 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700309
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500310
Chris Packhame8d2a122017-09-01 20:57:53 +1200311def _ReadBouncesFile(fname):
312 """Read in the bounces file if it exists
313
314 Args:
315 fname: Filename to read.
316 """
317 if os.path.exists(fname):
318 with open(fname) as fd:
319 for line in fd:
320 if line.startswith('#'):
321 continue
322 bounces.add(line.strip())
323
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500324
Simon Glass52a08642017-09-12 20:30:28 -0600325def GetItems(config, section):
326 """Get the items from a section of the config.
327
328 Args:
329 config: _ProjectConfigParser object containing settings
330 section: name of section to retrieve
331
332 Returns:
333 List of (name, value) tuples for the section
334 """
335 try:
336 return config.items(section)
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500337 except ConfigParser.NoSectionError:
Simon Glass52a08642017-09-12 20:30:28 -0600338 return []
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500339
Simon Glass52a08642017-09-12 20:30:28 -0600340
Simon Glass8b521cf2025-05-08 05:58:10 +0200341def Setup(parser, project_name, argv, config_fname=None):
Simon Glass26132882012-01-14 15:12:45 +0000342 """Set up the settings module by reading config files.
343
Maxim Cournoyercb8d3b12022-12-20 00:38:41 -0500344 Unless `config_fname` is specified, a `.patman` config file local
345 to the git repository is consulted, followed by the global
346 `$HOME/.patman`. If none exists, the later is created. Values
347 defined in the local config file take precedence over those
348 defined in the global one.
349
Simon Glass26132882012-01-14 15:12:45 +0000350 Args:
Maxim Cournoyerda6f7cf2022-12-20 00:38:39 -0500351 parser: The parser to update.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000352 project_name: Name of project that we're working on; we'll look
353 for sections named "project_section" as well.
Simon Glass8b521cf2025-05-08 05:58:10 +0200354 config_fname: Config filename to read, or None for default, or False
355 for an empty config. An error is raised if it does not exist.
356 argv (list of str or None): Arguments to parse, or None for default
Simon Glass26132882012-01-14 15:12:45 +0000357 """
Simon Glass33382692015-01-29 11:35:17 -0700358 # First read the git alias file if available
359 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000360 config = _ProjectConfigParser(project_name)
Maxim Cournoyerda6f7cf2022-12-20 00:38:39 -0500361
Maxim Cournoyer64a690f2022-12-20 00:38:40 -0500362 if config_fname and not os.path.exists(config_fname):
363 raise Exception(f'provided {config_fname} does not exist')
364
Simon Glass8b521cf2025-05-08 05:58:10 +0200365 if config_fname is None:
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000366 config_fname = '%s/.patman' % os.getenv('HOME')
Maxim Cournoyercb8d3b12022-12-20 00:38:41 -0500367 git_local_config_fname = os.path.join(gitutil.get_top_level(), '.patman')
Simon Glass8b521cf2025-05-08 05:58:10 +0200368
369 has_config = False
370 has_git_local_config = False
371 if config_fname is not False:
372 has_config = os.path.exists(config_fname)
373 has_git_local_config = os.path.exists(git_local_config_fname)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000374
Maxim Cournoyercb8d3b12022-12-20 00:38:41 -0500375 # Read the git local config last, so that its values override
376 # those of the global config, if any.
377 if has_config:
378 config.read(config_fname)
379 if has_git_local_config:
380 config.read(git_local_config_fname)
381
Simon Glass8b521cf2025-05-08 05:58:10 +0200382 if config_fname is not False and not (has_config or has_git_local_config):
Maxim Cournoyercb8d3b12022-12-20 00:38:41 -0500383 print("No config file found.\nCreating ~/.patman...\n")
384 CreatePatmanConfigFile(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000385
Simon Glass52a08642017-09-12 20:30:28 -0600386 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000387 alias[name] = value.split(',')
388
Chris Packhame8d2a122017-09-01 20:57:53 +1200389 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600390 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200391 bounces.add(value)
392
Simon Glass8b521cf2025-05-08 05:58:10 +0200393 return _UpdateDefaults(parser, config, argv)
Simon Glass26132882012-01-14 15:12:45 +0000394
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500395
Simon Glass26132882012-01-14 15:12:45 +0000396# These are the aliases we understand, indexed by alias. Each member is a list.
397alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200398bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000399
400if __name__ == "__main__":
401 import doctest
402
403 doctest.testmod()