blob: 732bd401067cd0d3f57875c23c019f6032e216b2 [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 """
Simon Glassfda1e372020-07-05 21:41:53 -0600236 defaults = parser.parse_known_args()[0]
237 defaults = vars(defaults)
Doug Anderson8568bae2012-12-03 14:43:17 +0000238 for name, val in config.items('settings'):
Simon Glassfda1e372020-07-05 21:41:53 -0600239 if name in defaults:
240 default_val = defaults[name]
Doug Anderson8568bae2012-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 Glassfda1e372020-07-05 21:41:53 -0600245 defaults[name] = val
Doug Anderson8568bae2012-12-03 14:43:17 +0000246 else:
Paul Burtona920a172016-09-27 16:03:50 +0100247 print("WARNING: Unknown setting %s" % name)
Simon Glassfda1e372020-07-05 21:41:53 -0600248 parser.set_defaults(**defaults)
Doug Anderson8568bae2012-12-03 14:43:17 +0000249
Simon Glass8895b3e2015-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 Glass272cd852019-10-31 07:42:51 -0600258 with open(fname, encoding='utf-8') as fd:
Simon Glass8895b3e2015-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 Sampsonb8a48fb2018-06-27 14:38:58 +0100265 words = line.split(None, 2)
Simon Glass8895b3e2015-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 Burtona920a172016-09-27 16:03:50 +0100273 print(bad_line)
Simon Glass8895b3e2015-01-29 11:35:17 -0700274
Chris Packhame11aa602017-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 Glassad893142017-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 Glassdd3dac22020-06-07 06:45:49 -0600305def Setup(gitutil, parser, project_name, config_fname=''):
Simon Glass0d24de92012-01-14 15:12:45 +0000306 """Set up the settings module by reading config files.
307
308 Args:
Doug Anderson8568bae2012-12-03 14:43:17 +0000309 parser: The parser to update
Doug Andersona1dcee82012-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 Glass0d24de92012-01-14 15:12:45 +0000312 config_fname: Config filename to read ('' for default)
313 """
Simon Glass8895b3e2015-01-29 11:35:17 -0700314 # First read the git alias file if available
315 _ReadAliasFile('doc/git-mailrc')
Doug Andersona1dcee82012-12-03 14:43:18 +0000316 config = _ProjectConfigParser(project_name)
Simon Glass0d24de92012-01-14 15:12:45 +0000317 if config_fname == '':
Vikram Narayanan2b36c752012-05-23 08:58:58 +0000318 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan87d65552012-05-23 09:01:06 +0000319
320 if not os.path.exists(config_fname):
Paul Burtona920a172016-09-27 16:03:50 +0100321 print("No config file found ~/.patman\nCreating one...\n")
Simon Glassdd3dac22020-06-07 06:45:49 -0600322 CreatePatmanConfigFile(gitutil, config_fname)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000323
Doug Anderson8568bae2012-12-03 14:43:17 +0000324 config.read(config_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000325
Simon Glassad893142017-09-12 20:30:28 -0600326 for name, value in GetItems(config, 'alias'):
Simon Glass0d24de92012-01-14 15:12:45 +0000327 alias[name] = value.split(',')
328
Chris Packhame11aa602017-09-01 20:57:53 +1200329 _ReadBouncesFile('doc/bounces')
Simon Glassad893142017-09-12 20:30:28 -0600330 for name, value in GetItems(config, 'bounces'):
Chris Packhame11aa602017-09-01 20:57:53 +1200331 bounces.add(value)
332
Doug Anderson8568bae2012-12-03 14:43:17 +0000333 _UpdateDefaults(parser, config)
Simon Glass0d24de92012-01-14 15:12:45 +0000334
335# These are the aliases we understand, indexed by alias. Each member is a list.
336alias = {}
Chris Packhame11aa602017-09-01 20:57:53 +1200337bounces = set()
Doug Andersona1dcee82012-12-03 14:43:18 +0000338
339if __name__ == "__main__":
340 import doctest
341
342 doctest.testmod()