Merge https://gitlab.denx.de/u-boot/custodians/u-boot-pmic
[platform/kernel/u-boot.git] / tools / patman / settings.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2011 The Chromium OS Authors.
3 #
4
5 try:
6     import configparser as ConfigParser
7 except:
8     import ConfigParser
9
10 import argparse
11 import os
12 import re
13
14 from patman import command
15 from patman import tools
16
17 """Default settings per-project.
18
19 These are used by _ProjectConfigParser.  Settings names should match
20 the "dest" of the option parser from patman.py.
21 """
22 _default_settings = {
23     "u-boot": {},
24     "linux": {
25         "process_tags": "False",
26         "check_patch_use_tree": "True",
27     },
28     "gcc": {
29         "process_tags": "False",
30         "add_signoff": "False",
31         "check_patch": "False",
32     },
33 }
34
35 class _ProjectConfigParser(ConfigParser.SafeConfigParser):
36     """ConfigParser that handles projects.
37
38     There are two main goals of this class:
39     - Load project-specific default settings.
40     - Merge general default settings/aliases with project-specific ones.
41
42     # Sample config used for tests below...
43     >>> from io import StringIO
44     >>> sample_config = '''
45     ... [alias]
46     ... me: Peter P. <likesspiders@example.com>
47     ... enemies: Evil <evil@example.com>
48     ...
49     ... [sm_alias]
50     ... enemies: Green G. <ugly@example.com>
51     ...
52     ... [sm2_alias]
53     ... enemies: Doc O. <pus@example.com>
54     ...
55     ... [settings]
56     ... am_hero: True
57     ... '''
58
59     # Check to make sure that bogus project gets general alias.
60     >>> config = _ProjectConfigParser("zzz")
61     >>> config.readfp(StringIO(sample_config))
62     >>> str(config.get("alias", "enemies"))
63     'Evil <evil@example.com>'
64
65     # Check to make sure that alias gets overridden by project.
66     >>> config = _ProjectConfigParser("sm")
67     >>> config.readfp(StringIO(sample_config))
68     >>> str(config.get("alias", "enemies"))
69     'Green G. <ugly@example.com>'
70
71     # Check to make sure that settings get merged with project.
72     >>> config = _ProjectConfigParser("linux")
73     >>> config.readfp(StringIO(sample_config))
74     >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
75     [('am_hero', 'True'), ('check_patch_use_tree', 'True'), ('process_tags', 'False')]
76
77     # Check to make sure that settings works with unknown project.
78     >>> config = _ProjectConfigParser("unknown")
79     >>> config.readfp(StringIO(sample_config))
80     >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
81     [('am_hero', 'True')]
82     """
83     def __init__(self, project_name):
84         """Construct _ProjectConfigParser.
85
86         In addition to standard SafeConfigParser initialization, this also loads
87         project defaults.
88
89         Args:
90             project_name: The name of the project.
91         """
92         self._project_name = project_name
93         ConfigParser.SafeConfigParser.__init__(self)
94
95         # Update the project settings in the config based on
96         # the _default_settings global.
97         project_settings = "%s_settings" % project_name
98         if not self.has_section(project_settings):
99             self.add_section(project_settings)
100         project_defaults = _default_settings.get(project_name, {})
101         for setting_name, setting_value in project_defaults.items():
102             self.set(project_settings, setting_name, setting_value)
103
104     def get(self, section, option, *args, **kwargs):
105         """Extend SafeConfigParser to try project_section before section.
106
107         Args:
108             See SafeConfigParser.
109         Returns:
110             See SafeConfigParser.
111         """
112         try:
113             val = ConfigParser.SafeConfigParser.get(
114                 self, "%s_%s" % (self._project_name, section), option,
115                 *args, **kwargs
116             )
117         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
118             val = ConfigParser.SafeConfigParser.get(
119                 self, section, option, *args, **kwargs
120             )
121         return val
122
123     def items(self, section, *args, **kwargs):
124         """Extend SafeConfigParser to add project_section to section.
125
126         Args:
127             See SafeConfigParser.
128         Returns:
129             See SafeConfigParser.
130         """
131         project_items = []
132         has_project_section = False
133         top_items = []
134
135         # Get items from the project section
136         try:
137             project_items = ConfigParser.SafeConfigParser.items(
138                 self, "%s_%s" % (self._project_name, section), *args, **kwargs
139             )
140             has_project_section = True
141         except ConfigParser.NoSectionError:
142             pass
143
144         # Get top-level items
145         try:
146             top_items = ConfigParser.SafeConfigParser.items(
147                 self, section, *args, **kwargs
148             )
149         except ConfigParser.NoSectionError:
150             # If neither section exists raise the error on...
151             if not has_project_section:
152                 raise
153
154         item_dict = dict(top_items)
155         item_dict.update(project_items)
156         return {(item, val) for item, val in item_dict.items()}
157
158 def ReadGitAliases(fname):
159     """Read a git alias file. This is in the form used by git:
160
161     alias uboot  u-boot@lists.denx.de
162     alias wd     Wolfgang Denk <wd@denx.de>
163
164     Args:
165         fname: Filename to read
166     """
167     try:
168         fd = open(fname, 'r', encoding='utf-8')
169     except IOError:
170         print("Warning: Cannot find alias file '%s'" % fname)
171         return
172
173     re_line = re.compile('alias\s+(\S+)\s+(.*)')
174     for line in fd.readlines():
175         line = line.strip()
176         if not line or line[0] == '#':
177             continue
178
179         m = re_line.match(line)
180         if not m:
181             print("Warning: Alias file line '%s' not understood" % line)
182             continue
183
184         list = alias.get(m.group(1), [])
185         for item in m.group(2).split(','):
186             item = item.strip()
187             if item:
188                 list.append(item)
189         alias[m.group(1)] = list
190
191     fd.close()
192
193 def CreatePatmanConfigFile(gitutil, config_fname):
194     """Creates a config file under $(HOME)/.patman if it can't find one.
195
196     Args:
197         config_fname: Default config filename i.e., $(HOME)/.patman
198
199     Returns:
200         None
201     """
202     name = gitutil.get_default_user_name()
203     if name == None:
204         name = input("Enter name: ")
205
206     email = gitutil.get_default_user_email()
207
208     if email == None:
209         email = input("Enter email: ")
210
211     try:
212         f = open(config_fname, 'w')
213     except IOError:
214         print("Couldn't create patman config file\n")
215         raise
216
217     print('''[alias]
218 me: %s <%s>
219
220 [bounces]
221 nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
222 ''' % (name, email), file=f)
223     f.close();
224
225 def _UpdateDefaults(main_parser, config):
226     """Update the given OptionParser defaults based on config.
227
228     We'll walk through all of the settings from all parsers.
229     For each setting we'll look for a default in the option parser.
230     If it's found we'll update the option parser default.
231
232     The idea here is that the .patman file should be able to update
233     defaults but that command line flags should still have the final
234     say.
235
236     Args:
237         parser: An instance of an ArgumentParser whose defaults will be
238             updated.
239         config: An instance of _ProjectConfigParser that we will query
240             for settings.
241     """
242     # Find all the parsers and subparsers
243     parsers = [main_parser]
244     parsers += [subparser for action in main_parser._actions
245                   if isinstance(action, argparse._SubParsersAction)
246                   for _, subparser in action.choices.items()]
247
248     # Collect the defaults from each parser
249     defaults = {}
250     parser_defaults = []
251     for parser in parsers:
252         pdefs = parser.parse_known_args()[0]
253         parser_defaults.append(pdefs)
254         defaults.update(vars(pdefs))
255
256     # Go through the settings and collect defaults
257     for name, val in config.items('settings'):
258         if name in defaults:
259             default_val = defaults[name]
260             if isinstance(default_val, bool):
261                 val = config.getboolean('settings', name)
262             elif isinstance(default_val, int):
263                 val = config.getint('settings', name)
264             elif isinstance(default_val, str):
265                 val = config.get('settings', name)
266             defaults[name] = val
267         else:
268             print("WARNING: Unknown setting %s" % name)
269
270     # Set all the defaults and manually propagate them to subparsers
271     main_parser.set_defaults(**defaults)
272     for parser, pdefs in zip(parsers, parser_defaults):
273         parser.set_defaults(**{ k: v for k, v in defaults.items()
274                                     if k in pdefs })
275
276 def _ReadAliasFile(fname):
277     """Read in the U-Boot git alias file if it exists.
278
279     Args:
280         fname: Filename to read.
281     """
282     if os.path.exists(fname):
283         bad_line = None
284         with open(fname, encoding='utf-8') as fd:
285             linenum = 0
286             for line in fd:
287                 linenum += 1
288                 line = line.strip()
289                 if not line or line.startswith('#'):
290                     continue
291                 words = line.split(None, 2)
292                 if len(words) < 3 or words[0] != 'alias':
293                     if not bad_line:
294                         bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
295                                                                 line)
296                     continue
297                 alias[words[1]] = [s.strip() for s in words[2].split(',')]
298         if bad_line:
299             print(bad_line)
300
301 def _ReadBouncesFile(fname):
302     """Read in the bounces file if it exists
303
304     Args:
305         fname: Filename to read.
306     """
307     if os.path.exists(fname):
308         with open(fname) as fd:
309             for line in fd:
310                 if line.startswith('#'):
311                     continue
312                 bounces.add(line.strip())
313
314 def GetItems(config, section):
315     """Get the items from a section of the config.
316
317     Args:
318         config: _ProjectConfigParser object containing settings
319         section: name of section to retrieve
320
321     Returns:
322         List of (name, value) tuples for the section
323     """
324     try:
325         return config.items(section)
326     except ConfigParser.NoSectionError as e:
327         return []
328     except:
329         raise
330
331 def Setup(gitutil, parser, project_name, config_fname=''):
332     """Set up the settings module by reading config files.
333
334     Args:
335         parser:         The parser to update
336         project_name:   Name of project that we're working on; we'll look
337             for sections named "project_section" as well.
338         config_fname:   Config filename to read ('' for default)
339     """
340     # First read the git alias file if available
341     _ReadAliasFile('doc/git-mailrc')
342     config = _ProjectConfigParser(project_name)
343     if config_fname == '':
344         config_fname = '%s/.patman' % os.getenv('HOME')
345
346     if not os.path.exists(config_fname):
347         print("No config file found ~/.patman\nCreating one...\n")
348         CreatePatmanConfigFile(gitutil, config_fname)
349
350     config.read(config_fname)
351
352     for name, value in GetItems(config, 'alias'):
353         alias[name] = value.split(',')
354
355     _ReadBouncesFile('doc/bounces')
356     for name, value in GetItems(config, 'bounces'):
357         bounces.add(value)
358
359     _UpdateDefaults(parser, config)
360
361 # These are the aliases we understand, indexed by alias. Each member is a list.
362 alias = {}
363 bounces = set()
364
365 if __name__ == "__main__":
366     import doctest
367
368     doctest.testmod()