blob: 68c93e313b3ab18c05bd4e4eecb2546a705df902 [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
Maxim Cournoyera81a41f2022-12-20 00:38:38 -050015from patman import gitutil
16
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 Glass9bb0a8d2020-11-03 13:54:13 -0700229def _UpdateDefaults(main_parser, config):
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.
245 """
Simon Glass9bb0a8d2020-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 Cournoyer97f4d762022-12-20 00:38:36 -0500249 if isinstance(action, argparse._SubParsersAction)
250 for _, subparser in action.choices.items()]
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700251
252 # Collect the defaults from each parser
253 defaults = {}
Sean Anderson461df862022-04-29 10:53:34 -0400254 parser_defaults = []
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700255 for parser in parsers:
256 pdefs = parser.parse_known_args()[0]
Sean Anderson461df862022-04-29 10:53:34 -0400257 parser_defaults.append(pdefs)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700258 defaults.update(vars(pdefs))
259
260 # Go through the settings and collect defaults
Doug Anderson3d3077c2012-12-03 14:43:17 +0000261 for name, val in config.items('settings'):
Simon Glasseb101ac2020-07-05 21:41:53 -0600262 if name in defaults:
263 default_val = defaults[name]
Doug Anderson3d3077c2012-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 Glass9bb0a8d2020-11-03 13:54:13 -0700268 elif isinstance(default_val, str):
269 val = config.get('settings', name)
Simon Glasseb101ac2020-07-05 21:41:53 -0600270 defaults[name] = val
Doug Anderson3d3077c2012-12-03 14:43:17 +0000271 else:
Paul Burtonc3931342016-09-27 16:03:50 +0100272 print("WARNING: Unknown setting %s" % name)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700273
Sean Anderson461df862022-04-29 10:53:34 -0400274 # Set all the defaults and manually propagate them to subparsers
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700275 main_parser.set_defaults(**defaults)
Sean Anderson461df862022-04-29 10:53:34 -0400276 for parser, pdefs in zip(parsers, parser_defaults):
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500277 parser.set_defaults(**{k: v for k, v in defaults.items()
278 if k in pdefs})
279
Doug Anderson3d3077c2012-12-03 14:43:17 +0000280
Simon Glass33382692015-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 Glassf544a2d2019-10-31 07:42:51 -0600289 with open(fname, encoding='utf-8') as fd:
Simon Glass33382692015-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 Sampson02651322018-06-27 14:38:58 +0100296 words = line.split(None, 2)
Simon Glass33382692015-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 Burtonc3931342016-09-27 16:03:50 +0100304 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700305
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500306
Chris Packhame8d2a122017-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 Cournoyer97f4d762022-12-20 00:38:36 -0500320
Simon Glass52a08642017-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 Cournoyer97f4d762022-12-20 00:38:36 -0500333 except ConfigParser.NoSectionError:
Simon Glass52a08642017-09-12 20:30:28 -0600334 return []
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500335
Simon Glass52a08642017-09-12 20:30:28 -0600336
Maxim Cournoyerda6f7cf2022-12-20 00:38:39 -0500337def Setup(parser, project_name, config_fname=None):
Simon Glass26132882012-01-14 15:12:45 +0000338 """Set up the settings module by reading config files.
339
Maxim Cournoyercb8d3b12022-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 Glass26132882012-01-14 15:12:45 +0000346 Args:
Maxim Cournoyerda6f7cf2022-12-20 00:38:39 -0500347 parser: The parser to update.
Doug Anderson31ffd7f2012-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 Cournoyer64a690f2022-12-20 00:38:40 -0500350 config_fname: Config filename to read. An error is raised if it
351 does not exist.
Simon Glass26132882012-01-14 15:12:45 +0000352 """
Simon Glass33382692015-01-29 11:35:17 -0700353 # First read the git alias file if available
354 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000355 config = _ProjectConfigParser(project_name)
Maxim Cournoyerda6f7cf2022-12-20 00:38:39 -0500356
Maxim Cournoyer64a690f2022-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 Cournoyerda6f7cf2022-12-20 00:38:39 -0500360 if not config_fname:
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000361 config_fname = '%s/.patman' % os.getenv('HOME')
Maxim Cournoyercb8d3b12022-12-20 00:38:41 -0500362 has_config = os.path.exists(config_fname)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000363
Maxim Cournoyercb8d3b12022-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)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000366
Maxim Cournoyercb8d3b12022-12-20 00:38:41 -0500367 # 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")
376 CreatePatmanConfigFile(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000377
Simon Glass52a08642017-09-12 20:30:28 -0600378 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000379 alias[name] = value.split(',')
380
Chris Packhame8d2a122017-09-01 20:57:53 +1200381 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600382 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200383 bounces.add(value)
384
Doug Anderson3d3077c2012-12-03 14:43:17 +0000385 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000386
Maxim Cournoyer97f4d762022-12-20 00:38:36 -0500387
Simon Glass26132882012-01-14 15:12:45 +0000388# These are the aliases we understand, indexed by alias. Each member is a list.
389alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200390bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000391
392if __name__ == "__main__":
393 import doctest
394
395 doctest.testmod()