2 # SPDX-License-Identifier: GPL-2.0+
4 # Author: Masahiro Yamada <yamada.masahiro@socionext.com>
8 Move config options from headers to defconfig files.
10 See doc/develop/moveconfig.rst for documentation.
13 from argparse import ArgumentParser
16 from contextlib import ExitStack
23 import multiprocessing
35 from buildman import bsettings
36 from buildman import kconfiglib
37 from buildman import toolchain
39 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
45 STATE_SAVEDEFCONFIG = 3
49 ACTION_NO_ENTRY_WARN = 2
59 COLOR_LIGHT_GRAY = '0;37'
60 COLOR_DARK_GRAY = '1;30'
61 COLOR_LIGHT_RED = '1;31'
62 COLOR_LIGHT_GREEN = '1;32'
64 COLOR_LIGHT_BLUE = '1;34'
65 COLOR_LIGHT_PURPLE = '1;35'
66 COLOR_LIGHT_CYAN = '1;36'
69 AUTO_CONF_PATH = 'include/config/auto.conf'
70 CONFIG_DATABASE = 'moveconfig.db'
72 CONFIG_LEN = len('CONFIG_')
75 'SZ_1': 0x00000001, 'SZ_2': 0x00000002,
76 'SZ_4': 0x00000004, 'SZ_8': 0x00000008,
77 'SZ_16': 0x00000010, 'SZ_32': 0x00000020,
78 'SZ_64': 0x00000040, 'SZ_128': 0x00000080,
79 'SZ_256': 0x00000100, 'SZ_512': 0x00000200,
80 'SZ_1K': 0x00000400, 'SZ_2K': 0x00000800,
81 'SZ_4K': 0x00001000, 'SZ_8K': 0x00002000,
82 'SZ_16K': 0x00004000, 'SZ_32K': 0x00008000,
83 'SZ_64K': 0x00010000, 'SZ_128K': 0x00020000,
84 'SZ_256K': 0x00040000, 'SZ_512K': 0x00080000,
85 'SZ_1M': 0x00100000, 'SZ_2M': 0x00200000,
86 'SZ_4M': 0x00400000, 'SZ_8M': 0x00800000,
87 'SZ_16M': 0x01000000, 'SZ_32M': 0x02000000,
88 'SZ_64M': 0x04000000, 'SZ_128M': 0x08000000,
89 'SZ_256M': 0x10000000, 'SZ_512M': 0x20000000,
90 'SZ_1G': 0x40000000, 'SZ_2G': 0x80000000,
94 RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
96 ### helper functions ###
97 def check_top_directory():
98 """Exit if we are not at the top of source directory."""
99 for fname in 'README', 'Licenses':
100 if not os.path.exists(fname):
101 sys.exit('Please run at the top of source directory.')
103 def check_clean_directory():
104 """Exit if the source tree is not clean."""
105 for fname in '.config', 'include/config':
106 if os.path.exists(fname):
107 sys.exit("source tree is not clean, please run 'make mrproper'")
110 """Get the command name of GNU Make.
112 U-Boot needs GNU Make for building, but the command name is not
113 necessarily "make". (for example, "gmake" on FreeBSD).
114 Returns the most appropriate command name on your system.
116 with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc:
117 ret = proc.communicate()
119 sys.exit('GNU Make not found')
120 return ret[0].rstrip()
122 def get_matched_defconfig(line):
123 """Get the defconfig files that match a pattern
126 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
127 'k2*_defconfig'. If no directory is provided, 'configs/' is
131 list of str: a list of matching defconfig files
133 dirname = os.path.dirname(line)
137 pattern = os.path.join('configs', line)
138 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
140 def get_matched_defconfigs(defconfigs_file):
141 """Get all the defconfig files that match the patterns in a file.
144 defconfigs_file (str): File containing a list of defconfigs to process,
145 or '-' to read the list from stdin
148 list of str: A list of paths to defconfig files, with no duplicates
151 with ExitStack() as stack:
152 if defconfigs_file == '-':
154 defconfigs_file = 'stdin'
156 inf = stack.enter_context(open(defconfigs_file, encoding='utf-8'))
157 for i, line in enumerate(inf):
160 continue # skip blank lines silently
162 line = line.split(' ')[0] # handle 'git log' input
163 matched = get_matched_defconfig(line)
165 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'",
168 defconfigs += matched
170 # use set() to drop multiple matching
171 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
173 def get_all_defconfigs():
174 """Get all the defconfig files under the configs/ directory.
177 list of str: List of paths to defconfig files
180 for (dirpath, _, filenames) in os.walk('configs'):
181 dirpath = dirpath[len('configs') + 1:]
182 for filename in fnmatch.filter(filenames, '*_defconfig'):
183 defconfigs.append(os.path.join(dirpath, filename))
187 def color_text(color_enabled, color, string):
188 """Return colored string."""
190 # LF should not be surrounded by the escape sequence.
191 # Otherwise, additional whitespace or line-feed might be printed.
192 return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
193 for s in string.split('\n') ])
196 def show_diff(alines, blines, file_path, color_enabled):
197 """Show unidified diff.
200 alines (list of str): A list of lines (before)
201 blines (list of str): A list of lines (after)
202 file_path (str): Path to the file
203 color_enabled (bool): Display the diff in color
205 diff = difflib.unified_diff(alines, blines,
206 fromfile=os.path.join('a', file_path),
207 tofile=os.path.join('b', file_path))
210 if line.startswith('-') and not line.startswith('--'):
211 print(color_text(color_enabled, COLOR_RED, line))
212 elif line.startswith('+') and not line.startswith('++'):
213 print(color_text(color_enabled, COLOR_GREEN, line))
217 def extend_matched_lines(lines, matched, pre_patterns, post_patterns,
218 extend_pre, extend_post):
219 """Extend matched lines if desired patterns are found before/after already
223 lines (list of str): list of lines handled.
224 matched (list of int): list of line numbers that have been already
225 matched (will be updated by this function)
226 pre_patterns (list of re.Pattern): list of regular expression that should
227 be matched as preamble
228 post_patterns (list of re.Pattern): list of regular expression that should
229 be matched as postamble
230 extend_pre (bool): Add the line number of matched preamble to the matched
232 extend_post (bool): Add the line number of matched postamble to the
235 extended_matched = []
248 for pat in pre_patterns:
249 if pat.search(lines[i - 1]):
255 for pat in post_patterns:
256 if pat.search(lines[j]):
263 extended_matched.append(i - 1)
265 extended_matched.append(j)
267 matched += extended_matched
270 def confirm(args, prompt):
271 """Ask the user to confirm something
274 args (Namespace ): program arguments
277 bool: True to confirm, False to cancel/stop
281 choice = input(f'{prompt} [y/n]: ')
282 choice = choice.lower()
284 if choice in ('y', 'n'):
292 def write_file(fname, data):
293 """Write data to a file
296 fname (str): Filename to write to
297 data (list of str): Lines to write (with or without trailing newline);
300 with open(fname, 'w', encoding='utf-8') as out:
301 if isinstance(data, list):
303 print(line.rstrip('\n'), file=out)
307 def read_file(fname, as_lines=True, skip_unicode=False):
308 """Read a file and return the contents
311 fname (str): Filename to read from
312 as_lines: Return file contents as a list of lines
313 skip_unicode (bool): True to report unicode errors and continue
316 iter of str: List of ;ines from the file with newline removed; str if
317 as_lines is False with newlines intact; or None if a unicode error
321 UnicodeDecodeError: Unicode error occurred when reading
323 with open(fname, encoding='utf-8') as inf:
326 return [line.rstrip('\n') for line in inf.readlines()]
329 except UnicodeDecodeError as e:
332 print("Failed on file %s': %s" % (fname, e))
335 def cleanup_empty_blocks(header_path, args):
336 """Clean up empty conditional blocks
339 header_path (str): path to the cleaned file.
340 args (Namespace): program arguments
342 pattern = re.compile(r'^\s*#\s*if.*$\n^\s*#\s*endif.*$\n*', flags=re.M)
343 data = read_file(header_path, as_lines=False, skip_unicode=True)
347 new_data = pattern.sub('\n', data)
349 show_diff(data.splitlines(True), new_data.splitlines(True), header_path,
356 write_file(header_path, new_data)
358 def cleanup_one_header(header_path, patterns, args):
359 """Clean regex-matched lines away from a file.
362 header_path: path to the cleaned file.
363 patterns: list of regex patterns. Any lines matching to these
364 patterns are deleted.
365 args (Namespace): program arguments
367 lines = read_file(header_path, skip_unicode=True)
372 for i, line in enumerate(lines):
373 if i - 1 in matched and lines[i - 1].endswith('\\'):
376 for pattern in patterns:
377 if pattern.search(line):
384 # remove empty #ifdef ... #endif, successive blank lines
385 pattern_if = re.compile(r'#\s*if(def|ndef)?\b') # #if, #ifdef, #ifndef
386 pattern_elif = re.compile(r'#\s*el(if|se)\b') # #elif, #else
387 pattern_endif = re.compile(r'#\s*endif\b') # #endif
388 pattern_blank = re.compile(r'^\s*$') # empty line
391 old_matched = copy.copy(matched)
392 extend_matched_lines(lines, matched, [pattern_if],
393 [pattern_endif], True, True)
394 extend_matched_lines(lines, matched, [pattern_elif],
395 [pattern_elif, pattern_endif], True, False)
396 extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
397 [pattern_blank], False, True)
398 extend_matched_lines(lines, matched, [pattern_blank],
399 [pattern_elif, pattern_endif], True, False)
400 extend_matched_lines(lines, matched, [pattern_blank],
401 [pattern_blank], True, False)
402 if matched == old_matched:
405 tolines = copy.copy(lines)
407 for i in reversed(matched):
410 show_diff(lines, tolines, header_path, args.color)
415 write_file(header_path, tolines)
417 def cleanup_headers(configs, args):
418 """Delete config defines from board headers.
421 configs: A list of CONFIGs to remove.
422 args (Namespace): program arguments
424 if not confirm(args, 'Clean up headers?'):
428 for config in configs:
429 patterns.append(re.compile(r'#\s*define\s+%s\b' % config))
430 patterns.append(re.compile(r'#\s*undef\s+%s\b' % config))
432 for dir in 'include', 'arch', 'board':
433 for (dirpath, dirnames, filenames) in os.walk(dir):
434 if dirpath == os.path.join('include', 'generated'):
436 for filename in filenames:
437 if not filename.endswith(('~', '.dts', '.dtsi', '.bin',
438 '.elf','.aml','.dat')):
439 header_path = os.path.join(dirpath, filename)
440 # This file contains UTF-16 data and no CONFIG symbols
441 if header_path == 'include/video_font_data.h':
443 cleanup_one_header(header_path, patterns, args)
444 cleanup_empty_blocks(header_path, args)
446 def cleanup_one_extra_option(defconfig_path, configs, args):
447 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
450 defconfig_path: path to the cleaned defconfig file.
451 configs: A list of CONFIGs to remove.
452 args (Namespace): program arguments
455 start = 'CONFIG_SYS_EXTRA_OPTIONS="'
458 lines = read_file(defconfig_path)
460 for i, line in enumerate(lines):
461 if line.startswith(start) and line.endswith(end):
464 # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
467 old_tokens = line[len(start):-len(end)].split(',')
470 for token in old_tokens:
471 pos = token.find('=')
472 if not (token[:pos] if pos >= 0 else token) in configs:
473 new_tokens.append(token)
475 if new_tokens == old_tokens:
478 tolines = copy.copy(lines)
481 tolines[i] = start + ','.join(new_tokens) + end
485 show_diff(lines, tolines, defconfig_path, args.color)
490 write_file(defconfig_path, tolines)
492 def cleanup_extra_options(configs, args):
493 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
496 configs: A list of CONFIGs to remove.
497 args (Namespace): program arguments
499 if not confirm(args, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'):
502 configs = [ config[len('CONFIG_'):] for config in configs ]
504 defconfigs = get_all_defconfigs()
506 for defconfig in defconfigs:
507 cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
510 def cleanup_whitelist(configs, args):
511 """Delete config whitelist entries
514 configs: A list of CONFIGs to remove.
515 args (Namespace): program arguments
517 if not confirm(args, 'Clean up whitelist entries?'):
520 lines = read_file(os.path.join('scripts', 'config_whitelist.txt'))
522 lines = [x for x in lines if x.strip() not in configs]
524 write_file(os.path.join('scripts', 'config_whitelist.txt'), lines)
526 def find_matching(patterns, line):
532 def cleanup_readme(configs, args):
533 """Delete config description in README
536 configs: A list of CONFIGs to remove.
537 args (Namespace): program arguments
539 if not confirm(args, 'Clean up README?'):
543 for config in configs:
544 patterns.append(re.compile(r'^\s+%s' % config))
546 lines = read_file('README')
552 found = find_matching(patterns, line)
556 if found and re.search(r'^\s+CONFIG', line):
560 newlines.append(line)
562 write_file('README', newlines)
564 def try_expand(line):
565 """If value looks like an expression, try expanding it
566 Otherwise just return the existing value
568 if line.find('=') == -1:
572 aeval = asteval.Interpreter( usersyms=SIZES, minimal=True )
573 cfg, val = re.split("=", line)
575 if re.search(r'[*+-/]|<<|SZ_+|\(([^\)]+)\)', val):
576 newval = hex(aeval(val))
577 print('\tExpanded expression %s to %s' % (val, newval))
578 return cfg+'='+newval
580 print('\tFailed to expand expression in %s' % line)
588 """Progress Indicator"""
590 def __init__(self, total):
591 """Create a new progress indicator.
594 total: A number of defconfig files to process.
600 """Increment the number of processed defconfig files."""
605 """Display the progress."""
606 print(' %d defconfigs out of %d\r' % (self.current, self.total), end=' ')
610 class KconfigScanner:
611 """Kconfig scanner."""
614 """Scan all the Kconfig files and create a Config object."""
615 # Define environment variables referenced from Kconfig
616 os.environ['srctree'] = os.getcwd()
617 os.environ['UBOOTVERSION'] = 'dummy'
618 os.environ['KCONFIG_OBJDIR'] = ''
619 self.conf = kconfiglib.Kconfig()
624 """A parser of .config and include/autoconf.mk."""
626 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
627 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
629 def __init__(self, configs, args, build_dir):
630 """Create a new parser.
633 configs: A list of CONFIGs to move.
634 args (Namespace): program arguments
635 build_dir: Build directory.
637 self.configs = configs
639 self.dotconfig = os.path.join(build_dir, '.config')
640 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
641 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
643 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
644 self.defconfig = os.path.join(build_dir, 'defconfig')
647 """Parse .config file and return the architecture.
650 Architecture name (e.g. 'arm').
654 for line in read_file(self.dotconfig):
655 m = self.re_arch.match(line)
659 m = self.re_cpu.match(line)
667 if arch == 'arm' and cpu == 'armv8':
672 def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
673 """Parse .config, defconfig, include/autoconf.mk for one config.
675 This function looks for the config options in the lines from
676 defconfig, .config, and include/autoconf.mk in order to decide
677 which action should be taken for this defconfig.
680 config: CONFIG name to parse.
681 dotconfig_lines: lines from the .config file.
682 autoconf_lines: lines from the include/autoconf.mk file.
685 A tupple of the action for this defconfig and the line
686 matched for the config.
688 not_set = '# %s is not set' % config
690 for line in autoconf_lines:
692 if line.startswith(config + '='):
698 new_val = try_expand(new_val)
700 for line in dotconfig_lines:
702 if line.startswith(config + '=') or line == not_set:
706 if new_val == not_set:
707 return (ACTION_NO_ENTRY, config)
709 return (ACTION_NO_ENTRY_WARN, config)
711 # If this CONFIG is neither bool nor trisate
712 if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
713 # tools/scripts/define2mk.sed changes '1' to 'y'.
714 # This is a problem if the CONFIG is int type.
715 # Check the type in Kconfig and handle it correctly.
716 if new_val[-2:] == '=y':
717 new_val = new_val[:-1] + '1'
719 return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
722 def update_dotconfig(self):
723 """Parse files for the config options and update the .config.
725 This function parses the generated .config and include/autoconf.mk
726 searching the target options.
727 Move the config option(s) to the .config as needed.
730 defconfig: defconfig name.
733 Return a tuple of (updated flag, log string).
734 The "updated flag" is True if the .config was updated, False
735 otherwise. The "log string" shows what happend to the .config.
741 rm_files = [self.config_autoconf, self.autoconf]
744 if os.path.exists(self.spl_autoconf):
745 autoconf_path = self.spl_autoconf
746 rm_files.append(self.spl_autoconf)
750 return (updated, suspicious,
751 color_text(self.args.color, COLOR_BROWN,
752 "SPL is not enabled. Skipped.") + '\n')
754 autoconf_path = self.autoconf
756 dotconfig_lines = read_file(self.dotconfig)
758 autoconf_lines = read_file(autoconf_path)
760 for config in self.configs:
761 result = self.parse_one_config(config, dotconfig_lines,
763 results.append(result)
767 for (action, value) in results:
768 if action == ACTION_MOVE:
769 actlog = "Move '%s'" % value
770 log_color = COLOR_LIGHT_GREEN
771 elif action == ACTION_NO_ENTRY:
772 actlog = '%s is not defined in Kconfig. Do nothing.' % value
773 log_color = COLOR_LIGHT_BLUE
774 elif action == ACTION_NO_ENTRY_WARN:
775 actlog = '%s is not defined in Kconfig (suspicious). Do nothing.' % value
776 log_color = COLOR_YELLOW
778 elif action == ACTION_NO_CHANGE:
779 actlog = "'%s' is the same as the define in Kconfig. Do nothing." \
781 log_color = COLOR_LIGHT_PURPLE
783 sys.exit('Internal Error. This should not happen.')
785 log += color_text(self.args.color, log_color, actlog) + '\n'
787 with open(self.dotconfig, 'a', encoding='utf-8') as out:
788 for (action, value) in results:
789 if action == ACTION_MOVE:
790 out.write(value + '\n')
793 self.results = results
797 return (updated, suspicious, log)
799 def check_defconfig(self):
800 """Check the defconfig after savedefconfig
803 Return additional log if moved CONFIGs were removed again by
804 'make savedefconfig'.
809 defconfig_lines = read_file(self.defconfig)
811 for (action, value) in self.results:
812 if action != ACTION_MOVE:
814 if not value in defconfig_lines:
815 log += color_text(self.args.color, COLOR_YELLOW,
816 "'%s' was removed by savedefconfig.\n" %
822 class DatabaseThread(threading.Thread):
823 """This thread processes results from Slot threads.
825 It collects the data in the master config directary. There is only one
826 result thread, and this helps to serialise the build output.
828 def __init__(self, config_db, db_queue):
829 """Set up a new result thread
832 builder: Builder which will be sent each result
834 threading.Thread.__init__(self)
835 self.config_db = config_db
836 self.db_queue= db_queue
839 """Called to start up the result thread.
841 We collect the next result job and pass it on to the build.
844 defconfig, configs = self.db_queue.get()
845 self.config_db[defconfig] = configs
846 self.db_queue.task_done()
851 """A slot to store a subprocess.
853 Each instance of this class handles one subprocess.
854 This class is useful to control multiple threads
855 for faster processing.
858 def __init__(self, toolchains, configs, args, progress, devnull,
859 make_cmd, reference_src_dir, db_queue):
860 """Create a new process slot.
863 toolchains: Toolchains object containing toolchains.
864 configs: A list of CONFIGs to move.
865 args: Program arguments
866 progress: A progress indicator.
867 devnull: A file object of '/dev/null'.
868 make_cmd: command name of GNU Make.
869 reference_src_dir: Determine the true starting config state from this
871 db_queue: output queue to write config info for the database
873 self.toolchains = toolchains
875 self.progress = progress
876 self.build_dir = tempfile.mkdtemp()
877 self.devnull = devnull
878 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
879 self.reference_src_dir = reference_src_dir
880 self.db_queue = db_queue
881 self.parser = KconfigParser(configs, args, self.build_dir)
882 self.state = STATE_IDLE
883 self.failed_boards = set()
884 self.suspicious_boards = set()
887 """Delete the working directory
889 This function makes sure the temporary directory is cleaned away
890 even if Python suddenly dies due to error. It should be done in here
891 because it is guaranteed the destructor is always invoked when the
892 instance of the class gets unreferenced.
894 If the subprocess is still running, wait until it finishes.
896 if self.state != STATE_IDLE:
897 while self.ps.poll() == None:
899 shutil.rmtree(self.build_dir)
901 def add(self, defconfig):
902 """Assign a new subprocess for defconfig and add it to the slot.
904 If the slot is vacant, create a new subprocess for processing the
905 given defconfig and add it to the slot. Just returns False if
906 the slot is occupied (i.e. the current subprocess is still running).
909 defconfig: defconfig name.
912 Return True on success or False on failure
914 if self.state != STATE_IDLE:
917 self.defconfig = defconfig
919 self.current_src_dir = self.reference_src_dir
924 """Check the status of the subprocess and handle it as needed.
926 Returns True if the slot is vacant (i.e. in idle state).
927 If the configuration is successfully finished, assign a new
928 subprocess to build include/autoconf.mk.
929 If include/autoconf.mk is generated, invoke the parser to
930 parse the .config and the include/autoconf.mk, moving
931 config options to the .config as needed.
932 If the .config was updated, run "make savedefconfig" to sync
933 it, update the original defconfig, and then set the slot back
937 Return True if the subprocess is terminated, False otherwise
939 if self.state == STATE_IDLE:
942 if self.ps.poll() == None:
945 if self.ps.poll() != 0:
947 elif self.state == STATE_DEFCONFIG:
948 if self.reference_src_dir and not self.current_src_dir:
949 self.do_savedefconfig()
952 elif self.state == STATE_AUTOCONF:
953 if self.current_src_dir:
954 self.current_src_dir = None
956 elif self.args.build_db:
959 self.do_savedefconfig()
960 elif self.state == STATE_SAVEDEFCONFIG:
961 self.update_defconfig()
963 sys.exit('Internal Error. This should not happen.')
965 return True if self.state == STATE_IDLE else False
967 def handle_error(self):
968 """Handle error cases."""
970 self.log += color_text(self.args.color, COLOR_LIGHT_RED,
971 'Failed to process.\n')
972 if self.args.verbose:
973 self.log += color_text(self.args.color, COLOR_LIGHT_CYAN,
974 self.ps.stderr.read().decode())
977 def do_defconfig(self):
978 """Run 'make <board>_defconfig' to create the .config file."""
980 cmd = list(self.make_cmd)
981 cmd.append(self.defconfig)
982 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
983 stderr=subprocess.PIPE,
984 cwd=self.current_src_dir)
985 self.state = STATE_DEFCONFIG
987 def do_autoconf(self):
988 """Run 'make AUTO_CONF_PATH'."""
990 arch = self.parser.get_arch()
992 toolchain = self.toolchains.Select(arch)
994 self.log += color_text(self.args.color, COLOR_YELLOW,
995 "Tool chain for '%s' is missing. Do nothing.\n" % arch)
998 env = toolchain.MakeEnvironment(False)
1000 cmd = list(self.make_cmd)
1001 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
1002 cmd.append(AUTO_CONF_PATH)
1003 self.ps = subprocess.Popen(cmd, stdout=self.devnull, env=env,
1004 stderr=subprocess.PIPE,
1005 cwd=self.current_src_dir)
1006 self.state = STATE_AUTOCONF
1008 def do_build_db(self):
1009 """Add the board to the database"""
1011 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
1012 if line.startswith('CONFIG'):
1013 config, value = line.split('=', 1)
1014 configs[config] = value.rstrip()
1015 self.db_queue.put([self.defconfig, configs])
1018 def do_savedefconfig(self):
1019 """Update the .config and run 'make savedefconfig'."""
1021 (updated, suspicious, log) = self.parser.update_dotconfig()
1023 self.suspicious_boards.add(self.defconfig)
1026 if not self.args.force_sync and not updated:
1030 self.log += color_text(self.args.color, COLOR_LIGHT_GREEN,
1031 'Syncing by savedefconfig...\n')
1033 self.log += 'Syncing by savedefconfig (forced by option)...\n'
1035 cmd = list(self.make_cmd)
1036 cmd.append('savedefconfig')
1037 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1038 stderr=subprocess.PIPE)
1039 self.state = STATE_SAVEDEFCONFIG
1041 def update_defconfig(self):
1042 """Update the input defconfig and go back to the idle state."""
1044 log = self.parser.check_defconfig()
1046 self.suspicious_boards.add(self.defconfig)
1048 orig_defconfig = os.path.join('configs', self.defconfig)
1049 new_defconfig = os.path.join(self.build_dir, 'defconfig')
1050 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
1053 self.log += color_text(self.args.color, COLOR_LIGHT_BLUE,
1054 'defconfig was updated.\n')
1056 if not self.args.dry_run and updated:
1057 shutil.move(new_defconfig, orig_defconfig)
1060 def finish(self, success):
1061 """Display log along with progress and go to the idle state.
1064 success: Should be True when the defconfig was processed
1065 successfully, or False when it fails.
1067 # output at least 30 characters to hide the "* defconfigs out of *".
1068 log = self.defconfig.ljust(30) + '\n'
1070 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ])
1071 # Some threads are running in parallel.
1072 # Print log atomically to not mix up logs from different threads.
1073 print(log, file=(sys.stdout if success else sys.stderr))
1076 if self.args.exit_on_error:
1077 sys.exit('Exit on error.')
1078 # If --exit-on-error flag is not set, skip this board and continue.
1079 # Record the failed board.
1080 self.failed_boards.add(self.defconfig)
1083 self.progress.show()
1084 self.state = STATE_IDLE
1086 def get_failed_boards(self):
1087 """Returns a set of failed boards (defconfigs) in this slot.
1089 return self.failed_boards
1091 def get_suspicious_boards(self):
1092 """Returns a set of boards (defconfigs) with possible misconversion.
1094 return self.suspicious_boards - self.failed_boards
1098 """Controller of the array of subprocess slots."""
1100 def __init__(self, toolchains, configs, args, progress,
1101 reference_src_dir, db_queue):
1102 """Create a new slots controller.
1105 toolchains: Toolchains object containing toolchains.
1106 configs: A list of CONFIGs to move.
1107 args: Program arguments
1108 progress: A progress indicator.
1109 reference_src_dir: Determine the true starting config state from this
1111 db_queue: output queue to write config info for the database
1115 devnull = subprocess.DEVNULL
1116 make_cmd = get_make_cmd()
1117 for i in range(args.jobs):
1118 self.slots.append(Slot(toolchains, configs, args, progress,
1119 devnull, make_cmd, reference_src_dir,
1122 def add(self, defconfig):
1123 """Add a new subprocess if a vacant slot is found.
1126 defconfig: defconfig name to be put into.
1129 Return True on success or False on failure
1131 for slot in self.slots:
1132 if slot.add(defconfig):
1136 def available(self):
1137 """Check if there is a vacant slot.
1140 Return True if at lease one vacant slot is found, False otherwise.
1142 for slot in self.slots:
1148 """Check if all slots are vacant.
1151 Return True if all the slots are vacant, False otherwise.
1154 for slot in self.slots:
1159 def show_failed_boards(self):
1160 """Display all of the failed boards (defconfigs)."""
1162 output_file = 'moveconfig.failed'
1164 for slot in self.slots:
1165 boards |= slot.get_failed_boards()
1168 boards = '\n'.join(boards) + '\n'
1169 msg = 'The following boards were not processed due to error:\n'
1171 msg += '(the list has been saved in %s)\n' % output_file
1172 print(color_text(self.args.color, COLOR_LIGHT_RED,
1173 msg), file=sys.stderr)
1175 write_file(output_file, boards)
1177 def show_suspicious_boards(self):
1178 """Display all boards (defconfigs) with possible misconversion."""
1180 output_file = 'moveconfig.suspicious'
1182 for slot in self.slots:
1183 boards |= slot.get_suspicious_boards()
1186 boards = '\n'.join(boards) + '\n'
1187 msg = 'The following boards might have been converted incorrectly.\n'
1188 msg += 'It is highly recommended to check them manually:\n'
1190 msg += '(the list has been saved in %s)\n' % output_file
1191 print(color_text(self.args.color, COLOR_YELLOW,
1192 msg), file=sys.stderr)
1194 write_file(output_file, boards)
1196 class ReferenceSource:
1198 """Reference source against which original configs should be parsed."""
1200 def __init__(self, commit):
1201 """Create a reference source directory based on a specified commit.
1204 commit: commit to git-clone
1206 self.src_dir = tempfile.mkdtemp()
1207 print('Cloning git repo to a separate work directory...')
1208 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1210 print("Checkout '%s' to build the original autoconf.mk." % \
1211 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip())
1212 subprocess.check_output(['git', 'checkout', commit],
1213 stderr=subprocess.STDOUT, cwd=self.src_dir)
1216 """Delete the reference source directory
1218 This function makes sure the temporary directory is cleaned away
1219 even if Python suddenly dies due to error. It should be done in here
1220 because it is guaranteed the destructor is always invoked when the
1221 instance of the class gets unreferenced.
1223 shutil.rmtree(self.src_dir)
1226 """Return the absolute path to the reference source directory."""
1230 def move_config(toolchains, configs, args, db_queue):
1231 """Move config options to defconfig files.
1234 configs: A list of CONFIGs to move.
1235 args: Program arguments
1237 if len(configs) == 0:
1239 print('No CONFIG is specified. You are probably syncing defconfigs.', end=' ')
1241 print('Building %s database' % CONFIG_DATABASE)
1243 print('Neither CONFIG nor --force-sync is specified. Nothing will happen.', end=' ')
1245 print('Move ' + ', '.join(configs), end=' ')
1246 print('(jobs: %d)\n' % args.jobs)
1249 reference_src = ReferenceSource(args.git_ref)
1250 reference_src_dir = reference_src.get_dir()
1252 reference_src_dir = None
1255 defconfigs = get_matched_defconfigs(args.defconfigs)
1257 defconfigs = get_all_defconfigs()
1259 progress = Progress(len(defconfigs))
1260 slots = Slots(toolchains, configs, args, progress, reference_src_dir,
1263 # Main loop to process defconfig files:
1264 # Add a new subprocess into a vacant slot.
1265 # Sleep if there is no available slot.
1266 for defconfig in defconfigs:
1267 while not slots.add(defconfig):
1268 while not slots.available():
1269 # No available slot: sleep for a while
1270 time.sleep(SLEEP_TIME)
1272 # wait until all the subprocesses finish
1273 while not slots.empty():
1274 time.sleep(SLEEP_TIME)
1277 slots.show_failed_boards()
1278 slots.show_suspicious_boards()
1280 def find_kconfig_rules(kconf, config, imply_config):
1281 """Check whether a config has a 'select' or 'imply' keyword
1284 kconf: Kconfiglib.Kconfig object
1285 config: Name of config to check (without CONFIG_ prefix)
1286 imply_config: Implying config (without CONFIG_ prefix) which may or
1287 may not have an 'imply' for 'config')
1290 Symbol object for 'config' if found, else None
1292 sym = kconf.syms.get(imply_config)
1294 for sel, cond in (sym.selects + sym.implies):
1295 if sel.name == config:
1299 def check_imply_rule(kconf, config, imply_config):
1300 """Check if we can add an 'imply' option
1302 This finds imply_config in the Kconfig and looks to see if it is possible
1303 to add an 'imply' for 'config' to that part of the Kconfig.
1306 kconf: Kconfiglib.Kconfig object
1307 config: Name of config to check (without CONFIG_ prefix)
1308 imply_config: Implying config (without CONFIG_ prefix) which may or
1309 may not have an 'imply' for 'config')
1313 filename of Kconfig file containing imply_config, or None if none
1314 line number within the Kconfig file, or 0 if none
1315 message indicating the result
1317 sym = kconf.syms.get(imply_config)
1319 return 'cannot find sym'
1322 return '%d locations' % len(nodes)
1324 fname, linenum = node.filename, node.linenr
1326 if cwd and fname.startswith(cwd):
1327 fname = fname[len(cwd) + 1:]
1328 file_line = ' at %s:%d' % (fname, linenum)
1329 data = read_file(fname)
1330 if data[linenum - 1] != 'config %s' % imply_config:
1331 return None, 0, 'bad sym format %s%s' % (data[linenum], file_line)
1332 return fname, linenum, 'adding%s' % file_line
1334 def add_imply_rule(config, fname, linenum):
1335 """Add a new 'imply' option to a Kconfig
1338 config: config option to add an imply for (without CONFIG_ prefix)
1339 fname: Kconfig filename to update
1340 linenum: Line number to place the 'imply' before
1343 Message indicating the result
1345 file_line = ' at %s:%d' % (fname, linenum)
1346 data = read_file(fname)
1349 for offset, line in enumerate(data[linenum:]):
1350 if line.strip().startswith('help') or not line:
1351 data.insert(linenum + offset, '\timply %s' % config)
1352 write_file(fname, data)
1353 return 'added%s' % file_line
1355 return 'could not insert%s'
1357 (IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
1361 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
1362 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
1363 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
1365 IMPLY_NON_ARCH_BOARD,
1366 'Allow Kconfig options outside arch/ and /board/ to imply'],
1370 def read_database():
1371 """Read in the config database
1375 set of all config options seen (each a str)
1376 set of all defconfigs seen (each a str)
1377 dict of configs for each defconfig:
1378 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
1381 value: Value of option
1382 dict of defconfigs for each config:
1384 value: set of boards using that option
1389 # key is defconfig name, value is dict of (CONFIG_xxx, value)
1392 # Set of all config options we have seen
1395 # Set of all defconfigs we have seen
1396 all_defconfigs = set()
1398 defconfig_db = collections.defaultdict(set)
1399 for line in read_file(CONFIG_DATABASE):
1400 line = line.rstrip()
1401 if not line: # Separator between defconfigs
1402 config_db[defconfig] = configs
1403 all_defconfigs.add(defconfig)
1405 elif line[0] == ' ': # CONFIG line
1406 config, value = line.strip().split('=', 1)
1407 configs[config] = value
1408 defconfig_db[config].add(defconfig)
1409 all_configs.add(config)
1410 else: # New defconfig
1413 return all_configs, all_defconfigs, config_db, defconfig_db
1416 def do_imply_config(config_list, add_imply, imply_flags, skip_added,
1417 check_kconfig=True, find_superset=False):
1418 """Find CONFIG options which imply those in the list
1420 Some CONFIG options can be implied by others and this can help to reduce
1421 the size of the defconfig files. For example, CONFIG_X86 implies
1422 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
1423 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
1424 each of the x86 defconfig files.
1426 This function uses the moveconfig database to find such options. It
1427 displays a list of things that could possibly imply those in the list.
1428 The algorithm ignores any that start with CONFIG_TARGET since these
1429 typically refer to only a few defconfigs (often one). It also does not
1430 display a config with less than 5 defconfigs.
1432 The algorithm works using sets. For each target config in config_list:
1433 - Get the set 'defconfigs' which use that target config
1434 - For each config (from a list of all configs):
1435 - Get the set 'imply_defconfig' of defconfigs which use that config
1437 - If imply_defconfigs contains anything not in defconfigs then
1438 this config does not imply the target config
1441 config_list: List of CONFIG options to check (each a string)
1442 add_imply: Automatically add an 'imply' for each config.
1443 imply_flags: Flags which control which implying configs are allowed
1445 skip_added: Don't show options which already have an imply added.
1446 check_kconfig: Check if implied symbols already have an 'imply' or
1447 'select' for the target config, and show this information if so.
1448 find_superset: True to look for configs which are a superset of those
1449 already found. So for example if CONFIG_EXYNOS5 implies an option,
1450 but CONFIG_EXYNOS covers a larger set of defconfigs and also
1451 implies that option, this will drop the former in favour of the
1452 latter. In practice this option has not proved very used.
1454 Note the terminoloy:
1455 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
1456 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
1458 kconf = KconfigScanner().conf if check_kconfig else None
1459 if add_imply and add_imply != 'all':
1460 add_imply = add_imply.split(',')
1462 all_configs, all_defconfigs, config_db, defconfig_db = read_database()
1464 # Work through each target config option in turn, independently
1465 for config in config_list:
1466 defconfigs = defconfig_db.get(config)
1468 print('%s not found in any defconfig' % config)
1471 # Get the set of defconfigs without this one (since a config cannot
1473 non_defconfigs = all_defconfigs - defconfigs
1474 num_defconfigs = len(defconfigs)
1475 print('%s found in %d/%d defconfigs' % (config, num_defconfigs,
1478 # This will hold the results: key=config, value=defconfigs containing it
1480 rest_configs = all_configs - set([config])
1482 # Look at every possible config, except the target one
1483 for imply_config in rest_configs:
1484 if 'ERRATUM' in imply_config:
1486 if not imply_flags & IMPLY_CMD:
1487 if 'CONFIG_CMD' in imply_config:
1489 if not imply_flags & IMPLY_TARGET:
1490 if 'CONFIG_TARGET' in imply_config:
1493 # Find set of defconfigs that have this config
1494 imply_defconfig = defconfig_db[imply_config]
1496 # Get the intersection of this with defconfigs containing the
1498 common_defconfigs = imply_defconfig & defconfigs
1500 # Get the set of defconfigs containing this config which DO NOT
1501 # also contain the taret config. If this set is non-empty it means
1502 # that this config affects other defconfigs as well as (possibly)
1503 # the ones affected by the target config. This means it implies
1504 # things we don't want to imply.
1505 not_common_defconfigs = imply_defconfig & non_defconfigs
1506 if not_common_defconfigs:
1509 # If there are common defconfigs, imply_config may be useful
1510 if common_defconfigs:
1513 for prev in list(imply_configs.keys()):
1514 prev_count = len(imply_configs[prev])
1515 count = len(common_defconfigs)
1516 if (prev_count > count and
1517 (imply_configs[prev] & common_defconfigs ==
1518 common_defconfigs)):
1519 # skip imply_config because prev is a superset
1522 elif count > prev_count:
1523 # delete prev because imply_config is a superset
1524 del imply_configs[prev]
1526 imply_configs[imply_config] = common_defconfigs
1528 # Now we have a dict imply_configs of configs which imply each config
1529 # The value of each dict item is the set of defconfigs containing that
1530 # config. Rank them so that we print the configs that imply the largest
1531 # number of defconfigs first.
1532 ranked_iconfigs = sorted(imply_configs,
1533 key=lambda k: len(imply_configs[k]), reverse=True)
1536 add_list = collections.defaultdict(list)
1537 for iconfig in ranked_iconfigs:
1538 num_common = len(imply_configs[iconfig])
1540 # Don't bother if there are less than 5 defconfigs affected.
1541 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
1543 missing = defconfigs - imply_configs[iconfig]
1544 missing_str = ', '.join(missing) if missing else 'all'
1548 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1549 iconfig[CONFIG_LEN:])
1554 fname, linenum = nodes[0].filename, nodes[0].linenr
1555 if cwd and fname.startswith(cwd):
1556 fname = fname[len(cwd) + 1:]
1557 kconfig_info = '%s:%d' % (fname, linenum)
1561 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
1566 fname, linenum = nodes[0].filename, nodes[0].linenr
1567 if cwd and fname.startswith(cwd):
1568 fname = fname[len(cwd) + 1:]
1569 in_arch_board = not sym or (fname.startswith('arch') or
1570 fname.startswith('board'))
1571 if (not in_arch_board and
1572 not imply_flags & IMPLY_NON_ARCH_BOARD):
1575 if add_imply and (add_imply == 'all' or
1576 iconfig in add_imply):
1577 fname, linenum, kconfig_info = (check_imply_rule(kconf,
1578 config[CONFIG_LEN:], iconfig[CONFIG_LEN:]))
1580 add_list[fname].append(linenum)
1582 if show and kconfig_info != 'skip':
1583 print('%5d : %-30s%-25s %s' % (num_common, iconfig.ljust(30),
1584 kconfig_info, missing_str))
1586 # Having collected a list of things to add, now we add them. We process
1587 # each file from the largest line number to the smallest so that
1588 # earlier additions do not affect our line numbers. E.g. if we added an
1589 # imply at line 20 it would change the position of each line after
1591 for fname, linenums in add_list.items():
1592 for linenum in sorted(linenums, reverse=True):
1593 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
1595 def defconfig_matches(configs, re_match):
1596 """Check if any CONFIG option matches a regex
1598 The match must be complete, i.e. from the start to end of the CONFIG option.
1601 configs (dict): Dict of CONFIG options:
1603 value: Value of option
1604 re_match (re.Pattern): Match to check
1607 bool: True if any CONFIG matches the regex
1610 if re_match.fullmatch(cfg):
1614 def do_find_config(config_list):
1615 """Find boards with a given combination of CONFIGs
1618 config_list: List of CONFIG options to check (each a regex consisting
1619 of a config option, with or without a CONFIG_ prefix. If an option
1620 is preceded by a tilde (~) then it must be false, otherwise it must
1623 all_configs, all_defconfigs, config_db, defconfig_db = read_database()
1626 adhoc_configs = set(read_file('scripts/config_whitelist.txt'))
1628 # Start with all defconfigs
1629 out = all_defconfigs
1631 # Work through each config in turn
1633 for item in config_list:
1634 # Get the real config name and whether we want this config or not
1641 if cfg in adhoc_configs:
1645 # Search everything that is still in the running. If it has a config
1646 # that we want, or doesn't have one that we don't, add it into the
1647 # running for the next stage
1650 re_match = re.compile(cfg)
1651 for defc in in_list:
1652 has_cfg = defconfig_matches(config_db[defc], re_match)
1656 print(f"Error: Not in Kconfig: %s" % ' '.join(adhoc))
1658 print(f'{len(out)} matches')
1659 print(' '.join(item.split('_defconfig')[0] for item in out))
1662 def prefix_config(cfg):
1663 """Prefix a config with CONFIG_ if needed
1665 This handles ~ operator, which indicates that the CONFIG should be disabled
1667 >>> prefix_config('FRED')
1669 >>> prefix_config('CONFIG_FRED')
1671 >>> prefix_config('~FRED')
1673 >>> prefix_config('~CONFIG_FRED')
1675 >>> prefix_config('A123')
1682 if not cfg.startswith('CONFIG_'):
1683 cfg = 'CONFIG_' + cfg
1689 cpu_count = multiprocessing.cpu_count()
1690 except NotImplementedError:
1693 epilog = '''Move config options from headers to defconfig files. See
1694 doc/develop/moveconfig.rst for documentation.'''
1696 parser = ArgumentParser(epilog=epilog)
1697 # Add arguments here
1698 parser.add_argument('-a', '--add-imply', type=str, default='',
1699 help='comma-separated list of CONFIG options to add '
1700 "an 'imply' statement to for the CONFIG in -i")
1701 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
1702 help="don't show options which are already marked as "
1704 parser.add_argument('-b', '--build-db', action='store_true', default=False,
1705 help='build a CONFIG database')
1706 parser.add_argument('-c', '--color', action='store_true', default=False,
1707 help='display the log in color')
1708 parser.add_argument('-C', '--commit', action='store_true', default=False,
1709 help='Create a git commit for the operation')
1710 parser.add_argument('-d', '--defconfigs', type=str,
1711 help='a file containing a list of defconfigs to move, '
1712 "one per line (for example 'snow_defconfig') "
1713 "or '-' to read from stdin")
1714 parser.add_argument('-e', '--exit-on-error', action='store_true',
1716 help='exit immediately on any error')
1717 parser.add_argument('-f', '--find', action='store_true', default=False,
1718 help='Find boards with a given config combination')
1719 parser.add_argument('-H', '--headers-only', dest='cleanup_headers_only',
1720 action='store_true', default=False,
1721 help='only cleanup the headers')
1722 parser.add_argument('-i', '--imply', action='store_true', default=False,
1723 help='find options which imply others')
1724 parser.add_argument('-I', '--imply-flags', type=str, default='',
1725 help="control the -i option ('help' for help")
1726 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
1727 help='the number of jobs to run simultaneously')
1728 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
1729 help='perform a trial run (show log with no changes)')
1730 parser.add_argument('-r', '--git-ref', type=str,
1731 help='the git ref to clone for building the autoconf.mk')
1732 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
1733 help='force sync by savedefconfig')
1734 parser.add_argument('-S', '--spl', action='store_true', default=False,
1735 help='parse config options defined for SPL build')
1736 parser.add_argument('-t', '--test', action='store_true', default=False,
1737 help='run unit tests')
1738 parser.add_argument('-y', '--yes', action='store_true', default=False,
1739 help="respond 'yes' to any prompts")
1740 parser.add_argument('-v', '--verbose', action='store_true', default=False,
1741 help='show any build errors as boards are built')
1742 parser.add_argument('configs', nargs='*')
1744 args = parser.parse_args()
1745 configs = args.configs
1748 sys.argv = [sys.argv[0]]
1749 fail, count = doctest.testmod()
1754 if not any((len(configs), args.force_sync, args.build_db, args.imply,
1756 parser.print_usage()
1759 # prefix the option name with CONFIG_ if missing
1760 configs = [prefix_config(cfg) for cfg in configs]
1762 check_top_directory()
1766 if args.imply_flags == 'all':
1769 elif args.imply_flags:
1770 for flag in args.imply_flags.split(','):
1771 bad = flag not in IMPLY_FLAGS
1773 print("Invalid flag '%s'" % flag)
1774 if flag == 'help' or bad:
1775 print("Imply flags: (separate with ',')")
1776 for name, info in IMPLY_FLAGS.items():
1777 print(' %-15s: %s' % (name, info[1]))
1778 parser.print_usage()
1780 imply_flags |= IMPLY_FLAGS[flag][0]
1782 do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
1786 do_find_config(configs)
1790 db_queue = queue.Queue()
1791 t = DatabaseThread(config_db, db_queue)
1795 if not args.cleanup_headers_only:
1796 check_clean_directory()
1798 toolchains = toolchain.Toolchains()
1799 toolchains.GetSettings()
1800 toolchains.Scan(verbose=False)
1801 move_config(toolchains, configs, args, db_queue)
1805 cleanup_headers(configs, args)
1806 cleanup_extra_options(configs, args)
1807 cleanup_whitelist(configs, args)
1808 cleanup_readme(configs, args)
1811 subprocess.call(['git', 'add', '-u'])
1813 msg = 'Convert %s %sto Kconfig' % (configs[0],
1814 'et al ' if len(configs) > 1 else '')
1815 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1816 '\n '.join(configs))
1818 msg = 'configs: Resync with savedefconfig'
1819 msg += '\n\nRsync all defconfig files using moveconfig.py'
1820 subprocess.call(['git', 'commit', '-s', '-m', msg])
1823 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as fd:
1824 for defconfig, configs in config_db.items():
1825 fd.write('%s\n' % defconfig)
1826 for config in sorted(configs.keys()):
1827 fd.write(' %s=%s\n' % (config, configs[config]))
1830 if __name__ == '__main__':