Skip to content
Snippets Groups Projects
moveconfig.py 65.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • #!/usr/bin/env python2
    #
    # Author: Masahiro Yamada <yamada.masahiro@socionext.com>
    #
    # SPDX-License-Identifier:	GPL-2.0+
    #
    
    """
    Move config options from headers to defconfig files.
    
    Since Kconfig was introduced to U-Boot, we have worked on moving
    config options from headers to Kconfig (defconfig).
    
    This tool intends to help this tremendous work.
    
    
    Usage
    -----
    
    
    First, you must edit the Kconfig to add the menu entries for the configs
    
    And then run this tool giving CONFIG names you want to move.
    For example, if you want to move CONFIG_CMD_USB and CONFIG_SYS_TEXT_BASE,
    simply type as follows:
    
      $ tools/moveconfig.py CONFIG_CMD_USB CONFIG_SYS_TEXT_BASE
    
    The tool walks through all the defconfig files and move the given CONFIGs.
    
    
    The log is also displayed on the terminal.
    
    
    The log is printed for each defconfig as follows:
    
    <defconfig_name>
        <action1>
        <action2>
        <action3>
        ...
    
    <defconfig_name> is the name of the defconfig.
    
    <action*> shows what the tool did for that defconfig.
    
    It looks like one of the following:
    
    
     - Move 'CONFIG_... '
       This config option was moved to the defconfig
    
    
     - CONFIG_... is not defined in Kconfig.  Do nothing.
    
       The entry for this CONFIG was not found in Kconfig.  The option is not
       defined in the config header, either.  So, this case can be just skipped.
    
     - CONFIG_... is not defined in Kconfig (suspicious).  Do nothing.
       This option is defined in the config header, but its entry was not found
       in Kconfig.
    
       There are two common cases:
         - You forgot to create an entry for the CONFIG before running
           this tool, or made a typo in a CONFIG passed to this tool.
         - The entry was hidden due to unmet 'depends on'.
    
       The tool does not know if the result is reasonable, so please check it
       manually.
    
    
     - 'CONFIG_...' is the same as the define in Kconfig.  Do nothing.
       The define in the config header matched the one in Kconfig.
       We do not need to touch it.
    
     - Compiler is missing.  Do nothing.
       The compiler specified for this architecture was not found
       in your PATH environment.
       (If -e option is passed, the tool exits immediately.)
    
     - Failed to process.
    
       An error occurred during processing this defconfig.  Skipped.
       (If -e option is passed, the tool exits immediately on error.)
    
    Finally, you will be asked, Clean up headers? [y/n]:
    
    If you say 'y' here, the unnecessary config defines are removed
    from the config headers (include/configs/*.h).
    It just uses the regex method, so you should not rely on it.
    Just in case, please do 'git diff' to see what happened.
    
    
    
    
    This tool runs configuration and builds include/autoconf.mk for every
    defconfig.  The config options defined in Kconfig appear in the .config
    file (unless they are hidden because of unmet dependency.)
    On the other hand, the config options defined by board headers are seen
    in include/autoconf.mk.  The tool looks for the specified options in both
    
    of them to decide the appropriate action for the options.  If the given
    config option is found in the .config, but its value does not match the
    one from the board header, the config option in the .config is replaced
    with the define in the board header.  Then, the .config is synced by
    "make savedefconfig" and the defconfig is updated with it.
    
    
    For faster processing, this tool handles multi-threading.  It creates
    separate build directories where the out-of-tree build is run.  The
    temporary build directories are automatically created and deleted as
    needed.  The number of threads are chosen based on the number of the CPU
    cores of your system although you can change it via -j (--jobs) option.
    
    
    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. This tool uses
    the same tools as buildman, so see that tool for setup (e.g. --fetch-arch).
    
    Tips and trips
    --------------
    
    To sync only X86 defconfigs:
    
       ./tools/moveconfig.py -s -d <(grep -l X86 configs/*)
    
    or:
    
       grep -l X86 configs/* | ./tools/moveconfig.py -s -d -
    
    To process CONFIG_CMD_FPGAD only for a subset of configs based on path match:
    
       ls configs/{hrcon*,iocon*,strider*} | \
           ./tools/moveconfig.py -Cy CONFIG_CMD_FPGAD -d -
    
    
    
    Finding implied CONFIGs
    -----------------------
    
    Some CONFIG options can be implied by others and this can help to reduce
    the size of the defconfig files. For example, CONFIG_X86 implies
    CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
    all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
    each of the x86 defconfig files.
    
    This tool can help find such configs. To use it, first build a database:
    
        ./tools/moveconfig.py -b
    
    Then try to query it:
    
        ./tools/moveconfig.py -i CONFIG_CMD_IRQ
        CONFIG_CMD_IRQ found in 311/2384 defconfigs
        44 : CONFIG_SYS_FSL_ERRATUM_IFC_A002769
        41 : CONFIG_SYS_FSL_ERRATUM_A007075
        31 : CONFIG_SYS_FSL_DDR_VER_44
        28 : CONFIG_ARCH_P1010
        28 : CONFIG_SYS_FSL_ERRATUM_P1010_A003549
        28 : CONFIG_SYS_FSL_ERRATUM_SEC_A003571
        28 : CONFIG_SYS_FSL_ERRATUM_IFC_A003399
        25 : CONFIG_SYS_FSL_ERRATUM_A008044
        22 : CONFIG_ARCH_P1020
        21 : CONFIG_SYS_FSL_DDR_VER_46
        20 : CONFIG_MAX_PIRQ_LINKS
        20 : CONFIG_HPET_ADDRESS
        20 : CONFIG_X86
        20 : CONFIG_PCIE_ECAM_SIZE
        20 : CONFIG_IRQ_SLOT_COUNT
        20 : CONFIG_I8259_PIC
        20 : CONFIG_CPU_ADDR_BITS
        20 : CONFIG_RAMBASE
        20 : CONFIG_SYS_FSL_ERRATUM_A005871
        20 : CONFIG_PCIE_ECAM_BASE
        20 : CONFIG_X86_TSC_TIMER
        20 : CONFIG_I8254_TIMER
        20 : CONFIG_CMD_GETTIME
        19 : CONFIG_SYS_FSL_ERRATUM_A005812
        18 : CONFIG_X86_RUN_32BIT
        17 : CONFIG_CMD_CHIP_CONFIG
        ...
    
    This shows a list of config options which might imply CONFIG_CMD_EEPROM along
    with how many defconfigs they cover. From this you can see that CONFIG_X86
    implies CONFIG_CMD_EEPROM. Therefore, instead of adding CONFIG_CMD_EEPROM to
    the defconfig of every x86 board, you could add a single imply line to the
    Kconfig file:
    
        config X86
            bool "x86 architecture"
            ...
            imply CMD_EEPROM
    
    That will cover 20 defconfigs. Many of the options listed are not suitable as
    they are not related. E.g. it would be odd for CONFIG_CMD_GETTIME to imply
    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
    -----------------
    
     -c, --color
       Surround each portion of the log with escape sequences to display it
       in color on the terminal.
    
    
     -C, --commit
       Create a git commit with the changes when the operation is complete. A
       standard commit message is used which may need to be edited.
    
    
      Specify a file containing a list of defconfigs to move.  The defconfig
    
      files can be given with shell-style wildcards. Use '-' to read from stdin.
    
       Perform a trial run that does not make any changes.  It is useful to
    
       see what is going to happen before one actually runs it.
    
     -e, --exit-on-error
       Exit immediately if Make exits with a non-zero status while processing
       a defconfig file.
    
    
     -s, --force-sync
       Do "make savedefconfig" forcibly for all the defconfig files.
       If not specified, "make savedefconfig" only occurs for cases
       where at least one CONFIG was moved.
    
    
     -S, --spl
       Look for moved config options in spl/include/autoconf.mk instead of
       include/autoconf.mk.  This is useful for moving options for SPL build
       because SPL related options (mostly prefixed with CONFIG_SPL_) are
       sometimes blocked by CONFIG_SPL_BUILD ifdef conditionals.
    
    
     -H, --headers-only
       Only cleanup the headers; skip the defconfig processing
    
    
     -j, --jobs
       Specify the number of threads to run simultaneously.  If not specified,
       the number of threads is the same as the number of CPU cores.
    
    
     -r, --git-ref
       Specify the git ref to clone for building the autoconf.mk. If unspecified
       use the CWD. This is useful for when changes to the Kconfig affect the
       default values and you want to capture the state of the defconfig from
       before that change was in effect. If in doubt, specify a ref pre-Kconfig
       changes (use HEAD if Kconfig changes are not committed). Worst case it will
       take a bit longer to run, but will always do the right thing.
    
    
     -v, --verbose
       Show any build errors as boards are built
    
    
     -y, --yes
       Instead of prompting, automatically go ahead with all operations. This
    
       includes cleaning up headers, CONFIG_SYS_EXTRA_OPTIONS, the config whitelist
       and the README.
    
    To see the complete list of supported options, run
    
      $ tools/moveconfig.py -h
    
    """
    
    
    import multiprocessing
    import optparse
    import os
    
    import re
    import shutil
    import subprocess
    import sys
    import tempfile
    
    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 toolchain
    
    SHOW_GNU_MAKE = 'scripts/show-gnu-make'
    SLEEP_TIME=0.03
    
    STATE_IDLE = 0
    STATE_DEFCONFIG = 1
    STATE_AUTOCONF = 2
    
    ACTION_NO_ENTRY_WARN = 2
    ACTION_NO_CHANGE = 3
    
    
    COLOR_BLACK        = '0;30'
    COLOR_RED          = '0;31'
    COLOR_GREEN        = '0;32'
    COLOR_BROWN        = '0;33'
    COLOR_BLUE         = '0;34'
    COLOR_PURPLE       = '0;35'
    COLOR_CYAN         = '0;36'
    COLOR_LIGHT_GRAY   = '0;37'
    COLOR_DARK_GRAY    = '1;30'
    COLOR_LIGHT_RED    = '1;31'
    COLOR_LIGHT_GREEN  = '1;32'
    COLOR_YELLOW       = '1;33'
    COLOR_LIGHT_BLUE   = '1;34'
    COLOR_LIGHT_PURPLE = '1;35'
    COLOR_LIGHT_CYAN   = '1;36'
    COLOR_WHITE        = '1;37'
    
    
    AUTO_CONF_PATH = 'include/config/auto.conf'
    
    CONFIG_DATABASE = 'moveconfig.db'
    
    CONFIG_LEN = len('CONFIG_')
    
    ### helper functions ###
    def get_devnull():
        """Get the file object of '/dev/null' device."""
        try:
            devnull = subprocess.DEVNULL # py3k
        except AttributeError:
            devnull = open(os.devnull, 'wb')
        return devnull
    
    def check_top_directory():
        """Exit if we are not at the top of source directory."""
        for f in ('README', 'Licenses'):
            if not os.path.exists(f):
                sys.exit('Please run at the top of source directory.')
    
    
    def check_clean_directory():
        """Exit if the source tree is not clean."""
        for f in ('.config', 'include/config'):
            if os.path.exists(f):
                sys.exit("source tree is not clean, please run 'make mrproper'")
    
    
    def get_make_cmd():
        """Get the command name of GNU Make.
    
        U-Boot needs GNU Make for building, but the command name is not
        necessarily "make". (for example, "gmake" on FreeBSD).
        Returns the most appropriate command name on your system.
        """
        process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
        ret = process.communicate()
        if process.returncode:
            sys.exit('GNU Make not found')
        return ret[0].rstrip()
    
    
    def get_matched_defconfig(line):
        """Get the defconfig files that match a pattern
    
        Args:
            line: Path or filename to match, e.g. 'configs/snow_defconfig' or
                'k2*_defconfig'. If no directory is provided, 'configs/' is
                prepended
    
        Returns:
            a list of matching defconfig files
        """
        dirname = os.path.dirname(line)
        if dirname:
            pattern = line
        else:
            pattern = os.path.join('configs', line)
        return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
    
    
    def get_matched_defconfigs(defconfigs_file):
    
        """Get all the defconfig files that match the patterns in a file.
    
        Args:
            defconfigs_file: File containing a list of defconfigs to process, or
                '-' to read the list from stdin
    
        Returns:
            A list of paths to defconfig files, with no duplicates
        """
    
        if defconfigs_file == '-':
            fd = sys.stdin
            defconfigs_file = 'stdin'
        else:
            fd = open(defconfigs_file)
        for i, line in enumerate(fd):
    
            line = line.strip()
            if not line:
                continue # skip blank lines silently
    
            if ' ' in line:
                line = line.split(' ')[0]  # handle 'git log' input
    
            matched = get_matched_defconfig(line)
    
            if not matched:
                print >> sys.stderr, "warning: %s:%d: no defconfig matched '%s'" % \
                                                     (defconfigs_file, i + 1, line)
    
            defconfigs += matched
    
        # use set() to drop multiple matching
        return [ defconfig[len('configs') + 1:]  for defconfig in set(defconfigs) ]
    
    
    def get_all_defconfigs():
        """Get all the defconfig files under the configs/ directory."""
        defconfigs = []
        for (dirpath, dirnames, filenames) in os.walk('configs'):
            dirpath = dirpath[len('configs') + 1:]
            for filename in fnmatch.filter(filenames, '*_defconfig'):
                defconfigs.append(os.path.join(dirpath, filename))
    
        return defconfigs
    
    
    def color_text(color_enabled, color, string):
        """Return colored string."""
        if color_enabled:
    
            # LF should not be surrounded by the escape sequence.
            # Otherwise, additional whitespace or line-feed might be printed.
            return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
                               for s in string.split('\n') ])
    
    def show_diff(a, b, file_path, color_enabled):
    
        """Show unidified diff.
    
        Arguments:
          a: A list of lines (before)
          b: A list of lines (after)
          file_path: Path to the file
    
          color_enabled: Display the diff in color
    
        """
    
        diff = difflib.unified_diff(a, b,
                                    fromfile=os.path.join('a', file_path),
                                    tofile=os.path.join('b', file_path))
    
        for line in diff:
    
            if line[0] == '-' and line[1] != '-':
                print color_text(color_enabled, COLOR_RED, line),
            elif line[0] == '+' and line[1] != '+':
                print color_text(color_enabled, COLOR_GREEN, line),
            else:
                print line,
    
    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
        matched lines.
    
        Arguments:
          lines: A list of lines handled.
          matched: A list of line numbers that have been already matched.
                   (will be updated by this function)
          pre_patterns: A list of regular expression that should be matched as
                        preamble.
          post_patterns: A list of regular expression that should be matched as
                         postamble.
          extend_pre: Add the line number of matched preamble to the matched list.
          extend_post: Add the line number of matched postamble to the matched list.
        """
        extended_matched = []
    
        j = matched[0]
    
        for i in matched:
            if i == 0 or i < j:
                continue
            j = i
            while j in matched:
                j += 1
            if j >= len(lines):
                break
    
            for p in pre_patterns:
                if p.search(lines[i - 1]):
                    break
            else:
                # not matched
                continue
    
            for p in post_patterns:
                if p.search(lines[j]):
                    break
            else:
                # not matched
                continue
    
            if extend_pre:
                extended_matched.append(i - 1)
            if extend_post:
                extended_matched.append(j)
    
        matched += extended_matched
        matched.sort()
    
    
    def confirm(options, prompt):
        if not options.yes:
            while True:
                choice = raw_input('{} [y/n]: '.format(prompt))
                choice = choice.lower()
                print choice
                if choice == 'y' or choice == 'n':
                    break
    
            if choice == 'n':
                return False
    
        return True
    
    
    def cleanup_one_header(header_path, patterns, options):
    
        """Clean regex-matched lines away from a file.
    
        Arguments:
          header_path: path to the cleaned file.
          patterns: list of regex patterns.  Any lines matching to these
                    patterns are deleted.
    
        """
        with open(header_path) as f:
            lines = f.readlines()
    
        matched = []
        for i, line in enumerate(lines):
    
            if i - 1 in matched and lines[i - 1][-2:] == '\\\n':
                matched.append(i)
                continue
    
        if not matched:
            return
    
        # remove empty #ifdef ... #endif, successive blank lines
        pattern_if = re.compile(r'#\s*if(def|ndef)?\W') #  #if, #ifdef, #ifndef
        pattern_elif = re.compile(r'#\s*el(if|se)\W')   #  #elif, #else
        pattern_endif = re.compile(r'#\s*endif\W')      #  #endif
        pattern_blank = re.compile(r'^\s*$')            #  empty line
    
        while True:
            old_matched = copy.copy(matched)
            extend_matched_lines(lines, matched, [pattern_if],
                                 [pattern_endif], True, True)
            extend_matched_lines(lines, matched, [pattern_elif],
                                 [pattern_elif, pattern_endif], True, False)
            extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
                                 [pattern_blank], False, True)
            extend_matched_lines(lines, matched, [pattern_blank],
                                 [pattern_elif, pattern_endif], True, False)
            extend_matched_lines(lines, matched, [pattern_blank],
                                 [pattern_blank], True, False)
            if matched == old_matched:
                break
    
    
        tolines = copy.copy(lines)
    
        for i in reversed(matched):
            tolines.pop(i)
    
    
        show_diff(lines, tolines, header_path, options.color)
    
    def cleanup_headers(configs, options):
    
        """Delete config defines from board headers.
    
        Arguments:
    
          configs: A list of CONFIGs to remove.
    
        if not confirm(options, 'Clean up headers?'):
            return
    
            patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
            patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
    
    
        for dir in 'include', 'arch', 'board':
            for (dirpath, dirnames, filenames) in os.walk(dir):
    
                if dirpath == os.path.join('include', 'generated'):
                    continue
    
                for filename in filenames:
                    if not fnmatch.fnmatch(filename, '*~'):
                        cleanup_one_header(os.path.join(dirpath, filename),
    
    def cleanup_one_extra_option(defconfig_path, configs, options):
        """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
    
        Arguments:
          defconfig_path: path to the cleaned defconfig file.
          configs: A list of CONFIGs to remove.
          options: option flags.
        """
    
        start = 'CONFIG_SYS_EXTRA_OPTIONS="'
        end = '"\n'
    
        with open(defconfig_path) as f:
            lines = f.readlines()
    
        for i, line in enumerate(lines):
            if line.startswith(start) and line.endswith(end):
                break
        else:
            # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
            return
    
        old_tokens = line[len(start):-len(end)].split(',')
        new_tokens = []
    
        for token in old_tokens:
            pos = token.find('=')
            if not (token[:pos] if pos >= 0 else token) in configs:
                new_tokens.append(token)
    
        if new_tokens == old_tokens:
            return
    
        tolines = copy.copy(lines)
    
        if new_tokens:
            tolines[i] = start + ','.join(new_tokens) + end
        else:
            tolines.pop(i)
    
        show_diff(lines, tolines, defconfig_path, options.color)
    
        if options.dry_run:
            return
    
        with open(defconfig_path, 'w') as f:
            for line in tolines:
                f.write(line)
    
    def cleanup_extra_options(configs, options):
        """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
    
        Arguments:
          configs: A list of CONFIGs to remove.
          options: option flags.
        """
    
        if not confirm(options, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'):
            return
    
    
        configs = [ config[len('CONFIG_'):] for config in configs ]
    
        defconfigs = get_all_defconfigs()
    
        for defconfig in defconfigs:
            cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
                                     options)
    
    
    def cleanup_whitelist(configs, options):
        """Delete config whitelist entries
    
        Arguments:
          configs: A list of CONFIGs to remove.
          options: option flags.
        """
        if not confirm(options, 'Clean up whitelist entries?'):
            return
    
        with open(os.path.join('scripts', 'config_whitelist.txt')) as f:
            lines = f.readlines()
    
        lines = [x for x in lines if x.strip() not in configs]
    
        with open(os.path.join('scripts', 'config_whitelist.txt'), 'w') as f:
            f.write(''.join(lines))
    
    
    def find_matching(patterns, line):
        for pat in patterns:
            if pat.search(line):
                return True
        return False
    
    def cleanup_readme(configs, options):
        """Delete config description in README
    
        Arguments:
          configs: A list of CONFIGs to remove.
          options: option flags.
        """
        if not confirm(options, 'Clean up README?'):
            return
    
        patterns = []
        for config in configs:
            patterns.append(re.compile(r'^\s+%s' % config))
    
        with open('README') as f:
            lines = f.readlines()
    
        found = False
        newlines = []
        for line in lines:
            if not found:
                found = find_matching(patterns, line)
                if found:
                    continue
    
            if found and re.search(r'^\s+CONFIG', line):
                found = False
    
            if not found:
                newlines.append(line)
    
        with open('README', 'w') as f:
            f.write(''.join(newlines))
    
    
    class Progress:
    
        """Progress Indicator"""
    
        def __init__(self, total):
            """Create a new progress indicator.
    
            Arguments:
              total: A number of defconfig files to process.
            """
            self.current = 0
            self.total = total
    
        def inc(self):
            """Increment the number of processed defconfig files."""
    
            self.current += 1
    
        def show(self):
            """Display the 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."""
    
        re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
        re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
    
    
        def __init__(self, configs, options, build_dir):
    
              configs: A list of CONFIGs to move.
    
              options: option flags.
              build_dir: Build directory.
            """
    
            self.dotconfig = os.path.join(build_dir, '.config')
            self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
    
            self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
                                             'autoconf.mk')
    
            self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
    
            self.defconfig = os.path.join(build_dir, 'defconfig')
    
        def get_arch(self):
            """Parse .config file and return the architecture.
    
              Architecture name (e.g. 'arm').
    
            for line in open(self.dotconfig):
    
                m = self.re_arch.match(line)
                if m:
                    arch = m.group(1)
                    continue
                m = self.re_cpu.match(line)
                if m:
                    cpu = m.group(1)
    
    
    
            # fix-up for aarch64
            if arch == 'arm' and cpu == 'armv8':
                arch = 'aarch64'
    
    
        def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
    
            """Parse .config, defconfig, include/autoconf.mk for one config.
    
            This function looks for the config options in the lines from
            defconfig, .config, and include/autoconf.mk in order to decide
            which action should be taken for this defconfig.
    
            Arguments:
    
              dotconfig_lines: lines from the .config file.
    
              autoconf_lines: lines from the include/autoconf.mk file.
    
            Returns:
              A tupple of the action for this defconfig and the line
              matched for the config.
            """
            not_set = '# %s is not set' % config
    
            for line in autoconf_lines:
                line = line.rstrip()
                if line.startswith(config + '='):
    
            for line in dotconfig_lines:
                line = line.rstrip()
                if line.startswith(config + '=') or line == not_set:
                    old_val = line
                    break
            else:
                if new_val == not_set:
                    return (ACTION_NO_ENTRY, config)
                else:
                    return (ACTION_NO_ENTRY_WARN, config)
    
    
            # If this CONFIG is neither bool nor trisate
            if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
                # tools/scripts/define2mk.sed changes '1' to 'y'.
                # This is a problem if the CONFIG is int type.
                # Check the type in Kconfig and handle it correctly.
                if new_val[-2:] == '=y':
                    new_val = new_val[:-1] + '1'
    
            return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
                    new_val)
    
            """Parse files for the config options and update the .config.
    
            This function parses the generated .config and include/autoconf.mk
            searching the target options.
    
            Move the config option(s) to the .config as needed.
    
              Return a tuple of (updated flag, log string).
              The "updated flag" is True if the .config was updated, False
              otherwise.  The "log string" shows what happend to the .config.
    
            rm_files = [self.config_autoconf, self.autoconf]
    
            if self.options.spl:
                if os.path.exists(self.spl_autoconf):
                    autoconf_path = self.spl_autoconf
                    rm_files.append(self.spl_autoconf)
                else:
                    for f in rm_files:
                        os.remove(f)
                    return (updated, suspicious,
                            color_text(self.options.color, COLOR_BROWN,
                                       "SPL is not enabled.  Skipped.") + '\n')
            else:
                autoconf_path = self.autoconf
    
            with open(self.dotconfig) as f:
    
            for config in self.configs:
                result = self.parse_one_config(config, dotconfig_lines,
    
                results.append(result)
    
            log = ''
    
            for (action, value) in results:
                if action == ACTION_MOVE:
                    actlog = "Move '%s'" % value
                    log_color = COLOR_LIGHT_GREEN
    
                elif action == ACTION_NO_ENTRY:
                    actlog = "%s is not defined in Kconfig.  Do nothing." % value
    
                elif action == ACTION_NO_ENTRY_WARN:
                    actlog = "%s is not defined in Kconfig (suspicious).  Do nothing." % value
                    log_color = COLOR_YELLOW
                    suspicious = True
    
                elif action == ACTION_NO_CHANGE:
                    actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
                             % value
    
                elif action == ACTION_SPL_NOT_EXIST:
                    actlog = "SPL is not enabled for this defconfig.  Skip."
                    log_color = COLOR_PURPLE
    
                else:
                    sys.exit("Internal Error. This should not happen.")
    
    
                log += color_text(self.options.color, log_color, actlog) + '\n'
    
            with open(self.dotconfig, 'a') as f:
    
                for (action, value) in results:
                    if action == ACTION_MOVE:
                        f.write(value + '\n')
    
        def check_defconfig(self):
            """Check the defconfig after savedefconfig
    
            Returns:
              Return additional log if moved CONFIGs were removed again by
              'make savedefconfig'.
            """
    
            log = ''
    
            with open(self.defconfig) as f:
                defconfig_lines = f.readlines()
    
            for (action, value) in self.results:
                if action != ACTION_MOVE:
                    continue
                if not value + '\n' in defconfig_lines:
                    log += color_text(self.options.color, COLOR_YELLOW,
                                      "'%s' was removed by savedefconfig.\n" %
                                      value)
    
            return log
    
    
    
    class DatabaseThread(threading.Thread):