blob: 60cdc1c102e633cc6a2510380035cc62a54367a9 [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",
26 }
27}
28
29class _ProjectConfigParser(ConfigParser.SafeConfigParser):
30 """ConfigParser that handles projects.
31
32 There are two main goals of this class:
33 - Load project-specific default settings.
34 - Merge general default settings/aliases with project-specific ones.
35
36 # Sample config used for tests below...
Simon Glass945328b2020-04-17 18:08:55 -060037 >>> from io import StringIO
Doug Anderson31ffd7f2012-12-03 14:43:18 +000038 >>> sample_config = '''
39 ... [alias]
40 ... me: Peter P. <likesspiders@example.com>
41 ... enemies: Evil <evil@example.com>
42 ...
43 ... [sm_alias]
44 ... enemies: Green G. <ugly@example.com>
45 ...
46 ... [sm2_alias]
47 ... enemies: Doc O. <pus@example.com>
48 ...
49 ... [settings]
50 ... am_hero: True
51 ... '''
52
53 # Check to make sure that bogus project gets general alias.
54 >>> config = _ProjectConfigParser("zzz")
Paul Burton44fc9ba2016-09-27 16:03:55 +010055 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060056 >>> str(config.get("alias", "enemies"))
57 'Evil <evil@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000058
59 # Check to make sure that alias gets overridden by project.
60 >>> config = _ProjectConfigParser("sm")
Paul Burton44fc9ba2016-09-27 16:03:55 +010061 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060062 >>> str(config.get("alias", "enemies"))
63 'Green G. <ugly@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000064
65 # Check to make sure that settings get merged with project.
66 >>> config = _ProjectConfigParser("linux")
Paul Burton44fc9ba2016-09-27 16:03:55 +010067 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060068 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
69 [('am_hero', 'True'), ('process_tags', 'False')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000070
71 # Check to make sure that settings works with unknown project.
72 >>> config = _ProjectConfigParser("unknown")
Paul Burton44fc9ba2016-09-27 16:03:55 +010073 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060074 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
75 [('am_hero', 'True')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000076 """
77 def __init__(self, project_name):
78 """Construct _ProjectConfigParser.
79
80 In addition to standard SafeConfigParser initialization, this also loads
81 project defaults.
82
83 Args:
84 project_name: The name of the project.
85 """
86 self._project_name = project_name
87 ConfigParser.SafeConfigParser.__init__(self)
88
89 # Update the project settings in the config based on
90 # the _default_settings global.
91 project_settings = "%s_settings" % project_name
92 if not self.has_section(project_settings):
93 self.add_section(project_settings)
94 project_defaults = _default_settings.get(project_name, {})
Paul Burton4466f1a2016-09-27 16:03:54 +010095 for setting_name, setting_value in project_defaults.items():
Doug Anderson31ffd7f2012-12-03 14:43:18 +000096 self.set(project_settings, setting_name, setting_value)
97
98 def get(self, section, option, *args, **kwargs):
99 """Extend SafeConfigParser to try project_section before section.
100
101 Args:
102 See SafeConfigParser.
103 Returns:
104 See SafeConfigParser.
105 """
106 try:
Simon Glass26f8b202018-10-01 21:12:33 -0600107 val = ConfigParser.SafeConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000108 self, "%s_%s" % (self._project_name, section), option,
109 *args, **kwargs
110 )
111 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
Simon Glass26f8b202018-10-01 21:12:33 -0600112 val = ConfigParser.SafeConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000113 self, section, option, *args, **kwargs
114 )
Simon Glass9dfb3112020-11-08 20:36:18 -0700115 return val
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000116
117 def items(self, section, *args, **kwargs):
118 """Extend SafeConfigParser to add project_section to section.
119
120 Args:
121 See SafeConfigParser.
122 Returns:
123 See SafeConfigParser.
124 """
125 project_items = []
126 has_project_section = False
127 top_items = []
128
129 # Get items from the project section
130 try:
131 project_items = ConfigParser.SafeConfigParser.items(
132 self, "%s_%s" % (self._project_name, section), *args, **kwargs
133 )
134 has_project_section = True
135 except ConfigParser.NoSectionError:
136 pass
137
138 # Get top-level items
139 try:
140 top_items = ConfigParser.SafeConfigParser.items(
141 self, section, *args, **kwargs
142 )
143 except ConfigParser.NoSectionError:
144 # If neither section exists raise the error on...
145 if not has_project_section:
146 raise
147
148 item_dict = dict(top_items)
149 item_dict.update(project_items)
Simon Glass9dfb3112020-11-08 20:36:18 -0700150 return {(item, val) for item, val in item_dict.items()}
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000151
Simon Glass26132882012-01-14 15:12:45 +0000152def ReadGitAliases(fname):
153 """Read a git alias file. This is in the form used by git:
154
155 alias uboot u-boot@lists.denx.de
156 alias wd Wolfgang Denk <wd@denx.de>
157
158 Args:
159 fname: Filename to read
160 """
161 try:
Simon Glassf544a2d2019-10-31 07:42:51 -0600162 fd = open(fname, 'r', encoding='utf-8')
Simon Glass26132882012-01-14 15:12:45 +0000163 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100164 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass26132882012-01-14 15:12:45 +0000165 return
166
167 re_line = re.compile('alias\s+(\S+)\s+(.*)')
168 for line in fd.readlines():
169 line = line.strip()
170 if not line or line[0] == '#':
171 continue
172
173 m = re_line.match(line)
174 if not m:
Paul Burtonc3931342016-09-27 16:03:50 +0100175 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass26132882012-01-14 15:12:45 +0000176 continue
177
178 list = alias.get(m.group(1), [])
179 for item in m.group(2).split(','):
180 item = item.strip()
181 if item:
182 list.append(item)
183 alias[m.group(1)] = list
184
185 fd.close()
186
Simon Glassff15dcd2020-06-07 06:45:49 -0600187def CreatePatmanConfigFile(gitutil, config_fname):
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000188 """Creates a config file under $(HOME)/.patman if it can't find one.
189
190 Args:
191 config_fname: Default config filename i.e., $(HOME)/.patman
192
193 Returns:
194 None
195 """
196 name = gitutil.GetDefaultUserName()
197 if name == None:
198 name = raw_input("Enter name: ")
199
200 email = gitutil.GetDefaultUserEmail()
201
202 if email == None:
203 email = raw_input("Enter email: ")
204
205 try:
206 f = open(config_fname, 'w')
207 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100208 print("Couldn't create patman config file\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000209 raise
210
Simon Glass52a08642017-09-12 20:30:28 -0600211 print('''[alias]
212me: %s <%s>
213
214[bounces]
215nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
216''' % (name, email), file=f)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000217 f.close();
218
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700219def _UpdateDefaults(main_parser, config):
Doug Anderson3d3077c2012-12-03 14:43:17 +0000220 """Update the given OptionParser defaults based on config.
221
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700222 We'll walk through all of the settings from all parsers.
Doug Anderson3d3077c2012-12-03 14:43:17 +0000223 For each setting we'll look for a default in the option parser.
224 If it's found we'll update the option parser default.
225
226 The idea here is that the .patman file should be able to update
227 defaults but that command line flags should still have the final
228 say.
229
230 Args:
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700231 parser: An instance of an ArgumentParser whose defaults will be
Doug Anderson3d3077c2012-12-03 14:43:17 +0000232 updated.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000233 config: An instance of _ProjectConfigParser that we will query
Doug Anderson3d3077c2012-12-03 14:43:17 +0000234 for settings.
235 """
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700236 # Find all the parsers and subparsers
237 parsers = [main_parser]
238 parsers += [subparser for action in main_parser._actions
239 if isinstance(action, argparse._SubParsersAction)
240 for _, subparser in action.choices.items()]
241
242 # Collect the defaults from each parser
243 defaults = {}
244 for parser in parsers:
245 pdefs = parser.parse_known_args()[0]
246 defaults.update(vars(pdefs))
247
248 # Go through the settings and collect defaults
Doug Anderson3d3077c2012-12-03 14:43:17 +0000249 for name, val in config.items('settings'):
Simon Glasseb101ac2020-07-05 21:41:53 -0600250 if name in defaults:
251 default_val = defaults[name]
Doug Anderson3d3077c2012-12-03 14:43:17 +0000252 if isinstance(default_val, bool):
253 val = config.getboolean('settings', name)
254 elif isinstance(default_val, int):
255 val = config.getint('settings', name)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700256 elif isinstance(default_val, str):
257 val = config.get('settings', name)
Simon Glasseb101ac2020-07-05 21:41:53 -0600258 defaults[name] = val
Doug Anderson3d3077c2012-12-03 14:43:17 +0000259 else:
Paul Burtonc3931342016-09-27 16:03:50 +0100260 print("WARNING: Unknown setting %s" % name)
Simon Glass9bb0a8d2020-11-03 13:54:13 -0700261
262 # Set all the defaults (this propagates through all subparsers)
263 main_parser.set_defaults(**defaults)
Doug Anderson3d3077c2012-12-03 14:43:17 +0000264
Simon Glass33382692015-01-29 11:35:17 -0700265def _ReadAliasFile(fname):
266 """Read in the U-Boot git alias file if it exists.
267
268 Args:
269 fname: Filename to read.
270 """
271 if os.path.exists(fname):
272 bad_line = None
Simon Glassf544a2d2019-10-31 07:42:51 -0600273 with open(fname, encoding='utf-8') as fd:
Simon Glass33382692015-01-29 11:35:17 -0700274 linenum = 0
275 for line in fd:
276 linenum += 1
277 line = line.strip()
278 if not line or line.startswith('#'):
279 continue
Adam Sampson02651322018-06-27 14:38:58 +0100280 words = line.split(None, 2)
Simon Glass33382692015-01-29 11:35:17 -0700281 if len(words) < 3 or words[0] != 'alias':
282 if not bad_line:
283 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
284 line)
285 continue
286 alias[words[1]] = [s.strip() for s in words[2].split(',')]
287 if bad_line:
Paul Burtonc3931342016-09-27 16:03:50 +0100288 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700289
Chris Packhame8d2a122017-09-01 20:57:53 +1200290def _ReadBouncesFile(fname):
291 """Read in the bounces file if it exists
292
293 Args:
294 fname: Filename to read.
295 """
296 if os.path.exists(fname):
297 with open(fname) as fd:
298 for line in fd:
299 if line.startswith('#'):
300 continue
301 bounces.add(line.strip())
302
Simon Glass52a08642017-09-12 20:30:28 -0600303def GetItems(config, section):
304 """Get the items from a section of the config.
305
306 Args:
307 config: _ProjectConfigParser object containing settings
308 section: name of section to retrieve
309
310 Returns:
311 List of (name, value) tuples for the section
312 """
313 try:
314 return config.items(section)
315 except ConfigParser.NoSectionError as e:
316 return []
317 except:
318 raise
319
Simon Glassff15dcd2020-06-07 06:45:49 -0600320def Setup(gitutil, parser, project_name, config_fname=''):
Simon Glass26132882012-01-14 15:12:45 +0000321 """Set up the settings module by reading config files.
322
323 Args:
Doug Anderson3d3077c2012-12-03 14:43:17 +0000324 parser: The parser to update
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000325 project_name: Name of project that we're working on; we'll look
326 for sections named "project_section" as well.
Simon Glass26132882012-01-14 15:12:45 +0000327 config_fname: Config filename to read ('' for default)
328 """
Simon Glass33382692015-01-29 11:35:17 -0700329 # First read the git alias file if available
330 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000331 config = _ProjectConfigParser(project_name)
Simon Glass26132882012-01-14 15:12:45 +0000332 if config_fname == '':
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000333 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000334
335 if not os.path.exists(config_fname):
Paul Burtonc3931342016-09-27 16:03:50 +0100336 print("No config file found ~/.patman\nCreating one...\n")
Simon Glassff15dcd2020-06-07 06:45:49 -0600337 CreatePatmanConfigFile(gitutil, config_fname)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000338
Doug Anderson3d3077c2012-12-03 14:43:17 +0000339 config.read(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000340
Simon Glass52a08642017-09-12 20:30:28 -0600341 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000342 alias[name] = value.split(',')
343
Chris Packhame8d2a122017-09-01 20:57:53 +1200344 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600345 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200346 bounces.add(value)
347
Doug Anderson3d3077c2012-12-03 14:43:17 +0000348 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000349
350# These are the aliases we understand, indexed by alias. Each member is a list.
351alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200352bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000353
354if __name__ == "__main__":
355 import doctest
356
357 doctest.testmod()