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_whitelist(configs, args):
447 """Delete config whitelist entries
450 configs: A list of CONFIGs to remove.
451 args (Namespace): program arguments
453 if not confirm(args, 'Clean up whitelist entries?'):
456 lines = read_file(os.path.join('scripts', 'config_whitelist.txt'))
458 lines = [x for x in lines if x.strip() not in configs]
460 write_file(os.path.join('scripts', 'config_whitelist.txt'), lines)
462 def find_matching(patterns, line):
468 def cleanup_readme(configs, args):
469 """Delete config description in README
472 configs: A list of CONFIGs to remove.
473 args (Namespace): program arguments
475 if not confirm(args, 'Clean up README?'):
479 for config in configs:
480 patterns.append(re.compile(r'^\s+%s' % config))
482 lines = read_file('README')
488 found = find_matching(patterns, line)
492 if found and re.search(r'^\s+CONFIG', line):
496 newlines.append(line)
498 write_file('README', newlines)
500 def try_expand(line):
501 """If value looks like an expression, try expanding it
502 Otherwise just return the existing value
504 if line.find('=') == -1:
508 aeval = asteval.Interpreter( usersyms=SIZES, minimal=True )
509 cfg, val = re.split("=", line)
511 if re.search(r'[*+-/]|<<|SZ_+|\(([^\)]+)\)', val):
512 newval = hex(aeval(val))
513 print('\tExpanded expression %s to %s' % (val, newval))
514 return cfg+'='+newval
516 print('\tFailed to expand expression in %s' % line)
524 """Progress Indicator"""
526 def __init__(self, total):
527 """Create a new progress indicator.
530 total: A number of defconfig files to process.
536 """Increment the number of processed defconfig files."""
541 """Display the progress."""
542 print(' %d defconfigs out of %d\r' % (self.current, self.total), end=' ')
546 class KconfigScanner:
547 """Kconfig scanner."""
550 """Scan all the Kconfig files and create a Config object."""
551 # Define environment variables referenced from Kconfig
552 os.environ['srctree'] = os.getcwd()
553 os.environ['UBOOTVERSION'] = 'dummy'
554 os.environ['KCONFIG_OBJDIR'] = ''
555 self.conf = kconfiglib.Kconfig()
560 """A parser of .config and include/autoconf.mk."""
562 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
563 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
565 def __init__(self, configs, args, build_dir):
566 """Create a new parser.
569 configs: A list of CONFIGs to move.
570 args (Namespace): program arguments
571 build_dir: Build directory.
573 self.configs = configs
575 self.dotconfig = os.path.join(build_dir, '.config')
576 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
577 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
579 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
580 self.defconfig = os.path.join(build_dir, 'defconfig')
583 """Parse .config file and return the architecture.
586 Architecture name (e.g. 'arm').
590 for line in read_file(self.dotconfig):
591 m = self.re_arch.match(line)
595 m = self.re_cpu.match(line)
603 if arch == 'arm' and cpu == 'armv8':
608 def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
609 """Parse .config, defconfig, include/autoconf.mk for one config.
611 This function looks for the config options in the lines from
612 defconfig, .config, and include/autoconf.mk in order to decide
613 which action should be taken for this defconfig.
616 config: CONFIG name to parse.
617 dotconfig_lines: lines from the .config file.
618 autoconf_lines: lines from the include/autoconf.mk file.
621 A tupple of the action for this defconfig and the line
622 matched for the config.
624 not_set = '# %s is not set' % config
626 for line in autoconf_lines:
628 if line.startswith(config + '='):
634 new_val = try_expand(new_val)
636 for line in dotconfig_lines:
638 if line.startswith(config + '=') or line == not_set:
642 if new_val == not_set:
643 return (ACTION_NO_ENTRY, config)
645 return (ACTION_NO_ENTRY_WARN, config)
647 # If this CONFIG is neither bool nor trisate
648 if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
649 # tools/scripts/define2mk.sed changes '1' to 'y'.
650 # This is a problem if the CONFIG is int type.
651 # Check the type in Kconfig and handle it correctly.
652 if new_val[-2:] == '=y':
653 new_val = new_val[:-1] + '1'
655 return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
658 def update_dotconfig(self):
659 """Parse files for the config options and update the .config.
661 This function parses the generated .config and include/autoconf.mk
662 searching the target options.
663 Move the config option(s) to the .config as needed.
666 defconfig: defconfig name.
669 Return a tuple of (updated flag, log string).
670 The "updated flag" is True if the .config was updated, False
671 otherwise. The "log string" shows what happend to the .config.
677 rm_files = [self.config_autoconf, self.autoconf]
680 if os.path.exists(self.spl_autoconf):
681 autoconf_path = self.spl_autoconf
682 rm_files.append(self.spl_autoconf)
686 return (updated, suspicious,
687 color_text(self.args.color, COLOR_BROWN,
688 "SPL is not enabled. Skipped.") + '\n')
690 autoconf_path = self.autoconf
692 dotconfig_lines = read_file(self.dotconfig)
694 autoconf_lines = read_file(autoconf_path)
696 for config in self.configs:
697 result = self.parse_one_config(config, dotconfig_lines,
699 results.append(result)
703 for (action, value) in results:
704 if action == ACTION_MOVE:
705 actlog = "Move '%s'" % value
706 log_color = COLOR_LIGHT_GREEN
707 elif action == ACTION_NO_ENTRY:
708 actlog = '%s is not defined in Kconfig. Do nothing.' % value
709 log_color = COLOR_LIGHT_BLUE
710 elif action == ACTION_NO_ENTRY_WARN:
711 actlog = '%s is not defined in Kconfig (suspicious). Do nothing.' % value
712 log_color = COLOR_YELLOW
714 elif action == ACTION_NO_CHANGE:
715 actlog = "'%s' is the same as the define in Kconfig. Do nothing." \
717 log_color = COLOR_LIGHT_PURPLE
719 sys.exit('Internal Error. This should not happen.')
721 log += color_text(self.args.color, log_color, actlog) + '\n'
723 with open(self.dotconfig, 'a', encoding='utf-8') as out:
724 for (action, value) in results:
725 if action == ACTION_MOVE:
726 out.write(value + '\n')
729 self.results = results
733 return (updated, suspicious, log)
735 def check_defconfig(self):
736 """Check the defconfig after savedefconfig
739 Return additional log if moved CONFIGs were removed again by
740 'make savedefconfig'.
745 defconfig_lines = read_file(self.defconfig)
747 for (action, value) in self.results:
748 if action != ACTION_MOVE:
750 if not value in defconfig_lines:
751 log += color_text(self.args.color, COLOR_YELLOW,
752 "'%s' was removed by savedefconfig.\n" %
758 class DatabaseThread(threading.Thread):
759 """This thread processes results from Slot threads.
761 It collects the data in the master config directary. There is only one
762 result thread, and this helps to serialise the build output.
764 def __init__(self, config_db, db_queue):
765 """Set up a new result thread
768 builder: Builder which will be sent each result
770 threading.Thread.__init__(self)
771 self.config_db = config_db
772 self.db_queue= db_queue
775 """Called to start up the result thread.
777 We collect the next result job and pass it on to the build.
780 defconfig, configs = self.db_queue.get()
781 self.config_db[defconfig] = configs
782 self.db_queue.task_done()
787 """A slot to store a subprocess.
789 Each instance of this class handles one subprocess.
790 This class is useful to control multiple threads
791 for faster processing.
794 def __init__(self, toolchains, configs, args, progress, devnull,
795 make_cmd, reference_src_dir, db_queue):
796 """Create a new process slot.
799 toolchains: Toolchains object containing toolchains.
800 configs: A list of CONFIGs to move.
801 args: Program arguments
802 progress: A progress indicator.
803 devnull: A file object of '/dev/null'.
804 make_cmd: command name of GNU Make.
805 reference_src_dir: Determine the true starting config state from this
807 db_queue: output queue to write config info for the database
809 self.toolchains = toolchains
811 self.progress = progress
812 self.build_dir = tempfile.mkdtemp()
813 self.devnull = devnull
814 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
815 self.reference_src_dir = reference_src_dir
816 self.db_queue = db_queue
817 self.parser = KconfigParser(configs, args, self.build_dir)
818 self.state = STATE_IDLE
819 self.failed_boards = set()
820 self.suspicious_boards = set()
823 """Delete the working directory
825 This function makes sure the temporary directory is cleaned away
826 even if Python suddenly dies due to error. It should be done in here
827 because it is guaranteed the destructor is always invoked when the
828 instance of the class gets unreferenced.
830 If the subprocess is still running, wait until it finishes.
832 if self.state != STATE_IDLE:
833 while self.ps.poll() == None:
835 shutil.rmtree(self.build_dir)
837 def add(self, defconfig):
838 """Assign a new subprocess for defconfig and add it to the slot.
840 If the slot is vacant, create a new subprocess for processing the
841 given defconfig and add it to the slot. Just returns False if
842 the slot is occupied (i.e. the current subprocess is still running).
845 defconfig: defconfig name.
848 Return True on success or False on failure
850 if self.state != STATE_IDLE:
853 self.defconfig = defconfig
855 self.current_src_dir = self.reference_src_dir
860 """Check the status of the subprocess and handle it as needed.
862 Returns True if the slot is vacant (i.e. in idle state).
863 If the configuration is successfully finished, assign a new
864 subprocess to build include/autoconf.mk.
865 If include/autoconf.mk is generated, invoke the parser to
866 parse the .config and the include/autoconf.mk, moving
867 config options to the .config as needed.
868 If the .config was updated, run "make savedefconfig" to sync
869 it, update the original defconfig, and then set the slot back
873 Return True if the subprocess is terminated, False otherwise
875 if self.state == STATE_IDLE:
878 if self.ps.poll() == None:
881 if self.ps.poll() != 0:
883 elif self.state == STATE_DEFCONFIG:
884 if self.reference_src_dir and not self.current_src_dir:
885 self.do_savedefconfig()
888 elif self.state == STATE_AUTOCONF:
889 if self.current_src_dir:
890 self.current_src_dir = None
892 elif self.args.build_db:
895 self.do_savedefconfig()
896 elif self.state == STATE_SAVEDEFCONFIG:
897 self.update_defconfig()
899 sys.exit('Internal Error. This should not happen.')
901 return True if self.state == STATE_IDLE else False
903 def handle_error(self):
904 """Handle error cases."""
906 self.log += color_text(self.args.color, COLOR_LIGHT_RED,
907 'Failed to process.\n')
908 if self.args.verbose:
909 self.log += color_text(self.args.color, COLOR_LIGHT_CYAN,
910 self.ps.stderr.read().decode())
913 def do_defconfig(self):
914 """Run 'make <board>_defconfig' to create the .config file."""
916 cmd = list(self.make_cmd)
917 cmd.append(self.defconfig)
918 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
919 stderr=subprocess.PIPE,
920 cwd=self.current_src_dir)
921 self.state = STATE_DEFCONFIG
923 def do_autoconf(self):
924 """Run 'make AUTO_CONF_PATH'."""
926 arch = self.parser.get_arch()
928 toolchain = self.toolchains.Select(arch)
930 self.log += color_text(self.args.color, COLOR_YELLOW,
931 "Tool chain for '%s' is missing. Do nothing.\n" % arch)
934 env = toolchain.MakeEnvironment(False)
936 cmd = list(self.make_cmd)
937 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
938 cmd.append(AUTO_CONF_PATH)
939 self.ps = subprocess.Popen(cmd, stdout=self.devnull, env=env,
940 stderr=subprocess.PIPE,
941 cwd=self.current_src_dir)
942 self.state = STATE_AUTOCONF
944 def do_build_db(self):
945 """Add the board to the database"""
947 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
948 if line.startswith('CONFIG'):
949 config, value = line.split('=', 1)
950 configs[config] = value.rstrip()
951 self.db_queue.put([self.defconfig, configs])
954 def do_savedefconfig(self):
955 """Update the .config and run 'make savedefconfig'."""
957 (updated, suspicious, log) = self.parser.update_dotconfig()
959 self.suspicious_boards.add(self.defconfig)
962 if not self.args.force_sync and not updated:
966 self.log += color_text(self.args.color, COLOR_LIGHT_GREEN,
967 'Syncing by savedefconfig...\n')
969 self.log += 'Syncing by savedefconfig (forced by option)...\n'
971 cmd = list(self.make_cmd)
972 cmd.append('savedefconfig')
973 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
974 stderr=subprocess.PIPE)
975 self.state = STATE_SAVEDEFCONFIG
977 def update_defconfig(self):
978 """Update the input defconfig and go back to the idle state."""
980 log = self.parser.check_defconfig()
982 self.suspicious_boards.add(self.defconfig)
984 orig_defconfig = os.path.join('configs', self.defconfig)
985 new_defconfig = os.path.join(self.build_dir, 'defconfig')
986 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
989 self.log += color_text(self.args.color, COLOR_LIGHT_BLUE,
990 'defconfig was updated.\n')
992 if not self.args.dry_run and updated:
993 shutil.move(new_defconfig, orig_defconfig)
996 def finish(self, success):
997 """Display log along with progress and go to the idle state.
1000 success: Should be True when the defconfig was processed
1001 successfully, or False when it fails.
1003 # output at least 30 characters to hide the "* defconfigs out of *".
1004 log = self.defconfig.ljust(30) + '\n'
1006 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ])
1007 # Some threads are running in parallel.
1008 # Print log atomically to not mix up logs from different threads.
1009 print(log, file=(sys.stdout if success else sys.stderr))
1012 if self.args.exit_on_error:
1013 sys.exit('Exit on error.')
1014 # If --exit-on-error flag is not set, skip this board and continue.
1015 # Record the failed board.
1016 self.failed_boards.add(self.defconfig)
1019 self.progress.show()
1020 self.state = STATE_IDLE
1022 def get_failed_boards(self):
1023 """Returns a set of failed boards (defconfigs) in this slot.
1025 return self.failed_boards
1027 def get_suspicious_boards(self):
1028 """Returns a set of boards (defconfigs) with possible misconversion.
1030 return self.suspicious_boards - self.failed_boards
1034 """Controller of the array of subprocess slots."""
1036 def __init__(self, toolchains, configs, args, progress,
1037 reference_src_dir, db_queue):
1038 """Create a new slots controller.
1041 toolchains: Toolchains object containing toolchains.
1042 configs: A list of CONFIGs to move.
1043 args: Program arguments
1044 progress: A progress indicator.
1045 reference_src_dir: Determine the true starting config state from this
1047 db_queue: output queue to write config info for the database
1051 devnull = subprocess.DEVNULL
1052 make_cmd = get_make_cmd()
1053 for i in range(args.jobs):
1054 self.slots.append(Slot(toolchains, configs, args, progress,
1055 devnull, make_cmd, reference_src_dir,
1058 def add(self, defconfig):
1059 """Add a new subprocess if a vacant slot is found.
1062 defconfig: defconfig name to be put into.
1065 Return True on success or False on failure
1067 for slot in self.slots:
1068 if slot.add(defconfig):
1072 def available(self):
1073 """Check if there is a vacant slot.
1076 Return True if at lease one vacant slot is found, False otherwise.
1078 for slot in self.slots:
1084 """Check if all slots are vacant.
1087 Return True if all the slots are vacant, False otherwise.
1090 for slot in self.slots:
1095 def show_failed_boards(self):
1096 """Display all of the failed boards (defconfigs)."""
1098 output_file = 'moveconfig.failed'
1100 for slot in self.slots:
1101 boards |= slot.get_failed_boards()
1104 boards = '\n'.join(boards) + '\n'
1105 msg = 'The following boards were not processed due to error:\n'
1107 msg += '(the list has been saved in %s)\n' % output_file
1108 print(color_text(self.args.color, COLOR_LIGHT_RED,
1109 msg), file=sys.stderr)
1111 write_file(output_file, boards)
1113 def show_suspicious_boards(self):
1114 """Display all boards (defconfigs) with possible misconversion."""
1116 output_file = 'moveconfig.suspicious'
1118 for slot in self.slots:
1119 boards |= slot.get_suspicious_boards()
1122 boards = '\n'.join(boards) + '\n'
1123 msg = 'The following boards might have been converted incorrectly.\n'
1124 msg += 'It is highly recommended to check them manually:\n'
1126 msg += '(the list has been saved in %s)\n' % output_file
1127 print(color_text(self.args.color, COLOR_YELLOW,
1128 msg), file=sys.stderr)
1130 write_file(output_file, boards)
1132 class ReferenceSource:
1134 """Reference source against which original configs should be parsed."""
1136 def __init__(self, commit):
1137 """Create a reference source directory based on a specified commit.
1140 commit: commit to git-clone
1142 self.src_dir = tempfile.mkdtemp()
1143 print('Cloning git repo to a separate work directory...')
1144 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1146 print("Checkout '%s' to build the original autoconf.mk." % \
1147 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip())
1148 subprocess.check_output(['git', 'checkout', commit],
1149 stderr=subprocess.STDOUT, cwd=self.src_dir)
1152 """Delete the reference source directory
1154 This function makes sure the temporary directory is cleaned away
1155 even if Python suddenly dies due to error. It should be done in here
1156 because it is guaranteed the destructor is always invoked when the
1157 instance of the class gets unreferenced.
1159 shutil.rmtree(self.src_dir)
1162 """Return the absolute path to the reference source directory."""
1166 def move_config(toolchains, configs, args, db_queue):
1167 """Move config options to defconfig files.
1170 configs: A list of CONFIGs to move.
1171 args: Program arguments
1173 if len(configs) == 0:
1175 print('No CONFIG is specified. You are probably syncing defconfigs.', end=' ')
1177 print('Building %s database' % CONFIG_DATABASE)
1179 print('Neither CONFIG nor --force-sync is specified. Nothing will happen.', end=' ')
1181 print('Move ' + ', '.join(configs), end=' ')
1182 print('(jobs: %d)\n' % args.jobs)
1185 reference_src = ReferenceSource(args.git_ref)
1186 reference_src_dir = reference_src.get_dir()
1188 reference_src_dir = None
1191 defconfigs = get_matched_defconfigs(args.defconfigs)
1193 defconfigs = get_all_defconfigs()
1195 progress = Progress(len(defconfigs))
1196 slots = Slots(toolchains, configs, args, progress, reference_src_dir,
1199 # Main loop to process defconfig files:
1200 # Add a new subprocess into a vacant slot.
1201 # Sleep if there is no available slot.
1202 for defconfig in defconfigs:
1203 while not slots.add(defconfig):
1204 while not slots.available():
1205 # No available slot: sleep for a while
1206 time.sleep(SLEEP_TIME)
1208 # wait until all the subprocesses finish
1209 while not slots.empty():
1210 time.sleep(SLEEP_TIME)
1213 slots.show_failed_boards()
1214 slots.show_suspicious_boards()
1216 def find_kconfig_rules(kconf, config, imply_config):
1217 """Check whether a config has a 'select' or 'imply' keyword
1220 kconf: Kconfiglib.Kconfig object
1221 config: Name of config to check (without CONFIG_ prefix)
1222 imply_config: Implying config (without CONFIG_ prefix) which may or
1223 may not have an 'imply' for 'config')
1226 Symbol object for 'config' if found, else None
1228 sym = kconf.syms.get(imply_config)
1230 for sel, cond in (sym.selects + sym.implies):
1231 if sel.name == config:
1235 def check_imply_rule(kconf, config, imply_config):
1236 """Check if we can add an 'imply' option
1238 This finds imply_config in the Kconfig and looks to see if it is possible
1239 to add an 'imply' for 'config' to that part of the Kconfig.
1242 kconf: Kconfiglib.Kconfig object
1243 config: Name of config to check (without CONFIG_ prefix)
1244 imply_config: Implying config (without CONFIG_ prefix) which may or
1245 may not have an 'imply' for 'config')
1249 filename of Kconfig file containing imply_config, or None if none
1250 line number within the Kconfig file, or 0 if none
1251 message indicating the result
1253 sym = kconf.syms.get(imply_config)
1255 return 'cannot find sym'
1258 return '%d locations' % len(nodes)
1260 fname, linenum = node.filename, node.linenr
1262 if cwd and fname.startswith(cwd):
1263 fname = fname[len(cwd) + 1:]
1264 file_line = ' at %s:%d' % (fname, linenum)
1265 data = read_file(fname)
1266 if data[linenum - 1] != 'config %s' % imply_config:
1267 return None, 0, 'bad sym format %s%s' % (data[linenum], file_line)
1268 return fname, linenum, 'adding%s' % file_line
1270 def add_imply_rule(config, fname, linenum):
1271 """Add a new 'imply' option to a Kconfig
1274 config: config option to add an imply for (without CONFIG_ prefix)
1275 fname: Kconfig filename to update
1276 linenum: Line number to place the 'imply' before
1279 Message indicating the result
1281 file_line = ' at %s:%d' % (fname, linenum)
1282 data = read_file(fname)
1285 for offset, line in enumerate(data[linenum:]):
1286 if line.strip().startswith('help') or not line:
1287 data.insert(linenum + offset, '\timply %s' % config)
1288 write_file(fname, data)
1289 return 'added%s' % file_line
1291 return 'could not insert%s'
1293 (IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
1297 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
1298 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
1299 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
1301 IMPLY_NON_ARCH_BOARD,
1302 'Allow Kconfig options outside arch/ and /board/ to imply'],
1306 def read_database():
1307 """Read in the config database
1311 set of all config options seen (each a str)
1312 set of all defconfigs seen (each a str)
1313 dict of configs for each defconfig:
1314 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
1317 value: Value of option
1318 dict of defconfigs for each config:
1320 value: set of boards using that option
1325 # key is defconfig name, value is dict of (CONFIG_xxx, value)
1328 # Set of all config options we have seen
1331 # Set of all defconfigs we have seen
1332 all_defconfigs = set()
1334 defconfig_db = collections.defaultdict(set)
1335 for line in read_file(CONFIG_DATABASE):
1336 line = line.rstrip()
1337 if not line: # Separator between defconfigs
1338 config_db[defconfig] = configs
1339 all_defconfigs.add(defconfig)
1341 elif line[0] == ' ': # CONFIG line
1342 config, value = line.strip().split('=', 1)
1343 configs[config] = value
1344 defconfig_db[config].add(defconfig)
1345 all_configs.add(config)
1346 else: # New defconfig
1349 return all_configs, all_defconfigs, config_db, defconfig_db
1352 def do_imply_config(config_list, add_imply, imply_flags, skip_added,
1353 check_kconfig=True, find_superset=False):
1354 """Find CONFIG options which imply those in the list
1356 Some CONFIG options can be implied by others and this can help to reduce
1357 the size of the defconfig files. For example, CONFIG_X86 implies
1358 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
1359 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
1360 each of the x86 defconfig files.
1362 This function uses the moveconfig database to find such options. It
1363 displays a list of things that could possibly imply those in the list.
1364 The algorithm ignores any that start with CONFIG_TARGET since these
1365 typically refer to only a few defconfigs (often one). It also does not
1366 display a config with less than 5 defconfigs.
1368 The algorithm works using sets. For each target config in config_list:
1369 - Get the set 'defconfigs' which use that target config
1370 - For each config (from a list of all configs):
1371 - Get the set 'imply_defconfig' of defconfigs which use that config
1373 - If imply_defconfigs contains anything not in defconfigs then
1374 this config does not imply the target config
1377 config_list: List of CONFIG options to check (each a string)
1378 add_imply: Automatically add an 'imply' for each config.
1379 imply_flags: Flags which control which implying configs are allowed
1381 skip_added: Don't show options which already have an imply added.
1382 check_kconfig: Check if implied symbols already have an 'imply' or
1383 'select' for the target config, and show this information if so.
1384 find_superset: True to look for configs which are a superset of those
1385 already found. So for example if CONFIG_EXYNOS5 implies an option,
1386 but CONFIG_EXYNOS covers a larger set of defconfigs and also
1387 implies that option, this will drop the former in favour of the
1388 latter. In practice this option has not proved very used.
1390 Note the terminoloy:
1391 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
1392 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
1394 kconf = KconfigScanner().conf if check_kconfig else None
1395 if add_imply and add_imply != 'all':
1396 add_imply = add_imply.split(',')
1398 all_configs, all_defconfigs, config_db, defconfig_db = read_database()
1400 # Work through each target config option in turn, independently
1401 for config in config_list:
1402 defconfigs = defconfig_db.get(config)
1404 print('%s not found in any defconfig' % config)
1407 # Get the set of defconfigs without this one (since a config cannot
1409 non_defconfigs = all_defconfigs - defconfigs
1410 num_defconfigs = len(defconfigs)
1411 print('%s found in %d/%d defconfigs' % (config, num_defconfigs,
1414 # This will hold the results: key=config, value=defconfigs containing it
1416 rest_configs = all_configs - set([config])
1418 # Look at every possible config, except the target one
1419 for imply_config in rest_configs:
1420 if 'ERRATUM' in imply_config:
1422 if not imply_flags & IMPLY_CMD:
1423 if 'CONFIG_CMD' in imply_config:
1425 if not imply_flags & IMPLY_TARGET:
1426 if 'CONFIG_TARGET' in imply_config:
1429 # Find set of defconfigs that have this config
1430 imply_defconfig = defconfig_db[imply_config]
1432 # Get the intersection of this with defconfigs containing the
1434 common_defconfigs = imply_defconfig & defconfigs
1436 # Get the set of defconfigs containing this config which DO NOT
1437 # also contain the taret config. If this set is non-empty it means
1438 # that this config affects other defconfigs as well as (possibly)
1439 # the ones affected by the target config. This means it implies
1440 # things we don't want to imply.
1441 not_common_defconfigs = imply_defconfig & non_defconfigs
1442 if not_common_defconfigs:
1445 # If there are common defconfigs, imply_config may be useful
1446 if common_defconfigs:
1449 for prev in list(imply_configs.keys()):
1450 prev_count = len(imply_configs[prev])
1451 count = len(common_defconfigs)
1452 if (prev_count > count and
1453 (imply_configs[prev] & common_defconfigs ==
1454 common_defconfigs)):
1455 # skip imply_config because prev is a superset
1458 elif count > prev_count:
1459 # delete prev because imply_config is a superset
1460 del imply_configs[prev]
1462 imply_configs[imply_config] = common_defconfigs
1464 # Now we have a dict imply_configs of configs which imply each config
1465 # The value of each dict item is the set of defconfigs containing that
1466 # config. Rank them so that we print the configs that imply the largest
1467 # number of defconfigs first.
1468 ranked_iconfigs = sorted(imply_configs,
1469 key=lambda k: len(imply_configs[k]), reverse=True)
1472 add_list = collections.defaultdict(list)
1473 for iconfig in ranked_iconfigs:
1474 num_common = len(imply_configs[iconfig])
1476 # Don't bother if there are less than 5 defconfigs affected.
1477 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
1479 missing = defconfigs - imply_configs[iconfig]
1480 missing_str = ', '.join(missing) if missing else 'all'
1484 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1485 iconfig[CONFIG_LEN:])
1490 fname, linenum = nodes[0].filename, nodes[0].linenr
1491 if cwd and fname.startswith(cwd):
1492 fname = fname[len(cwd) + 1:]
1493 kconfig_info = '%s:%d' % (fname, linenum)
1497 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
1502 fname, linenum = nodes[0].filename, nodes[0].linenr
1503 if cwd and fname.startswith(cwd):
1504 fname = fname[len(cwd) + 1:]
1505 in_arch_board = not sym or (fname.startswith('arch') or
1506 fname.startswith('board'))
1507 if (not in_arch_board and
1508 not imply_flags & IMPLY_NON_ARCH_BOARD):
1511 if add_imply and (add_imply == 'all' or
1512 iconfig in add_imply):
1513 fname, linenum, kconfig_info = (check_imply_rule(kconf,
1514 config[CONFIG_LEN:], iconfig[CONFIG_LEN:]))
1516 add_list[fname].append(linenum)
1518 if show and kconfig_info != 'skip':
1519 print('%5d : %-30s%-25s %s' % (num_common, iconfig.ljust(30),
1520 kconfig_info, missing_str))
1522 # Having collected a list of things to add, now we add them. We process
1523 # each file from the largest line number to the smallest so that
1524 # earlier additions do not affect our line numbers. E.g. if we added an
1525 # imply at line 20 it would change the position of each line after
1527 for fname, linenums in add_list.items():
1528 for linenum in sorted(linenums, reverse=True):
1529 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
1531 def defconfig_matches(configs, re_match):
1532 """Check if any CONFIG option matches a regex
1534 The match must be complete, i.e. from the start to end of the CONFIG option.
1537 configs (dict): Dict of CONFIG options:
1539 value: Value of option
1540 re_match (re.Pattern): Match to check
1543 bool: True if any CONFIG matches the regex
1546 if re_match.fullmatch(cfg):
1550 def do_find_config(config_list):
1551 """Find boards with a given combination of CONFIGs
1554 config_list: List of CONFIG options to check (each a regex consisting
1555 of a config option, with or without a CONFIG_ prefix. If an option
1556 is preceded by a tilde (~) then it must be false, otherwise it must
1559 all_configs, all_defconfigs, config_db, defconfig_db = read_database()
1562 adhoc_configs = set(read_file('scripts/config_whitelist.txt'))
1564 # Start with all defconfigs
1565 out = all_defconfigs
1567 # Work through each config in turn
1569 for item in config_list:
1570 # Get the real config name and whether we want this config or not
1577 if cfg in adhoc_configs:
1581 # Search everything that is still in the running. If it has a config
1582 # that we want, or doesn't have one that we don't, add it into the
1583 # running for the next stage
1586 re_match = re.compile(cfg)
1587 for defc in in_list:
1588 has_cfg = defconfig_matches(config_db[defc], re_match)
1592 print(f"Error: Not in Kconfig: %s" % ' '.join(adhoc))
1594 print(f'{len(out)} matches')
1595 print(' '.join(item.split('_defconfig')[0] for item in out))
1598 def prefix_config(cfg):
1599 """Prefix a config with CONFIG_ if needed
1601 This handles ~ operator, which indicates that the CONFIG should be disabled
1603 >>> prefix_config('FRED')
1605 >>> prefix_config('CONFIG_FRED')
1607 >>> prefix_config('~FRED')
1609 >>> prefix_config('~CONFIG_FRED')
1611 >>> prefix_config('A123')
1618 if not cfg.startswith('CONFIG_'):
1619 cfg = 'CONFIG_' + cfg
1625 cpu_count = multiprocessing.cpu_count()
1626 except NotImplementedError:
1629 epilog = '''Move config options from headers to defconfig files. See
1630 doc/develop/moveconfig.rst for documentation.'''
1632 parser = ArgumentParser(epilog=epilog)
1633 # Add arguments here
1634 parser.add_argument('-a', '--add-imply', type=str, default='',
1635 help='comma-separated list of CONFIG options to add '
1636 "an 'imply' statement to for the CONFIG in -i")
1637 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
1638 help="don't show options which are already marked as "
1640 parser.add_argument('-b', '--build-db', action='store_true', default=False,
1641 help='build a CONFIG database')
1642 parser.add_argument('-c', '--color', action='store_true', default=False,
1643 help='display the log in color')
1644 parser.add_argument('-C', '--commit', action='store_true', default=False,
1645 help='Create a git commit for the operation')
1646 parser.add_argument('-d', '--defconfigs', type=str,
1647 help='a file containing a list of defconfigs to move, '
1648 "one per line (for example 'snow_defconfig') "
1649 "or '-' to read from stdin")
1650 parser.add_argument('-e', '--exit-on-error', action='store_true',
1652 help='exit immediately on any error')
1653 parser.add_argument('-f', '--find', action='store_true', default=False,
1654 help='Find boards with a given config combination')
1655 parser.add_argument('-H', '--headers-only', dest='cleanup_headers_only',
1656 action='store_true', default=False,
1657 help='only cleanup the headers')
1658 parser.add_argument('-i', '--imply', action='store_true', default=False,
1659 help='find options which imply others')
1660 parser.add_argument('-I', '--imply-flags', type=str, default='',
1661 help="control the -i option ('help' for help")
1662 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
1663 help='the number of jobs to run simultaneously')
1664 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
1665 help='perform a trial run (show log with no changes)')
1666 parser.add_argument('-r', '--git-ref', type=str,
1667 help='the git ref to clone for building the autoconf.mk')
1668 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
1669 help='force sync by savedefconfig')
1670 parser.add_argument('-S', '--spl', action='store_true', default=False,
1671 help='parse config options defined for SPL build')
1672 parser.add_argument('-t', '--test', action='store_true', default=False,
1673 help='run unit tests')
1674 parser.add_argument('-y', '--yes', action='store_true', default=False,
1675 help="respond 'yes' to any prompts")
1676 parser.add_argument('-v', '--verbose', action='store_true', default=False,
1677 help='show any build errors as boards are built')
1678 parser.add_argument('configs', nargs='*')
1680 args = parser.parse_args()
1681 configs = args.configs
1684 sys.argv = [sys.argv[0]]
1685 fail, count = doctest.testmod()
1690 if not any((len(configs), args.force_sync, args.build_db, args.imply,
1692 parser.print_usage()
1695 # prefix the option name with CONFIG_ if missing
1696 configs = [prefix_config(cfg) for cfg in configs]
1698 check_top_directory()
1702 if args.imply_flags == 'all':
1705 elif args.imply_flags:
1706 for flag in args.imply_flags.split(','):
1707 bad = flag not in IMPLY_FLAGS
1709 print("Invalid flag '%s'" % flag)
1710 if flag == 'help' or bad:
1711 print("Imply flags: (separate with ',')")
1712 for name, info in IMPLY_FLAGS.items():
1713 print(' %-15s: %s' % (name, info[1]))
1714 parser.print_usage()
1716 imply_flags |= IMPLY_FLAGS[flag][0]
1718 do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
1722 do_find_config(configs)
1726 db_queue = queue.Queue()
1727 t = DatabaseThread(config_db, db_queue)
1731 if not args.cleanup_headers_only:
1732 check_clean_directory()
1734 toolchains = toolchain.Toolchains()
1735 toolchains.GetSettings()
1736 toolchains.Scan(verbose=False)
1737 move_config(toolchains, configs, args, db_queue)
1741 cleanup_headers(configs, args)
1742 cleanup_whitelist(configs, args)
1743 cleanup_readme(configs, args)
1746 subprocess.call(['git', 'add', '-u'])
1748 msg = 'Convert %s %sto Kconfig' % (configs[0],
1749 'et al ' if len(configs) > 1 else '')
1750 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1751 '\n '.join(configs))
1753 msg = 'configs: Resync with savedefconfig'
1754 msg += '\n\nRsync all defconfig files using moveconfig.py'
1755 subprocess.call(['git', 'commit', '-s', '-m', msg])
1758 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as fd:
1759 for defconfig, configs in config_db.items():
1760 fd.write('%s\n' % defconfig)
1761 for config in sorted(configs.keys()):
1762 fd.write(' %s=%s\n' % (config, configs[config]))
1765 if __name__ == '__main__':