]> git.sur5r.net Git - u-boot/blobdiff - tools/moveconfig.py
Merge git://git.denx.de/u-boot-imx
[u-boot] / tools / moveconfig.py
index e765acc4a46c269945eeae3cc52e3c82775c3fce..caa81ac2ed77af9e635c2b577e3838b314812cdc 100755 (executable)
@@ -1,9 +1,8 @@
 #!/usr/bin/env python2
+# SPDX-License-Identifier: GPL-2.0+
 #
 # Author: Masahiro Yamada <yamada.masahiro@socionext.com>
 #
-# SPDX-License-Identifier:     GPL-2.0+
-#
 
 """
 Move config options from headers to defconfig files.
@@ -107,12 +106,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 +189,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 <list> 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 +313,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 +352,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 +480,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 +779,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 +816,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 +840,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 +1032,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 +1047,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 +1164,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 +1275,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 +1293,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 +1407,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 +1437,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,15 +1457,98 @@ def move_config(configs, options, db_queue):
     slots.show_failed_boards()
     slots.show_suspicious_boards()
 
-(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD) = (1, 2, 4)
+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, imply_flags, find_superset=False):
+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
@@ -1497,8 +1573,12 @@ def do_imply_config(config_list, imply_flags, 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
@@ -1509,6 +1589,10 @@ def do_imply_config(config_list, imply_flags, 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 = {}
 
@@ -1607,19 +1691,68 @@ def do_imply_config(config_list, imply_flags, 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 < (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():
@@ -1630,6 +1763,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,
@@ -1681,16 +1820,24 @@ def main():
 
     if options.imply:
         imply_flags = 0
-        for flag in options.imply_flags.split():
-            if flag == 'help' or flag not in IMPLY_FLAGS:
-                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, imply_flags)
+        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 = {}
@@ -1701,8 +1848,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:
@@ -1726,10 +1876,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()