xref: /openbmc/u-boot/tools/patman/settings.py (revision b8a48fbc)
11a459660SWolfgang Denk# SPDX-License-Identifier: GPL-2.0+
283d290c5STom Rini# Copyright (c) 2011 The Chromium OS Authors.
30d24de9dSSimon Glass#
40d24de9dSSimon Glass
5a920a17bSPaul Burtonfrom __future__ import print_function
6a920a17bSPaul Burton
72ce7b21eSPaul Burtontry:
82ce7b21eSPaul Burton    import configparser as ConfigParser
92ce7b21eSPaul Burtonexcept:
100d24de9dSSimon Glass    import ConfigParser
112ce7b21eSPaul Burton
120d24de9dSSimon Glassimport os
130d24de9dSSimon Glassimport re
140d24de9dSSimon Glass
150d24de9dSSimon Glassimport command
1687d65558SVikram Narayananimport gitutil
170d24de9dSSimon Glass
18a1dcee84SDoug Anderson"""Default settings per-project.
19a1dcee84SDoug Anderson
20a1dcee84SDoug AndersonThese are used by _ProjectConfigParser.  Settings names should match
21a1dcee84SDoug Andersonthe "dest" of the option parser from patman.py.
22a1dcee84SDoug Anderson"""
23a1dcee84SDoug Anderson_default_settings = {
24a1dcee84SDoug Anderson    "u-boot": {},
25a1dcee84SDoug Anderson    "linux": {
26a1dcee84SDoug Anderson        "process_tags": "False",
27a1dcee84SDoug Anderson    }
28a1dcee84SDoug Anderson}
29a1dcee84SDoug Anderson
30a1dcee84SDoug Andersonclass _ProjectConfigParser(ConfigParser.SafeConfigParser):
31a1dcee84SDoug Anderson    """ConfigParser that handles projects.
32a1dcee84SDoug Anderson
33a1dcee84SDoug Anderson    There are two main goals of this class:
34a1dcee84SDoug Anderson    - Load project-specific default settings.
35a1dcee84SDoug Anderson    - Merge general default settings/aliases with project-specific ones.
36a1dcee84SDoug Anderson
37a1dcee84SDoug Anderson    # Sample config used for tests below...
38f5d44b9bSPaul Burton    >>> try:
39f5d44b9bSPaul Burton    ...     from StringIO import StringIO
40f5d44b9bSPaul Burton    ... except ImportError:
41f5d44b9bSPaul Burton    ...     from io import StringIO
42a1dcee84SDoug Anderson    >>> sample_config = '''
43a1dcee84SDoug Anderson    ... [alias]
44a1dcee84SDoug Anderson    ... me: Peter P. <likesspiders@example.com>
45a1dcee84SDoug Anderson    ... enemies: Evil <evil@example.com>
46a1dcee84SDoug Anderson    ...
47a1dcee84SDoug Anderson    ... [sm_alias]
48a1dcee84SDoug Anderson    ... enemies: Green G. <ugly@example.com>
49a1dcee84SDoug Anderson    ...
50a1dcee84SDoug Anderson    ... [sm2_alias]
51a1dcee84SDoug Anderson    ... enemies: Doc O. <pus@example.com>
52a1dcee84SDoug Anderson    ...
53a1dcee84SDoug Anderson    ... [settings]
54a1dcee84SDoug Anderson    ... am_hero: True
55a1dcee84SDoug Anderson    ... '''
56a1dcee84SDoug Anderson
57a1dcee84SDoug Anderson    # Check to make sure that bogus project gets general alias.
58a1dcee84SDoug Anderson    >>> config = _ProjectConfigParser("zzz")
59f5d44b9bSPaul Burton    >>> config.readfp(StringIO(sample_config))
60a1dcee84SDoug Anderson    >>> config.get("alias", "enemies")
61a1dcee84SDoug Anderson    'Evil <evil@example.com>'
62a1dcee84SDoug Anderson
63a1dcee84SDoug Anderson    # Check to make sure that alias gets overridden by project.
64a1dcee84SDoug Anderson    >>> config = _ProjectConfigParser("sm")
65f5d44b9bSPaul Burton    >>> config.readfp(StringIO(sample_config))
66a1dcee84SDoug Anderson    >>> config.get("alias", "enemies")
67a1dcee84SDoug Anderson    'Green G. <ugly@example.com>'
68a1dcee84SDoug Anderson
69a1dcee84SDoug Anderson    # Check to make sure that settings get merged with project.
70a1dcee84SDoug Anderson    >>> config = _ProjectConfigParser("linux")
71f5d44b9bSPaul Burton    >>> config.readfp(StringIO(sample_config))
72a1dcee84SDoug Anderson    >>> sorted(config.items("settings"))
73a1dcee84SDoug Anderson    [('am_hero', 'True'), ('process_tags', 'False')]
74a1dcee84SDoug Anderson
75a1dcee84SDoug Anderson    # Check to make sure that settings works with unknown project.
76a1dcee84SDoug Anderson    >>> config = _ProjectConfigParser("unknown")
77f5d44b9bSPaul Burton    >>> config.readfp(StringIO(sample_config))
78a1dcee84SDoug Anderson    >>> sorted(config.items("settings"))
79a1dcee84SDoug Anderson    [('am_hero', 'True')]
80a1dcee84SDoug Anderson    """
81a1dcee84SDoug Anderson    def __init__(self, project_name):
82a1dcee84SDoug Anderson        """Construct _ProjectConfigParser.
83a1dcee84SDoug Anderson
84a1dcee84SDoug Anderson        In addition to standard SafeConfigParser initialization, this also loads
85a1dcee84SDoug Anderson        project defaults.
86a1dcee84SDoug Anderson
87a1dcee84SDoug Anderson        Args:
88a1dcee84SDoug Anderson            project_name: The name of the project.
89a1dcee84SDoug Anderson        """
90a1dcee84SDoug Anderson        self._project_name = project_name
91a1dcee84SDoug Anderson        ConfigParser.SafeConfigParser.__init__(self)
92a1dcee84SDoug Anderson
93a1dcee84SDoug Anderson        # Update the project settings in the config based on
94a1dcee84SDoug Anderson        # the _default_settings global.
95a1dcee84SDoug Anderson        project_settings = "%s_settings" % project_name
96a1dcee84SDoug Anderson        if not self.has_section(project_settings):
97a1dcee84SDoug Anderson            self.add_section(project_settings)
98a1dcee84SDoug Anderson        project_defaults = _default_settings.get(project_name, {})
99c9eac38aSPaul Burton        for setting_name, setting_value in project_defaults.items():
100a1dcee84SDoug Anderson            self.set(project_settings, setting_name, setting_value)
101a1dcee84SDoug Anderson
102a1dcee84SDoug Anderson    def get(self, section, option, *args, **kwargs):
103a1dcee84SDoug Anderson        """Extend SafeConfigParser to try project_section before section.
104a1dcee84SDoug Anderson
105a1dcee84SDoug Anderson        Args:
106a1dcee84SDoug Anderson            See SafeConfigParser.
107a1dcee84SDoug Anderson        Returns:
108a1dcee84SDoug Anderson            See SafeConfigParser.
109a1dcee84SDoug Anderson        """
110a1dcee84SDoug Anderson        try:
111a1dcee84SDoug Anderson            return ConfigParser.SafeConfigParser.get(
112a1dcee84SDoug Anderson                self, "%s_%s" % (self._project_name, section), option,
113a1dcee84SDoug Anderson                *args, **kwargs
114a1dcee84SDoug Anderson            )
115a1dcee84SDoug Anderson        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
116a1dcee84SDoug Anderson            return ConfigParser.SafeConfigParser.get(
117a1dcee84SDoug Anderson                self, section, option, *args, **kwargs
118a1dcee84SDoug Anderson            )
119a1dcee84SDoug Anderson
120a1dcee84SDoug Anderson    def items(self, section, *args, **kwargs):
121a1dcee84SDoug Anderson        """Extend SafeConfigParser to add project_section to section.
122a1dcee84SDoug Anderson
123a1dcee84SDoug Anderson        Args:
124a1dcee84SDoug Anderson            See SafeConfigParser.
125a1dcee84SDoug Anderson        Returns:
126a1dcee84SDoug Anderson            See SafeConfigParser.
127a1dcee84SDoug Anderson        """
128a1dcee84SDoug Anderson        project_items = []
129a1dcee84SDoug Anderson        has_project_section = False
130a1dcee84SDoug Anderson        top_items = []
131a1dcee84SDoug Anderson
132a1dcee84SDoug Anderson        # Get items from the project section
133a1dcee84SDoug Anderson        try:
134a1dcee84SDoug Anderson            project_items = ConfigParser.SafeConfigParser.items(
135a1dcee84SDoug Anderson                self, "%s_%s" % (self._project_name, section), *args, **kwargs
136a1dcee84SDoug Anderson            )
137a1dcee84SDoug Anderson            has_project_section = True
138a1dcee84SDoug Anderson        except ConfigParser.NoSectionError:
139a1dcee84SDoug Anderson            pass
140a1dcee84SDoug Anderson
141a1dcee84SDoug Anderson        # Get top-level items
142a1dcee84SDoug Anderson        try:
143a1dcee84SDoug Anderson            top_items = ConfigParser.SafeConfigParser.items(
144a1dcee84SDoug Anderson                self, section, *args, **kwargs
145a1dcee84SDoug Anderson            )
146a1dcee84SDoug Anderson        except ConfigParser.NoSectionError:
147a1dcee84SDoug Anderson            # If neither section exists raise the error on...
148a1dcee84SDoug Anderson            if not has_project_section:
149a1dcee84SDoug Anderson                raise
150a1dcee84SDoug Anderson
151a1dcee84SDoug Anderson        item_dict = dict(top_items)
152a1dcee84SDoug Anderson        item_dict.update(project_items)
153a1dcee84SDoug Anderson        return item_dict.items()
154a1dcee84SDoug Anderson
1550d24de9dSSimon Glassdef ReadGitAliases(fname):
1560d24de9dSSimon Glass    """Read a git alias file. This is in the form used by git:
1570d24de9dSSimon Glass
1580d24de9dSSimon Glass    alias uboot  u-boot@lists.denx.de
1590d24de9dSSimon Glass    alias wd     Wolfgang Denk <wd@denx.de>
1600d24de9dSSimon Glass
1610d24de9dSSimon Glass    Args:
1620d24de9dSSimon Glass        fname: Filename to read
1630d24de9dSSimon Glass    """
1640d24de9dSSimon Glass    try:
1650d24de9dSSimon Glass        fd = open(fname, 'r')
1660d24de9dSSimon Glass    except IOError:
167a920a17bSPaul Burton        print("Warning: Cannot find alias file '%s'" % fname)
1680d24de9dSSimon Glass        return
1690d24de9dSSimon Glass
1700d24de9dSSimon Glass    re_line = re.compile('alias\s+(\S+)\s+(.*)')
1710d24de9dSSimon Glass    for line in fd.readlines():
1720d24de9dSSimon Glass        line = line.strip()
1730d24de9dSSimon Glass        if not line or line[0] == '#':
1740d24de9dSSimon Glass            continue
1750d24de9dSSimon Glass
1760d24de9dSSimon Glass        m = re_line.match(line)
1770d24de9dSSimon Glass        if not m:
178a920a17bSPaul Burton            print("Warning: Alias file line '%s' not understood" % line)
1790d24de9dSSimon Glass            continue
1800d24de9dSSimon Glass
1810d24de9dSSimon Glass        list = alias.get(m.group(1), [])
1820d24de9dSSimon Glass        for item in m.group(2).split(','):
1830d24de9dSSimon Glass            item = item.strip()
1840d24de9dSSimon Glass            if item:
1850d24de9dSSimon Glass                list.append(item)
1860d24de9dSSimon Glass        alias[m.group(1)] = list
1870d24de9dSSimon Glass
1880d24de9dSSimon Glass    fd.close()
1890d24de9dSSimon Glass
19087d65558SVikram Narayanandef CreatePatmanConfigFile(config_fname):
19187d65558SVikram Narayanan    """Creates a config file under $(HOME)/.patman if it can't find one.
19287d65558SVikram Narayanan
19387d65558SVikram Narayanan    Args:
19487d65558SVikram Narayanan        config_fname: Default config filename i.e., $(HOME)/.patman
19587d65558SVikram Narayanan
19687d65558SVikram Narayanan    Returns:
19787d65558SVikram Narayanan        None
19887d65558SVikram Narayanan    """
19987d65558SVikram Narayanan    name = gitutil.GetDefaultUserName()
20087d65558SVikram Narayanan    if name == None:
20187d65558SVikram Narayanan        name = raw_input("Enter name: ")
20287d65558SVikram Narayanan
20387d65558SVikram Narayanan    email = gitutil.GetDefaultUserEmail()
20487d65558SVikram Narayanan
20587d65558SVikram Narayanan    if email == None:
20687d65558SVikram Narayanan        email = raw_input("Enter email: ")
20787d65558SVikram Narayanan
20887d65558SVikram Narayanan    try:
20987d65558SVikram Narayanan        f = open(config_fname, 'w')
21087d65558SVikram Narayanan    except IOError:
211a920a17bSPaul Burton        print("Couldn't create patman config file\n")
21287d65558SVikram Narayanan        raise
21387d65558SVikram Narayanan
214ad893140SSimon Glass    print('''[alias]
215ad893140SSimon Glassme: %s <%s>
216ad893140SSimon Glass
217ad893140SSimon Glass[bounces]
218ad893140SSimon Glassnxp = Zhikang Zhang <zhikang.zhang@nxp.com>
219ad893140SSimon Glass''' % (name, email), file=f)
22087d65558SVikram Narayanan    f.close();
22187d65558SVikram Narayanan
2228568baedSDoug Andersondef _UpdateDefaults(parser, config):
2238568baedSDoug Anderson    """Update the given OptionParser defaults based on config.
2248568baedSDoug Anderson
2258568baedSDoug Anderson    We'll walk through all of the settings from the parser
2268568baedSDoug Anderson    For each setting we'll look for a default in the option parser.
2278568baedSDoug Anderson    If it's found we'll update the option parser default.
2288568baedSDoug Anderson
2298568baedSDoug Anderson    The idea here is that the .patman file should be able to update
2308568baedSDoug Anderson    defaults but that command line flags should still have the final
2318568baedSDoug Anderson    say.
2328568baedSDoug Anderson
2338568baedSDoug Anderson    Args:
2348568baedSDoug Anderson        parser: An instance of an OptionParser whose defaults will be
2358568baedSDoug Anderson            updated.
236a1dcee84SDoug Anderson        config: An instance of _ProjectConfigParser that we will query
2378568baedSDoug Anderson            for settings.
2388568baedSDoug Anderson    """
2398568baedSDoug Anderson    defaults = parser.get_default_values()
2408568baedSDoug Anderson    for name, val in config.items('settings'):
2418568baedSDoug Anderson        if hasattr(defaults, name):
2428568baedSDoug Anderson            default_val = getattr(defaults, name)
2438568baedSDoug Anderson            if isinstance(default_val, bool):
2448568baedSDoug Anderson                val = config.getboolean('settings', name)
2458568baedSDoug Anderson            elif isinstance(default_val, int):
2468568baedSDoug Anderson                val = config.getint('settings', name)
2478568baedSDoug Anderson            parser.set_default(name, val)
2488568baedSDoug Anderson        else:
249a920a17bSPaul Burton            print("WARNING: Unknown setting %s" % name)
2508568baedSDoug Anderson
2518895b3e1SSimon Glassdef _ReadAliasFile(fname):
2528895b3e1SSimon Glass    """Read in the U-Boot git alias file if it exists.
2538895b3e1SSimon Glass
2548895b3e1SSimon Glass    Args:
2558895b3e1SSimon Glass        fname: Filename to read.
2568895b3e1SSimon Glass    """
2578895b3e1SSimon Glass    if os.path.exists(fname):
2588895b3e1SSimon Glass        bad_line = None
2598895b3e1SSimon Glass        with open(fname) as fd:
2608895b3e1SSimon Glass            linenum = 0
2618895b3e1SSimon Glass            for line in fd:
2628895b3e1SSimon Glass                linenum += 1
2638895b3e1SSimon Glass                line = line.strip()
2648895b3e1SSimon Glass                if not line or line.startswith('#'):
2658895b3e1SSimon Glass                    continue
266*b8a48fbcSAdam Sampson                words = line.split(None, 2)
2678895b3e1SSimon Glass                if len(words) < 3 or words[0] != 'alias':
2688895b3e1SSimon Glass                    if not bad_line:
2698895b3e1SSimon Glass                        bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum,
2708895b3e1SSimon Glass                                                                line)
2718895b3e1SSimon Glass                    continue
2728895b3e1SSimon Glass                alias[words[1]] = [s.strip() for s in words[2].split(',')]
2738895b3e1SSimon Glass        if bad_line:
274a920a17bSPaul Burton            print(bad_line)
2758895b3e1SSimon Glass
276e11aa602SChris Packhamdef _ReadBouncesFile(fname):
277e11aa602SChris Packham    """Read in the bounces file if it exists
278e11aa602SChris Packham
279e11aa602SChris Packham    Args:
280e11aa602SChris Packham        fname: Filename to read.
281e11aa602SChris Packham    """
282e11aa602SChris Packham    if os.path.exists(fname):
283e11aa602SChris Packham        with open(fname) as fd:
284e11aa602SChris Packham            for line in fd:
285e11aa602SChris Packham                if line.startswith('#'):
286e11aa602SChris Packham                    continue
287e11aa602SChris Packham                bounces.add(line.strip())
288e11aa602SChris Packham
289ad893140SSimon Glassdef GetItems(config, section):
290ad893140SSimon Glass    """Get the items from a section of the config.
291ad893140SSimon Glass
292ad893140SSimon Glass    Args:
293ad893140SSimon Glass        config: _ProjectConfigParser object containing settings
294ad893140SSimon Glass        section: name of section to retrieve
295ad893140SSimon Glass
296ad893140SSimon Glass    Returns:
297ad893140SSimon Glass        List of (name, value) tuples for the section
298ad893140SSimon Glass    """
299ad893140SSimon Glass    try:
300ad893140SSimon Glass        return config.items(section)
301ad893140SSimon Glass    except ConfigParser.NoSectionError as e:
302ad893140SSimon Glass        return []
303ad893140SSimon Glass    except:
304ad893140SSimon Glass        raise
305ad893140SSimon Glass
306a1dcee84SDoug Andersondef Setup(parser, project_name, config_fname=''):
3070d24de9dSSimon Glass    """Set up the settings module by reading config files.
3080d24de9dSSimon Glass
3090d24de9dSSimon Glass    Args:
3108568baedSDoug Anderson        parser:         The parser to update
311a1dcee84SDoug Anderson        project_name:   Name of project that we're working on; we'll look
312a1dcee84SDoug Anderson            for sections named "project_section" as well.
3130d24de9dSSimon Glass        config_fname:   Config filename to read ('' for default)
3140d24de9dSSimon Glass    """
3158895b3e1SSimon Glass    # First read the git alias file if available
3168895b3e1SSimon Glass    _ReadAliasFile('doc/git-mailrc')
317a1dcee84SDoug Anderson    config = _ProjectConfigParser(project_name)
3180d24de9dSSimon Glass    if config_fname == '':
3192b36c75dSVikram Narayanan        config_fname = '%s/.patman' % os.getenv('HOME')
32087d65558SVikram Narayanan
32187d65558SVikram Narayanan    if not os.path.exists(config_fname):
322a920a17bSPaul Burton        print("No config file found ~/.patman\nCreating one...\n")
32387d65558SVikram Narayanan        CreatePatmanConfigFile(config_fname)
32487d65558SVikram Narayanan
3258568baedSDoug Anderson    config.read(config_fname)
3260d24de9dSSimon Glass
327ad893140SSimon Glass    for name, value in GetItems(config, 'alias'):
3280d24de9dSSimon Glass        alias[name] = value.split(',')
3290d24de9dSSimon Glass
330e11aa602SChris Packham    _ReadBouncesFile('doc/bounces')
331ad893140SSimon Glass    for name, value in GetItems(config, 'bounces'):
332e11aa602SChris Packham        bounces.add(value)
333e11aa602SChris Packham
3348568baedSDoug Anderson    _UpdateDefaults(parser, config)
3350d24de9dSSimon Glass
3360d24de9dSSimon Glass# These are the aliases we understand, indexed by alias. Each member is a list.
3370d24de9dSSimon Glassalias = {}
338e11aa602SChris Packhambounces = set()
339a1dcee84SDoug Anderson
340a1dcee84SDoug Andersonif __name__ == "__main__":
341a1dcee84SDoug Anderson    import doctest
342a1dcee84SDoug Anderson
343a1dcee84SDoug Anderson    doctest.testmod()
344