[U-Boot] [PATCH 2/2] RFC: moveconfig: Add an experimental 'automatic imply' feature

Simon Glass sjg at chromium.org
Mon Jun 5 19:08:15 UTC 2017


This is a work-in-progress feature which:

- shows what CONFIG options are already implied by others
- automatically adds 'imply' keywords to implying options

I have found this useful for minimising the size of defconfig files when
moving options to Kconfig.

This need some tidy-up and more testing, but I'd like to get feedback if
anyone wants to try it out.

Signed-off-by: Simon Glass <sjg at chromium.org>
---

 tools/moveconfig.py | 194 +++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 183 insertions(+), 11 deletions(-)

diff --git a/tools/moveconfig.py b/tools/moveconfig.py
index 4295b47d7c..00cea779a6 100755
--- a/tools/moveconfig.py
+++ b/tools/moveconfig.py
@@ -276,6 +276,9 @@ import tempfile
 import threading
 import time
 
+sys.path.append(os.path.join(os.path.dirname(__file__), 'buildman'))
+import kconfiglib
+
 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
 SLEEP_TIME=0.03
 
@@ -804,6 +807,19 @@ class Progress:
         print ' %d defconfigs out of %d\r' % (self.current, self.total),
         sys.stdout.flush()
 
+
+class KconfigScanner:
+    """Kconfig scanner."""
+
+    def __init__(self):
+        """Scan all the Kconfig files and create a Config object."""
+        # Define environment variables referenced from Kconfig
+        os.environ['srctree'] = os.getcwd()
+        os.environ['UBOOTVERSION'] = 'dummy'
+        os.environ['KCONFIG_OBJDIR'] = ''
+        self.conf = kconfiglib.Config()
+
+
 class KconfigParser:
 
     """A parser of .config and include/autoconf.mk."""
@@ -1466,7 +1482,98 @@ def move_config(configs, options, db_queue):
     slots.show_failed_boards()
     slots.show_suspicious_boards()
 
