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 ### helper functions ###
95 def check_top_directory():
96 """Exit if we are not at the top of source directory."""
97 for fname in 'README', 'Licenses':
98 if not os.path.exists(fname):
99 sys.exit('Please run at the top of source directory.')
101 def check_clean_directory():
102 """Exit if the source tree is not clean."""
103 for fname in '.config', 'include/config':
104 if os.path.exists(fname):
105 sys.exit("source tree is not clean, please run 'make mrproper'")
108 """Get the command name of GNU Make.
110 U-Boot needs GNU Make for building, but the command name is not
111 necessarily "make". (for example, "gmake" on FreeBSD).
112 Returns the most appropriate command name on your system.
114 with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc:
115 ret = proc.communicate()
117 sys.exit('GNU Make not found')
118 return ret[0].rstrip()
120 def get_matched_defconfig(line):
121 """Get the defconfig files that match a pattern
124 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
125 'k2*_defconfig'. If no directory is provided, 'configs/' is
129 list of str: a list of matching defconfig files
131 dirname = os.path.dirname(line)
135 pattern = os.path.join('configs', line)
136 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
138 def get_matched_defconfigs(defconfigs_file):
139 """Get all the defconfig files that match the patterns in a file.
142 defconfigs_file (str): File containing a list of defconfigs to process,
143 or '-' to read the list from stdin
146 list of str: A list of paths to defconfig files, with no duplicates
149 with ExitStack() as stack:
150 if defconfigs_file == '-':
152 defconfigs_file = 'stdin'
154 inf = stack.enter_context(open(defconfigs_file, encoding='utf-8'))
155 for i, line in enumerate(inf):
158 continue # skip blank lines silently
160 line = line.split(' ')[0] # handle 'git log' input
161 matched = get_matched_defconfig(line)
163 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'",
166 defconfigs += matched
168 # use set() to drop multiple matching
169 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
171 def get_all_defconfigs():
172 """Get all the defconfig files under the configs/ directory.
175 list of str: List of paths to defconfig files
178 for (dirpath, _, filenames) in os.walk('configs'):
179 dirpath = dirpath[len('configs') + 1:]
180 for filename in fnmatch.filter(filenames, '*_defconfig'):
181 defconfigs.append(os.path.join(dirpath, filename))
185 def color_text(color_enabled, color, string):
186 """Return colored string."""
188 # LF should not be surrounded by the escape sequence.
189 # Otherwise, additional whitespace or line-feed might be printed.
190 return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
191 for s in string.split('\n') ])
194 def show_diff(alines, blines, file_path, color_enabled):
195 """Show unidified diff.
198 alines (list of str): A list of lines (before)
199 blines (list of str): A list of lines (after)
200 file_path (str): Path to the file
201 color_enabled (bool): Display the diff in color
203 diff = difflib.unified_diff(alines, blines,
204 fromfile=os.path.join('a', file_path),
205 tofile=os.path.join('b', file_path))
208 if line.startswith('-') and not line.startswith('--'):
209 print(color_text(color_enabled, COLOR_RED, line))
210 elif line.startswith('+') and not line.startswith('++'):
211 print(color_text(color_enabled, COLOR_GREEN, line))
215 def extend_matched_lines(lines, matched, pre_patterns, post_patterns,
216 extend_pre, extend_post):
217 """Extend matched lines if desired patterns are found before/after already
221 lines (list of str): list of lines handled.
222 matched (list of int): list of line numbers that have been already
223 matched (will be updated by this function)
224 pre_patterns (list of re.Pattern): list of regular expression that should
225 be matched as preamble
226 post_patterns (list of re.Pattern): list of regular expression that should
227 be matched as postamble
228 extend_pre (bool): Add the line number of matched preamble to the matched
230 extend_post (bool): Add the line number of matched postamble to the
233 extended_matched = []
246 for pat in pre_patterns:
247 if pat.search(lines[i - 1]):
253 for pat in post_patterns:
254 if pat.search(lines[j]):
261 extended_matched.append(i - 1)
263 extended_matched.append(j)
265 matched += extended_matched
268 def confirm(args, prompt):
269 """Ask the user to confirm something
272 args (Namespace ): program arguments
275 bool: True to confirm, False to cancel/stop
279 choice = input(f'{prompt} [y/n]: ')
280 choice = choice.lower()
282 if choice in ('y', 'n'):
290 def write_file(fname, data):
291 """Write data to a file
294 fname (str): Filename to write to
295 data (list of str): Lines to write (with or without trailing newline);
298 with open(fname, 'w', encoding='utf-8') as out:
299 if isinstance(data, list):
301 print(line.rstrip('\n'), file=out)
305 def read_file(fname, as_lines=True, skip_unicode=False):
306 """Read a file and return the contents
309 fname (str): Filename to read from
310 as_lines: Return file contents as a list of lines
311 skip_unicode (bool): True to report unicode errors and continue
314 iter of str: List of ;ines from the file with newline removed; str if
315 as_lines is False with newlines intact; or None if a unicode error
319 UnicodeDecodeError: Unicode error occurred when reading
321 with open(fname, encoding='utf-8') as inf:
324 return [line.rstrip('\n') for line in inf.readlines()]
327 except UnicodeDecodeError as e:
330 print("Failed on file %s': %s" % (fname, e))
333 def cleanup_empty_blocks(header_path, args):
334 """Clean up empty conditional blocks
337 header_path (str): path to the cleaned file.
338 args (Namespace): program arguments
340 pattern = re.compile(r'^\s*#\s*if.*$\n^\s*#\s*endif.*$\n*', flags=re.M)
341 data = read_file(header_path, as_lines=False, skip_unicode=True)
345 new_data = pattern.sub('\n', data)
347 show_diff(data.splitlines(True), new_data.splitlines(True), header_path,
354 write_file(header_path, new_data)
356 def cleanup_one_header(header_path, patterns, args):
357 """Clean regex-matched lines away from a file.
360 header_path: path to the cleaned file.
361 patterns: list of regex patterns. Any lines matching to these
362 patterns are deleted.
363 args (Namespace): program arguments
365 lines = read_file(header_path, skip_unicode=True)
370 for i, line in enumerate(lines):
371 if i - 1 in matched and lines[i - 1].endswith('\\'):
374 for pattern in patterns:
375 if pattern.search(line):
382 # remove empty #ifdef ... #endif, successive blank lines
383 pattern_if = re.compile(r'#\s*if(def|ndef)?\b') # #if, #ifdef, #ifndef
384 pattern_elif = re.compile(r'#\s*el(if|se)\b') # #elif, #else
385 pattern_endif = re.compile(r'#\s*endif\b') # #endif
386 pattern_blank = re.compile(r'^\s*$') # empty line
389 old_matched = copy.copy(matched)
390 extend_matched_lines(lines, matched, [pattern_if],
391 [pattern_endif], True, True)
392 extend_matched_lines(lines, matched, [pattern_elif],
393 [pattern_elif, pattern_endif], True, False)
394 extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
395 [pattern_blank], False, True)
396 extend_matched_lines(lines, matched, [pattern_blank],
397 [pattern_elif, pattern_endif], True, False)
398 extend_matched_lines(lines, matched, [pattern_blank],
399 [pattern_blank], True, False)
400 if matched == old_matched:
403 tolines = copy.copy(lines)
405 for i in reversed(matched):
408 show_diff(lines, tolines, header_path, args.color)
413 write_file(header_path, tolines)
415 def cleanup_headers(configs, args):
416 """Delete config defines from board headers.
419 configs: A list of CONFIGs to remove.
420 args (Namespace): program arguments
422 if not confirm(args, 'Clean up headers?'):
426 for config in configs:
427 patterns.append(re.compile(r'#\s*define\s+%s\b' % config))
428 patterns.append(re.compile(r'#\s*undef\s+%s\b' % config))
430 for dir in 'include', 'arch', 'board':
431 for (dirpath, dirnames, filenames) in os.walk(dir):
432 if dirpath == os.path.join('include', 'generated'):
434 for filename in filenames:
435 if not filename.endswith(('~', '.dts', '.dtsi', '.bin',
436 '.elf','.aml','.dat')):
437 header_path = os.path.join(dirpath, filename)
438 # This file contains UTF-16 data and no CONFIG symbols
439 if header_path == 'include/video_font_data.h':
441 cleanup_one_header(header_path, patterns, args)
442 cleanup_empty_blocks(header_path, args)
444 def cleanup_one_extra_option(defconfig_path, configs, args):
445 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
448 defconfig_path: path to the cleaned defconfig file.
449 configs: A list of CONFIGs to remove.
450 args (Namespace): program arguments
453 start = 'CONFIG_SYS_EXTRA_OPTIONS="'
456 lines = read_file(defconfig_path)
458 for i, line in enumerate(lines):
459 if line.startswith(start) and line.endswith(end):
462 # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
465 old_tokens = line[len(start):-len(end)].split(',')
468 for token in old_tokens:
469 pos = token.find('=')
470 if not (token[:pos] if pos >= 0 else token) in configs:
471 new_tokens.append(token)
473 if new_tokens == old_tokens:
476 tolines = copy.copy(lines)
479 tolines[i] = start + ','.join(new_tokens) + end
483 show_diff(lines, tolines, defconfig_path, args.color)
488 write_file(defconfig_path, tolines)
490 def cleanup_extra_options(configs, args):
491 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
494 configs: A list of CONFIGs to remove.
495 args (Namespace): program arguments
497 if not confirm(args, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'):
500 configs = [ config[len('CONFIG_'):] for config in configs ]
502 defconfigs = get_all_defconfigs()
504 for defconfig in defconfigs:
505 cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
508 def cleanup_whitelist(configs, args):
509 """Delete config whitelist entries
512 configs: A list of CONFIGs to remove.
513 args (Namespace): program arguments
515 if not confirm(args, 'Clean up whitelist entries?'):
518 lines = read_file(os.path.join('scripts', 'config_whitelist.txt'))
520 lines = [x for x in lines if x.strip() not in configs]
522 write_file(os.path.join('scripts', 'config_whitelist.txt'), lines)
524 def find_matching(patterns, line):
530 def cleanup_readme(configs, args):
531 """Delete config description in README
534 configs: A list of CONFIGs to remove.
535 args (Namespace): program arguments
537 if not confirm(args, 'Clean up README?'):
541 for config in configs:
542 patterns.append(re.compile(r'^\s+%s' % config))
544 lines = read_file('README')
550 found = find_matching(patterns, line)
554 if found and re.search(r'^\s+CONFIG', line):
558 newlines.append(line)
560 write_file('README', newlines)
562 def try_expand(line):
563 """If value looks like an expression, try expanding it
564 Otherwise just return the existing value
566 if line.find('=') == -1:
570 aeval = asteval.Interpreter( usersyms=SIZES, minimal=True )
571 cfg, val = re.split("=", line)
573 if re.search(r'[*+-/]|<<|SZ_+|\(([^\)]+)\)', val):
574 newval = hex(aeval(val))
575 print('\tExpanded expression %s to %s' % (val, newval))
576 return cfg+'='+newval
578 print('\tFailed to expand expression in %s' % line)
586 """Progress Indicator"""
588 def __init__(self, total):
589 """Create a new progress indicator.
592 total: A number of defconfig files to process.
598 """Increment the number of processed defconfig files."""
603 """Display the progress."""
604 print(' %d defconfigs out of %d\r' % (self.current, self.total), end=' ')
608 class KconfigScanner:
609 """Kconfig scanner."""
612 """Scan all the Kconfig files and create a Config object."""
613 # Define environment variables referenced from Kconfig
614 os.environ['srctree'] = os.getcwd()
615 os.environ['UBOOTVERSION'] = 'dummy'
616 os.environ['KCONFIG_OBJDIR'] = ''
617 self.conf = kconfiglib.Kconfig()
622 """A parser of .config and include/autoconf.mk."""
624 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
625 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
627 def __init__(self, configs, args, build_dir):
628 """Create a new parser.
631 configs: A list of CONFIGs to move.
632 args (Namespace): program arguments
633 build_dir: Build directory.
635 self.configs = configs
637 self.dotconfig = os.path.join(build_dir, '.config')
638 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
639 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
641 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
642 self.defconfig = os.path.join(build_dir, 'defconfig')
645 """Parse .config file and return the architecture.
648 Architecture name (e.g. 'arm').
652 for line in read_file(self.dotconfig):
653 m = self.re_arch.match(line)
657 m = self.re_cpu.match(line)
665 if arch == 'arm' and cpu == 'armv8':
670 def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
671 """Parse .config, defconfig, include/autoconf.mk for one config.
673 This function looks for the config options in the lines from
674 defconfig, .config, and include/autoconf.mk in order to decide
675 which action should be taken for this defconfig.
678 config: CONFIG name to parse.
679 dotconfig_lines: lines from the .config file.
680 autoconf_lines: lines from the include/autoconf.mk file.
683 A tupple of the action for this defconfig and the line
684 matched for the config.
686 not_set = '# %s is not set' % config
688 for line in autoconf_lines:
690 if line.startswith(config + '='):
696 new_val = try_expand(new_val)
698 for line in dotconfig_lines:
700 if line.startswith(config + '=') or line == not_set:
704 if new_val == not_set:
705 return (ACTION_NO_ENTRY, config)
707 return (ACTION_NO_ENTRY_WARN, config)
709 # If this CONFIG is neither bool nor trisate
710 if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
711 # tools/scripts/define2mk.sed changes '1' to 'y'.
712 # This is a problem if the CONFIG is int type.
713 # Check the type in Kconfig and handle it correctly.
714 if new_val[-2:] == '=y':
715 new_val = new_val[:-1] + '1'
717 return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
720 def update_dotconfig(self):
721 """Parse files for the config options and update the .config.
723 This function parses the generated .config and include/autoconf.mk
724 searching the target options.
725 Move the config option(s) to the .config as needed.
728 defconfig: defconfig name.
731 Return a tuple of (updated flag, log string).
732 The "updated flag" is True if the .config was updated, False
733 otherwise. The "log string" shows what happend to the .config.
739 rm_files = [self.config_autoconf, self.autoconf]
742 if os.path.exists(self.spl_autoconf):
743 autoconf_path = self.spl_autoconf
744 rm_files.append(self.spl_autoconf)
748 return (updated, suspicious,
749 color_text(self.args.color, COLOR_BROWN,
750 "SPL is not enabled. Skipped.") + '\n')
752 autoconf_path = self.autoconf
754 dotconfig_lines = read_file(self.dotconfig)
756 autoconf_lines = read_file(autoconf_path)
758 for config in self.configs:
759 result = self.parse_one_config(config, dotconfig_lines,
761 results.append(result)
765 for (action, value) in results:
766 if action == ACTION_MOVE:
767 actlog = "Move '%s'" % value
768 log_color = COLOR_LIGHT_GREEN
769 elif action == ACTION_NO_ENTRY:
770 actlog = '%s is not defined in Kconfig. Do nothing.' % value
771 log_color = COLOR_LIGHT_BLUE
772 elif action == ACTION_NO_ENTRY_WARN:
773 actlog = '%s is not defined in Kconfig (suspicious). Do nothing.' % value
774 log_color = COLOR_YELLOW
776 elif action == ACTION_NO_CHANGE:
777 actlog = "'%s' is the same as the define in Kconfig. Do nothing." \
779 log_color = COLOR_LIGHT_PURPLE
780 elif action == ACTION_SPL_NOT_EXIST:
781 actlog = 'SPL is not enabled for this defconfig. Skip.'
782 log_color = COLOR_PURPLE
784 sys.exit('Internal Error. This should not happen.')
786 log += color_text(self.args.color, log_color, actlog) + '\n'
788 with open(self.dotconfig, 'a', encoding='utf-8') as out:
789 for (action, value) in results:
790 if action == ACTION_MOVE:
791 out.write(value + '\n')
794 self.results = results
798 return (updated, suspicious, log)
800 def check_defconfig(self):
801 """Check the defconfig after savedefconfig
804 Return additional log if moved CONFIGs were removed again by
805 'make savedefconfig'.
810 defconfig_lines = read_file(self.defconfig)
812 for (action, value) in self.results:
813 if action != ACTION_MOVE:
815 if not value in defconfig_lines:
816 log += color_text(self.args.color, COLOR_YELLOW,
817 "'%s' was removed by savedefconfig.\n" %
823 class DatabaseThread(threading.Thread):
824 """This thread processes results from Slot threads.
826 It collects the data in the master config directary. There is only one
827 result thread, and this helps to serialise the build output.
829 def __init__(self, config_db, db_queue):
830 """Set up a new result thread
833 builder: Builder which will be sent each result
835 threading.Thread.__init__(self)
836 self.config_db = config_db
837 self.db_queue= db_queue
840 """Called to start up the result thread.
842 We collect the next result job and pass it on to the build.
845 defconfig, configs = self.db_queue.get()
846 self.config_db[defconfig] = configs
847 self.db_queue.task_done()
852 """A slot to store a subprocess.
854 Each instance of this class handles one subprocess.
855 This class is useful to control multiple threads
856 for faster processing.
859 def __init__(self, toolchains, configs, args, progress, devnull,
860 make_cmd, reference_src_dir, db_queue):
861 """Create a new process slot.
864 toolchains: Toolchains object containing toolchains.
865 configs: A list of CONFIGs to move.
866 args: Program arguments
867 progress: A progress indicator.
868 devnull: A file object of '/dev/null'.
869 make_cmd: command name of GNU Make.
870 reference_src_dir: Determine the true starting config state from this
872 db_queue: output queue to write config info for the database
874 self.toolchains = toolchains
876 self.progress = progress
877 self.build_dir = tempfile.mkdtemp()
878 self.devnull = devnull
879 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
880 self.reference_src_dir = reference_src_dir
881 self.db_queue = db_queue
882 self.parser = KconfigParser(configs, args, self.build_dir)
883 self.state = STATE_IDLE
884 self.failed_boards = set()
885 self.suspicious_boards = set()
888 """Delete the working directory
890 This function makes sure the temporary directory is cleaned away
891 even if Python suddenly dies due to error. It should be done in here
892 because it is guaranteed the destructor is always invoked when the
893 instance of the class gets unreferenced.
895 If the subprocess is still running, wait until it finishes.
897 if self.state != STATE_IDLE:
898 while self.ps.poll() == None:
900 shutil.rmtree(self.build_dir)
902 def add(self, defconfig):
903 """Assign a new subprocess for defconfig and add it to the slot.
905 If the slot is vacant, create a new subprocess for processing the
906 given defconfig and add it to the slot. Just returns False if
907 the slot is occupied (i.e. the current subprocess is still running).
910 defconfig: defconfig name.
913 Return True on success or False on failure
915 if self.state != STATE_IDLE:
918 self.defconfig = defconfig
920 self.current_src_dir = self.reference_src_dir
925 """Check the status of the subprocess and handle it as needed.
927 Returns True if the slot is vacant (i.e. in idle state).
928 If the configuration is successfully finished, assign a new
929 subprocess to build include/autoconf.mk.
930 If include/autoconf.mk is generated, invoke the parser to
931 parse the .config and the include/autoconf.mk, moving
932 config options to the .config as needed.
933 If the .config was updated, run "make savedefconfig" to sync
934 it, update the original defconfig, and then set the slot back
938 Return True if the subprocess is terminated, False otherwise
940 if self.state == STATE_IDLE:
943 if self.ps.poll() == None:
946 if self.ps.poll() != 0:
948 elif self.state == STATE_DEFCONFIG:
949 if self.reference_src_dir and not self.current_src_dir:
950 self.do_savedefconfig()
953 elif self.state == STATE_AUTOCONF:
954 if self.current_src_dir:
955 self.current_src_dir = None
957 elif self.args.build_db:
960 self.do_savedefconfig()
961 elif self.state == STATE_SAVEDEFCONFIG:
962 self.update_defconfig()
964 sys.exit('Internal Error. This should not happen.')
966 return True if self.state == STATE_IDLE else False
968 def handle_error(self):
969 """Handle error cases."""
971 self.log += color_text(self.args.color, COLOR_LIGHT_RED,
972 'Failed to process.\n')
973 if self.args.verbose:
974 self.log += color_text(self.args.color, COLOR_LIGHT_CYAN,
975 self.ps.stderr.read().decode())
978 def do_defconfig(self):
979 """Run 'make <board>_defconfig' to create the .config file."""
981 cmd = list(self.make_cmd)
982 cmd.append(self.defconfig)
983 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
984 stderr=subprocess.PIPE,
985 cwd=self.current_src_dir)
986 self.state = STATE_DEFCONFIG
988 def do_autoconf(self):
989 """Run 'make AUTO_CONF_PATH'."""
991 arch = self.parser.get_arch()
993 toolchain = self.toolchains.Select(arch)
995 self.log += color_text(self.args.color, COLOR_YELLOW,
996 "Tool chain for '%s' is missing. Do nothing.\n" % arch)
999 env = toolchain.MakeEnvironment(False)
1001 cmd = list(self.make_cmd)
1002 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
1003 cmd.append(AUTO_CONF_PATH)
1004 self.ps = subprocess.Popen(cmd, stdout=self.devnull, env=env,
1005 stderr=subprocess.PIPE,
1006 cwd=self.current_src_dir)
1007 self.state = STATE_AUTOCONF
1009 def do_build_db(self):
1010 """Add the board to the database"""
1012 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
1013 if line.startswith('CONFIG'):
1014 config, value = line.split('=', 1)
1015 configs[config] = value.rstrip()
1016 self.db_queue.put([self.defconfig, configs])
1019 def do_savedefconfig(self):
1020 """Update the .config and run 'make savedefconfig'."""
1022 (updated, suspicious, log) = self.parser.update_dotconfig()
1024 self.suspicious_boards.add(self.defconfig)
1027 if not self.args.force_sync and not updated:
1031 self.log += color_text(self.args.color, COLOR_LIGHT_GREEN,
1032 'Syncing by savedefconfig...\n')
1034 self.log += 'Syncing by savedefconfig (forced by option)...\n'
1036 cmd = list(self.make_cmd)
1037 cmd.append('savedefconfig')
1038 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1039 stderr=subprocess.PIPE)
1040 self.state = STATE_SAVEDEFCONFIG
1042 def update_defconfig(self):
1043 """Update the input defconfig and go back to the idle state."""
1045 log = self.parser.check_defconfig()
1047 self.suspicious_boards.add(self.defconfig)
1049 orig_defconfig = os.path.join('configs', self.defconfig)
1050 new_defconfig = os.path.join(self.build_dir, 'defconfig')
1051 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
1054 self.log += color_text(self.args.color, COLOR_LIGHT_BLUE,
1055 'defconfig was updated.\n')
1057 if not self.args.dry_run and updated:
1058 shutil.move(new_defconfig, orig_defconfig)
1061 def finish(self, success):
1062 """Display log along with progress and go to the idle state.
1065 success: Should be True when the defconfig was processed
1066 successfully, or False when it fails.
1068 # output at least 30 characters to hide the "* defconfigs out of *".
1069 log = self.defconfig.ljust(30) + '\n'
1071 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ])
1072 # Some threads are running in parallel.
1073 # Print log atomically to not mix up logs from different threads.
1074 print(log, file=(sys.stdout if success else sys.stderr))
1077 if self.args.exit_on_error:
1078 sys.exit('Exit on error.')
1079 # If --exit-on-error flag is not set, skip this board and continue.
1080 # Record the failed board.
1081 self.failed_boards.add(self.defconfig)
1084 self.progress.show()
1085 self.state = STATE_IDLE
1087 def get_failed_boards(self):
1088 """Returns a set of failed boards (defconfigs) in this slot.
1090 return self.failed_boards
1092 def get_suspicious_boards(self):
1093 """Returns a set of boards (defconfigs) with possible misconversion.
1095 return self.suspicious_boards - self.failed_boards
1099 """Controller of the array of subprocess slots."""
1101 def __init__(self, toolchains, configs, args, progress,
1102 reference_src_dir, db_queue):
1103 """Create a new slots controller.
1106 toolchains: Toolchains object containing toolchains.
1107 configs: A list of CONFIGs to move.
1108 args: Program arguments
1109 progress: A progress indicator.
1110 reference_src_dir: Determine the true starting config state from this
1112 db_queue: output queue to write config info for the database
1116 devnull = subprocess.DEVNULL
1117 make_cmd = get_make_cmd()
1118 for i in range(args.jobs):
1119 self.slots.append(Slot(toolchains, configs, args, progress,
1120 devnull, make_cmd, reference_src_dir,
1123 def add(self, defconfig):
1124 """Add a new subprocess if a vacant slot is found.
1127 defconfig: defconfig name to be put into.
1130 Return True on success or False on failure
1132 for slot in self.slots:
1133 if slot.add(defconfig):
1137 def available(self):
1138 """Check if there is a vacant slot.
1141 Return True if at lease one vacant slot is found, False otherwise.
1143 for slot in self.slots:
1149 """Check if all slots are vacant.
1152 Return True if all the slots are vacant, False otherwise.
1155 for slot in self.slots:
1160 def show_failed_boards(self):
1161 """Display all of the failed boards (defconfigs)."""
1163 output_file = 'moveconfig.failed'
1165 for slot in self.slots:
1166 boards |= slot.get_failed_boards()
1169 boards = '\n'.join(boards) + '\n'
1170 msg = 'The following boards were not processed due to error:\n'
1172 msg += '(the list has been saved in %s)\n' % output_file
1173 print(color_text(self.args.color, COLOR_LIGHT_RED,
1174 msg), file=sys.stderr)
1176 write_file(output_file, boards)
1178 def show_suspicious_boards(self):
1179 """Display all boards (defconfigs) with possible misconversion."""
1181 output_file = 'moveconfig.suspicious'
1183 for slot in self.slots:
1184 boards |= slot.get_suspicious_boards()
1187 boards = '\n'.join(boards) + '\n'
1188 msg = 'The following boards might have been converted incorrectly.\n'
1189 msg += 'It is highly recommended to check them manually:\n'
1191 msg += '(the list has been saved in %s)\n' % output_file
1192 print(color_text(self.args.color, COLOR_YELLOW,
1193 msg), file=sys.stderr)
1195 write_file(output_file, boards)
1197 class ReferenceSource:
1199 """Reference source against which original configs should be parsed."""
1201 def __init__(self, commit):
1202 """Create a reference source directory based on a specified commit.
1205 commit: commit to git-clone
1207 self.src_dir = tempfile.mkdtemp()
1208 print('Cloning git repo to a separate work directory...')
1209 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1211 print("Checkout '%s' to build the original autoconf.mk." % \
1212 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip())
1213 subprocess.check_output(['git', 'checkout', commit],
1214 stderr=subprocess.STDOUT, cwd=self.src_dir)
1217 """Delete the reference source directory
1219 This function makes sure the temporary directory is cleaned away
1220 even if Python suddenly dies due to error. It should be done in here
1221 because it is guaranteed the destructor is always invoked when the
1222 instance of the class gets unreferenced.
1224 shutil.rmtree(self.src_dir)
1227 """Return the absolute path to the reference source directory."""
1231 def move_config(toolchains, configs, args, db_queue):
1232 """Move config options to defconfig files.
1235 configs: A list of CONFIGs to move.
1236 args: Program arguments
1238 if len(configs) == 0:
1240 print('No CONFIG is specified. You are probably syncing defconfigs.', end=' ')
1242 print('Building %s database' % CONFIG_DATABASE)
1244 print('Neither CONFIG nor --force-sync is specified. Nothing will happen.', end=' ')
1246 print('Move ' + ', '.join(configs), end=' ')
1247 print('(jobs: %d)\n' % args.jobs)
1250 reference_src = ReferenceSource(args.git_ref)
1251 reference_src_dir = reference_src.get_dir()
1253 reference_src_dir = None
1256 defconfigs = get_matched_defconfigs(args.defconfigs)
1258 defconfigs = get_all_defconfigs()
1260 progress = Progress(len(defconfigs))
1261 slots = Slots(toolchains, configs, args, progress, reference_src_dir,
1264 # Main loop to process defconfig files:
1265 # Add a new subprocess into a vacant slot.
1266 # Sleep if there is no available slot.
1267 for defconfig in defconfigs:
1268 while not slots.add(defconfig):
1269 while not slots.available():
1270 # No available slot: sleep for a while
1271 time.sleep(SLEEP_TIME)
1273 # wait until all the subprocesses finish
1274 while not slots.empty():
1275 time.sleep(SLEEP_TIME)
1278 slots.show_failed_boards()
1279 slots.show_suspicious_boards()
1281 def find_kconfig_rules(kconf, config, imply_config):
1282 """Check whether a config has a 'select' or 'imply' keyword
1285 kconf: Kconfiglib.Kconfig object
1286 config: Name of config to check (without CONFIG_ prefix)
1287 imply_config: Implying config (without CONFIG_ prefix) which may or
1288 may not have an 'imply' for 'config')
1291 Symbol object for 'config' if found, else None
1293 sym = kconf.syms.get(imply_config)
1295 for sel, cond in (sym.selects + sym.implies):
1296 if sel.name == config:
1300 def check_imply_rule(kconf, config, imply_config):
1301 """Check if we can add an 'imply' option
1303 This finds imply_config in the Kconfig and looks to see if it is possible
1304 to add an 'imply' for 'config' to that part of the Kconfig.
1307 kconf: Kconfiglib.Kconfig object
1308 config: Name of config to check (without CONFIG_ prefix)
1309 imply_config: Implying config (without CONFIG_ prefix) which may or
1310 may not have an 'imply' for 'config')
1314 filename of Kconfig file containing imply_config, or None if none
1315 line number within the Kconfig file, or 0 if none
1316 message indicating the result
1318 sym = kconf.syms.get(imply_config)
1320 return 'cannot find sym'
1323 return '%d locations' % len(nodes)
1325 fname, linenum = node.filename, node.linenr
1327 if cwd and fname.startswith(cwd):
1328 fname = fname[len(cwd) + 1:]
1329 file_line = ' at %s:%d' % (fname, linenum)
1330 data = read_file(fname)
1331 if data[linenum - 1] != 'config %s' % imply_config:
1332 return None, 0, 'bad sym format %s%s' % (data[linenum], file_line)
1333 return fname, linenum, 'adding%s' % file_line
1335 def add_imply_rule(config, fname, linenum):
1336 """Add a new 'imply' option to a Kconfig
1339 config: config option to add an imply for (without CONFIG_ prefix)
1340 fname: Kconfig filename to update
1341 linenum: Line number to place the 'imply' before
1344 Message indicating the result
1346 file_line = ' at %s:%d' % (fname, linenum)
1347 data = read_file(fname)
1350 for offset, line in enumerate(data[linenum:]):
1351 if line.strip().startswith('help') or not line:
1352 data.insert(linenum + offset, '\timply %s' % config)
1353 write_file(fname, data)
1354 return 'added%s' % file_line
1356 return 'could not insert%s'
1358 (IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
1362 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
1363 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
1364 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
1366 IMPLY_NON_ARCH_BOARD,
1367 'Allow Kconfig options outside arch/ and /board/ to imply'],
1371 def read_database():
1372 """Read in the config database
1376 set of all config options seen (each a str)
1377 set of all defconfigs seen (each a str)
1378 dict of configs for each defconfig:
1379 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
1382 value: Value of option
1383 dict of defconfigs for each config:
1385 value: set of boards using that option
1390 # key is defconfig name, value is dict of (CONFIG_xxx, value)
1393 # Set of all config options we have seen
1396 # Set of all defconfigs we have seen
1397 all_defconfigs = set()
1399 defconfig_db = collections.defaultdict(set)
1400 for line in read_file(CONFIG_DATABASE):
1401 line = line.rstrip()
1402 if not line: # Separator between defconfigs
1403 config_db[defconfig] = configs
1404 all_defconfigs.add(defconfig)
1406 elif line[0] == ' ': # CONFIG line
1407 config, value = line.strip().split('=', 1)
1408 configs[config] = value
1409 defconfig_db[config].add(defconfig)
1410 all_configs.add(config)
1411 else: # New defconfig
1414 return all_configs, all_defconfigs, config_db, defconfig_db
1417 def do_imply_config(config_list, add_imply, imply_flags, skip_added,
1418 check_kconfig=True, find_superset=False):
1419 """Find CONFIG options which imply those in the list
1421 Some CONFIG options can be implied by others and this can help to reduce
1422 the size of the defconfig files. For example, CONFIG_X86 implies
1423 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
1424 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
1425 each of the x86 defconfig files.
1427 This function uses the moveconfig database to find such options. It
1428 displays a list of things that could possibly imply those in the list.
1429 The algorithm ignores any that start with CONFIG_TARGET since these
1430 typically refer to only a few defconfigs (often one). It also does not
1431 display a config with less than 5 defconfigs.
1433 The algorithm works using sets. For each target config in config_list:
1434 - Get the set 'defconfigs' which use that target config
1435 - For each config (from a list of all configs):
1436 - Get the set 'imply_defconfig' of defconfigs which use that config
1438 - If imply_defconfigs contains anything not in defconfigs then
1439 this config does not imply the target config
1442 config_list: List of CONFIG options to check (each a string)
1443 add_imply: Automatically add an 'imply' for each config.
1444 imply_flags: Flags which control which implying configs are allowed
1446 skip_added: Don't show options which already have an imply added.
1447 check_kconfig: Check if implied symbols already have an 'imply' or
1448 'select' for the target config, and show this information if so.
1449 find_superset: True to look for configs which are a superset of those
1450 already found. So for example if CONFIG_EXYNOS5 implies an option,
1451 but CONFIG_EXYNOS covers a larger set of defconfigs and also
1452 implies that option, this will drop the former in favour of the
1453 latter. In practice this option has not proved very used.
1455 Note the terminoloy:
1456 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
1457 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
1459 kconf = KconfigScanner().conf if check_kconfig else None
1460 if add_imply and add_imply != 'all':
1461 add_imply = add_imply.split(',')
1463 all_configs, all_defconfigs, config_db, defconfig_db = read_database()
1465 # Work through each target config option in turn, independently
1466 for config in config_list:
1467 defconfigs = defconfig_db.get(config)
1469 print('%s not found in any defconfig' % config)
1472 # Get the set of defconfigs without this one (since a config cannot
1474 non_defconfigs = all_defconfigs - defconfigs
1475 num_defconfigs = len(defconfigs)
1476 print('%s found in %d/%d defconfigs' % (config, num_defconfigs,
1479 # This will hold the results: key=config, value=defconfigs containing it
1481 rest_configs = all_configs - set([config])
1483 # Look at every possible config, except the target one
1484 for imply_config in rest_configs:
1485 if 'ERRATUM' in imply_config:
1487 if not imply_flags & IMPLY_CMD:
1488 if 'CONFIG_CMD' in imply_config:
1490 if not imply_flags & IMPLY_TARGET:
1491 if 'CONFIG_TARGET' in imply_config:
1494 # Find set of defconfigs that have this config
1495 imply_defconfig = defconfig_db[imply_config]
1497 # Get the intersection of this with defconfigs containing the
1499 common_defconfigs = imply_defconfig & defconfigs
1501 # Get the set of defconfigs containing this config which DO NOT
1502 # also contain the taret config. If this set is non-empty it means
1503 # that this config affects other defconfigs as well as (possibly)
1504 # the ones affected by the target config. This means it implies
1505 # things we don't want to imply.
1506 not_common_defconfigs = imply_defconfig & non_defconfigs
1507 if not_common_defconfigs:
1510 # If there are common defconfigs, imply_config may be useful
1511 if common_defconfigs:
1514 for prev in list(imply_configs.keys()):
1515 prev_count = len(imply_configs[prev])
1516 count = len(common_defconfigs)
1517 if (prev_count > count and
1518 (imply_configs[prev] & common_defconfigs ==
1519 common_defconfigs)):
1520 # skip imply_config because prev is a superset
1523 elif count > prev_count:
1524 # delete prev because imply_config is a superset
1525 del imply_configs[prev]
1527 imply_configs[imply_config] = common_defconfigs
1529 # Now we have a dict imply_configs of configs which imply each config
1530 # The value of each dict item is the set of defconfigs containing that
1531 # config. Rank them so that we print the configs that imply the largest
1532 # number of defconfigs first.
1533 ranked_iconfigs = sorted(imply_configs,
1534 key=lambda k: len(imply_configs[k]), reverse=True)
1537 add_list = collections.defaultdict(list)
1538 for iconfig in ranked_iconfigs:
1539 num_common = len(imply_configs[iconfig])
1541 # Don't bother if there are less than 5 defconfigs affected.
1542 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
1544 missing = defconfigs - imply_configs[iconfig]
1545 missing_str = ', '.join(missing) if missing else 'all'
1549 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1550 iconfig[CONFIG_LEN:])
1555 fname, linenum = nodes[0].filename, nodes[0].linenr
1556 if cwd and fname.startswith(cwd):
1557 fname = fname[len(cwd) + 1:]
1558 kconfig_info = '%s:%d' % (fname, linenum)
1562 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
1567 fname, linenum = nodes[0].filename, nodes[0].linenr
1568 if cwd and fname.startswith(cwd):
1569 fname = fname[len(cwd) + 1:]
1570 in_arch_board = not sym or (fname.startswith('arch') or
1571 fname.startswith('board'))
1572 if (not in_arch_board and
1573 not imply_flags & IMPLY_NON_ARCH_BOARD):
1576 if add_imply and (add_imply == 'all' or
1577 iconfig in add_imply):
1578 fname, linenum, kconfig_info = (check_imply_rule(kconf,
1579 config[CONFIG_LEN:], iconfig[CONFIG_LEN:]))
1581 add_list[fname].append(linenum)
1583 if show and kconfig_info != 'skip':
1584 print('%5d : %-30s%-25s %s' % (num_common, iconfig.ljust(30),
1585 kconfig_info, missing_str))
1587 # Having collected a list of things to add, now we add them. We process
1588 # each file from the largest line number to the smallest so that
1589 # earlier additions do not affect our line numbers. E.g. if we added an
1590 # imply at line 20 it would change the position of each line after
1592 for fname, linenums in add_list.items():
1593 for linenum in sorted(linenums, reverse=True):
1594 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
1597 def do_find_config(config_list):
1598 """Find boards with a given combination of CONFIGs
1601 config_list: List of CONFIG options to check (each a string consisting
1602 of a config option, with or without a CONFIG_ prefix. If an option
1603 is preceded by a tilde (~) then it must be false, otherwise it must
1606 all_configs, all_defconfigs, config_db, defconfig_db = read_database()
1609 adhoc_configs = set(read_file('scripts/config_whitelist.txt'))
1611 # Start with all defconfigs
1612 out = all_defconfigs
1614 # Work through each config in turn
1616 for item in config_list:
1617 # Get the real config name and whether we want this config or not
1624 if cfg in adhoc_configs:
1628 # Search everything that is still in the running. If it has a config
1629 # that we want, or doesn't have one that we don't, add it into the
1630 # running for the next stage
1633 for defc in in_list:
1634 has_cfg = cfg in config_db[defc]
1638 print(f"Error: Not in Kconfig: %s" % ' '.join(adhoc))
1640 print(f'{len(out)} matches')
1641 print(' '.join(out))
1644 def prefix_config(cfg):
1645 """Prefix a config with CONFIG_ if needed
1647 This handles ~ operator, which indicates that the CONFIG should be disabled
1649 >>> prefix_config('FRED')
1651 >>> prefix_config('CONFIG_FRED')
1653 >>> prefix_config('~FRED')
1655 >>> prefix_config('~CONFIG_FRED')
1657 >>> prefix_config('A123')
1664 if not cfg.startswith('CONFIG_'):
1665 cfg = 'CONFIG_' + cfg
1671 cpu_count = multiprocessing.cpu_count()
1672 except NotImplementedError:
1675 epilog = '''Move config options from headers to defconfig files. See
1676 doc/develop/moveconfig.rst for documentation.'''
1678 parser = ArgumentParser(epilog=epilog)
1679 # Add arguments here
1680 parser.add_argument('-a', '--add-imply', type=str, default='',
1681 help='comma-separated list of CONFIG options to add '
1682 "an 'imply' statement to for the CONFIG in -i")
1683 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
1684 help="don't show options which are already marked as "
1686 parser.add_argument('-b', '--build-db', action='store_true', default=False,
1687 help='build a CONFIG database')
1688 parser.add_argument('-c', '--color', action='store_true', default=False,
1689 help='display the log in color')
1690 parser.add_argument('-C', '--commit', action='store_true', default=False,
1691 help='Create a git commit for the operation')
1692 parser.add_argument('-d', '--defconfigs', type=str,
1693 help='a file containing a list of defconfigs to move, '
1694 "one per line (for example 'snow_defconfig') "
1695 "or '-' to read from stdin")
1696 parser.add_argument('-e', '--exit-on-error', action='store_true',
1698 help='exit immediately on any error')
1699 parser.add_argument('-f', '--find', action='store_true', default=False,
1700 help='Find boards with a given config combination')
1701 parser.add_argument('-H', '--headers-only', dest='cleanup_headers_only',
1702 action='store_true', default=False,
1703 help='only cleanup the headers')
1704 parser.add_argument('-i', '--imply', action='store_true', default=False,
1705 help='find options which imply others')
1706 parser.add_argument('-I', '--imply-flags', type=str, default='',
1707 help="control the -i option ('help' for help")
1708 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
1709 help='the number of jobs to run simultaneously')
1710 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
1711 help='perform a trial run (show log with no changes)')
1712 parser.add_argument('-r', '--git-ref', type=str,
1713 help='the git ref to clone for building the autoconf.mk')
1714 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
1715 help='force sync by savedefconfig')
1716 parser.add_argument('-S', '--spl', action='store_true', default=False,
1717 help='parse config options defined for SPL build')
1718 parser.add_argument('-t', '--test', action='store_true', default=False,
1719 help='run unit tests')
1720 parser.add_argument('-y', '--yes', action='store_true', default=False,
1721 help="respond 'yes' to any prompts")
1722 parser.add_argument('-v', '--verbose', action='store_true', default=False,
1723 help='show any build errors as boards are built')
1724 parser.add_argument('configs', nargs='*')
1726 args = parser.parse_args()
1727 configs = args.configs
1730 sys.argv = [sys.argv[0]]
1731 fail, count = doctest.testmod()
1736 if not any((len(configs), args.force_sync, args.build_db, args.imply,
1738 parser.print_usage()
1741 # prefix the option name with CONFIG_ if missing
1742 configs = [prefix_config(cfg) for cfg in configs]
1744 check_top_directory()
1748 if args.imply_flags == 'all':
1751 elif args.imply_flags:
1752 for flag in args.imply_flags.split(','):
1753 bad = flag not in IMPLY_FLAGS
1755 print("Invalid flag '%s'" % flag)
1756 if flag == 'help' or bad:
1757 print("Imply flags: (separate with ',')")
1758 for name, info in IMPLY_FLAGS.items():
1759 print(' %-15s: %s' % (name, info[1]))
1760 parser.print_usage()
1762 imply_flags |= IMPLY_FLAGS[flag][0]
1764 do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
1768 do_find_config(configs)
1772 db_queue = queue.Queue()
1773 t = DatabaseThread(config_db, db_queue)
1777 if not args.cleanup_headers_only:
1778 check_clean_directory()
1780 toolchains = toolchain.Toolchains()
1781 toolchains.GetSettings()
1782 toolchains.Scan(verbose=False)
1783 move_config(toolchains, configs, args, db_queue)
1787 cleanup_headers(configs, args)
1788 cleanup_extra_options(configs, args)
1789 cleanup_whitelist(configs, args)
1790 cleanup_readme(configs, args)
1793 subprocess.call(['git', 'add', '-u'])
1795 msg = 'Convert %s %sto Kconfig' % (configs[0],
1796 'et al ' if len(configs) > 1 else '')
1797 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1798 '\n '.join(configs))
1800 msg = 'configs: Resync with savedefconfig'
1801 msg += '\n\nRsync all defconfig files using moveconfig.py'
1802 subprocess.call(['git', 'commit', '-s', '-m', msg])
1805 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as fd:
1806 for defconfig, configs in config_db.items():
1807 fd.write('%s\n' % defconfig)
1808 for config in sorted(configs.keys()):
1809 fd.write(' %s=%s\n' % (config, configs[config]))
1812 if __name__ == '__main__':