Prepare v2023.10
[platform/kernel/u-boot.git] / tools / patman / settings.py
index c98911d..636983e 100644 (file)
@@ -1,20 +1,18 @@
 # SPDX-License-Identifier: GPL-2.0+
 # Copyright (c) 2011 The Chromium OS Authors.
+# Copyright (c) 2022 Maxim Cournoyer <maxim.cournoyer@savoirfairelinux.com>
 #
 
-from __future__ import print_function
-
 try:
     import configparser as ConfigParser
-except:
+except Exception:
     import ConfigParser
 
+import argparse
 import os
 import re
 
-import command
-import gitutil
-import tools
+from patman import gitutil
 
 """Default settings per-project.
 
@@ -25,10 +23,17 @@ _default_settings = {
     "u-boot": {},
     "linux": {
         "process_tags": "False",
-    }
+        "check_patch_use_tree": "True",
+    },
+    "gcc": {
+        "process_tags": "False",
+        "add_signoff": "False",
+        "check_patch": "False",
+    },
 }
 
-class _ProjectConfigParser(ConfigParser.SafeConfigParser):
+
+class _ProjectConfigParser(ConfigParser.ConfigParser):
     """ConfigParser that handles projects.
 
     There are two main goals of this class:
@@ -36,10 +41,7 @@ class _ProjectConfigParser(ConfigParser.SafeConfigParser):
     - Merge general default settings/aliases with project-specific ones.
 
     # Sample config used for tests below...