-def imply_config(config_list, find_superset=False):
+def find_kconfig_rules(kconf, config, imply_config):
+    """Check whether a config has a 'select' or 'imply' keyword
+
+    Args:
+        kconf: kconfiglib.Config object
+        config: target config (without CONFIG_ prefix) being implied
+        imply_config: Option which might imply or select 'config' (without
+            CONFIG_ prefix)
+
+    Returns:
+        Symbol object for 'config' if 'imply_config' implies it, or None if
+            not
+    """
+    sym = kconf.get_symbol(imply_config)
+    if sym:
+        for sel in sym.get_selected_symbols():
+            if sel.get_name() == config:
+                return sym
+    return None
+
+def check_imply_rule(kconf, config, imply_config):
+    """Check whether an 'imply' option can be added to imply_config
+
+    Find the imply_config in its Kconfig file and see if an 'imply' can be
+    added for 'config'. If so, returns the location where it can be added
+
+    Args:
+        kconf: kconfig.Config object
+        config: target config (without CONFIG_ prefix) being implied
+        imply_config: Option which might imply or select 'config' (without
+            CONFIG_ prefix)
+
+    Returns:
+        Tuple:
+            filename of Kconfig file containing imply_config (None if not found)
+            line number wthin that Kconfig (or 0)
+            description of the operation which will be performed
+    """
+    sym = kconf.get_symbol(imply_config)
+    if not sym:
+        return 'cannot find sym'
+    locs = sym.get_def_locations()
+    if len(locs) != 1:
+        return '%d locations' % len(locs)
+    fname, linenum = locs[0]
+    cwd = os.getcwd()
+    if cwd and fname.startswith(cwd):
+        fname = fname[len(cwd) + 1:]
+    file_line = ' at %s:%d' % (fname, linenum)
+    with open(fname) as fd:
+        data = fd.read().splitlines()
+    if data[linenum - 1] != 'config %s' % imply_config:
+        return None, 0, 'bad sym format %s%s' % (data[linenum], file_line)
+    return fname, linenum, 'adding%s' % file_line
+
+def add_imply_rule(config, fname, linenum):
+    """Add an 'imply' rule to an existing config option
+
+    The 'imply' keyword will be added before the help if any, otherwise at the
+    end of the config. If a suitable location cannot be found, the function
+    fails.
+
+    Args:
+        config: CONFIG to add an 'imply' for
+        fname: Filename of Kconfig file to add the 'imply' to
+        linenum: Line number of the 'config' option if the Kconfig file
+
+    Returns:
+        description of the operation which was performed
+    """
+    file_line = ' at %s:%d' % (fname, linenum)
+    data = open(fname).read().splitlines()
+    linenum -= 1
+
+    for offset, line in enumerate(data[linenum:]):
+        if line.strip().startswith('help') or not line:
+            data.insert(linenum + offset, '\timply %s' % config)
+            with open(fname, 'w') as fd:
+                fd.write('\n'.join(data) + '\n')
+            return 'added%s' % file_line
+
+    return 'could not insert%s'
+
+(IMPLY_MORE_THAN_2,     # Show any implying config which affects >=2 defconfigs
+                        # (normally there is a minimum of 5)
+ IMPLY_TARGET,          # Include CONFIG_TARGET_... (normaly excluded)
+ IMPLY_CMD,             # Include CONFIG_CMD_... (normaly excluded)
+ IMPLY_NON_ARCH_BAORD) = (1, 2, 4, 8)   # Include configs not in arch/ or
+                                        # board/ (normally excluded)
+
+def imply_config(config_list, add_imply, imply_more, skip_added,
+                 check_kconfig=True, find_superset=False):
     """Find CONFIG options which imply those in the list
 
     Some CONFIG options can be implied by others and this can help to reduce
@@ -1485,12 +1592,18 @@ def imply_config(config_list, find_superset=False):
         - Get the set 'defconfigs' which use that target config
         - For each config (from a list of all configs):
             - Get the set 'imply_defconfig' of defconfigs which use that config
-            -
             - If imply_defconfigs contains anything not in defconfigs then
               this config does not imply the target config
 
     Params:
         config_list: List of CONFIG options to check (each a string)
+        add_imply: List of CONFIG options to add an 'imply' keyword for,
+            separated by whitespace. If this is 'all', all configs will have
+            an imply added (be careful!). If '' then none will be added.
+        imply_more: Flags controlling what implying configs are found: see
+            IMPLY_... above
+        check_kconfig: Check if implied symbols already have an 'imply' or
+            'select' for the target config, and show this information if so.
         find_superset: True to look for configs which are a superset of those
             already found. So for example if CONFIG_EXYNOS5 implies an option,
             but CONFIG_EXYNOS covers a larger set of defconfigs and also
@@ -1501,6 +1614,10 @@ def imply_config(config_list, find_superset=False):
         config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
         defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
     """
+    kconf = KconfigScanner().conf if check_kconfig else None
+    if add_imply and add_imply != 'all':
+        add_imply = add_imply.split()
+
     # key is defconfig name, value is dict of (CONFIG_xxx, value)
     config_db = {}
 
@@ -1551,8 +1668,11 @@ def imply_config(config_list, find_superset=False):
 
         # Look at every possible config, except the target one
         for imply_config in rest_configs:
-            if 'CONFIG_TARGET' in imply_config:
+            if 'ERRATUM' in imply_config or 'CONFIG_CMD' in imply_config:
                 continue
+            if not (imply_more & IMPLY_TARGET):
+                if 'CONFIG_TARGET' in imply_config:
+                    continue
 
             # Find set of defconfigs that have this config
             imply_defconfig = defconfig_db[imply_config]
@@ -1593,19 +1713,61 @@ def imply_config(config_list, find_superset=False):
         # The value of each dict item is the set of defconfigs containing that
         # config. Rank them so that we print the configs that imply the largest
         # number of defconfigs first.
