Merge tag 'dm-pull-14dec20' of git://git.denx.de/u-boot-dm into next
[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     }
27 }
28
29 class _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...
37     >>> from io import StringIO
38     >>> 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")
55     >>> config.readfp(StringIO(sample_config))
56     >>> str(config.get("alias", "enemies"))
57     'Evil <evil@example.com>'
58
59     # Check to make sure that alias gets overridden by project.
60     >>> config = _ProjectConfigParser("sm")
61     >>> config.readfp(StringIO(sample_config))
62     >>> str(config.get("alias", "enemies"))
63     'Green G. <ugly@example.com>'
64
65     # Check to make sure that settings get merged with project.
66     >>> config = _ProjectConfigParser("linux")
67     >>> config.readfp(StringIO(sample_config))
68     >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
69     [('am_hero', 'True'), ('process_tags', 'False')]
70
71     # Check to make sure that settings works with unknown project.
72     >>> config = _ProjectConfigParser("unknown")
73     >>> config.readfp(StringIO(sample_config))
74     >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
75     [('am_hero', 'True')]
76     """
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, {})
95         for setting_name, setting_value in project_defaults.items():
96             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:
107             val = ConfigParser.SafeConfigParser.get(
108                 self, "%s_%s" % (self._project_name, section), option,
109                 *args, **kwargs
110             )
111         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
112             val = ConfigParser.SafeConfigParser.get(
113                 self, section, option, *args, **kwargs
114             )
115         return val
116
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)
150         return {(item, val) for item, val in item_dict.items()}
151
152 def 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:
162         fd = open(fname, 'r', encoding='utf-8')
163     except IOError:
164         print("Warning: Cannot find alias file '%s'" % fname)
165         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:
175             print("Warning: Alias file line '%s' not understood" % line)
176             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
187 def CreatePatmanConfigFile(gitutil, config_fname):
188     """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:
208         print("Couldn't create patman config file\n")
209         raise
210
211     print('''[alias]
212 me: %s <%s>
213
214 [bounces]
215 nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
216 ''' % (name, email), file=f)
217     f.close();
218
219 def _UpdateDefaults(main_parser, config):
220     """Update the given OptionParser defaults based on config.
221
222     We'll walk through all of the settings from all parsers.
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 ArgumentParser whose defaults will be
232             updated.
233         config: An instance of _ProjectConfigParser that we will query
234             for settings.
235     """
236     # 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
249     for name, val in config.items('settings'):
250         if name in defaults:
251             default_val = defaults[name]
252             if isinstance(default_val, bool):
253                 val = config.getboolean('settings', name)
254             elif isinstance(default_val, int):
255                 val = config.getint('settings', name)
256             elif isinstance(default_val, str):
257                 val = config.get('settings', name)
258             defaults[name] = val
259         else:
260             print("WARNING: Unknown setting %s" % name)
261
262     # Set all the defaults (this propagates through all subparsers)
263     main_parser.set_defaults(**defaults)
264
265 def _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
273         with open(fname, encoding='utf-8') as fd:
274             linenum = 0
275             for line in fd:
276                 linenum += 1
277                 line = line.strip()
278                 if not line or line.startswith('#'):
279                     continue
280                 words = line.split(None, 2)
281                 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:
288             print(bad_line)
289
290 def _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
303 def 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
320 def Setup(gitutil, parser, project_name, config_fname=''):
321     """Set up the settings module by reading config files.
322
323     Args:
324         parser:         The parser to update
325         project_name:   Name of project that we're working on; we'll look
326             for sections named "project_section" as well.
327         config_fname:   Config filename to read ('' for default)
328     """
329     # First read the git alias file if available
330     _ReadAliasFile('doc/git-mailrc')
331     config = _ProjectConfigParser(project_name)
332     if config_fname == '':
333         config_fname = '%s/.patman' % os.getenv('HOME')
334
335     if not os.path.exists(config_fname):
336         print("No config file found ~/.patman\nCreating one...\n")
337         CreatePatmanConfigFile(gitutil, config_fname)
338
339     config.read(config_fname)
340
341     for name, value in GetItems(config, 'alias'):
342         alias[name] = value.split(',')
343
344     _ReadBouncesFile('doc/bounces')
345     for name, value in GetItems(config, 'bounces'):
346         bounces.add(value)
347
348     _UpdateDefaults(parser, config)
349
350 # These are the aliases we understand, indexed by alias. Each member is a list.
351 alias = {}
352 bounces = set()
353
354 if __name__ == "__main__":
355     import doctest
356
357     doctest.testmod()