blob: 13c1ee4f5695886bf9f8ee8439bdf1f8bf2eed5e [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",
Philipp Tomsichddc44c22020-11-24 18:14:53 +010026 },
27 "gcc": {
28 "process_tags": "False",
29 "add_signoff": "False",
30 "check_patch": "False",
31 },
Doug Andersona1dcee82012-12-03 14:43:18 +000032}
33
34class _ProjectConfigParser(ConfigParser.SafeConfigParser):
35 """ConfigParser that handles projects.
36
37 There are two main goals of this class:
38 - Load project-specific default settings.
39 - Merge general default settings/aliases with project-specific ones.
40
41 # Sample config used for tests below...
Simon Glassc3a13cc2020-04-17 18:08:55 -060042 >>> from io import StringIO
Doug Andersona1dcee82012-12-03 14:43:18 +000043 >>> sample_config = '''
44 ... [alias]
45 ... me: Peter P. <likesspiders@example.com>
46 ... enemies: Evil <evil@example.com>
47 ...
48 ... [sm_alias]
49 ... enemies: Green G. <ugly@example.com>
50 ...
51 ... [sm2_alias]
52 ... enemies: Doc O. <pus@example.com>
53 ...
54 ... [settings]
55 ... am_hero: True
56 ... '''
57
58 # Check to make sure that bogus project gets general alias.
59 >>> config = _ProjectConfigParser("zzz")
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 'Evil <evil@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000063
64 # Check to make sure that alias gets overridden by project.
65 >>> config = _ProjectConfigParser("sm")
Paul Burtonf5d44b92016-09-27 16:03:55 +010066 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060067 >>> str(config.get("alias", "enemies"))
68 'Green G. <ugly@example.com>'
Doug Andersona1dcee82012-12-03 14:43:18 +000069
70 # Check to make sure that settings get merged with project.
71 >>> config = _ProjectConfigParser("linux")
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'), ('process_tags', 'False')]
Doug Andersona1dcee82012-12-03 14:43:18 +000075
76 # Check to make sure that settings works with unknown project.
77 >>> config = _ProjectConfigParser("unknown")
Paul Burtonf5d44b92016-09-27 16:03:55 +010078 >>> config.readfp(StringIO(sample_config))
Simon Glass7ebb45c2019-05-14 15:53:52 -060079 >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
80 [('am_hero', 'True')]
Doug Andersona1dcee82012-12-03 14:43:18 +000081 """
82 def __init__(self, project_name):
83 """Construct _ProjectConfigParser.
84
85 In addition to standard SafeConfigParser initialization, this also loads
86 project defaults.
87
88 Args:
89 project_name: The name of the project.
90 """
91 self._project_name = project_name
92 ConfigParser.SafeConfigParser.__init__(self)
93
94 # Update the project settings in the config based on
95 # the _default_settings global.
96 project_settings = "%s_settings" % project_name
97 if not self.has_section(project_settings):
98 self.add_section(project_settings)
99 project_defaults = _default_settings.get(project_name, {})
Paul Burtonc9eac382016-09-27 16:03:54 +0100100 for setting_name, setting_value in project_defaults.items():
Doug Andersona1dcee82012-12-03 14:43:18 +0000101 self.set(project_settings, setting_name, setting_value)
102
103 def get(self, section, option, *args, **kwargs):
104 """Extend SafeConfigParser to try project_section before section.
105
106 Args:
107 See SafeConfigParser.
108 Returns:
109 See SafeConfigParser.
110 """
111 try:
Simon Glassec9e0f42018-10-01 21:12:33 -0600112 val = ConfigParser.SafeConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000113 self, "%s_%s" % (self._project_name, section), option,
114 *args, **kwargs
115 )
116 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
Simon Glassec9e0f42018-10-01 21:12:33 -0600117 val = ConfigParser.SafeConfigParser.get(
Doug Andersona1dcee82012-12-03 14:43:18 +0000118 self, section, option, *args, **kwargs
119 )
Simon Glassfc0056e2020-11-08 20:36:18 -0700120 return val
Doug Andersona1dcee82012-12-03 14:43:18 +0000121
122 def items(self, section, *args, **kwargs):
123 """Extend SafeConfigParser to add project_section to section.
124
125 Args:
126 See SafeConfigParser.
127 Returns:
128 See SafeConfigParser.
129 """
130 project_items = []
131 has_project_section = False
132 top_items = []
133
134 # Get items from the project section
135 try:
136 project_items = ConfigParser.SafeConfigParser.items(
137 self, "%s_%s" % (self._project_name, section), *args, **kwargs
138 )
139 has_project_section = True
140 except ConfigParser.NoSectionError:
141 pass
142
143 # Get top-level items
144 try:
145 top_items = ConfigParser.SafeConfigParser.items(
146 self, section, *args, **kwargs
147 )
148 except ConfigParser.NoSectionError:
149 # If neither section exists raise the error on...
150 if not has_project_section:
151 raise
152
153 item_dict = dict(top_items)
154 item_dict.update(project_items)
Simon Glassfc0056e2020-11-08 20:36:18 -0700155 return {(item, val) for item, val in item_dict.items()}
Doug Andersona1dcee82012-12-03 14:43:18 +0000156
Simon Glass0d24de92012-01-14 15:12:45 +0000157def ReadGitAliases(fname):
158 """Read a git alias file. This is in the form used by git:
159
160 alias uboot u-boot@lists.denx.de
161 alias wd Wolfgang Denk <wd@denx.de>
162
163 Args:
164 fname: Filename to read
165 """
166 try:
Simon Glass272cd852019-10-31 07:42:51 -0600167 fd = open(fname, 'r', encoding='utf-8')
Simon Glass0d24de92012-01-14 15:12:45 +0000168 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100169 print("Warning: Cannot find alias file '%s'" % fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000170 return
171
172 re_line = re.compile('alias\s+(\S+)\s+(.*)')
173 for line in fd.readlines():
174 line = line.strip()
175 if not line or line[0] == '#':
176 continue
177
178 m = re_line.match(line)
179 if not m:
Paul Burtona920a172016-09-27 16:03:50 +0100180 print("Warning: Alias file line '%s' not understood" % line)
Simon Glass0d24de92012-01-14 15:12:45 +0000181 continue
182
183 list = alias.get(m.group(1), [])
184 for item in m.group(2).split(','):
185 item = item.strip()
186 if item:
187 list.append(item)
188 alias[m.group(1)] = list
189
190 fd.close()
191
Simon Glassdd3dac22020-06-07 06:45:49 -0600192def CreatePatmanConfigFile(gitutil, config_fname):
Vikram Narayanan87d65552012-05-23 09:01:06 +0000193 """Creates a config file under $(HOME)/.patman if it can't find one.
194
195 Args:
196 config_fname: Default config filename i.e., $(HOME)/.patman
197
198 Returns:
199 None
200 """
201 name = gitutil.GetDefaultUserName()
202 if name == None:
203 name = raw_input("Enter name: ")
204
205 email = gitutil.GetDefaultUserEmail()
206
207 if email == None:
208 email = raw_input("Enter email: ")
209
210 try:
211 f = open(config_fname, 'w')
212 except IOError:
Paul Burtona920a172016-09-27 16:03:50 +0100213 print("Couldn't create patman config file\n")
Vikram Narayanan87d65552012-05-23 09:01:06 +0000214 raise
215
Simon Glassad893142017-09-12 20:30:28 -0600216 print('''[alias]
217me: %s <%s>
218
219[bounces]
220nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
221''' % (name, email), file=f)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000222 f.close();
223
Simon Glass3145b632020-11-03 13:54:13 -0700224def _UpdateDefaults(main_parser, config):
Doug Anderson8568bae2012-12-03 14:43:17 +0000225 """Update the given OptionParser defaults based on config.
226
Simon Glass3145b632020-11-03 13:54:13 -0700227 We'll walk through all of the settings from all parsers.
Doug Anderson8568bae2012-12-03 14:43:17 +0000228 For each setting we'll look for a default in the option parser.
229 If it's found we'll update the option parser default.
230
231 The idea here is that the .patman file should be able to update
232 defaults but that command line flags should still have the final
233 say.
234
235 Args:
Simon Glass3145b632020-11-03 13:54:13 -0700236 parser: An instance of an ArgumentParser whose defaults will be
Doug Anderson8568bae2012-12-03 14:43:17 +0000237 updated.
Doug Andersona1dcee82012-12-03 14:43:18 +0000238 config: An instance of _ProjectConfigParser that we will query
Doug Anderson8568bae2012-12-03 14:43:17 +0000239 for settings.
240 """
Simon Glass3145b632020-11-03 13:54:13 -0700241 # Find all the parsers and subparsers
242 parsers = [main_parser]
243 parsers += [subparser for action in main_parser._actions
244 if isinstance(action, argparse._SubParsersAction)
245 for _, subparser in action.choices.items()]
246
247 # Collect the defaults from each parser
248 defaults = {}
249 for parser in parsers:
250 pdefs = parser.parse_known_args()[0]
251 defaults.update(vars(pdefs))
252
253 # Go through the settings and collect defaults
Doug Anderson8568bae2012-12-03 14:43:17 +0000254 for name, val in config.items('settings'):
Simon Glassfda1e372020-07-05 21:41:53 -0600255 if name in defaults:
256 default_val = defaults[name]
Doug Anderson8568bae2012-12-03 14:43:17 +0000257 if isinstance(default_val, bool):
258 val = config.getboolean('settings', name)
259 elif isinstance(default_val, int):
260 val = config.getint('settings', name)
Simon Glass3145b632020-11-03 13:54:13 -0700261 elif isinstance(default_val, str):
262 val = config.get('settings', name)
Simon Glassfda1e372020-07-05 21:41:53 -0600263 defaults[name] = val
Doug Anderson8568bae2012-12-03 14:43:17 +0000264 else:
Paul Burtona920a172016-09-27 16:03:50 +0100265 print("WARNING: Unknown setting %s" % name)
Simon Glass3145b632020-11-03 13:54:13 -0700266
267 # Set all the defaults (this propagates through all subparsers)
268 main_parser.set_defaults(**defaults)
Doug Anderson8568bae2012-12-03 14:43:17 +0000269
Simon Glass8895b3e2015-01-29 11:35:17 -0700270def _ReadAliasFile(fname):
271 """Read in the U-Boot git alias file if it exists.
272
273 Args:
274 fname: Filename to read.
275 """
276 if os.path.exists(fname):
277 bad_line = None
Simon Glass272cd852019-10-31 07:42:51 -0600278 with open(fname, encoding='utf-8') as fd:
Simon Glass8895b3e2015-01-29 11:35:17 -0700279 linenum = 0
280 for line in fd:
281 linenum += 1
282 line = line.strip()
283 if not line or line.startswith('#'):
284 continue
Adam Sampsonb8a48fb2018-06-27 14:38:58 +0100285 words = line.split(None, 2)
Simon Glass8895b3e2015-01-29 11:35:17 -0700286 if len(words) < 3 or words[0] != 'alias':
287 if not bad_line:
288 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
289 line)
290 continue
291 alias[words[1]] = [s.strip() for s in words[2].split(',')]
292 if bad_line:
Paul Burtona920a172016-09-27 16:03:50 +0100293 print(bad_line)
Simon Glass8895b3e2015-01-29 11:35:17 -0700294
Chris Packhame11aa602017-09-01 20:57:53 +1200295def _ReadBouncesFile(fname):
296 """Read in the bounces file if it exists
297
298 Args:
299 fname: Filename to read.
300 """
301 if os.path.exists(fname):
302 with open(fname) as fd:
303 for line in fd:
304 if line.startswith('#'):
305 continue
306 bounces.add(line.strip())
307
Simon Glassad893142017-09-12 20:30:28 -0600308def GetItems(config, section):
309 """Get the items from a section of the config.
310
311 Args:
312 config: _ProjectConfigParser object containing settings
313 section: name of section to retrieve
314
315 Returns:
316 List of (name, value) tuples for the section
317 """
318 try:
319 return config.items(section)
320 except ConfigParser.NoSectionError as e:
321 return []
322 except:
323 raise
324
Simon Glassdd3dac22020-06-07 06:45:49 -0600325def Setup(gitutil, parser, project_name, config_fname=''):
Simon Glass0d24de92012-01-14 15:12:45 +0000326 """Set up the settings module by reading config files.
327
328 Args:
Doug Anderson8568bae2012-12-03 14:43:17 +0000329 parser: The parser to update
Doug Andersona1dcee82012-12-03 14:43:18 +0000330 project_name: Name of project that we're working on; we'll look
331 for sections named "project_section" as well.
Simon Glass0d24de92012-01-14 15:12:45 +0000332 config_fname: Config filename to read ('' for default)
333 """
Simon Glass8895b3e2015-01-29 11:35:17 -0700334 # First read the git alias file if available
335 _ReadAliasFile('doc/git-mailrc')
Doug Andersona1dcee82012-12-03 14:43:18 +0000336 config = _ProjectConfigParser(project_name)
Simon Glass0d24de92012-01-14 15:12:45 +0000337 if config_fname == '':
Vikram Narayanan2b36c752012-05-23 08:58:58 +0000338 config_fname = '%s/.patman' % os.getenv('HOME')
Vikram Narayanan87d65552012-05-23 09:01:06 +0000339
340 if not os.path.exists(config_fname):
Paul Burtona920a172016-09-27 16:03:50 +0100341 print("No config file found ~/.patman\nCreating one...\n")
Simon Glassdd3dac22020-06-07 06:45:49 -0600342 CreatePatmanConfigFile(gitutil, config_fname)
Vikram Narayanan87d65552012-05-23 09:01:06 +0000343
Doug Anderson8568bae2012-12-03 14:43:17 +0000344 config.read(config_fname)
Simon Glass0d24de92012-01-14 15:12:45 +0000345
Simon Glassad893142017-09-12 20:30:28 -0600346 for name, value in GetItems(config, 'alias'):
Simon Glass0d24de92012-01-14 15:12:45 +0000347 alias[name] = value.split(',')
348
Chris Packhame11aa602017-09-01 20:57:53 +1200349 _ReadBouncesFile('doc/bounces')
Simon Glassad893142017-09-12 20:30:28 -0600350 for name, value in GetItems(config, 'bounces'):
Chris Packhame11aa602017-09-01 20:57:53 +1200351 bounces.add(value)
352
Doug Anderson8568bae2012-12-03 14:43:17 +0000353 _UpdateDefaults(parser, config)
Simon Glass0d24de92012-01-14 15:12:45 +0000354
355# These are the aliases we understand, indexed by alias. Each member is a list.
356alias = {}
Chris Packhame11aa602017-09-01 20:57:53 +1200357bounces = set()
Doug Andersona1dcee82012-12-03 14:43:18 +0000358
359if __name__ == "__main__":
360 import doctest
361
362 doctest.testmod()