[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