-        ranked_configs = sorted(imply_configs,
+        ranked_iconfigs = sorted(imply_configs,
                             key=lambda k: len(imply_configs[k]), reverse=True)
-        for config in ranked_configs:
-            num_common = len(imply_configs[config])
+        kconfig_info = ''
+        cwd = os.getcwd()
+        add_list = collections.defaultdict(list)
+        for iconfig in ranked_iconfigs:
+            num_common = len(imply_configs[iconfig])
 
             # Don't bother if there are less than 5 defconfigs affected.
-            if num_common < 5:
+            if num_common < (2 if imply_more & IMPLY_MORE_THAN_2 else 5):
                 continue
-            missing = defconfigs - imply_configs[config]
+            missing = defconfigs - imply_configs[iconfig]
             missing_str = ', '.join(missing) if missing else 'all'
             missing_str = ''
-            print '    %d : %-30s%s' % (num_common, config.ljust(30),
-                                        missing_str)
+            show = True
+            if kconf:
+                sym = find_kconfig_rules(kconf, config[7:], iconfig[7:])
+                kconfig_info = ''
+                if sym:
+                    locs = sym.get_def_locations()
+                    if len(locs) == 1:
+                        fname, linenum = locs[0]
+                        if cwd and fname.startswith(cwd):
+                            fname = fname[len(cwd) + 1:]
+                        kconfig_info = '%s:%d' % (fname, linenum)
+                        if skip_added:
+                            show = False
+                else:
+                    sym = kconf.get_symbol(iconfig[7:])
+                    fname = ''
+                    if sym:
+                        locs = sym.get_def_locations()
+                        if len(locs) == 1:
+                            fname, linenum = locs[0]
+                            if cwd and fname.startswith(cwd):
+                                fname = fname[len(cwd) + 1:]
+                    in_arch_board = not sym or (fname.startswith('arch') or
+                                                fname.startswith('board'))
+                    if (not in_arch_board and
+                        not (imply_more & IMPLY_NON_ARCH_BAORD)):
+                        continue
+
+                    if add_imply and (add_imply == 'all' or
+                                      iconfig in add_imply):
+                        fname, linenum, kconfig_info = (check_imply_rule(kconf,
+                                        config[7:], iconfig[7:]))
+                        if fname:
+                            add_list[fname].append(linenum)
+
+            if show and kconfig_info != 'skip':
+                print '%5d : %-30s%-25s %s' % (num_common, iconfig.ljust(30),
+                                              kconfig_info, missing_str)
+        for fname, linenums in add_list.iteritems():
+            for linenum in sorted(linenums, reverse=True):
+                add_imply_rule(config[7:], fname, linenum)
 
 
 def main():
@@ -1616,6 +1778,12 @@ def main():
 
     parser = optparse.OptionParser()
     # Add options here
+    parser.add_option('-a', '--add-imply', type='string', default='',
+                      help='comma-separated list of CONFIG options to add '
+                      "an 'imply' statement to for the CONFIG in -i")
+    parser.add_option('-A', '--skip-added', action='store_true', default=False,
+                      help="don't show options which are already marked as "
+                      'implying others')
     parser.add_option('-b', '--build-db', action='store_true', default=False,
                       help='build a CONFIG database')
     parser.add_option('-c', '--color', action='store_true', default=False,
@@ -1628,6 +1796,9 @@ def main():
                       "or '-' to read from stdin")
     parser.add_option('-i', '--imply', action='store_true', default=False,
                       help='find options which imply others')
+    parser.add_option('-I', '--imply-more', type='int', default=0,
+                      help='1=include those that imply > 2, '
+                      '2=include TARGET, 4=include CMD')
     parser.add_option('-n', '--dry-run', action='store_true', default=False,
                       help='perform a trial run (show log with no changes)')
     parser.add_option('-e', '--exit-on-error', action='store_true',
@@ -1664,7 +1835,8 @@ def main():
     check_top_directory()
 
     if options.imply:
-        imply_config(configs)
+        imply_config(configs, options.add_imply, options.imply_more,
+                     options.skip_added)
         return
 
     config_db = {}
-- 
2.13.0.506.g27d5fe0cd-goog



More information about the U-Boot mailing list