blob: 635561ac056c27c14d9d25e4e1abe67e7cb46415 [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 Burton2ce7b212016-09-27 16:03:52 +01005try:
6 import configparser as ConfigParser
7except:
8 import ConfigParser
9
Simon Glass0d24de92012-01-14 15:12:45 +000010import os
11import re
12
Simon Glassbf776672020-04-17 18:09:04 -060013from patman import command
Simon Glassbf776672020-04-17 18:09:04 -060014from patman import tools
Simon Glass0d24de92012-01-14 15:12:45 +000015
Doug Andersona1dcee82012-12-03 14:43:18 +000016"""Default settings per-project.
17
18These are used by _ProjectConfigParser. Settings names should match
19the "dest" of the option parser from patman.py.
20"""
21_default_settings = {
22 "u-boot": {},
23 "linux": {
24 "process_tags": "False",
25 }
26}
27
28class _ProjectConfigParser(ConfigParser.SafeConfigParser):
29 """ConfigParser that handles projects.
30
31 There are two main goals of this class:
32 - Load project-specific default settings.
33 - Merge general default settings/aliases with project-specific ones.
34
35 # Sample config used for tests below...
Simon Glassc3a13cc2020-04-17 18:08:55 -060036 >>> from io import StringIO
Doug Andersona1dcee82012-12-03 14:43:18 +000037 >>> sample_config = '''
38 ... [alias]
39 ... me: Peter P. <likesspiders@example.com>
40 ... enemies: Evil <evil@example.com>
41 ...
42 ... [sm_alias]
43 ... enemies: Green G. <ugly@example.com>
44 ...
45 ... [sm2_alias]
46 ... enemies: Doc O. <pus@example.com>
47 ...
48 ... [settings]
49 ... am_hero: True
50 ... '''
51
52 # Check to make sure that bogus project gets general alias.
53 >>> config = _ProjectConfigParser("zzz")
Paul Burtonf5d44b92016-09-27 16:03:55 +010054 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060055 >>> str(config.get("alias", "enemies"))
56 'Evil <evil@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000057
58 # Check to make sure that alias gets overridden by project.
59 >>> config = _ProjectConfigParser("sm")
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 'Green G. <ugly@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000063
64 # Check to make sure that settings get merged with project.
65 >>> config = _ProjectConfigParser("linux")
Paul Burtonf5d44b92016-09-27 16:03:55 +010066 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060067 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
68 [('am_hero', 'True'), ('process_tags', 'False')]
Doug Andersona1dcee82012-12-03 14:43:18 +000069
70 # Check to make sure that settings works with unknown project.
71 >>> config = _ProjectConfigParser("unknown")
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')]
Doug Andersona1dcee82012-12-03 14:43:18 +000075 """
76 def __init__(self, project_name):
77 """Construct _ProjectConfigParser.
78
79 In addition to standard SafeConfigParser initialization, this also loads
80 project defaults.
81
82 Args:
83 project_name: The name of the project.
84 """
85 self._project_name = project_name
86 ConfigParser.SafeConfigParser.__init__(self)
87
88 # Update the project settings in the config based on
89 # the _default_settings global.
90 project_settings = "%s_settings" % project_name
91 if not self.has_section(project_settings):
92 self.add_section(project_settings)
93 project_defaults = _default_settings.get(project_name, {})
Paul Burtonc9eac382016-09-27 16:03:54 +010094 for setting_name, setting_value in project_defaults.items():
Doug Andersona1dcee82012-12-03 14:43:18 +000095 self.set(project_settings, setting_name, setting_value)
96
97 def get(self, section, option, *args, **kwargs):
98 """Extend SafeConfigParser to try project_section before section.
99
100 Args:
101 See SafeConfigParser.
102 Returns:
103 See SafeConfigParser.
104 """
105 try:
Simon Glassec9e0f42018-10-01 21:12:33 -0600106 val = ConfigParser.SafeConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000107 self, "%s_%s" % (self._project_name, section), option,
108 *args, **kwargs
109 )
110 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
Simon Glassec9e0f42018-10-01 21:12:33 -0600111 val = ConfigParser.SafeConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000112 self, section, option, *args, **kwargs
113 )
Simon Glass513eace2019-05-14 15:53:50 -0600114 return tools.ToUnicode(val)
Doug Andersona1dcee82012-12-03 14:43:18 +0000115
116 def items(self, section, *args, **kwargs):
117 """Extend SafeConfigParser to add project_section to section.
118
119 Args:
120 See SafeConfigParser.
121 Returns:
122 See SafeConfigParser.
123 """
124 project_items = []
125 has_project_section = False
126 top_items = []
127
128 # Get items from the project section
129 try:
130 project_items = ConfigParser.SafeConfigParser.items(
131 self, "%s_%s" % (self._project_name, section), *args, **kwargs
132 )
133 has_project_section = True
134 except ConfigParser.NoSectionError:
135 pass
136
137 # Get top-level items
138 try:
139 top_items = ConfigParser.SafeConfigParser.items(
140 self, section, *args, **kwargs
141 )
142 except ConfigParser.NoSectionError:
143 # If neither section exists raise the error on...
144 if not has_project_section:
145 raise
146
147 item_dict = dict(top_items)
148 item_dict.update(project_items)
Simon Glass513eace2019-05-14 15:53:50 -0600149 return {(tools.ToUnicode(item), tools.ToUnicode(val))
Simon Glass4a4c5dd2019-05-14 15:53:40 -0600150 for item, val in item_dict.items()}
Doug Andersona1dcee82012-12-03 14:43:18 +0000151
Simon Glass0d24de92012-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 Glass272cd852019-10-31 07:42:51 -0600162 fd = open(fname, 'r', encoding='utf-8')
Simon Glass0d24de92012-01-14 15:12:45 +0000163 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100164 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass0d24de92012-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 Burtona920a172016-09-27 16:03:50 +0100175 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass0d24de92012-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 Glassdd3dac22020-06-07 06:45:49 -0600187def CreatePatmanConfigFile(gitutil, config_fname):
Vikram Narayanan87d65552012-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 Burtona920a172016-09-27 16:03:50 +0100208 print("Couldn't create patman config file\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000209 raise
210
Simon Glassad893142017-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 Narayanan87d65552012-05-23 09:01:06 +0000217 f.close();
218
Doug Anderson8568bae2012-12-03 14:43:17 +0000219def _UpdateDefaults(parser, config):
220 """Update the given OptionParser defaults based on config.
221
222 We'll walk through all of the settings from the parser
223 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:
231 parser: An instance of an OptionParser whose defaults will be
232 updated.
Doug Andersona1dcee82012-12-03 14:43:18 +0000233 config: An instance of _ProjectConfigParser that we will query
Doug Anderson8568bae2012-12-03 14:43:17 +0000234 for settings.
235 """
236 defaults = parser.get_default_values()
237 for name, val in config.items('settings'):
238 if hasattr(defaults, name):
239 default_val = getattr(defaults, name)
240 if isinstance(default_val, bool):
241 val = config.getboolean('settings', name)
242 elif isinstance(default_val, int):
243 val = config.getint('settings', name)
244 parser.set_default(name, val)
245 else:
Paul Burtona920a172016-09-27 16:03:50 +0100246 print("WARNING: Unknown setting %s" % name)
Doug Anderson8568bae2012-12-03 14:43:17 +0000247
Simon Glass8895b3e2015-01-29 11:35:17 -0700248def _ReadAliasFile(fname):
249 """Read in the U-Boot git alias file if it exists.
250
251 Args:
252 fname: Filename to read.
253 """
254 if os.path.exists(fname):
255 bad_line = None
Simon Glass272cd852019-10-31 07:42:51 -0600256 with open(fname, encoding='utf-8') as fd:
Simon Glass8895b3e2015-01-29 11:35:17 -0700257 linenum = 0
258 for line in fd:
259 linenum += 1
260 line = line.strip()
261 if not line or line.startswith('#'):
262 continue
Adam Sampsonb8a48fb2018-06-27 14:38:58 +0100263 words = line.split(None, 2)
Simon Glass8895b3e2015-01-29 11:35:17 -0700264 if len(words) < 3 or words[0] != 'alias':
265 if not bad_line:
266 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
267 line)
268 continue
269 alias[words[1]] = [s.strip() for s in words[2].split(',')]
270 if bad_line:
Paul Burtona920a172016-09-27 16:03:50 +0100271 print(bad_line)
Simon Glass8895b3e2015-01-29 11:35:17 -0700272
Chris Packhame11aa602017-09-01 20:57:53 +1200273def _ReadBouncesFile(fname):
274 """Read in the bounces file if it exists
275
276 Args:
277 fname: Filename to read.
278 """
279 if os.path.exists(fname):
280 with open(fname) as fd:
281 for line in fd:
282 if line.startswith('#'):
283 continue
284 bounces.add(line.strip())
285
Simon Glassad893142017-09-12 20:30:28 -0600286def GetItems(config, section):
287 """Get the items from a section of the config.
288
289 Args:
290 config: _ProjectConfigParser object containing settings
291 section: name of section to retrieve
292
293 Returns:
294 List of (name, value) tuples for the section
295 """
296 try:
297 return config.items(section)
298 except ConfigParser.NoSectionError as e:
299 return []
300 except:
301 raise
302
Simon Glassdd3dac22020-06-07 06:45:49 -0600303def Setup(gitutil, parser, project_name, config_fname=''):
Simon Glass0d24de92012-01-14 15:12:45 +0000304 """Set up the settings module by reading config files.
305
306 Args:
Doug Anderson8568bae2012-12-03 14:43:17 +0000307 parser: The parser to update
Doug Andersona1dcee82012-12-03 14:43:18 +0000308 project_name: Name of project that we're working on; we'll look
309 for sections named "project_section" as well.
Simon Glass0d24de92012-01-14 15:12:45 +0000310 config_fname: Config filename to read ('' for default)
311 """
Simon Glass8895b3e2015-01-29 11:35:17 -0700312 # First read the git alias file if available
313 _ReadAliasFile('doc/git-mailrc')
Doug Andersona1dcee82012-12-03 14:43:18 +0000314 config = _ProjectConfigParser(project_name)
Simon Glass0d24de92012-01-14 15:12:45 +0000315 if config_fname == '':
Vikram Narayanan2b36c752012-05-23 08:58:58 +0000316 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan87d65552012-05-23 09:01:06 +0000317
318 if not os.path.exists(config_fname):
Paul Burtona920a172016-09-27 16:03:50 +0100319 print("No config file found ~/.patman\nCreating one...\n")
Simon Glassdd3dac22020-06-07 06:45:49 -0600320 CreatePatmanConfigFile(gitutil, config_fname)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000321
Doug Anderson8568bae2012-12-03 14:43:17 +0000322 config.read(config_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000323
Simon Glassad893142017-09-12 20:30:28 -0600324 for name, value in GetItems(config, 'alias'):
Simon Glass0d24de92012-01-14 15:12:45 +0000325 alias[name] = value.split(',')
326
Chris Packhame11aa602017-09-01 20:57:53 +1200327 _ReadBouncesFile('doc/bounces')
Simon Glassad893142017-09-12 20:30:28 -0600328 for name, value in GetItems(config, 'bounces'):
Chris Packhame11aa602017-09-01 20:57:53 +1200329 bounces.add(value)
330
Doug Anderson8568bae2012-12-03 14:43:17 +0000331 _UpdateDefaults(parser, config)
Simon Glass0d24de92012-01-14 15:12:45 +0000332
333# These are the aliases we understand, indexed by alias. Each member is a list.
334alias = {}
Chris Packhame11aa602017-09-01 20:57:53 +1200335bounces = set()
Doug Andersona1dcee82012-12-03 14:43:18 +0000336
337if __name__ == "__main__":
338 import doctest
339
340 doctest.testmod()