Prepare v2022.04-rc4
[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     "gcc": {
28         "process_tags": "False",
29         "add_signoff": "False",
30         "check_patch": "False",
31     },
32 }
33
34 class _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...
42     >>> from io import StringIO
43     >>> 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")
60     >>> config.readfp(StringIO(sample_config))
61     >>> str(config.get("alias", "enemies"))
62     'Evil <evil@example.com>'
63
64     # Check to make sure that alias gets overridden by project.
65     >>> config = _ProjectConfigParser("sm")
66     >>> config.readfp(StringIO(sample_config))
67     >>> str(config.get("alias", "enemies"))
68     'Green G. <ugly@example.com>'
69
70     # Check to make sure that settings get merged with project.
71     >>> config = _ProjectConfigParser("linux")
72     >>> config.readfp(StringIO(sample_config))
73     >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
74     [('am_hero', 'True'), ('process_tags', 'False')]
75
76     # Check to make sure that settings works with unknown project.
77     >>> config = _ProjectConfigParser("unknown")
78     >>> config.readfp(StringIO(sample_config))
79     >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
80     [('am_hero', 'True')]
81     """
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, {})
100         for setting_name, setting_value in project_defaults.items():
101             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:
112             val = ConfigParser.SafeConfigParser.get(
113                 self, "%s_%s" % (self._project_name, section), option,
114                 *args, **kwargs
115             )
116         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
117             val = ConfigParser.SafeConfigParser.get(
118                 self, section, option, *args, **kwargs
119             )
120         return val
121
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)
155         return {(item, val) for item, val in item_dict.items()}
156
157 def 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:
167         fd = open(fname, 'r', encoding='utf-8')
168     except IOError:
169         print("Warning: Cannot find alias file '%s'" % fname)
170         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:
180             print("Warning: Alias file line '%s' not understood" % line)
181             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
192 def CreatePatmanConfigFile(gitutil, config_fname):
193     """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.get_default_user_name()
202     if name == None:
203         name = raw_input("Enter name: ")
204
205     email = gitutil.get_default_user_email()
206
207     if email == None:
208         email = raw_input("Enter email: ")
209
210     try:
211         f = open(config_fname, 'w')
212     except IOError:
213         print("Couldn't create patman config file\n")
214         raise
215
216     print('''[alias]
217 me: %s <%s>
218
219 [bounces]
220 nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
221 ''' % (name, email), file=f)
222     f.close();
223
224 def _UpdateDefaults(main_parser, config):
225     """Update the given OptionParser defaults based on config.
226
227     We'll walk through all of the settings from all parsers.
228     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:
236         parser: An instance of an ArgumentParser whose defaults will be
237             updated.
238         config: An instance of _ProjectConfigParser that we will query
239             for settings.
240     """
241     # 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
254     for name, val in config.items('settings'):
255         if name in defaults:
256             default_val = defaults[name]
257             if isinstance(default_val, bool):
258                 val = config.getboolean('settings', name)
259             elif isinstance(default_val, int):
260                 val = config.getint('settings', name)
261             elif isinstance(default_val, str):
262                 val = config.get('settings', name)
263             defaults[name] = val
264         else:
265             print("WARNING: Unknown setting %s" % name)
266
267     # Set all the defaults (this propagates through all subparsers)
268     main_parser.set_defaults(**defaults)
269
270 def _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
278         with open(fname, encoding='utf-8') as fd:
279             linenum = 0
280             for line in fd:
281                 linenum += 1
282                 line = line.strip()
283                 if not line or line.startswith('#'):
284                     continue
285                 words = line.split(None, 2)
286                 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:
293             print(bad_line)
294
295 def _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
308 def 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
325 def Setup(gitutil, parser, project_name, config_fname=''):
326     """Set up the settings module by reading config files.
327
328     Args:
329         parser:         The parser to update
330         project_name:   Name of project that we're working on; we'll look
331             for sections named "project_section" as well.
332         config_fname:   Config filename to read ('' for default)
333     """
334     # First read the git alias file if available
335     _ReadAliasFile('doc/git-mailrc')
336     config = _ProjectConfigParser(project_name)
337     if config_fname == '':
338         config_fname = '%s/.patman' % os.getenv('HOME')
339
340     if not os.path.exists(config_fname):
341         print("No config file found ~/.patman\nCreating one...\n")
342         CreatePatmanConfigFile(gitutil, config_fname)
343
344     config.read(config_fname)
345
346     for name, value in GetItems(config, 'alias'):
347         alias[name] = value.split(',')
348
349     _ReadBouncesFile('doc/bounces')
350     for name, value in GetItems(config, 'bounces'):
351         bounces.add(value)
352
353     _UpdateDefaults(parser, config)
354
355 # These are the aliases we understand, indexed by alias. Each member is a list.
356 alias = {}
357 bounces = set()
358
359 if __name__ == "__main__":
360     import doctest
361
362     doctest.testmod()