-    >>> try:
-    ...     from StringIO import StringIO
-    ... except ImportError:
-    ...     from io import StringIO
+    >>> from io import StringIO
     >>> sample_config = '''
     ... [alias]
     ... me: Peter P. <likesspiders@example.com>
@@ -71,7 +73,7 @@ class _ProjectConfigParser(ConfigParser.SafeConfigParser):
     >>> config = _ProjectConfigParser("linux")
     >>> config.readfp(StringIO(sample_config))
     >>> sorted((str(a), str(b)) for (a, b) in config.items("settings"))
-    [('am_hero', 'True'), ('process_tags', 'False')]
+    [('am_hero', 'True'), ('check_patch_use_tree', 'True'), ('process_tags', 'False')]
 
     # Check to make sure that settings works with unknown project.
     >>> config = _ProjectConfigParser("unknown")
@@ -82,14 +84,14 @@ class _ProjectConfigParser(ConfigParser.SafeConfigParser):
     def __init__(self, project_name):
         """Construct _ProjectConfigParser.
 
-        In addition to standard SafeConfigParser initialization, this also loads
-        project defaults.
+        In addition to standard ConfigParser initialization, this also
+        loads project defaults.
 
         Args:
             project_name: The name of the project.
         """
         self._project_name = project_name
-        ConfigParser.SafeConfigParser.__init__(self)
+        ConfigParser.ConfigParser.__init__(self)
 
         # Update the project settings in the config based on
         # the _default_settings global.
@@ -101,31 +103,31 @@ class _ProjectConfigParser(ConfigParser.SafeConfigParser):
             self.set(project_settings, setting_name, setting_value)
 
     def get(self, section, option, *args, **kwargs):
-        """Extend SafeConfigParser to try project_section before section.
+        """Extend ConfigParser to try project_section before section.
 
         Args:
-            See SafeConfigParser.
+            See ConfigParser.
         Returns:
-            See SafeConfigParser.
+            See ConfigParser.
         """
         try:
-            val = ConfigParser.SafeConfigParser.get(
+            val = ConfigParser.ConfigParser.get(
                 self, "%s_%s" % (self._project_name, section), option,
                 *args, **kwargs
             )
         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
-            val = ConfigParser.SafeConfigParser.get(
+            val = ConfigParser.ConfigParser.get(
                 self, section, option, *args, **kwargs
             )
-        return tools.ToUnicode(val)
+        return val
 
     def items(self, section, *args, **kwargs):
-        """Extend SafeConfigParser to add project_section to section.
+        """Extend ConfigParser to add project_section to section.
 
         Args:
-            See SafeConfigParser.
+            See ConfigParser.
         Returns:
-            See SafeConfigParser.
+            See ConfigParser.
         """
         project_items = []
         has_project_section = False
@@ -133,7 +135,7 @@ class _ProjectConfigParser(ConfigParser.SafeConfigParser):
 
         # Get items from the project section
         try:
-            project_items = ConfigParser.SafeConfigParser.items(
+            project_items = ConfigParser.ConfigParser.items(
                 self, "%s_%s" % (self._project_name, section), *args, **kwargs
             )
             has_project_section = True
@@ -142,7 +144,7 @@ class _ProjectConfigParser(ConfigParser.SafeConfigParser):
 
         # Get top-level items
         try:
-            top_items = ConfigParser.SafeConfigParser.items(
+            top_items = ConfigParser.ConfigParser.items(
                 self, section, *args, **kwargs
             )
         except ConfigParser.NoSectionError:
@@ -152,8 +154,8 @@ class _ProjectConfigParser(ConfigParser.SafeConfigParser):
 
         item_dict = dict(top_items)
         item_dict.update(project_items)
-        return {(tools.ToUnicode(item), tools.ToUnicode(val))
-                for item, val in item_dict.items()}
+        return {(item, val) for item, val in item_dict.items()}
+
 
 def ReadGitAliases(fname):
     """Read a git alias file. This is in the form used by git:
@@ -165,12 +167,12 @@ def ReadGitAliases(fname):
         fname: Filename to read
     """
     try:
-        fd = open(fname, 'r')
+        fd = open(fname, 'r', encoding='utf-8')
     except IOError:
         print("Warning: Cannot find alias file '%s'" % fname)
         return
 
-    re_line = re.compile('alias\s+(\S+)\s+(.*)')
+    re_line = re.compile(r'alias\s+(\S+)\s+(.*)')
     for line in fd.readlines():
         line = line.strip()
         if not line or line[0] == '#':
@@ -190,6 +192,7 @@ def ReadGitAliases(fname):
 
     fd.close()
 
+
 def CreatePatmanConfigFile(config_fname):
     """Creates a config file under $(HOME)/.patman if it can't find one.
 
@@ -199,14 +202,14 @@ def CreatePatmanConfigFile(config_fname):
     Returns:
         None
     """
-    name = gitutil.GetDefaultUserName()
-    if name == None:
-        name = raw_input("Enter name: ")
+    name = gitutil.get_default_user_name()
+    if name is None:
+        name = input("Enter name: ")
 
-    email = gitutil.GetDefaultUserEmail()
+    email = gitutil.get_default_user_email()
 
-    if email == None:
-        email = raw_input("Enter email: ")
+    if email is None:
+        email = input("Enter email: ")
 
     try:
         f = open(config_fname, 'w')
@@ -220,12 +223,13 @@ me: %s <%s>
 [bounces]
 nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
 ''' % (name, email), file=f)
-    f.close();
+    f.close()
 
-def _UpdateDefaults(parser, config):
+
+def _UpdateDefaults(main_parser, config):
     """Update the given OptionParser defaults based on config.
 
-    We'll walk through all of the settings from the parser
+    We'll walk through all of the settings from all parsers.
     For each setting we'll look for a default in the option parser.
     If it's found we'll update the option parser default.
 
@@ -234,23 +238,46 @@ def _UpdateDefaults(parser, config):
     say.
 
     Args:
-        parser: An instance of an OptionParser whose defaults will be
+        parser: An instance of an ArgumentParser whose defaults will be
             updated.
         config: An instance of _ProjectConfigParser that we will query
             for settings.
     """
-    defaults = parser.get_default_values()
+    # Find all the parsers and subparsers
+    parsers = [main_parser]
+    parsers += [subparser for action in main_parser._actions
+                if isinstance(action, argparse._SubParsersAction)
+                for _, subparser in action.choices.items()]
+
+    # Collect the defaults from each parser
+    defaults = {}
+    parser_defaults = []
+    for parser in parsers:
+        pdefs = parser.parse_known_args()[0]
+        parser_defaults.append(pdefs)
+        defaults.update(vars(pdefs))
+
+    # Go through the settings and collect defaults
     for name, val in config.items('settings'):
