blob: 635561ac056c27c14d9d25e4e1abe67e7cb46415 [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 Glass26132882012-01-14 15:12:45 +000010import os
11import re
12
Simon Glassa997ea52020-04-17 18:09:04 -060013from patman import command
Simon Glassa997ea52020-04-17 18:09:04 -060014from patman import tools
Simon Glass26132882012-01-14 15:12:45 +000015
Doug Anderson31ffd7f2012-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 Glass945328b2020-04-17 18:08:55 -060036 >>> from io import StringIO
Doug Anderson31ffd7f2012-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 Burton44fc9ba2016-09-27 16:03:55 +010054 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-05-14 15:53:52 -060055 >>> str(config.get("alias", "enemies"))
56 'Evil <evil@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000057
58 # Check to make sure that alias gets overridden by project.
59 >>> config = _ProjectConfigParser("sm")
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 'Green G. <ugly@example.com>'
Doug Anderson31ffd7f2012-12-03 14:43:18 +000063
64 # Check to make sure that settings get merged with project.
65 >>> config = _ProjectConfigParser("linux")
Paul Burton44fc9ba2016-09-27 16:03:55 +010066 >>> config.readfp(StringIO(sample_config))
Simon Glassaa82db62019-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 Anderson31ffd7f2012-12-03 14:43:18 +000069
70 # Check to make sure that settings works with unknown project.
71 >>> config = _ProjectConfigParser("unknown")
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')]
Doug Anderson31ffd7f2012-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 Burton4466f1a2016-09-27 16:03:54 +010094 for setting_name, setting_value in project_defaults.items():
Doug Anderson31ffd7f2012-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 Glass26f8b202018-10-01 21:12:33 -0600106 val = ConfigParser.SafeConfigParser.get(
Doug Anderson31ffd7f2012-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 Glass26f8b202018-10-01 21:12:33 -0600111 val = ConfigParser.SafeConfigParser.get(
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000112 self, section, option, *args, **kwargs
113 )
Simon Glass8bb7a7a2019-05-14 15:53:50 -0600114 return tools.ToUnicode(val)
Doug Anderson31ffd7f2012-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 Glass8bb7a7a2019-05-14 15:53:50 -0600149 return {(tools.ToUnicode(item), tools.ToUnicode(val))
Simon Glass10017372019-05-14 15:53:40 -0600150 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
Doug Anderson3d3077c2012-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 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 """
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 Burtonc3931342016-09-27 16:03:50 +0100246 print("WARNING: Unknown setting %s" % name)
Doug Anderson3d3077c2012-12-03 14:43:17 +0000247
Simon Glass33382692015-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 Glassf544a2d2019-10-31 07:42:51 -0600256 with open(fname, encoding='utf-8') as fd:
Simon Glass33382692015-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 Sampson02651322018-06-27 14:38:58 +0100263 words = line.split(None, 2)
Simon Glass33382692015-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 Burtonc3931342016-09-27 16:03:50 +0100271 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700272
Chris Packhame8d2a122017-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 Glass52a08642017-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 Glassff15dcd2020-06-07 06:45:49 -0600303def Setup(gitutil, parser, project_name, config_fname=''):
Simon Glass26132882012-01-14 15:12:45 +0000304 """Set up the settings module by reading config files.
305
306 Args:
Doug Anderson3d3077c2012-12-03 14:43:17 +0000307 parser: The parser to update
Doug Anderson31ffd7f2012-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 Glass26132882012-01-14 15:12:45 +0000310 config_fname: Config filename to read ('' for default)
311 """
Simon Glass33382692015-01-29 11:35:17 -0700312 # First read the git alias file if available
313 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000314 config = _ProjectConfigParser(project_name)
Simon Glass26132882012-01-14 15:12:45 +0000315 if config_fname == '':
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000316 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000317
318 if not os.path.exists(config_fname):
Paul Burtonc3931342016-09-27 16:03:50 +0100319 print("No config file found ~/.patman\nCreating one...\n")
Simon Glassff15dcd2020-06-07 06:45:49 -0600320 CreatePatmanConfigFile(gitutil, config_fname)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000321
Doug Anderson3d3077c2012-12-03 14:43:17 +0000322 config.read(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000323
Simon Glass52a08642017-09-12 20:30:28 -0600324 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000325 alias[name] = value.split(',')
326
Chris Packhame8d2a122017-09-01 20:57:53 +1200327 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600328 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200329 bounces.add(value)
330
Doug Anderson3d3077c2012-12-03 14:43:17 +0000331 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000332
333# These are the aliases we understand, indexed by alias. Each member is a list.
334alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200335bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000336
337if __name__ == "__main__":
338 import doctest
339
340 doctest.testmod()