blob: 5dc83a850023c779d6ce76ce96aaea9df3056646 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass0d24de92012-01-14 15:12:45 +00002# Copyright (c) 2011 The Chromium OS Authors.
3#
Simon Glass0d24de92012-01-14 15:12:45 +00004
Paul Burtona920a172016-09-27 16:03:50 +01005from __future__ import print_function
6
Paul Burton2ce7b212016-09-27 16:03:52 +01007try:
8 import configparser as ConfigParser
9except:
10 import ConfigParser
11
Simon Glass0d24de92012-01-14 15:12:45 +000012import os
13import re
14
15import command
Vikram Narayanan87d65552012-05-23 09:01:06 +000016import gitutil
Simon Glass513eace2019-05-14 15:53:50 -060017import tools
Simon Glass0d24de92012-01-14 15:12:45 +000018
Doug Andersona1dcee82012-12-03 14:43:18 +000019"""Default settings per-project.
20
21These are used by _ProjectConfigParser. Settings names should match
22the "dest" of the option parser from patman.py.
23"""
24_default_settings = {
25 "u-boot": {},
26 "linux": {
27 "process_tags": "False",
28 }
29}
30
31class _ProjectConfigParser(ConfigParser.SafeConfigParser):
32 """ConfigParser that handles projects.
33
34 There are two main goals of this class:
35 - Load project-specific default settings.
36 - Merge general default settings/aliases with project-specific ones.
37
38 # Sample config used for tests below...
Paul Burtonf5d44b92016-09-27 16:03:55 +010039 >>> try:
40 ... from StringIO import StringIO
41 ... except ImportError:
42 ... from io import StringIO
Doug Andersona1dcee82012-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 Burtonf5d44b92016-09-27 16:03:55 +010060 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060061 >>> str(config.get("alias", "enemies"))
62 'Evil <evil@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000063
64 # Check to make sure that alias gets overridden by project.
65 >>> config = _ProjectConfigParser("sm")
Paul Burtonf5d44b92016-09-27 16:03:55 +010066 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060067 >>> str(config.get("alias", "enemies"))
68 'Green G. <ugly@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000069
70 # Check to make sure that settings get merged with project.
71 >>> config = _ProjectConfigParser("linux")
Paul Burtonf5d44b92016-09-27 16:03:55 +010072 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-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 Andersona1dcee82012-12-03 14:43:18 +000075
76 # Check to make sure that settings works with unknown project.
77 >>> config = _ProjectConfigParser("unknown")
Paul Burtonf5d44b92016-09-27 16:03:55 +010078 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060079 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
80 [('am_hero', 'True')]
Doug Andersona1dcee82012-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 Burtonc9eac382016-09-27 16:03:54 +0100100 for setting_name, setting_value in project_defaults.items():
Doug Andersona1dcee82012-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 Glassec9e0f42018-10-01 21:12:33 -0600112 val = ConfigParser.SafeConfigParser.get(
Doug Andersona1dcee82012-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 Glassec9e0f42018-10-01 21:12:33 -0600117 val = ConfigParser.SafeConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000118 self, section, option, *args, **kwargs
119 )
Simon Glass513eace2019-05-14 15:53:50 -0600120 return tools.ToUnicode(val)
Doug Andersona1dcee82012-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 Glass513eace2019-05-14 15:53:50 -0600155 return {(tools.ToUnicode(item), tools.ToUnicode(val))
Simon Glass4a4c5dd2019-05-14 15:53:40 -0600156 for item, val in item_dict.items()}
Doug Andersona1dcee82012-12-03 14:43:18 +0000157
Simon Glass0d24de92012-01-14 15:12:45 +0000158def ReadGitAliases(fname):
159 """Read a git alias file. This is in the form used by git:
160
161 alias uboot u-boot@lists.denx.de
162 alias wd Wolfgang Denk <wd@denx.de>
163
164 Args:
165 fname: Filename to read
166 """
167 try:
Simon Glass272cd852019-10-31 07:42:51 -0600168 fd = open(fname, 'r', encoding='utf-8')
Simon Glass0d24de92012-01-14 15:12:45 +0000169 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100170 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000171 return
172
173 re_line = re.compile('alias\s+(\S+)\s+(.*)')
174 for line in fd.readlines():
175 line = line.strip()
176 if not line or line[0] == '#':
177 continue
178
179 m = re_line.match(line)
180 if not m:
Paul Burtona920a172016-09-27 16:03:50 +0100181 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass0d24de92012-01-14 15:12:45 +0000182 continue
183
184 list = alias.get(m.group(1), [])
185 for item in m.group(2).split(','):
186 item = item.strip()
187 if item:
188 list.append(item)
189 alias[m.group(1)] = list
190
191 fd.close()
192
Vikram Narayanan87d65552012-05-23 09:01:06 +0000193def CreatePatmanConfigFile(config_fname):
194 """Creates a config file under $(HOME)/.patman if it can't find one.
195
196 Args:
197 config_fname: Default config filename i.e., $(HOME)/.patman
198
199 Returns:
200 None
201 """
202 name = gitutil.GetDefaultUserName()
203 if name == None:
204 name = raw_input("Enter name: ")
205
206 email = gitutil.GetDefaultUserEmail()
207
208 if email == None:
209 email = raw_input("Enter email: ")
210
211 try:
212 f = open(config_fname, 'w')
213 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100214 print("Couldn't create patman config file\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000215 raise
216
Simon Glassad893142017-09-12 20:30:28 -0600217 print('''[alias]
218me: %s <%s>
219
220[bounces]
221nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
222''' % (name, email), file=f)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000223 f.close();
224
Doug Anderson8568bae2012-12-03 14:43:17 +0000225def _UpdateDefaults(parser, config):
226 """Update the given OptionParser defaults based on config.
227
228 We'll walk through all of the settings from the parser
229 For each setting we'll look for a default in the option parser.
230 If it's found we'll update the option parser default.
231
232 The idea here is that the .patman file should be able to update
233 defaults but that command line flags should still have the final
234 say.
235
236 Args:
237 parser: An instance of an OptionParser whose defaults will be
238 updated.
Doug Andersona1dcee82012-12-03 14:43:18 +0000239 config: An instance of _ProjectConfigParser that we will query
Doug Anderson8568bae2012-12-03 14:43:17 +0000240 for settings.
241 """
242 defaults = parser.get_default_values()
243 for name, val in config.items('settings'):
244 if hasattr(defaults, name):
245 default_val = getattr(defaults, name)
246 if isinstance(default_val, bool):
247 val = config.getboolean('settings', name)
248 elif isinstance(default_val, int):
249 val = config.getint('settings', name)
250 parser.set_default(name, val)
251 else:
Paul Burtona920a172016-09-27 16:03:50 +0100252 print("WARNING: Unknown setting %s" % name)
Doug Anderson8568bae2012-12-03 14:43:17 +0000253
Simon Glass8895b3e2015-01-29 11:35:17 -0700254def _ReadAliasFile(fname):
255 """Read in the U-Boot git alias file if it exists.
256
257 Args:
258 fname: Filename to read.
259 """
260 if os.path.exists(fname):
261 bad_line = None
Simon Glass272cd852019-10-31 07:42:51 -0600262 with open(fname, encoding='utf-8') as fd:
Simon Glass8895b3e2015-01-29 11:35:17 -0700263 linenum = 0
264 for line in fd:
265 linenum += 1
266 line = line.strip()
267 if not line or line.startswith('#'):
268 continue
Adam Sampsonb8a48fb2018-06-27 14:38:58 +0100269 words = line.split(None, 2)
Simon Glass8895b3e2015-01-29 11:35:17 -0700270 if len(words) < 3 or words[0] != 'alias':
271 if not bad_line:
272 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
273 line)
274 continue
275 alias[words[1]] = [s.strip() for s in words[2].split(',')]
276 if bad_line:
Paul Burtona920a172016-09-27 16:03:50 +0100277 print(bad_line)
Simon Glass8895b3e2015-01-29 11:35:17 -0700278
Chris Packhame11aa602017-09-01 20:57:53 +1200279def _ReadBouncesFile(fname):
280 """Read in the bounces file if it exists
281
282 Args:
283 fname: Filename to read.
284 """
285 if os.path.exists(fname):
286 with open(fname) as fd:
287 for line in fd:
288 if line.startswith('#'):
289 continue
290 bounces.add(line.strip())
291
Simon Glassad893142017-09-12 20:30:28 -0600292def GetItems(config, section):
293 """Get the items from a section of the config.
294
295 Args:
296 config: _ProjectConfigParser object containing settings
297 section: name of section to retrieve
298
299 Returns:
300 List of (name, value) tuples for the section
301 """
302 try:
303 return config.items(section)
304 except ConfigParser.NoSectionError as e:
305 return []
306 except:
307 raise
308
Doug Andersona1dcee82012-12-03 14:43:18 +0000309def Setup(parser, project_name, config_fname=''):
Simon Glass0d24de92012-01-14 15:12:45 +0000310 """Set up the settings module by reading config files.
311
312 Args:
Doug Anderson8568bae2012-12-03 14:43:17 +0000313 parser: The parser to update
Doug Andersona1dcee82012-12-03 14:43:18 +0000314 project_name: Name of project that we're working on; we'll look
315 for sections named "project_section" as well.
Simon Glass0d24de92012-01-14 15:12:45 +0000316 config_fname: Config filename to read ('' for default)
317 """
Simon Glass8895b3e2015-01-29 11:35:17 -0700318 # First read the git alias file if available
319 _ReadAliasFile('doc/git-mailrc')
Doug Andersona1dcee82012-12-03 14:43:18 +0000320 config = _ProjectConfigParser(project_name)
Simon Glass0d24de92012-01-14 15:12:45 +0000321 if config_fname == '':
Vikram Narayanan2b36c752012-05-23 08:58:58 +0000322 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan87d65552012-05-23 09:01:06 +0000323
324 if not os.path.exists(config_fname):
Paul Burtona920a172016-09-27 16:03:50 +0100325 print("No config file found ~/.patman\nCreating one...\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000326 CreatePatmanConfigFile(config_fname)
327
Doug Anderson8568bae2012-12-03 14:43:17 +0000328 config.read(config_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000329
Simon Glassad893142017-09-12 20:30:28 -0600330 for name, value in GetItems(config, 'alias'):
Simon Glass0d24de92012-01-14 15:12:45 +0000331 alias[name] = value.split(',')
332
Chris Packhame11aa602017-09-01 20:57:53 +1200333 _ReadBouncesFile('doc/bounces')
Simon Glassad893142017-09-12 20:30:28 -0600334 for name, value in GetItems(config, 'bounces'):
Chris Packhame11aa602017-09-01 20:57:53 +1200335 bounces.add(value)
336
Doug Anderson8568bae2012-12-03 14:43:17 +0000337 _UpdateDefaults(parser, config)
Simon Glass0d24de92012-01-14 15:12:45 +0000338
339# These are the aliases we understand, indexed by alias. Each member is a list.
340alias = {}
Chris Packhame11aa602017-09-01 20:57:53 +1200341bounces = set()
Doug Andersona1dcee82012-12-03 14:43:18 +0000342
343if __name__ == "__main__":
344 import doctest
345
346 doctest.testmod()