X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=tools%2Fmoveconfig.py;h=e3116461bad0ec577b5081ff332077c5babdefe6;hb=f855a7bc12dc3bdf83905b4c72a6d795ee8d8ee5;hp=6921135b0f8896fb216b7a3c3f0b7d198de91199;hpb=2ddd85dc34c4b5fdefe363b735d2eea8d9d87c6c;p=u-boot diff --git a/tools/moveconfig.py b/tools/moveconfig.py index 6921135b0f..e3116461ba 100755 --- a/tools/moveconfig.py +++ b/tools/moveconfig.py @@ -107,12 +107,8 @@ Toolchains Appropriate toolchain are necessary to generate include/autoconf.mk for all the architectures supported by U-Boot. Most of them are available -at the kernel.org site, some are not provided by kernel.org. - -The default per-arch CROSS_COMPILE used by this tool is specified by -the list below, CROSS_COMPILE. You may wish to update the list to -use your own. Instead of modifying the list directly, you can give -them via environments. +at the kernel.org site, some are not provided by kernel.org. This tool uses +the same tools as buildman, so see that tool for setup (e.g. --fetch-arch). Tips and trips @@ -194,6 +190,48 @@ CMD_EEPROM. Using this search you can reduce the size of moveconfig patches. +You can automatically add 'imply' statements in the Kconfig with the -a +option: + + ./tools/moveconfig.py -s -i CONFIG_SCSI \ + -a CONFIG_ARCH_LS1021A,CONFIG_ARCH_LS1043A + +This will add 'imply SCSI' to the two CONFIG options mentioned, assuming that +the database indicates that they do actually imply CONFIG_SCSI and do not +already have an 'imply SCSI'. + +The output shows where the imply is added: + + 18 : CONFIG_ARCH_LS1021A arch/arm/cpu/armv7/ls102xa/Kconfig:1 + 13 : CONFIG_ARCH_LS1043A arch/arm/cpu/armv8/fsl-layerscape/Kconfig:11 + 12 : CONFIG_ARCH_LS1046A arch/arm/cpu/armv8/fsl-layerscape/Kconfig:31 + +The first number is the number of boards which can avoid having a special +CONFIG_SCSI option in their defconfig file if this 'imply' is added. +The location at the right is the Kconfig file and line number where the config +appears. For example, adding 'imply CONFIG_SCSI' to the 'config ARCH_LS1021A' +in arch/arm/cpu/armv7/ls102xa/Kconfig at line 1 will help 18 boards to reduce +the size of their defconfig files. + +If you want to add an 'imply' to every imply config in the list, you can use + + ./tools/moveconfig.py -s -i CONFIG_SCSI -a all + +To control which ones are displayed, use -I where list is a list of +options (use '-I help' to see possible options and their meaning). + +To skip showing you options that already have an 'imply' attached, use -A. + +When you have finished adding 'imply' options you can regenerate the +defconfig files for affected boards with something like: + + git show --stat | ./tools/moveconfig.py -s -d - + +This will regenerate only those defconfigs changed in the current commit. +If you start with (say) 100 defconfigs being changed in the commit, and add +a few 'imply' options as above, then regenerate, hopefully you can reduce the +number of defconfigs changed in the commit. + Available options ----------------- @@ -276,31 +314,15 @@ import tempfile import threading import time +sys.path.append(os.path.join(os.path.dirname(__file__), 'buildman')) +sys.path.append(os.path.join(os.path.dirname(__file__), 'patman')) +import bsettings +import kconfiglib +import toolchain + SHOW_GNU_MAKE = 'scripts/show-gnu-make' SLEEP_TIME=0.03 -# Here is the list of cross-tools I use. -# Most of them are available at kernel.org -# (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the following: -# arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases -# nds32: http://osdk.andestech.com/packages/nds32le-linux-glibc-v1.tgz -# nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545 -# sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu -CROSS_COMPILE = { - 'arc': 'arc-linux-', - 'aarch64': 'aarch64-linux-', - 'arm': 'arm-unknown-linux-gnueabi-', - 'm68k': 'm68k-linux-', - 'microblaze': 'microblaze-linux-', - 'mips': 'mips-linux-', - 'nds32': 'nds32le-linux-', - 'nios2': 'nios2-linux-gnu-', - 'powerpc': 'powerpc-linux-', - 'sh': 'sh-linux-gnu-', - 'x86': 'i386-linux-', - 'xtensa': 'xtensa-linux-' -} - STATE_IDLE = 0 STATE_DEFCONFIG = 1 STATE_AUTOCONF = 2 @@ -331,6 +353,7 @@ COLOR_WHITE = '1;37' AUTO_CONF_PATH = 'include/config/auto.conf' CONFIG_DATABASE = 'moveconfig.db' +CONFIG_LEN = len('CONFIG_') ### helper functions ### def get_devnull(): @@ -458,51 +481,6 @@ def show_diff(a, b, file_path, color_enabled): else: print line, -def update_cross_compile(color_enabled): - """Update per-arch CROSS_COMPILE via environment variables - - The default CROSS_COMPILE values are available - in the CROSS_COMPILE list above. - - You can override them via environment variables - CROSS_COMPILE_{ARCH}. - - For example, if you want to override toolchain prefixes - for ARM and PowerPC, you can do as follows in your shell: - - export CROSS_COMPILE_ARM=... - export CROSS_COMPILE_POWERPC=... - - Then, this function checks if specified compilers really exist in your - PATH environment. - """ - archs = [] - - for arch in os.listdir('arch'): - if os.path.exists(os.path.join('arch', arch, 'Makefile')): - archs.append(arch) - - # arm64 is a special case - archs.append('aarch64') - - for arch in archs: - env = 'CROSS_COMPILE_' + arch.upper() - cross_compile = os.environ.get(env) - if not cross_compile: - cross_compile = CROSS_COMPILE.get(arch, '') - - for path in os.environ["PATH"].split(os.pathsep): - gcc_path = os.path.join(path, cross_compile + 'gcc') - if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK): - break - else: - print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW, - 'warning: %sgcc: not found in PATH. %s architecture boards will be skipped' - % (cross_compile, arch)) - cross_compile = None - - CROSS_COMPILE[arch] = cross_compile - def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre, extend_post): """Extend matched lines if desired patterns are found before/after already @@ -802,6 +780,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.""" @@ -826,15 +817,11 @@ class KconfigParser: self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH) self.defconfig = os.path.join(build_dir, 'defconfig') - def get_cross_compile(self): - """Parse .config file and return CROSS_COMPILE. + def get_arch(self): + """Parse .config file and return the architecture. Returns: - A string storing the compiler prefix for the architecture. - Return a NULL string for architectures that do not require - compiler prefix (Sandbox and native build is the case). - Return None if the specified compiler is missing in your PATH. - Caller should distinguish '' and None. + Architecture name (e.g. 'arm'). """ arch = '' cpu = '' @@ -854,7 +841,7 @@ class KconfigParser: if arch == 'arm' and cpu == 'armv8': arch = 'aarch64' - return CROSS_COMPILE.get(arch, None) + return arch def parse_one_config(self, config, dotconfig_lines, autoconf_lines): """Parse .config, defconfig, include/autoconf.mk for one config. @@ -1046,11 +1033,12 @@ class Slot: for faster processing. """ - def __init__(self, configs, options, progress, devnull, make_cmd, - reference_src_dir, db_queue): + def __init__(self, toolchains, configs, options, progress, devnull, + make_cmd, reference_src_dir, db_queue): """Create a new process slot. Arguments: + toolchains: Toolchains object containing toolchains. configs: A list of CONFIGs to move. options: option flags. progress: A progress indicator. @@ -1060,6 +1048,7 @@ class Slot: source tree. db_queue: output queue to write config info for the database """ + self.toolchains = toolchains self.options = options self.progress = progress self.build_dir = tempfile.mkdtemp() @@ -1176,19 +1165,20 @@ class Slot: def do_autoconf(self): """Run 'make AUTO_CONF_PATH'.""" - self.cross_compile = self.parser.get_cross_compile() - if self.cross_compile is None: + arch = self.parser.get_arch() + try: + toolchain = self.toolchains.Select(arch) + except ValueError: self.log += color_text(self.options.color, COLOR_YELLOW, - "Compiler is missing. Do nothing.\n") + "Tool chain for '%s' is missing. Do nothing.\n % arch") self.finish(False) return + env = toolchain.MakeEnvironment(False) cmd = list(self.make_cmd) - if self.cross_compile: - cmd.append('CROSS_COMPILE=%s' % self.cross_compile) cmd.append('KCONFIG_IGNORE_DUPLICATES=1') cmd.append(AUTO_CONF_PATH) - self.ps = subprocess.Popen(cmd, stdout=self.devnull, + self.ps = subprocess.Popen(cmd, stdout=self.devnull, env=env, stderr=subprocess.PIPE, cwd=self.current_src_dir) self.state = STATE_AUTOCONF @@ -1286,10 +1276,12 @@ class Slots: """Controller of the array of subprocess slots.""" - def __init__(self, configs, options, progress, reference_src_dir, db_queue): + def __init__(self, toolchains, configs, options, progress, + reference_src_dir, db_queue): """Create a new slots controller. Arguments: + toolchains: Toolchains object containing toolchains. configs: A list of CONFIGs to move. options: option flags. progress: A progress indicator. @@ -1302,8 +1294,9 @@ class Slots: devnull = get_devnull() make_cmd = get_make_cmd() for i in range(options.jobs): - self.slots.append(Slot(configs, options, progress, devnull, - make_cmd, reference_src_dir, db_queue)) + self.slots.append(Slot(toolchains, configs, options, progress, + devnull, make_cmd, reference_src_dir, + db_queue)) def add(self, defconfig): """Add a new subprocess if a vacant slot is found. @@ -1415,7 +1408,7 @@ class ReferenceSource: return self.src_dir -def move_config(configs, options, db_queue): +def move_config(toolchains, configs, options, db_queue): """Move config options to defconfig files. Arguments: @@ -1445,7 +1438,8 @@ def move_config(configs, options, db_queue): defconfigs = get_all_defconfigs() progress = Progress(len(defconfigs)) - slots = Slots(configs, options, progress, reference_src_dir, db_queue) + slots = Slots(toolchains, configs, options, progress, reference_src_dir, + db_queue) # Main loop to process defconfig files: # Add a new subprocess into a vacant slot. @@ -1464,7 +1458,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: Kconfig.Config object + config: Name of config to check (without CONFIG_ prefix) + imply_config: Implying config (without CONFIG_ prefix) which may or + may not have an 'imply' for 'config') + + Returns: + Symbol object for 'config' if found, else None + """ + sym = kconf.get_symbol(imply_config) + if sym: + for sel in sym.get_selected_symbols() | sym.get_implied_symbols(): + if sel.get_name() == config: + return sym + return None + +def check_imply_rule(kconf, config, imply_config): + """Check if we can add an 'imply' option + + This finds imply_config in the Kconfig and looks to see if it is possible + to add an 'imply' for 'config' to that part of the Kconfig. + + Args: + kconf: Kconfig.Config object + config: Name of config to check (without CONFIG_ prefix) + imply_config: Implying config (without CONFIG_ prefix) which may or + may not have an 'imply' for 'config') + + Returns: + tuple: + filename of Kconfig file containing imply_config, or None if none + line number within the Kconfig file, or 0 if none + message indicating the result + """ + 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 a new 'imply' option to a Kconfig + + Args: + config: config option to add an imply for (without CONFIG_ prefix) + fname: Kconfig filename to update + linenum: Line number to place the 'imply' before + + Returns: + Message indicating the result + """ + 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_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = ( + 1, 2, 4, 8) + +IMPLY_FLAGS = { + 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'], + 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'], + 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'], + 'non-arch-board': [ + IMPLY_NON_ARCH_BOARD, + 'Allow Kconfig options outside arch/ and /board/ to imply'], +}; + +def do_imply_config(config_list, add_imply, imply_flags, 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 @@ -1489,6 +1574,12 @@ def imply_config(config_list, find_superset=False): Params: config_list: List of CONFIG options to check (each a string) + add_imply: Automatically add an 'imply' for each config. + imply_flags: Flags which control which implying configs are allowed + (IMPLY_...) + skip_added: Don't show options which already have an imply added. + 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 @@ -1499,6 +1590,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 = {} @@ -1549,8 +1644,14 @@ 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: continue + if not (imply_flags & IMPLY_CMD): + if 'CONFIG_CMD' in imply_config: + continue + if not (imply_flags & IMPLY_TARGET): + if 'CONFIG_TARGET' in imply_config: + continue # Find set of defconfigs that have this config imply_defconfig = defconfig_db[imply_config] @@ -1591,19 +1692,68 @@ 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_flags & IMPLY_MIN_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[CONFIG_LEN:], + iconfig[CONFIG_LEN:]) + 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[CONFIG_LEN:]) + 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_flags & IMPLY_NON_ARCH_BOARD)): + continue + + if add_imply and (add_imply == 'all' or + iconfig in add_imply): + fname, linenum, kconfig_info = (check_imply_rule(kconf, + config[CONFIG_LEN:], iconfig[CONFIG_LEN:])) + 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) + + # Having collected a list of things to add, now we add them. We process + # each file from the largest line number to the smallest so that + # earlier additions do not affect our line numbers. E.g. if we added an + # imply at line 20 it would change the position of each line after + # that. + for fname, linenums in add_list.iteritems(): + for linenum in sorted(linenums, reverse=True): + add_imply_rule(config[CONFIG_LEN:], fname, linenum) def main(): @@ -1614,6 +1764,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, @@ -1626,6 +1782,8 @@ 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-flags', type='string', default='', + help="control the -i option ('help' for help") 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', @@ -1662,7 +1820,25 @@ def main(): check_top_directory() if options.imply: - imply_config(configs) + imply_flags = 0 + if options.imply_flags == 'all': + imply_flags = -1 + + elif options.imply_flags: + for flag in options.imply_flags.split(','): + bad = flag not in IMPLY_FLAGS + if bad: + print "Invalid flag '%s'" % flag + if flag == 'help' or bad: + print "Imply flags: (separate with ',')" + for name, info in IMPLY_FLAGS.iteritems(): + print ' %-15s: %s' % (name, info[1]) + parser.print_usage() + sys.exit(1) + imply_flags |= IMPLY_FLAGS[flag][0] + + do_imply_config(configs, options.add_imply, imply_flags, + options.skip_added) return config_db = {} @@ -1673,8 +1849,11 @@ def main(): if not options.cleanup_headers_only: check_clean_directory() - update_cross_compile(options.color) - move_config(configs, options, db_queue) + bsettings.Setup('') + toolchains = toolchain.Toolchains() + toolchains.GetSettings() + toolchains.Scan(verbose=False) + move_config(toolchains, configs, options, db_queue) db_queue.join() if configs: @@ -1698,10 +1877,10 @@ def main(): if options.build_db: with open(CONFIG_DATABASE, 'w') as fd: for defconfig, configs in config_db.iteritems(): - print >>fd, '%s' % defconfig + fd.write('%s\n' % defconfig) for config in sorted(configs.keys()): - print >>fd, ' %s=%s' % (config, configs[config]) - print >>fd + fd.write(' %s=%s\n' % (config, configs[config])) + fd.write('\n') if __name__ == '__main__': main()