
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@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 = {}