Merge tag 'dm-pull-10jul20' of https://gitlab.denx.de/u-boot/custodians/u-boot-dm
[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 os
11 import re
12
13 from patman import command
14 from patman import tools
15
16 """Default settings per-project.
17
18 These are used by _ProjectConfigParser.  Settings names should match
19 the "dest" of the option parser from patman.py.
20 """
21 _default_settings = {
22     "u-boot": {},
23     "linux": {
24         "process_tags": "False",
25     }
26 }
27
28 class _ProjectConfigParser(ConfigParser.SafeConfigParser):
29     """ConfigParser that handles projects.
30
31     There are two main goals of this class:
32     - Load project-specific default settings.
33     - Merge general default settings/aliases with project-specific ones.
34
35     # Sample config used for tests below...
36     >>> from io import StringIO
37     >>> sample_config = '''
38     ... [alias]
39     ... me: Peter P. <likesspiders@example.com>
40     ... enemies: Evil <evil@example.com>
41     ...
42     ... [sm_alias]
43     ... enemies: Green G. <ugly@example.com>
44     ...
45     ... [sm2_alias]
46     ... enemies: Doc O. <pus@example.com>
47     ...
48     ... [settings]
49     ... am_hero: True
50     ... '''
51
52     # Check to make sure that bogus project gets general alias.
53     >>> config = _ProjectConfigParser("zzz")
54     >>> config.readfp(StringIO(sample_config))
55     >>> str(config.get("alias", "enemies"))
56     'Evil <evil@example.com>'
57
58     # Check to make sure that alias gets overridden by project.
59     >>> config = _ProjectConfigParser("sm")
60     >>> config.readfp(StringIO(sample_config))
61     >>> str(config.get("alias", "enemies"))
62     'Green G. <ugly@example.com>'
63
64     # Check to make sure that settings get merged with project.
65     >>> config = _ProjectConfigParser("linux")
66     >>> config.readfp(StringIO(sample_config))
67     >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
68     [('am_hero', 'True'), ('process_tags', 'False')]
69
70     # Check to make sure that settings works with unknown project.
71     >>> config = _ProjectConfigParser("unknown")
72     >>> config.readfp(StringIO(sample_config))
73     >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
74     [('am_hero', 'True')]
75     """
76     def __init__(self, project_name):
77         """Construct _ProjectConfigParser.
78
79         In addition to standard SafeConfigParser initialization, this also loads
80         project defaults.
81
82         Args:
83             project_name: The name of the project.
84         """
85         self._project_name = project_name
86         ConfigParser.SafeConfigParser.__init__(self)
87
88         # Update the project settings in the config based on
89         # the _default_settings global.
90         project_settings = "%s_settings" % project_name
91         if not self.has_section(project_settings):
92             self.add_section(project_settings)
93         project_defaults = _default_settings.get(project_name, {})
94         for setting_name, setting_value in project_defaults.items():
95             self.set(project_settings, setting_name, setting_value)
96
97     def get(self, section, option, *args, **kwargs):
98         """Extend SafeConfigParser to try project_section before section.
99
100         Args:
101             See SafeConfigParser.
102         Returns:
103             See SafeConfigParser.
104         """
105         try:
106             val = ConfigParser.SafeConfigParser.get(
107                 self, "%s_%s" % (self._project_name, section), option,
108                 *args, **kwargs
109             )
110         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
111             val = ConfigParser.SafeConfigParser.get(
112                 self, section, option, *args, **kwargs
113             )
114         return tools.ToUnicode(val)
115
116     def items(self, section, *args, **kwargs):
117         """Extend SafeConfigParser to add project_section to section.
118
119         Args:
120             See SafeConfigParser.
121         Returns:
122             See SafeConfigParser.
123         """
124         project_items = []
125         has_project_section = False
126         top_items = []
127
128         # Get items from the project section
129         try:
130             project_items = ConfigParser.SafeConfigParser.items(
131                 self, "%s_%s" % (self._project_name, section), *args, **kwargs
132             )
133             has_project_section = True
134         except ConfigParser.NoSectionError:
135             pass
136
137         # Get top-level items
138         try:
139             top_items = ConfigParser.SafeConfigParser.items(
140                 self, section, *args, **kwargs
141             )
142         except ConfigParser.NoSectionError:
143             # If neither section exists raise the error on...
144             if not has_project_section:
145                 raise
146
147         item_dict = dict(top_items)
148         item_dict.update(project_items)
149         return {(tools.ToUnicode(item), tools.ToUnicode(val))
150                 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(parser, config):
220     """Update the given OptionParser defaults based on config.
221
222     We'll walk through all of the settings from the parser
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 OptionParser whose defaults will be
232             updated.
233         config: An instance of _ProjectConfigParser that we will query
234             for settings.
235     """
236     defaults = parser.get_default_values()
237     for name, val in config.items('settings'):
238         if hasattr(defaults, name):
239             default_val = getattr(defaults, name)
240             if isinstance(default_val, bool):
241                 val = config.getboolean('settings', name)
242             elif isinstance(default_val, int):
243                 val = config.getint('settings', name)
244             parser.set_default(name, val)
245         else:
246             print("WARNING: Unknown setting %s" % name)
247
248 def _ReadAliasFile(fname):
249     """Read in the U-Boot git alias file if it exists.
250
251     Args:
252         fname: Filename to read.
253     """
254     if os.path.exists(fname):
255         bad_line = None
256         with open(fname, encoding='utf-8') as fd:
257             linenum = 0
258             for line in fd:
259                 linenum += 1
260                 line = line.strip()
261                 if not line or line.startswith('#'):
262                     continue
263                 words = line.split(None, 2)
264                 if len(words) < 3 or words[0] != 'alias':
265                     if not bad_line:
266                         bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
267                                                                 line)
268                     continue
269                 alias[words[1]] = [s.strip() for s in words[2].split(',')]
270         if bad_line:
271             print(bad_line)
272
273 def _ReadBouncesFile(fname):
274     """Read in the bounces file if it exists
275
276     Args:
277         fname: Filename to read.
278     """
279     if os.path.exists(fname):
280         with open(fname) as fd:
281             for line in fd:
282                 if line.startswith('#'):
283                     continue
284                 bounces.add(line.strip())
285
286 def GetItems(config, section):
287     """Get the items from a section of the config.
288
289     Args:
290         config: _ProjectConfigParser object containing settings
291         section: name of section to retrieve
292
293     Returns:
294         List of (name, value) tuples for the section
295     """
296     try:
297         return config.items(section)
298     except ConfigParser.NoSectionError as e:
299         return []
300     except:
301         raise
302
303 def Setup(gitutil, parser, project_name, config_fname=''):
304     """Set up the settings module by reading config files.
305
306     Args:
307         parser:         The parser to update
308         project_name:   Name of project that we're working on; we'll look
309             for sections named "project_section" as well.
310         config_fname:   Config filename to read ('' for default)
311     """
312     # First read the git alias file if available
313     _ReadAliasFile('doc/git-mailrc')
314     config = _ProjectConfigParser(project_name)
315     if config_fname == '':
316         config_fname = '%s/.patman' % os.getenv('HOME')
317
318     if not os.path.exists(config_fname):
319         print("No config file found ~/.patman\nCreating one...\n")
320         CreatePatmanConfigFile(gitutil, config_fname)
321
322     config.read(config_fname)
323
324     for name, value in GetItems(config, 'alias'):
325         alias[name] = value.split(',')
326
327     _ReadBouncesFile('doc/bounces')
328     for name, value in GetItems(config, 'bounces'):
329         bounces.add(value)
330
331     _UpdateDefaults(parser, config)
332
333 # These are the aliases we understand, indexed by alias. Each member is a list.
334 alias = {}
335 bounces = set()
336
337 if __name__ == "__main__":
338     import doctest
339
340     doctest.testmod()