-        if hasattr(defaults, name):
-            default_val = getattr(defaults, name)
+        if name in defaults:
+            default_val = defaults[name]
             if isinstance(default_val, bool):
                 val = config.getboolean('settings', name)
             elif isinstance(default_val, int):
                 val = config.getint('settings', name)
-            parser.set_default(name, val)
+            elif isinstance(default_val, str):
+                val = config.get('settings', name)
+            defaults[name] = val
         else:
             print("WARNING: Unknown setting %s" % name)
 
+    # Set all the defaults and manually propagate them to subparsers
+    main_parser.set_defaults(**defaults)
+    for parser, pdefs in zip(parsers, parser_defaults):
+        parser.set_defaults(**{k: v for k, v in defaults.items()
+                               if k in pdefs})
+
+
 def _ReadAliasFile(fname):
     """Read in the U-Boot git alias file if it exists.
 
@@ -259,7 +286,7 @@ def _ReadAliasFile(fname):
     """
     if os.path.exists(fname):
         bad_line = None
-        with open(fname) as fd:
+        with open(fname, encoding='utf-8') as fd:
             linenum = 0
             for line in fd:
                 linenum += 1
@@ -276,6 +303,7 @@ def _ReadAliasFile(fname):
         if bad_line:
             print(bad_line)
 
+
 def _ReadBouncesFile(fname):
     """Read in the bounces file if it exists
 
@@ -289,6 +317,7 @@ def _ReadBouncesFile(fname):
                     continue
                 bounces.add(line.strip())
 
+
 def GetItems(config, section):
     """Get the items from a section of the config.
 
@@ -301,31 +330,50 @@ def GetItems(config, section):
     """
     try:
         return config.items(section)
-    except ConfigParser.NoSectionError as e:
+    except ConfigParser.NoSectionError:
         return []
-    except:
-        raise
 
-def Setup(parser, project_name, config_fname=''):
+
+def Setup(parser, project_name, config_fname=None):
     """Set up the settings module by reading config files.
 
+    Unless `config_fname` is specified, a `.patman` config file local
+    to the git repository is consulted, followed by the global
+    `$HOME/.patman`. If none exists, the later is created. Values
+    defined in the local config file take precedence over those
+    defined in the global one.
+
     Args:
-        parser:         The parser to update
+        parser:         The parser to update.
         project_name:   Name of project that we're working on; we'll look
             for sections named "project_section" as well.
-        config_fname:   Config filename to read ('' for default)
+        config_fname:   Config filename to read.  An error is raised if it
+            does not exist.
     """
     # First read the git alias file if available
     _ReadAliasFile('doc/git-mailrc')
     config = _ProjectConfigParser(project_name)
-    if config_fname == '':
+
+    if config_fname and not os.path.exists(config_fname):
+        raise Exception(f'provided {config_fname} does not exist')
+
+    if not config_fname:
         config_fname = '%s/.patman' % os.getenv('HOME')
+    has_config = os.path.exists(config_fname)
 
-    if not os.path.exists(config_fname):
-        print("No config file found ~/.patman\nCreating one...\n")
-        CreatePatmanConfigFile(config_fname)
+    git_local_config_fname = os.path.join(gitutil.get_top_level(), '.patman')
+    has_git_local_config = os.path.exists(git_local_config_fname)
 
-    config.read(config_fname)
+    # Read the git local config last, so that its values override
+    # those of the global config, if any.
+    if has_config:
+        config.read(config_fname)
+    if has_git_local_config:
+        config.read(git_local_config_fname)
+
+    if not (has_config or has_git_local_config):
+        print("No config file found.\nCreating ~/.patman...\n")
+        CreatePatmanConfigFile(config_fname)
 
     for name, value in GetItems(config, 'alias'):
         alias[name] = value.split(',')
@@ -336,6 +384,7 @@ def Setup(parser, project_name, config_fname=''):
 
     _UpdateDefaults(parser, config)
 
+
 # These are the aliases we understand, indexed by alias. Each member is a list.
 alias = {}
 bounces = set()