blob: ea2bc74f759b2aa3a9d931a4444393a33546da5e [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 Burtonc3931342016-09-27 16:03:50 +01005from __future__ import print_function
6
Paul Burton352f1102016-09-27 16:03:52 +01007try:
8 import configparser as ConfigParser
9except:
10 import ConfigParser
11
Simon Glass26132882012-01-14 15:12:45 +000012import os
13import re
14
15import command
Vikram Narayanan12fb29a2012-05-23 09:01:06 +000016import gitutil
Simon Glass26132882012-01-14 15:12:45 +000017
Doug Anderson31ffd7f2012-12-03 14:43:18 +000018"""Default settings per-project.
19
20These are used by _ProjectConfigParser. Settings names should match
21the "dest" of the option parser from patman.py.
22"""
23_default_settings = {
24 "u-boot": {},
25 "linux": {
26 "process_tags": "False",
27 }
28}
29
30class _ProjectConfigParser(ConfigParser.SafeConfigParser):
31 """ConfigParser that handles projects.
32
33 There are two main goals of this class:
34 - Load project-specific default settings.
35 - Merge general default settings/aliases with project-specific ones.
36
37 # Sample config used for tests below...
Paul Burton44fc9ba2016-09-27 16:03:55 +010038 >>> try:
39 ... from StringIO import StringIO
40 ... except ImportError:
41 ... 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))
Doug Anderson31ffd7f2012-12-03 14:43:18 +000060 >>> config.get("alias", "enemies")
Simon Glass26f8b202018-10-01 21:12:33 -060061 u'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))
Doug Anderson31ffd7f2012-12-03 14:43:18 +000066 >>> config.get("alias", "enemies")
Simon Glass26f8b202018-10-01 21:12:33 -060067 u'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))
Doug Anderson31ffd7f2012-12-03 14:43:18 +000072 >>> sorted(config.items("settings"))
Simon Glass26f8b202018-10-01 21:12:33 -060073 [(u'am_hero', u'True'), (u'process_tags', u'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))
Doug Anderson31ffd7f2012-12-03 14:43:18 +000078 >>> sorted(config.items("settings"))
Simon Glass26f8b202018-10-01 21:12:33 -060079 [(u'am_hero', u'True')]
Doug Anderson31ffd7f2012-12-03 14:43:18 +000080 """
81 def __init__(self, project_name):
82 """Construct _ProjectConfigParser.
83
84 In addition to standard SafeConfigParser initialization, this also loads
85 project defaults.
86
87 Args:
88 project_name: The name of the project.
89 """
90 self._project_name = project_name
91 ConfigParser.SafeConfigParser.__init__(self)
92
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
Simon Glass26f8b202018-10-01 21:12:33 -0600102 def _to_unicode(self, val):
103 """Make sure a value is of type 'unicode'
104
105 Args:
106 val: string or unicode object
107
108 Returns:
109 unicode version of val
110 """
111 return val if isinstance(val, unicode) else val.decode('utf-8')
112
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000113 def get(self, section, option, *args, **kwargs):
114 """Extend SafeConfigParser to try project_section before section.
115
116 Args:
117 See SafeConfigParser.
118 Returns:
119 See SafeConfigParser.
120 """
121 try:
Simon Glass26f8b202018-10-01 21:12:33 -0600122 val = ConfigParser.SafeConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000123 self, "%s_%s" % (self._project_name, section), option,
124 *args, **kwargs
125 )
126 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
Simon Glass26f8b202018-10-01 21:12:33 -0600127 val = ConfigParser.SafeConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000128 self, section, option, *args, **kwargs
129 )
Simon Glass26f8b202018-10-01 21:12:33 -0600130 return self._to_unicode(val)
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000131
132 def items(self, section, *args, **kwargs):
133 """Extend SafeConfigParser to add project_section to section.
134
135 Args:
136 See SafeConfigParser.
137 Returns:
138 See SafeConfigParser.
139 """
140 project_items = []
141 has_project_section = False
142 top_items = []
143
144 # Get items from the project section
145 try:
146 project_items = ConfigParser.SafeConfigParser.items(
147 self, "%s_%s" % (self._project_name, section), *args, **kwargs
148 )
149 has_project_section = True
150 except ConfigParser.NoSectionError:
151 pass
152
153 # Get top-level items
154 try:
155 top_items = ConfigParser.SafeConfigParser.items(
156 self, section, *args, **kwargs
157 )
158 except ConfigParser.NoSectionError:
159 # If neither section exists raise the error on...
160 if not has_project_section:
161 raise
162
163 item_dict = dict(top_items)
164 item_dict.update(project_items)
Simon Glass26f8b202018-10-01 21:12:33 -0600165 return {(self._to_unicode(item), self._to_unicode(val))
166 for item, val in item_dict.iteritems()}
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000167
Simon Glass26132882012-01-14 15:12:45 +0000168def ReadGitAliases(fname):
169 """Read a git alias file. This is in the form used by git:
170
171 alias uboot u-boot@lists.denx.de
172 alias wd Wolfgang Denk <wd@denx.de>
173
174 Args:
175 fname: Filename to read
176 """
177 try:
178 fd = open(fname, 'r')
179 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100180 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass26132882012-01-14 15:12:45 +0000181 return
182
183 re_line = re.compile('alias\s+(\S+)\s+(.*)')
184 for line in fd.readlines():
185 line = line.strip()
186 if not line or line[0] == '#':
187 continue
188
189 m = re_line.match(line)
190 if not m:
Paul Burtonc3931342016-09-27 16:03:50 +0100191 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass26132882012-01-14 15:12:45 +0000192 continue
193
194 list = alias.get(m.group(1), [])
195 for item in m.group(2).split(','):
196 item = item.strip()
197 if item:
198 list.append(item)
199 alias[m.group(1)] = list
200
201 fd.close()
202
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000203def CreatePatmanConfigFile(config_fname):
204 """Creates a config file under $(HOME)/.patman if it can't find one.
205
206 Args:
207 config_fname: Default config filename i.e., $(HOME)/.patman
208
209 Returns:
210 None
211 """
212 name = gitutil.GetDefaultUserName()
213 if name == None:
214 name = raw_input("Enter name: ")
215
216 email = gitutil.GetDefaultUserEmail()
217
218 if email == None:
219 email = raw_input("Enter email: ")
220
221 try:
222 f = open(config_fname, 'w')
223 except IOError:
Paul Burtonc3931342016-09-27 16:03:50 +0100224 print("Couldn't create patman config file\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000225 raise
226
Simon Glass52a08642017-09-12 20:30:28 -0600227 print('''[alias]
228me: %s <%s>
229
230[bounces]
231nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
232''' % (name, email), file=f)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000233 f.close();
234
Doug Anderson3d3077c2012-12-03 14:43:17 +0000235def _UpdateDefaults(parser, config):
236 """Update the given OptionParser defaults based on config.
237
238 We'll walk through all of the settings from the parser
239 For each setting we'll look for a default in the option parser.
240 If it's found we'll update the option parser default.
241
242 The idea here is that the .patman file should be able to update
243 defaults but that command line flags should still have the final
244 say.
245
246 Args:
247 parser: An instance of an OptionParser whose defaults will be
248 updated.
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000249 config: An instance of _ProjectConfigParser that we will query
Doug Anderson3d3077c2012-12-03 14:43:17 +0000250 for settings.
251 """
252 defaults = parser.get_default_values()
253 for name, val in config.items('settings'):
254 if hasattr(defaults, name):
255 default_val = getattr(defaults, name)
256 if isinstance(default_val, bool):
257 val = config.getboolean('settings', name)
258 elif isinstance(default_val, int):
259 val = config.getint('settings', name)
260 parser.set_default(name, val)
261 else:
Paul Burtonc3931342016-09-27 16:03:50 +0100262 print("WARNING: Unknown setting %s" % name)
Doug Anderson3d3077c2012-12-03 14:43:17 +0000263
Simon Glass33382692015-01-29 11:35:17 -0700264def _ReadAliasFile(fname):
265 """Read in the U-Boot git alias file if it exists.
266
267 Args:
268 fname: Filename to read.
269 """
270 if os.path.exists(fname):
271 bad_line = None
272 with open(fname) as fd:
273 linenum = 0
274 for line in fd:
275 linenum += 1
276 line = line.strip()
277 if not line or line.startswith('#'):
278 continue
Adam Sampson02651322018-06-27 14:38:58 +0100279 words = line.split(None, 2)
Simon Glass33382692015-01-29 11:35:17 -0700280 if len(words) < 3 or words[0] != 'alias':
281 if not bad_line:
282 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
283 line)
284 continue
285 alias[words[1]] = [s.strip() for s in words[2].split(',')]
286 if bad_line:
Paul Burtonc3931342016-09-27 16:03:50 +0100287 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700288
Chris Packhame8d2a122017-09-01 20:57:53 +1200289def _ReadBouncesFile(fname):
290 """Read in the bounces file if it exists
291
292 Args:
293 fname: Filename to read.
294 """
295 if os.path.exists(fname):
296 with open(fname) as fd:
297 for line in fd:
298 if line.startswith('#'):
299 continue
300 bounces.add(line.strip())
301
Simon Glass52a08642017-09-12 20:30:28 -0600302def GetItems(config, section):
303 """Get the items from a section of the config.
304
305 Args:
306 config: _ProjectConfigParser object containing settings
307 section: name of section to retrieve
308
309 Returns:
310 List of (name, value) tuples for the section
311 """
312 try:
313 return config.items(section)
314 except ConfigParser.NoSectionError as e:
315 return []
316 except:
317 raise
318
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000319def Setup(parser, project_name, config_fname=''):
Simon Glass26132882012-01-14 15:12:45 +0000320 """Set up the settings module by reading config files.
321
322 Args:
Doug Anderson3d3077c2012-12-03 14:43:17 +0000323 parser: The parser to update
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000324 project_name: Name of project that we're working on; we'll look
325 for sections named "project_section" as well.
Simon Glass26132882012-01-14 15:12:45 +0000326 config_fname: Config filename to read ('' for default)
327 """
Simon Glass33382692015-01-29 11:35:17 -0700328 # First read the git alias file if available
329 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000330 config = _ProjectConfigParser(project_name)
Simon Glass26132882012-01-14 15:12:45 +0000331 if config_fname == '':
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000332 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000333
334 if not os.path.exists(config_fname):
Paul Burtonc3931342016-09-27 16:03:50 +0100335 print("No config file found ~/.patman\nCreating one...\n")
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000336 CreatePatmanConfigFile(config_fname)
337
Doug Anderson3d3077c2012-12-03 14:43:17 +0000338 config.read(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000339
Simon Glass52a08642017-09-12 20:30:28 -0600340 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000341 alias[name] = value.split(',')
342
Chris Packhame8d2a122017-09-01 20:57:53 +1200343 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600344 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200345 bounces.add(value)
346
Doug Anderson3d3077c2012-12-03 14:43:17 +0000347 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000348
349# These are the aliases we understand, indexed by alias. Each member is a list.
350alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200351bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000352
353if __name__ == "__main__":
354 import doctest
355
356 doctest.testmod()