blob: 732bd401067cd0d3f57875c23c019f6032e216b2 [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 """
Simon Glasseb101ac2020-07-05 21:41:53 -0600236 defaults = parser.parse_known_args()[0]
237 defaults = vars(defaults)
Doug Anderson3d3077c2012-12-03 14:43:17 +0000238 for name, val in config.items('settings'):
Simon Glasseb101ac2020-07-05 21:41:53 -0600239 if name in defaults:
240 default_val = defaults[name]
Doug Anderson3d3077c2012-12-03 14:43:17 +0000241 if isinstance(default_val, bool):
242 val = config.getboolean('settings', name)
243 elif isinstance(default_val, int):
244 val = config.getint('settings', name)
Simon Glasseb101ac2020-07-05 21:41:53 -0600245 defaults[name] = val
Doug Anderson3d3077c2012-12-03 14:43:17 +0000246 else:
Paul Burtonc3931342016-09-27 16:03:50 +0100247 print("WARNING: Unknown setting %s" % name)
Simon Glasseb101ac2020-07-05 21:41:53 -0600248 parser.set_defaults(**defaults)
Doug Anderson3d3077c2012-12-03 14:43:17 +0000249
Simon Glass33382692015-01-29 11:35:17 -0700250def _ReadAliasFile(fname):
251 """Read in the U-Boot git alias file if it exists.
252
253 Args:
254 fname: Filename to read.
255 """
256 if os.path.exists(fname):
257 bad_line = None
Simon Glassf544a2d2019-10-31 07:42:51 -0600258 with open(fname, encoding='utf-8') as fd:
Simon Glass33382692015-01-29 11:35:17 -0700259 linenum = 0
260 for line in fd:
261 linenum += 1
262 line = line.strip()
263 if not line or line.startswith('#'):
264 continue
Adam Sampson02651322018-06-27 14:38:58 +0100265 words = line.split(None, 2)
Simon Glass33382692015-01-29 11:35:17 -0700266 if len(words) < 3 or words[0] != 'alias':
267 if not bad_line:
268 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
269 line)
270 continue
271 alias[words[1]] = [s.strip() for s in words[2].split(',')]
272 if bad_line:
Paul Burtonc3931342016-09-27 16:03:50 +0100273 print(bad_line)
Simon Glass33382692015-01-29 11:35:17 -0700274
Chris Packhame8d2a122017-09-01 20:57:53 +1200275def _ReadBouncesFile(fname):
276 """Read in the bounces file if it exists
277
278 Args:
279 fname: Filename to read.
280 """
281 if os.path.exists(fname):
282 with open(fname) as fd:
283 for line in fd:
284 if line.startswith('#'):
285 continue
286 bounces.add(line.strip())
287
Simon Glass52a08642017-09-12 20:30:28 -0600288def GetItems(config, section):
289 """Get the items from a section of the config.
290
291 Args:
292 config: _ProjectConfigParser object containing settings
293 section: name of section to retrieve
294
295 Returns:
296 List of (name, value) tuples for the section
297 """
298 try:
299 return config.items(section)
300 except ConfigParser.NoSectionError as e:
301 return []
302 except:
303 raise
304
Simon Glassff15dcd2020-06-07 06:45:49 -0600305def Setup(gitutil, parser, project_name, config_fname=''):
Simon Glass26132882012-01-14 15:12:45 +0000306 """Set up the settings module by reading config files.
307
308 Args:
Doug Anderson3d3077c2012-12-03 14:43:17 +0000309 parser: The parser to update
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000310 project_name: Name of project that we're working on; we'll look
311 for sections named "project_section" as well.
Simon Glass26132882012-01-14 15:12:45 +0000312 config_fname: Config filename to read ('' for default)
313 """
Simon Glass33382692015-01-29 11:35:17 -0700314 # First read the git alias file if available
315 _ReadAliasFile('doc/git-mailrc')
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000316 config = _ProjectConfigParser(project_name)
Simon Glass26132882012-01-14 15:12:45 +0000317 if config_fname == '':
Vikram Narayananc387d36d2012-05-23 08:58:58 +0000318 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000319
320 if not os.path.exists(config_fname):
Paul Burtonc3931342016-09-27 16:03:50 +0100321 print("No config file found ~/.patman\nCreating one...\n")
Simon Glassff15dcd2020-06-07 06:45:49 -0600322 CreatePatmanConfigFile(gitutil, config_fname)
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000323
Doug Anderson3d3077c2012-12-03 14:43:17 +0000324 config.read(config_fname)
Simon Glass26132882012-01-14 15:12:45 +0000325
Simon Glass52a08642017-09-12 20:30:28 -0600326 for name, value in GetItems(config, 'alias'):
Simon Glass26132882012-01-14 15:12:45 +0000327 alias[name] = value.split(',')
328
Chris Packhame8d2a122017-09-01 20:57:53 +1200329 _ReadBouncesFile('doc/bounces')
Simon Glass52a08642017-09-12 20:30:28 -0600330 for name, value in GetItems(config, 'bounces'):
Chris Packhame8d2a122017-09-01 20:57:53 +1200331 bounces.add(value)
332
Doug Anderson3d3077c2012-12-03 14:43:17 +0000333 _UpdateDefaults(parser, config)
Simon Glass26132882012-01-14 15:12:45 +0000334
335# These are the aliases we understand, indexed by alias. Each member is a list.
336alias = {}
Chris Packhame8d2a122017-09-01 20:57:53 +1200337bounces = set()
Doug Anderson31ffd7f2012-12-03 14:43:18 +0000338
339if __name__ == "__main__":
340 import doctest
341
342 doctest.testmod()