blob: 7c2b5c196c0621696d5715ab7d24602b6920467e [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
7except:
8 import ConfigParser
9
Simon Glass9bb0a8d2020-11-03 13:54:13 -070010import argparse
Simon Glass26132882012-01-14 15:12:45 +000011import os
12import re
13
Simon Glassa997ea52020-04-17 18:09:04 -060014from patman import command
Simon Glassa997ea52020-04-17 18:09:04 -060015from patman import tools
Simon Glass26132882012-01-14 15:12:45 +000016
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",
Philipp Tomsich98373d42020-11-24 18:14:53 +010026 },
27 "gcc": {
28 "process_tags": "False",
29 "add_signoff": "False",
30 "check_patch": "False",
31 },
Doug Anderson31ffd7f2012-12-03 14:43:18 +000032}
33
34class _ProjectConfigParser(ConfigParser.SafeConfigParser):
35 """ConfigParser that handles projects.
36
37 There are two main goals of this class:
38 - Load project-specific default settings.
39 - Merge general default settings/aliases with project-specific ones.
40
41 # Sample config used for tests below...
Simon Glass945328b2020-04-17 18:08:55 -060042 >>> from io import StringIO
Doug Anderson31ffd7f2012-12-03 14:43:18 +000043 >>> sample_config = '''
44 ... [alias]
45 ... me: Peter P. <likesspiders@example.com>
46 ... enemies: Evil <evil@example.com>
47 ...
48 ... [sm_alias]
49 ... enemies: Green G. <ugly@example.com>
50 ...
51 ... [sm2_alias]
52 ... enemies: Doc O. <pus@example.com>
53 ...
54 ... [settings]
55 ... am_hero: True
56 ... '''
57
58 # Check to make sure that bogus project gets general alias.
59 >>> config = _ProjectConfigParser("zzz")
Paul Burton44fc9ba2016-09-27 16:03:55 +010060 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060061 >>> str(config.get("alias", "enemies"))
62 'Evil <evil@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000063
64 # Check to make sure that alias gets overridden by project.
65 >>> config = _ProjectConfigParser("sm")
Paul Burton44fc9ba2016-09-27 16:03:55 +010066 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060067 >>> str(config.get("alias", "enemies"))
68 'Green G. <ugly@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000069
70 # Check to make sure that settings get merged with project.
71 >>> config = _ProjectConfigParser("linux")
Paul Burton44fc9ba2016-09-27 16:03:55 +010072 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060073 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
74 [('am_hero', 'True'), ('process_tags', 'False')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000075
76 # Check to make sure that settings works with unknown project.
77 >>> config = _ProjectConfigParser("unknown")
Paul Burton44fc9ba2016-09-27 16:03:55 +010078 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060079 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
80 [('am_hero', 'True')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000081 """
82 def __init__(self, project_name):
83 """Construct _ProjectConfigParser.
84
85 In addition to standard SafeConfigParser initialization, this also loads
86 project defaults.
87
88 Args:
89 project_name: The name of the project.
90 """
91 self._project_name = project_name
92 ConfigParser.SafeConfigParser.__init__(self)
93
94 # Update the project settings in the config based on
95 # the _default_settings global.
96 project_settings = "%s_settings" % project_name
97 if not self.has_section(project_settings):
98 self.add_section(project_settings)
99 project_defaults = _default_settings.get(project_name, {})
Paul Burton4466f1a2016-09-27 16:03:54 +0100100 for setting_name, setting_value in project_defaults.items():
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000101 self.set(project_settings, setting_name, setting_value)
102
103 def get(self, section, option, *args, **kwargs):
104 """Extend SafeConfigParser to try project_section before section.
105
106 Args:
107 See SafeConfigParser.
108 Returns:
109 See SafeConfigParser.
110 """
111 try:
Simon Glass26f8b202018-10-01 21:12:33 -0600112 val = ConfigParser.SafeConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000113 self, "%s_%s" % (self._project_name, section), option,
114 *args, **kwargs
115 )
116 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
Simon Glass26f8b202018-10-01 21:12:33 -0600117 val = ConfigParser.SafeConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000118 self, section, option, *args, **kwargs
119 )
Simon Glass9dfb3112020-11-08 20:36:18 -0700120 return val
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000121
122 def items(self, section, *args, **kwargs):
123 """Extend SafeConfigParser to add project_section to section.
124
125 Args:
126 See SafeConfigParser.
127 Returns:
128 See SafeConfigParser.
129 """
130 project_items = []
131 has_project_section = False
132 top_items = []
133
134 # Get items from the project section
135 try:
136 project_items = ConfigParser.SafeConfigParser.items(
137 self, "%s_%s" % (self._project_name, section), *args, **kwargs
138 )
139 has_project_section = True
140 except ConfigParser.NoSectionError:
141 pass
142
143 # Get top-level items
144 try:
145 top_items = ConfigParser.SafeConfigParser.items(
146 self, section, *args, **kwargs
147 )
148 except ConfigParser.NoSectionError:
149 # If neither section exists raise the error on...
150 if not has_project_section:
151 raise
152
153 item_dict = dict(top_items)
154 item_dict.update(project_items)
Simon Glass9dfb3112020-11-08 20:36:18 -0700155 return {(item, val) for item, val in item_dict.items()}
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000156
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
172 re_line = re.compile('alias\s+(\S+)\s+(.*)')
173 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
Simon Glassff15dcd2020-06-07 06:45:49 -0600192def CreatePatmanConfigFile(gitutil, config_fname):
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000193 """Creates a config file under $(HOME)/.patman if it can't find one.
194
195 Args:
196 config_fname: Default config filename i.e., $(HOME)/.patman
197
198 Returns:
199 None
200 """
Simon Glass761648b2022-01-29 14:14:11 -0700201 name = gitutil.get_default_user_name()
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000202 if name == None:
Simon Glass547cba62022-02-11 13:23:18 -0700203 name = input("Enter name: ")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000204
Simon Glass761648b2022-01-29 14:14:11 -0700205 email = gitutil.get_default_user_email()
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000206
207 if email == None:
Simon Glass547cba62022-02-11 13:23:18 -0700208 email = input("Enter email: ")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000209
210 try:
211 f = open(config_fname, 'w')
212 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100213 print("Couldn't create patman config file\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000214 raise
215
Simon Glass52a08642017-09-12 20:30:28 -0600216 print('''[alias]
217me: %s <%s>
218
219[bounces]
220nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
221''' % (name, email), file=f)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000222 f.close();
223
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700224def _UpdateDefaults(main_parser, config):
Doug Anderson3d3077c2012-12-03 14:43:17 +0000225 """Update the given OptionParser defaults based on config.
226
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700227 We'll walk through all of the settings from all parsers.
Doug Anderson3d3077c2012-12-03 14:43:17 +0000228 For each setting we'll look for a default in the option parser.
229 If it's found we'll update the option parser default.
230
231 The idea here is that the .patman file should be able to update
232 defaults but that command line flags should still have the final
233 say.
234
235 Args:
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700236 parser: An instance of an ArgumentParser whose defaults will be
Doug Anderson3d3077c2012-12-03 14:43:17 +0000237 updated.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000238 config: An instance of _ProjectConfigParser that we will query
Doug Anderson3d3077c2012-12-03 14:43:17 +0000239 for settings.
240 """
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700241 # Find all the parsers and subparsers
242 parsers = [main_parser]
243 parsers += [subparser for action in main_parser._actions
244 if isinstance(action, argparse._SubParsersAction)
245 for _, subparser in action.choices.items()]
246
247 # Collect the defaults from each parser
248 defaults = {}
249 for parser in parsers:
250 pdefs = parser.parse_known_args()[0]
251 defaults.update(vars(pdefs))
252
253 # Go through the settings and collect defaults
Doug Anderson3d3077c2012-12-03 14:43:17 +0000254 for name, val in config.items('settings'):
Simon Glasseb101ac2020-07-05 21:41:53 -0600255 if name in defaults:
256 default_val = defaults[name]
Doug Anderson3d3077c2012-12-03 14:43:17 +0000257 if isinstance(default_val, bool):
258 val = config.getboolean('settings', name)
259 elif isinstance(default_val, int):
260 val = config.getint('settings', name)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700261 elif isinstance(default_val, str):
262 val = config.get('settings', name)
Simon Glasseb101ac2020-07-05 21:41:53 -0600263 defaults[name] = val
Doug Anderson3d3077c2012-12-03 14:43:17 +0000264 else:
Paul Burtonc3931342016-09-27 16:03:50 +0100265 print("WARNING: Unknown setting %s" % name)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700266
267 # Set all the defaults (this propagates through all subparsers)
268 main_parser.set_defaults(**defaults)
Doug Anderson3d3077c2012-12-03 14:43:17 +0000269
Simon Glass33382692015-01-29 11:35:17 -0700270def _ReadAliasFile(fname):
271 """Read in the U-Boot git alias file if it exists.
272
273 Args:
274 fname: Filename to read.
275 """
276 if os.path.exists(fname):
277 bad_line = None
Simon Glassf544a2d2019-10-31 07:42:51 -0600278 with open(fname, encoding='utf-8') as fd:
Simon Glass33382692015-01-29 11:35:17 -0700279 linenum = 0
280 for line in fd:
281 linenum += 1
282 line = line.strip()
283 if not line or line.startswith('#'):
284 continue
Adam Sampson02651322018-06-27 14:38:58 +0100285 words = line.split(None, 2)
Simon Glass33382692015-01-29 11:35:17 -0700286 if len(words) < 3 or words[0] != 'alias':
287 if not bad_line:
288 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
289 line)
290 continue
291 alias[words[1]] = [s.strip() for s in words[2].split(',')]
292 if bad_line:
Paul Burtonc3931342016-09-27 16:03:50 +0100293 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700294
Chris Packhame8d2a122017-09-01 20:57:53 +1200295def _ReadBouncesFile(fname):
296 """Read in the bounces file if it exists
297
298 Args:
299 fname: Filename to read.
300 """
301 if os.path.exists(fname):
302 with open(fname) as fd:
303 for line in fd:
304 if line.startswith('#'):
305 continue
306 bounces.add(line.strip())
307
Simon Glass52a08642017-09-12 20:30:28 -0600308def GetItems(config, section):
309 """Get the items from a section of the config.
310
311 Args:
312 config: _ProjectConfigParser object containing settings
313 section: name of section to retrieve
314
315 Returns:
316 List of (name, value) tuples for the section
317 """
318 try:
319 return config.items(section)
320 except ConfigParser.NoSectionError as e:
321 return []
322 except:
323 raise
324
Simon Glassff15dcd2020-06-07 06:45:49 -0600325def Setup(gitutil, parser, project_name, config_fname=''):
Simon Glass26132882012-01-14 15:12:45 +0000326 """Set up the settings module by reading config files.
327
328 Args:
Doug Anderson3d3077c2012-12-03 14:43:17 +0000329 parser: The parser to update
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000330 project_name: Name of project that we're working on; we'll look
331 for sections named "project_section" as well.
Simon Glass26132882012-01-14 15:12:45 +0000332 config_fname: Config filename to read ('' for default)
333 """
Simon Glass33382692015-01-29 11:35:17 -0700334 # First read the git alias file if available
335 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000336 config = _ProjectConfigParser(project_name)
Simon Glass26132882012-01-14 15:12:45 +0000337 if config_fname == '':
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000338 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000339
340 if not os.path.exists(config_fname):
Paul Burtonc3931342016-09-27 16:03:50 +0100341 print("No config file found ~/.patman\nCreating one...\n")
Simon Glassff15dcd2020-06-07 06:45:49 -0600342 CreatePatmanConfigFile(gitutil, config_fname)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000343
Doug Anderson3d3077c2012-12-03 14:43:17 +0000344 config.read(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000345
Simon Glass52a08642017-09-12 20:30:28 -0600346 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000347 alias[name] = value.split(',')
348
Chris Packhame8d2a122017-09-01 20:57:53 +1200349 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600350 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200351 bounces.add(value)
352
Doug Anderson3d3077c2012-12-03 14:43:17 +0000353 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000354
355# These are the aliases we understand, indexed by alias. Each member is a list.
356alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200357bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000358
359if __name__ == "__main__":
360 import doctest
361
362 doctest.testmod()