Merge tag 'u-boot-atmel-fixes-2021.01-b' of https://gitlab.denx.de/u-boot/custodians...
[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 tools.ToUnicode(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 {(tools.ToUnicode(item), tools.ToUnicode(val))
151                 for item, val in item_dict.items()}
152
153 def ReadGitAliases(fname):
154     """Read a git alias file. This is in the form used by git:
155
156     alias uboot  u-boot@lists.denx.de
157     alias wd     Wolfgang Denk <wd@denx.de>
158
159     Args:
160         fname: Filename to read
161     """
162     try:
163         fd = open(fname, 'r', encoding='utf-8')
164     except IOError:
165         print("Warning: Cannot find alias file '%s'" % fname)
166         return
167
168     re_line = re.compile('alias\s+(\S+)\s+(.*)')
169     for line in fd.readlines():
170         line = line.strip()
171         if not line or line[0] == '#':
172             continue
173
174         m = re_line.match(line)
175         if not m:
176             print("Warning: Alias file line '%s' not understood" % line)
177             continue
178
179         list = alias.get(m.group(1), [])
180         for item in m.group(2).split(','):
181             item = item.strip()
182             if item:
183                 list.append(item)
184         alias[m.group(1)] = list
185
186     fd.close()
187
188 def CreatePatmanConfigFile(gitutil, config_fname):
189     """Creates a config file under $(HOME)/.patman if it can't find one.
190
191     Args:
192         config_fname: Default config filename i.e., $(HOME)/.patman
193
194     Returns:
195         None
196     """
197     name = gitutil.GetDefaultUserName()
198     if name == None:
199         name = raw_input("Enter name: ")
200
201     email = gitutil.GetDefaultUserEmail()
202
203     if email == None:
204         email = raw_input("Enter email: ")
205
206     try:
207         f = open(config_fname, 'w')
208     except IOError:
209         print("Couldn't create patman config file\n")
210         raise
211
212     print('''[alias]
213 me: %s <%s>
214
215 [bounces]
216 nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
217 ''' % (name, email), file=f)
218     f.close();
219
220 def _UpdateDefaults(main_parser, config):
221     """Update the given OptionParser defaults based on config.
222
223     We'll walk through all of the settings from all parsers.
224     For each setting we'll look for a default in the option parser.
225     If it's found we'll update the option parser default.
226
227     The idea here is that the .patman file should be able to update
228     defaults but that command line flags should still have the final
229     say.
230
231     Args:
232         parser: An instance of an ArgumentParser whose defaults will be
233             updated.
234         config: An instance of _ProjectConfigParser that we will query
235             for settings.
236     """
237     # Find all the parsers and subparsers
238     parsers = [main_parser]
239     parsers += [subparser for action in main_parser._actions
240                   if isinstance(action, argparse._SubParsersAction)
241                   for _, subparser in action.choices.items()]
242
243     # Collect the defaults from each parser
244     defaults = {}
245     for parser in parsers:
246         pdefs = parser.parse_known_args()[0]
247         defaults.update(vars(pdefs))
248
249     # Go through the settings and collect defaults
250     for name, val in config.items('settings'):
251         if name in defaults:
252             default_val = defaults[name]
253             if isinstance(default_val, bool):
254                 val = config.getboolean('settings', name)
255             elif isinstance(default_val, int):
256                 val = config.getint('settings', name)
257             elif isinstance(default_val, str):
258                 val = config.get('settings', name)
259             defaults[name] = val
260         else:
261             print("WARNING: Unknown setting %s" % name)
262
263     # Set all the defaults (this propagates through all subparsers)
264     main_parser.set_defaults(**defaults)
265
266 def _ReadAliasFile(fname):
267     """Read in the U-Boot git alias file if it exists.
268
269     Args:
270         fname: Filename to read.
271     """
272     if os.path.exists(fname):
273         bad_line = None
274         with open(fname, encoding='utf-8') as fd:
275             linenum = 0
276             for line in fd:
277                 linenum += 1
278                 line = line.strip()
279                 if not line or line.startswith('#'):
280                     continue
281                 words = line.split(None, 2)
282                 if len(words) < 3 or words[0] != 'alias':
283                     if not bad_line:
284                         bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
285                                                                 line)
286                     continue
287                 alias[words[1]] = [s.strip() for s in words[2].split(',')]
288         if bad_line:
289             print(bad_line)
290
291 def _ReadBouncesFile(fname):
292     """Read in the bounces file if it exists
293
294     Args:
295         fname: Filename to read.
296     """
297     if os.path.exists(fname):
298         with open(fname) as fd:
299             for line in fd:
300                 if line.startswith('#'):
301                     continue
302                 bounces.add(line.strip())
303
304 def GetItems(config, section):
305     """Get the items from a section of the config.
306
307     Args:
308         config: _ProjectConfigParser object containing settings
309         section: name of section to retrieve
310
311     Returns:
312         List of (name, value) tuples for the section
313     """
314     try:
315         return config.items(section)
316     except ConfigParser.NoSectionError as e:
317         return []
318     except:
319         raise
320
321 def Setup(gitutil, parser, project_name, config_fname=''):
322     """Set up the settings module by reading config files.
323
324     Args:
325         parser:         The parser to update
326         project_name:   Name of project that we're working on; we'll look
327             for sections named "project_section" as well.
328         config_fname:   Config filename to read ('' for default)
329     """
330     # First read the git alias file if available
331     _ReadAliasFile('doc/git-mailrc')
332     config = _ProjectConfigParser(project_name)
333     if config_fname == '':
334         config_fname = '%s/.patman' % os.getenv('HOME')
335
336     if not os.path.exists(config_fname):
337         print("No config file found ~/.patman\nCreating one...\n")
338         CreatePatmanConfigFile(gitutil, config_fname)
339
340     config.read(config_fname)
341
342     for name, value in GetItems(config, 'alias'):
343         alias[name] = value.split(',')
344
345     _ReadBouncesFile('doc/bounces')
346     for name, value in GetItems(config, 'bounces'):
347         bounces.add(value)
348
349     _UpdateDefaults(parser, config)
350
351 # These are the aliases we understand, indexed by alias. Each member is a list.
352 alias = {}
353 bounces = set()
354
355 if __name__ == "__main__":
356     import doctest
357
358     doctest.testmod()