blob: 60cdc1c102e633cc6a2510380035cc62a54367a9 [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 Glass3145b632020-11-03 13:54:13 -070010import argparse
Simon Glass0d24de92012-01-14 15:12:45 +000011import os
12import re
13
Simon Glassbf776672020-04-17 18:09:04 -060014from patman import command
Simon Glassbf776672020-04-17 18:09:04 -060015from patman import tools
Simon Glass0d24de92012-01-14 15:12:45 +000016
Doug Andersona1dcee82012-12-03 14:43:18 +000017"""Default settings per-project.
18
19These are used by _ProjectConfigParser. Settings names should match
20the "dest" of the option parser from patman.py.
21"""
22_default_settings = {
23 "u-boot": {},
24 "linux": {
25 "process_tags": "False",
26 }
27}
28
29class _ProjectConfigParser(ConfigParser.SafeConfigParser):
30 """ConfigParser that handles projects.
31
32 There are two main goals of this class:
33 - Load project-specific default settings.
34 - Merge general default settings/aliases with project-specific ones.
35
36 # Sample config used for tests below...
Simon Glassc3a13cc2020-04-17 18:08:55 -060037 >>> from io import StringIO
Doug Andersona1dcee82012-12-03 14:43:18 +000038 >>> sample_config = '''
39 ... [alias]
40 ... me: Peter P. <likesspiders@example.com>
41 ... enemies: Evil <evil@example.com>
42 ...
43 ... [sm_alias]
44 ... enemies: Green G. <ugly@example.com>
45 ...
46 ... [sm2_alias]
47 ... enemies: Doc O. <pus@example.com>
48 ...
49 ... [settings]
50 ... am_hero: True
51 ... '''
52
53 # Check to make sure that bogus project gets general alias.
54 >>> config = _ProjectConfigParser("zzz")
Paul Burtonf5d44b92016-09-27 16:03:55 +010055 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060056 >>> str(config.get("alias", "enemies"))
57 'Evil <evil@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000058
59 # Check to make sure that alias gets overridden by project.
60 >>> config = _ProjectConfigParser("sm")
Paul Burtonf5d44b92016-09-27 16:03:55 +010061 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060062 >>> str(config.get("alias", "enemies"))
63 'Green G. <ugly@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000064
65 # Check to make sure that settings get merged with project.
66 >>> config = _ProjectConfigParser("linux")
Paul Burtonf5d44b92016-09-27 16:03:55 +010067 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060068 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
69 [('am_hero', 'True'), ('process_tags', 'False')]
Doug Andersona1dcee82012-12-03 14:43:18 +000070
71 # Check to make sure that settings works with unknown project.
72 >>> config = _ProjectConfigParser("unknown")
Paul Burtonf5d44b92016-09-27 16:03:55 +010073 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060074 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
75 [('am_hero', 'True')]
Doug Andersona1dcee82012-12-03 14:43:18 +000076 """
77 def __init__(self, project_name):
78 """Construct _ProjectConfigParser.
79
80 In addition to standard SafeConfigParser initialization, this also loads
81 project defaults.
82
83 Args:
84 project_name: The name of the project.
85 """
86 self._project_name = project_name
87 ConfigParser.SafeConfigParser.__init__(self)
88
89 # Update the project settings in the config based on
90 # the _default_settings global.
91 project_settings = "%s_settings" % project_name
92 if not self.has_section(project_settings):
93 self.add_section(project_settings)
94 project_defaults = _default_settings.get(project_name, {})
Paul Burtonc9eac382016-09-27 16:03:54 +010095 for setting_name, setting_value in project_defaults.items():
Doug Andersona1dcee82012-12-03 14:43:18 +000096 self.set(project_settings, setting_name, setting_value)
97
98 def get(self, section, option, *args, **kwargs):
99 """Extend SafeConfigParser to try project_section before section.
100
101 Args:
102 See SafeConfigParser.
103 Returns:
104 See SafeConfigParser.
105 """
106 try:
Simon Glassec9e0f42018-10-01 21:12:33 -0600107 val = ConfigParser.SafeConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000108 self, "%s_%s" % (self._project_name, section), option,
109 *args, **kwargs
110 )
111 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
Simon Glassec9e0f42018-10-01 21:12:33 -0600112 val = ConfigParser.SafeConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000113 self, section, option, *args, **kwargs
114 )
Simon Glassfc0056e2020-11-08 20:36:18 -0700115 return val
Doug Andersona1dcee82012-12-03 14:43:18 +0000116
117 def items(self, section, *args, **kwargs):
118 """Extend SafeConfigParser to add project_section to section.
119
120 Args:
121 See SafeConfigParser.
122 Returns:
123 See SafeConfigParser.
124 """
125 project_items = []
126 has_project_section = False
127 top_items = []
128
129 # Get items from the project section
130 try:
131 project_items = ConfigParser.SafeConfigParser.items(
132 self, "%s_%s" % (self._project_name, section), *args, **kwargs
133 )
134 has_project_section = True
135 except ConfigParser.NoSectionError:
136 pass
137
138 # Get top-level items
139 try:
140 top_items = ConfigParser.SafeConfigParser.items(
141 self, section, *args, **kwargs
142 )
143 except ConfigParser.NoSectionError:
144 # If neither section exists raise the error on...
145 if not has_project_section:
146 raise
147
148 item_dict = dict(top_items)
149 item_dict.update(project_items)
Simon Glassfc0056e2020-11-08 20:36:18 -0700150 return {(item, val) 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
Simon Glass3145b632020-11-03 13:54:13 -0700219def _UpdateDefaults(main_parser, config):
Doug Anderson8568bae2012-12-03 14:43:17 +0000220 """Update the given OptionParser defaults based on config.
221
Simon Glass3145b632020-11-03 13:54:13 -0700222 We'll walk through all of the settings from all parsers.
Doug Anderson8568bae2012-12-03 14:43:17 +0000223 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:
Simon Glass3145b632020-11-03 13:54:13 -0700231 parser: An instance of an ArgumentParser whose defaults will be
Doug Anderson8568bae2012-12-03 14:43:17 +0000232 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 Glass3145b632020-11-03 13:54:13 -0700236 # Find all the parsers and subparsers
237 parsers = [main_parser]
238 parsers += [subparser for action in main_parser._actions
239 if isinstance(action, argparse._SubParsersAction)
240 for _, subparser in action.choices.items()]
241
242 # Collect the defaults from each parser
243 defaults = {}
244 for parser in parsers:
245 pdefs = parser.parse_known_args()[0]
246 defaults.update(vars(pdefs))
247
248 # Go through the settings and collect defaults
Doug Anderson8568bae2012-12-03 14:43:17 +0000249 for name, val in config.items('settings'):
Simon Glassfda1e372020-07-05 21:41:53 -0600250 if name in defaults:
251 default_val = defaults[name]
Doug Anderson8568bae2012-12-03 14:43:17 +0000252 if isinstance(default_val, bool):
253 val = config.getboolean('settings', name)
254 elif isinstance(default_val, int):
255 val = config.getint('settings', name)
Simon Glass3145b632020-11-03 13:54:13 -0700256 elif isinstance(default_val, str):
257 val = config.get('settings', name)
Simon Glassfda1e372020-07-05 21:41:53 -0600258 defaults[name] = val
Doug Anderson8568bae2012-12-03 14:43:17 +0000259 else:
Paul Burtona920a172016-09-27 16:03:50 +0100260 print("WARNING: Unknown setting %s" % name)
Simon Glass3145b632020-11-03 13:54:13 -0700261
262 # Set all the defaults (this propagates through all subparsers)
263 main_parser.set_defaults(**defaults)
Doug Anderson8568bae2012-12-03 14:43:17 +0000264
Simon Glass8895b3e2015-01-29 11:35:17 -0700265def _ReadAliasFile(fname):
266 """Read in the U-Boot git alias file if it exists.
267
268 Args:
269 fname: Filename to read.
270 """
271 if os.path.exists(fname):
272 bad_line = None
Simon Glass272cd852019-10-31 07:42:51 -0600273 with open(fname, encoding='utf-8') as fd:
Simon Glass8895b3e2015-01-29 11:35:17 -0700274 linenum = 0
275 for line in fd:
276 linenum += 1
277 line = line.strip()
278 if not line or line.startswith('#'):
279 continue
Adam Sampsonb8a48fb2018-06-27 14:38:58 +0100280 words = line.split(None, 2)
Simon Glass8895b3e2015-01-29 11:35:17 -0700281 if len(words) < 3 or words[0] != 'alias':
282 if not bad_line:
283 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
284 line)
285 continue
286 alias[words[1]] = [s.strip() for s in words[2].split(',')]
287 if bad_line:
Paul Burtona920a172016-09-27 16:03:50 +0100288 print(bad_line)
Simon Glass8895b3e2015-01-29 11:35:17 -0700289
Chris Packhame11aa602017-09-01 20:57:53 +1200290def _ReadBouncesFile(fname):
291 """Read in the bounces file if it exists
292
293 Args:
294 fname: Filename to read.
295 """
296 if os.path.exists(fname):
297 with open(fname) as fd:
298 for line in fd:
299 if line.startswith('#'):
300 continue
301 bounces.add(line.strip())
302
Simon Glassad893142017-09-12 20:30:28 -0600303def GetItems(config, section):
304 """Get the items from a section of the config.
305
306 Args:
307 config: _ProjectConfigParser object containing settings
308 section: name of section to retrieve
309
310 Returns:
311 List of (name, value) tuples for the section
312 """
313 try:
314 return config.items(section)
315 except ConfigParser.NoSectionError as e:
316 return []
317 except:
318 raise
319
Simon Glassdd3dac22020-06-07 06:45:49 -0600320def Setup(gitutil, parser, project_name, config_fname=''):
Simon Glass0d24de92012-01-14 15:12:45 +0000321 """Set up the settings module by reading config files.
322
323 Args:
Doug Anderson8568bae2012-12-03 14:43:17 +0000324 parser: The parser to update
Doug Andersona1dcee82012-12-03 14:43:18 +0000325 project_name: Name of project that we're working on; we'll look
326 for sections named "project_section" as well.
Simon Glass0d24de92012-01-14 15:12:45 +0000327 config_fname: Config filename to read ('' for default)
328 """
Simon Glass8895b3e2015-01-29 11:35:17 -0700329 # First read the git alias file if available
330 _ReadAliasFile('doc/git-mailrc')
Doug Andersona1dcee82012-12-03 14:43:18 +0000331 config = _ProjectConfigParser(project_name)
Simon Glass0d24de92012-01-14 15:12:45 +0000332 if config_fname == '':
Vikram Narayanan2b36c752012-05-23 08:58:58 +0000333 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan87d65552012-05-23 09:01:06 +0000334
335 if not os.path.exists(config_fname):
Paul Burtona920a172016-09-27 16:03:50 +0100336 print("No config file found ~/.patman\nCreating one...\n")
Simon Glassdd3dac22020-06-07 06:45:49 -0600337 CreatePatmanConfigFile(gitutil, config_fname)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000338
Doug Anderson8568bae2012-12-03 14:43:17 +0000339 config.read(config_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000340
Simon Glassad893142017-09-12 20:30:28 -0600341 for name, value in GetItems(config, 'alias'):
Simon Glass0d24de92012-01-14 15:12:45 +0000342 alias[name] = value.split(',')
343
Chris Packhame11aa602017-09-01 20:57:53 +1200344 _ReadBouncesFile('doc/bounces')
Simon Glassad893142017-09-12 20:30:28 -0600345 for name, value in GetItems(config, 'bounces'):
Chris Packhame11aa602017-09-01 20:57:53 +1200346 bounces.add(value)
347
Doug Anderson8568bae2012-12-03 14:43:17 +0000348 _UpdateDefaults(parser, config)
Simon Glass0d24de92012-01-14 15:12:45 +0000349
350# These are the aliases we understand, indexed by alias. Each member is a list.
351alias = {}
Chris Packhame11aa602017-09-01 20:57:53 +1200352bounces = set()
Doug Andersona1dcee82012-12-03 14:43:18 +0000353
354if __name__ == "__main__":
355 import doctest
356
357 doctest.testmod()