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.
20 import multiprocessing
32 from buildman import bsettings
33 from buildman import kconfiglib
34 from buildman import toolchain
36 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
42 STATE_SAVEDEFCONFIG = 3
46 ACTION_NO_ENTRY_WARN = 2
56 COLOR_LIGHT_GRAY = '0;37'
57 COLOR_DARK_GRAY = '1;30'
58 COLOR_LIGHT_RED = '1;31'
59 COLOR_LIGHT_GREEN = '1;32'
61 COLOR_LIGHT_BLUE = '1;34'
62 COLOR_LIGHT_PURPLE = '1;35'
63 COLOR_LIGHT_CYAN = '1;36'
66 AUTO_CONF_PATH = 'include/config/auto.conf'
67 CONFIG_DATABASE = 'moveconfig.db'
69 CONFIG_LEN = len('CONFIG_')
72 "SZ_1": 0x00000001, "SZ_2": 0x00000002,
73 "SZ_4": 0x00000004, "SZ_8": 0x00000008,
74 "SZ_16": 0x00000010, "SZ_32": 0x00000020,
75 "SZ_64": 0x00000040, "SZ_128": 0x00000080,
76 "SZ_256": 0x00000100, "SZ_512": 0x00000200,
77 "SZ_1K": 0x00000400, "SZ_2K": 0x00000800,
78 "SZ_4K": 0x00001000, "SZ_8K": 0x00002000,
79 "SZ_16K": 0x00004000, "SZ_32K": 0x00008000,
80 "SZ_64K": 0x00010000, "SZ_128K": 0x00020000,
81 "SZ_256K": 0x00040000, "SZ_512K": 0x00080000,
82 "SZ_1M": 0x00100000, "SZ_2M": 0x00200000,
83 "SZ_4M": 0x00400000, "SZ_8M": 0x00800000,
84 "SZ_16M": 0x01000000, "SZ_32M": 0x02000000,
85 "SZ_64M": 0x04000000, "SZ_128M": 0x08000000,
86 "SZ_256M": 0x10000000, "SZ_512M": 0x20000000,
87 "SZ_1G": 0x40000000, "SZ_2G": 0x80000000,
91 ### helper functions ###
93 """Get the file object of '/dev/null' device."""
95 devnull = subprocess.DEVNULL # py3k
96 except AttributeError:
97 devnull = open(os.devnull, 'wb')
100 def check_top_directory():
101 """Exit if we are not at the top of source directory."""
102 for f in ('README', 'Licenses'):
103 if not os.path.exists(f):
104 sys.exit('Please run at the top of source directory.')
106 def check_clean_directory():
107 """Exit if the source tree is not clean."""
108 for f in ('.config', 'include/config'):
109 if os.path.exists(f):
110 sys.exit("source tree is not clean, please run 'make mrproper'")
113 """Get the command name of GNU Make.
115 U-Boot needs GNU Make for building, but the command name is not
116 necessarily "make". (for example, "gmake" on FreeBSD).
117 Returns the most appropriate command name on your system.
119 process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
120 ret = process.communicate()
121 if process.returncode:
122 sys.exit('GNU Make not found')
123 return ret[0].rstrip()
125 def get_matched_defconfig(line):
126 """Get the defconfig files that match a pattern
129 line: Path or filename to match, e.g. 'configs/snow_defconfig' or
130 'k2*_defconfig'. If no directory is provided, 'configs/' is
134 a list of matching defconfig files
136 dirname = os.path.dirname(line)
140 pattern = os.path.join('configs', line)
141 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
143 def get_matched_defconfigs(defconfigs_file):
144 """Get all the defconfig files that match the patterns in a file.
147 defconfigs_file: File containing a list of defconfigs to process, or
148 '-' to read the list from stdin
151 A list of paths to defconfig files, with no duplicates
154 if defconfigs_file == '-':
156 defconfigs_file = 'stdin'
158 fd = open(defconfigs_file)
159 for i, line in enumerate(fd):
162 continue # skip blank lines silently
164 line = line.split(' ')[0] # handle 'git log' input
165 matched = get_matched_defconfig(line)
167 print("warning: %s:%d: no defconfig matched '%s'" % \
168 (defconfigs_file, i + 1, line), file=sys.stderr)
170 defconfigs += matched
172 # use set() to drop multiple matching
173 return [ defconfig[len('configs') + 1:] for defconfig in set(defconfigs) ]
175 def get_all_defconfigs():
176 """Get all the defconfig files under the configs/ directory."""
178 for (dirpath, dirnames, 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') ])
195 def show_diff(a, b, file_path, color_enabled):
196 """Show unidified diff.
199 a: A list of lines (before)
200 b: A list of lines (after)
201 file_path: Path to the file
202 color_enabled: Display the diff in color
205 diff = difflib.unified_diff(a, b,
206 fromfile=os.path.join('a', file_path),
207 tofile=os.path.join('b', file_path))
210 if line[0] == '-' and line[1] != '-':
211 print(color_text(color_enabled, COLOR_RED, line), end=' ')
212 elif line[0] == '+' and line[1] != '+':
213 print(color_text(color_enabled, COLOR_GREEN, line), end=' ')
217 def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre,
219 """Extend matched lines if desired patterns are found before/after already
223 lines: A list of lines handled.
224 matched: A list of line numbers that have been already matched.
225 (will be updated by this function)
226 pre_patterns: A list of regular expression that should be matched as
228 post_patterns: A list of regular expression that should be matched as
230 extend_pre: Add the line number of matched preamble to the matched list.
231 extend_post: Add the line number of matched postamble to the matched list.
233 extended_matched = []
246 for p in pre_patterns:
247 if p.search(lines[i - 1]):
253 for p in post_patterns:
254 if p.search(lines[j]):
261 extended_matched.append(i - 1)
263 extended_matched.append(j)
265 matched += extended_matched
268 def confirm(options, prompt):
271 choice = input('{} [y/n]: '.format(prompt))
272 choice = choice.lower()
274 if choice == 'y' or choice == 'n':
282 def cleanup_empty_blocks(header_path, options):
283 """Clean up empty conditional blocks
286 header_path: path to the cleaned file.
287 options: option flags.
289 pattern = re.compile(r'^\s*#\s*if.*$\n^\s*#\s*endif.*$\n*', flags=re.M)
290 with open(header_path) as f:
293 except UnicodeDecodeError as e:
294 print("Failed on file %s': %s" % (header_path, e))
297 new_data = pattern.sub('\n', data)
299 show_diff(data.splitlines(True), new_data.splitlines(True), header_path,
305 with open(header_path, 'w') as f:
308 def cleanup_one_header(header_path, patterns, options):
309 """Clean regex-matched lines away from a file.
312 header_path: path to the cleaned file.
313 patterns: list of regex patterns. Any lines matching to these
314 patterns are deleted.
315 options: option flags.
317 with open(header_path) as f:
319 lines = f.readlines()
320 except UnicodeDecodeError as e:
321 print("Failed on file %s': %s" % (header_path, e))
325 for i, line in enumerate(lines):
326 if i - 1 in matched and lines[i - 1][-2:] == '\\\n':
329 for pattern in patterns:
330 if pattern.search(line):
337 # remove empty #ifdef ... #endif, successive blank lines
338 pattern_if = re.compile(r'#\s*if(def|ndef)?\W') # #if, #ifdef, #ifndef
339 pattern_elif = re.compile(r'#\s*el(if|se)\W') # #elif, #else
340 pattern_endif = re.compile(r'#\s*endif\W') # #endif
341 pattern_blank = re.compile(r'^\s*$') # empty line
344 old_matched = copy.copy(matched)
345 extend_matched_lines(lines, matched, [pattern_if],
346 [pattern_endif], True, True)
347 extend_matched_lines(lines, matched, [pattern_elif],
348 [pattern_elif, pattern_endif], True, False)
349 extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
350 [pattern_blank], False, True)
351 extend_matched_lines(lines, matched, [pattern_blank],
352 [pattern_elif, pattern_endif], True, False)
353 extend_matched_lines(lines, matched, [pattern_blank],
354 [pattern_blank], True, False)
355 if matched == old_matched:
358 tolines = copy.copy(lines)
360 for i in reversed(matched):
363 show_diff(lines, tolines, header_path, options.color)
368 with open(header_path, 'w') as f:
372 def cleanup_headers(configs, options):
373 """Delete config defines from board headers.
376 configs: A list of CONFIGs to remove.
377 options: option flags.
379 if not confirm(options, 'Clean up headers?'):
383 for config in configs:
384 patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
385 patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
387 for dir in 'include', 'arch', 'board':
388 for (dirpath, dirnames, filenames) in os.walk(dir):
389 if dirpath == os.path.join('include', 'generated'):
391 for filename in filenames:
392 if not filename.endswith(('~', '.dts', '.dtsi', '.bin',
393 '.elf','.aml','.dat')):
394 header_path = os.path.join(dirpath, filename)
395 # This file contains UTF-16 data and no CONFIG symbols
396 if header_path == 'include/video_font_data.h':
398 cleanup_one_header(header_path, patterns, options)
399 cleanup_empty_blocks(header_path, options)
401 def cleanup_one_extra_option(defconfig_path, configs, options):
402 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
405 defconfig_path: path to the cleaned defconfig file.
406 configs: A list of CONFIGs to remove.
407 options: option flags.
410 start = 'CONFIG_SYS_EXTRA_OPTIONS="'
413 with open(defconfig_path) as f:
414 lines = f.readlines()
416 for i, line in enumerate(lines):
417 if line.startswith(start) and line.endswith(end):
420 # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
423 old_tokens = line[len(start):-len(end)].split(',')
426 for token in old_tokens:
427 pos = token.find('=')
428 if not (token[:pos] if pos >= 0 else token) in configs:
429 new_tokens.append(token)
431 if new_tokens == old_tokens:
434 tolines = copy.copy(lines)
437 tolines[i] = start + ','.join(new_tokens) + end
441 show_diff(lines, tolines, defconfig_path, options.color)
446 with open(defconfig_path, 'w') as f:
450 def cleanup_extra_options(configs, options):
451 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
454 configs: A list of CONFIGs to remove.
455 options: option flags.
457 if not confirm(options, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'):
460 configs = [ config[len('CONFIG_'):] for config in configs ]
462 defconfigs = get_all_defconfigs()
464 for defconfig in defconfigs:
465 cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
468 def cleanup_whitelist(configs, options):
469 """Delete config whitelist entries
472 configs: A list of CONFIGs to remove.
473 options: option flags.
475 if not confirm(options, 'Clean up whitelist entries?'):
478 with open(os.path.join('scripts', 'config_whitelist.txt')) as f:
479 lines = f.readlines()
481 lines = [x for x in lines if x.strip() not in configs]
483 with open(os.path.join('scripts', 'config_whitelist.txt'), 'w') as f:
484 f.write(''.join(lines))
486 def find_matching(patterns, line):
492 def cleanup_readme(configs, options):
493 """Delete config description in README
496 configs: A list of CONFIGs to remove.
497 options: option flags.
499 if not confirm(options, 'Clean up README?'):
503 for config in configs:
504 patterns.append(re.compile(r'^\s+%s' % config))
506 with open('README') as f:
507 lines = f.readlines()
513 found = find_matching(patterns, line)
517 if found and re.search(r'^\s+CONFIG', line):
521 newlines.append(line)
523 with open('README', 'w') as f:
524 f.write(''.join(newlines))
526 def try_expand(line):
527 """If value looks like an expression, try expanding it
528 Otherwise just return the existing value
530 if line.find('=') == -1:
534 aeval = asteval.Interpreter( usersyms=SIZES, minimal=True )
535 cfg, val = re.split("=", line)
537 if re.search("[*+-/]|<<|SZ_+|\(([^\)]+)\)", val):
538 newval = hex(aeval(val))
539 print("\tExpanded expression %s to %s" % (val, newval))
540 return cfg+'='+newval
542 print("\tFailed to expand expression in %s" % line)
550 """Progress Indicator"""
552 def __init__(self, total):
553 """Create a new progress indicator.
556 total: A number of defconfig files to process.
562 """Increment the number of processed defconfig files."""
567 """Display the progress."""
568 print(' %d defconfigs out of %d\r' % (self.current, self.total), end=' ')
572 class KconfigScanner:
573 """Kconfig scanner."""
576 """Scan all the Kconfig files and create a Config object."""
577 # Define environment variables referenced from Kconfig
578 os.environ['srctree'] = os.getcwd()
579 os.environ['UBOOTVERSION'] = 'dummy'
580 os.environ['KCONFIG_OBJDIR'] = ''
581 self.conf = kconfiglib.Kconfig()
586 """A parser of .config and include/autoconf.mk."""
588 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
589 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
591 def __init__(self, configs, options, build_dir):
592 """Create a new parser.
595 configs: A list of CONFIGs to move.
596 options: option flags.
597 build_dir: Build directory.
599 self.configs = configs
600 self.options = options
601 self.dotconfig = os.path.join(build_dir, '.config')
602 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
603 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
605 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
606 self.defconfig = os.path.join(build_dir, 'defconfig')
609 """Parse .config file and return the architecture.
612 Architecture name (e.g. 'arm').
616 for line in open(self.dotconfig):
617 m = self.re_arch.match(line)
621 m = self.re_cpu.match(line)
629 if arch == 'arm' and cpu == 'armv8':
634 def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
635 """Parse .config, defconfig, include/autoconf.mk for one config.
637 This function looks for the config options in the lines from
638 defconfig, .config, and include/autoconf.mk in order to decide
639 which action should be taken for this defconfig.
642 config: CONFIG name to parse.
643 dotconfig_lines: lines from the .config file.
644 autoconf_lines: lines from the include/autoconf.mk file.
647 A tupple of the action for this defconfig and the line
648 matched for the config.
650 not_set = '# %s is not set' % config
652 for line in autoconf_lines:
654 if line.startswith(config + '='):
660 new_val = try_expand(new_val)
662 for line in dotconfig_lines:
664 if line.startswith(config + '=') or line == not_set:
668 if new_val == not_set:
669 return (ACTION_NO_ENTRY, config)
671 return (ACTION_NO_ENTRY_WARN, config)
673 # If this CONFIG is neither bool nor trisate
674 if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
675 # tools/scripts/define2mk.sed changes '1' to 'y'.
676 # This is a problem if the CONFIG is int type.
677 # Check the type in Kconfig and handle it correctly.
678 if new_val[-2:] == '=y':
679 new_val = new_val[:-1] + '1'
681 return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
684 def update_dotconfig(self):
685 """Parse files for the config options and update the .config.
687 This function parses the generated .config and include/autoconf.mk
688 searching the target options.
689 Move the config option(s) to the .config as needed.
692 defconfig: defconfig name.
695 Return a tuple of (updated flag, log string).
696 The "updated flag" is True if the .config was updated, False
697 otherwise. The "log string" shows what happend to the .config.
703 rm_files = [self.config_autoconf, self.autoconf]
706 if os.path.exists(self.spl_autoconf):
707 autoconf_path = self.spl_autoconf
708 rm_files.append(self.spl_autoconf)
712 return (updated, suspicious,
713 color_text(self.options.color, COLOR_BROWN,
714 "SPL is not enabled. Skipped.") + '\n')
716 autoconf_path = self.autoconf
718 with open(self.dotconfig) as f:
719 dotconfig_lines = f.readlines()
721 with open(autoconf_path) as f:
722 autoconf_lines = f.readlines()
724 for config in self.configs:
725 result = self.parse_one_config(config, dotconfig_lines,
727 results.append(result)
731 for (action, value) in results:
732 if action == ACTION_MOVE:
733 actlog = "Move '%s'" % value
734 log_color = COLOR_LIGHT_GREEN
735 elif action == ACTION_NO_ENTRY:
736 actlog = "%s is not defined in Kconfig. Do nothing." % value
737 log_color = COLOR_LIGHT_BLUE
738 elif action == ACTION_NO_ENTRY_WARN:
739 actlog = "%s is not defined in Kconfig (suspicious). Do nothing." % value
740 log_color = COLOR_YELLOW
742 elif action == ACTION_NO_CHANGE:
743 actlog = "'%s' is the same as the define in Kconfig. Do nothing." \
745 log_color = COLOR_LIGHT_PURPLE
746 elif action == ACTION_SPL_NOT_EXIST:
747 actlog = "SPL is not enabled for this defconfig. Skip."
748 log_color = COLOR_PURPLE
750 sys.exit("Internal Error. This should not happen.")
752 log += color_text(self.options.color, log_color, actlog) + '\n'
754 with open(self.dotconfig, 'a') as f:
755 for (action, value) in results:
756 if action == ACTION_MOVE:
757 f.write(value + '\n')
760 self.results = results
764 return (updated, suspicious, log)
766 def check_defconfig(self):
767 """Check the defconfig after savedefconfig
770 Return additional log if moved CONFIGs were removed again by
771 'make savedefconfig'.
776 with open(self.defconfig) as f:
777 defconfig_lines = f.readlines()
779 for (action, value) in self.results:
780 if action != ACTION_MOVE:
782 if not value + '\n' in defconfig_lines:
783 log += color_text(self.options.color, COLOR_YELLOW,
784 "'%s' was removed by savedefconfig.\n" %
790 class DatabaseThread(threading.Thread):
791 """This thread processes results from Slot threads.
793 It collects the data in the master config directary. There is only one
794 result thread, and this helps to serialise the build output.
796 def __init__(self, config_db, db_queue):
797 """Set up a new result thread
800 builder: Builder which will be sent each result
802 threading.Thread.__init__(self)
803 self.config_db = config_db
804 self.db_queue= db_queue
807 """Called to start up the result thread.
809 We collect the next result job and pass it on to the build.
812 defconfig, configs = self.db_queue.get()
813 self.config_db[defconfig] = configs
814 self.db_queue.task_done()
819 """A slot to store a subprocess.
821 Each instance of this class handles one subprocess.
822 This class is useful to control multiple threads
823 for faster processing.
826 def __init__(self, toolchains, configs, options, progress, devnull,
827 make_cmd, reference_src_dir, db_queue):
828 """Create a new process slot.
831 toolchains: Toolchains object containing toolchains.
832 configs: A list of CONFIGs to move.
833 options: option flags.
834 progress: A progress indicator.
835 devnull: A file object of '/dev/null'.
836 make_cmd: command name of GNU Make.
837 reference_src_dir: Determine the true starting config state from this
839 db_queue: output queue to write config info for the database
841 self.toolchains = toolchains
842 self.options = options
843 self.progress = progress
844 self.build_dir = tempfile.mkdtemp()
845 self.devnull = devnull
846 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
847 self.reference_src_dir = reference_src_dir
848 self.db_queue = db_queue
849 self.parser = KconfigParser(configs, options, self.build_dir)
850 self.state = STATE_IDLE
851 self.failed_boards = set()
852 self.suspicious_boards = set()
855 """Delete the working directory
857 This function makes sure the temporary directory is cleaned away
858 even if Python suddenly dies due to error. It should be done in here
859 because it is guaranteed the destructor is always invoked when the
860 instance of the class gets unreferenced.
862 If the subprocess is still running, wait until it finishes.
864 if self.state != STATE_IDLE:
865 while self.ps.poll() == None:
867 shutil.rmtree(self.build_dir)
869 def add(self, defconfig):
870 """Assign a new subprocess for defconfig and add it to the slot.
872 If the slot is vacant, create a new subprocess for processing the
873 given defconfig and add it to the slot. Just returns False if
874 the slot is occupied (i.e. the current subprocess is still running).
877 defconfig: defconfig name.
880 Return True on success or False on failure
882 if self.state != STATE_IDLE:
885 self.defconfig = defconfig
887 self.current_src_dir = self.reference_src_dir
892 """Check the status of the subprocess and handle it as needed.
894 Returns True if the slot is vacant (i.e. in idle state).
895 If the configuration is successfully finished, assign a new
896 subprocess to build include/autoconf.mk.
897 If include/autoconf.mk is generated, invoke the parser to
898 parse the .config and the include/autoconf.mk, moving
899 config options to the .config as needed.
900 If the .config was updated, run "make savedefconfig" to sync
901 it, update the original defconfig, and then set the slot back
905 Return True if the subprocess is terminated, False otherwise
907 if self.state == STATE_IDLE:
910 if self.ps.poll() == None:
913 if self.ps.poll() != 0:
915 elif self.state == STATE_DEFCONFIG:
916 if self.reference_src_dir and not self.current_src_dir:
917 self.do_savedefconfig()
920 elif self.state == STATE_AUTOCONF:
921 if self.current_src_dir:
922 self.current_src_dir = None
924 elif self.options.build_db:
927 self.do_savedefconfig()
928 elif self.state == STATE_SAVEDEFCONFIG:
929 self.update_defconfig()
931 sys.exit("Internal Error. This should not happen.")
933 return True if self.state == STATE_IDLE else False
935 def handle_error(self):
936 """Handle error cases."""
938 self.log += color_text(self.options.color, COLOR_LIGHT_RED,
939 "Failed to process.\n")
940 if self.options.verbose:
941 self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
942 self.ps.stderr.read().decode())
945 def do_defconfig(self):
946 """Run 'make <board>_defconfig' to create the .config file."""
948 cmd = list(self.make_cmd)
949 cmd.append(self.defconfig)
950 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
951 stderr=subprocess.PIPE,
952 cwd=self.current_src_dir)
953 self.state = STATE_DEFCONFIG
955 def do_autoconf(self):
956 """Run 'make AUTO_CONF_PATH'."""
958 arch = self.parser.get_arch()
960 toolchain = self.toolchains.Select(arch)
962 self.log += color_text(self.options.color, COLOR_YELLOW,
963 "Tool chain for '%s' is missing. Do nothing.\n" % arch)
966 env = toolchain.MakeEnvironment(False)
968 cmd = list(self.make_cmd)
969 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
970 cmd.append(AUTO_CONF_PATH)
971 self.ps = subprocess.Popen(cmd, stdout=self.devnull, env=env,
972 stderr=subprocess.PIPE,
973 cwd=self.current_src_dir)
974 self.state = STATE_AUTOCONF
976 def do_build_db(self):
977 """Add the board to the database"""
979 with open(os.path.join(self.build_dir, AUTO_CONF_PATH)) as fd:
980 for line in fd.readlines():
981 if line.startswith('CONFIG'):
982 config, value = line.split('=', 1)
983 configs[config] = value.rstrip()
984 self.db_queue.put([self.defconfig, configs])
987 def do_savedefconfig(self):
988 """Update the .config and run 'make savedefconfig'."""
990 (updated, suspicious, log) = self.parser.update_dotconfig()
992 self.suspicious_boards.add(self.defconfig)
995 if not self.options.force_sync and not updated:
999 self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
1000 "Syncing by savedefconfig...\n")
1002 self.log += "Syncing by savedefconfig (forced by option)...\n"
1004 cmd = list(self.make_cmd)
1005 cmd.append('savedefconfig')
1006 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1007 stderr=subprocess.PIPE)
1008 self.state = STATE_SAVEDEFCONFIG
1010 def update_defconfig(self):
1011 """Update the input defconfig and go back to the idle state."""
1013 log = self.parser.check_defconfig()
1015 self.suspicious_boards.add(self.defconfig)
1017 orig_defconfig = os.path.join('configs', self.defconfig)
1018 new_defconfig = os.path.join(self.build_dir, 'defconfig')
1019 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
1022 self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
1023 "defconfig was updated.\n")
1025 if not self.options.dry_run and updated:
1026 shutil.move(new_defconfig, orig_defconfig)
1029 def finish(self, success):
1030 """Display log along with progress and go to the idle state.
1033 success: Should be True when the defconfig was processed
1034 successfully, or False when it fails.
1036 # output at least 30 characters to hide the "* defconfigs out of *".
1037 log = self.defconfig.ljust(30) + '\n'
1039 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ])
1040 # Some threads are running in parallel.
1041 # Print log atomically to not mix up logs from different threads.
1042 print(log, file=(sys.stdout if success else sys.stderr))
1045 if self.options.exit_on_error:
1046 sys.exit("Exit on error.")
1047 # If --exit-on-error flag is not set, skip this board and continue.
1048 # Record the failed board.
1049 self.failed_boards.add(self.defconfig)
1052 self.progress.show()
1053 self.state = STATE_IDLE
1055 def get_failed_boards(self):
1056 """Returns a set of failed boards (defconfigs) in this slot.
1058 return self.failed_boards
1060 def get_suspicious_boards(self):
1061 """Returns a set of boards (defconfigs) with possible misconversion.
1063 return self.suspicious_boards - self.failed_boards
1067 """Controller of the array of subprocess slots."""
1069 def __init__(self, toolchains, configs, options, progress,
1070 reference_src_dir, db_queue):
1071 """Create a new slots controller.
1074 toolchains: Toolchains object containing toolchains.
1075 configs: A list of CONFIGs to move.
1076 options: option flags.
1077 progress: A progress indicator.
1078 reference_src_dir: Determine the true starting config state from this
1080 db_queue: output queue to write config info for the database
1082 self.options = options
1084 devnull = get_devnull()
1085 make_cmd = get_make_cmd()
1086 for i in range(options.jobs):
1087 self.slots.append(Slot(toolchains, configs, options, progress,
1088 devnull, make_cmd, reference_src_dir,
1091 def add(self, defconfig):
1092 """Add a new subprocess if a vacant slot is found.
1095 defconfig: defconfig name to be put into.
1098 Return True on success or False on failure
1100 for slot in self.slots:
1101 if slot.add(defconfig):
1105 def available(self):
1106 """Check if there is a vacant slot.
1109 Return True if at lease one vacant slot is found, False otherwise.
1111 for slot in self.slots:
1117 """Check if all slots are vacant.
1120 Return True if all the slots are vacant, False otherwise.
1123 for slot in self.slots:
1128 def show_failed_boards(self):
1129 """Display all of the failed boards (defconfigs)."""
1131 output_file = 'moveconfig.failed'
1133 for slot in self.slots:
1134 boards |= slot.get_failed_boards()
1137 boards = '\n'.join(boards) + '\n'
1138 msg = "The following boards were not processed due to error:\n"
1140 msg += "(the list has been saved in %s)\n" % output_file
1141 print(color_text(self.options.color, COLOR_LIGHT_RED,
1142 msg), file=sys.stderr)
1144 with open(output_file, 'w') as f:
1147 def show_suspicious_boards(self):
1148 """Display all boards (defconfigs) with possible misconversion."""
1150 output_file = 'moveconfig.suspicious'
1152 for slot in self.slots:
1153 boards |= slot.get_suspicious_boards()
1156 boards = '\n'.join(boards) + '\n'
1157 msg = "The following boards might have been converted incorrectly.\n"
1158 msg += "It is highly recommended to check them manually:\n"
1160 msg += "(the list has been saved in %s)\n" % output_file
1161 print(color_text(self.options.color, COLOR_YELLOW,
1162 msg), file=sys.stderr)
1164 with open(output_file, 'w') as f:
1167 class ReferenceSource:
1169 """Reference source against which original configs should be parsed."""
1171 def __init__(self, commit):
1172 """Create a reference source directory based on a specified commit.
1175 commit: commit to git-clone
1177 self.src_dir = tempfile.mkdtemp()
1178 print("Cloning git repo to a separate work directory...")
1179 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1181 print("Checkout '%s' to build the original autoconf.mk." % \
1182 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip())
1183 subprocess.check_output(['git', 'checkout', commit],
1184 stderr=subprocess.STDOUT, cwd=self.src_dir)
1187 """Delete the reference source directory
1189 This function makes sure the temporary directory is cleaned away
1190 even if Python suddenly dies due to error. It should be done in here
1191 because it is guaranteed the destructor is always invoked when the
1192 instance of the class gets unreferenced.
1194 shutil.rmtree(self.src_dir)
1197 """Return the absolute path to the reference source directory."""
1201 def move_config(toolchains, configs, options, db_queue):
1202 """Move config options to defconfig files.
1205 configs: A list of CONFIGs to move.
1206 options: option flags
1208 if len(configs) == 0:
1209 if options.force_sync:
1210 print('No CONFIG is specified. You are probably syncing defconfigs.', end=' ')
1211 elif options.build_db:
1212 print('Building %s database' % CONFIG_DATABASE)
1214 print('Neither CONFIG nor --force-sync is specified. Nothing will happen.', end=' ')
1216 print('Move ' + ', '.join(configs), end=' ')
1217 print('(jobs: %d)\n' % options.jobs)
1220 reference_src = ReferenceSource(options.git_ref)
1221 reference_src_dir = reference_src.get_dir()
1223 reference_src_dir = None
1225 if options.defconfigs:
1226 defconfigs = get_matched_defconfigs(options.defconfigs)
1228 defconfigs = get_all_defconfigs()
1230 progress = Progress(len(defconfigs))
1231 slots = Slots(toolchains, configs, options, progress, reference_src_dir,
1234 # Main loop to process defconfig files:
1235 # Add a new subprocess into a vacant slot.
1236 # Sleep if there is no available slot.
1237 for defconfig in defconfigs:
1238 while not slots.add(defconfig):
1239 while not slots.available():
1240 # No available slot: sleep for a while
1241 time.sleep(SLEEP_TIME)
1243 # wait until all the subprocesses finish
1244 while not slots.empty():
1245 time.sleep(SLEEP_TIME)
1248 slots.show_failed_boards()
1249 slots.show_suspicious_boards()
1251 def find_kconfig_rules(kconf, config, imply_config):
1252 """Check whether a config has a 'select' or 'imply' keyword
1255 kconf: Kconfiglib.Kconfig object
1256 config: Name of config to check (without CONFIG_ prefix)
1257 imply_config: Implying config (without CONFIG_ prefix) which may or
1258 may not have an 'imply' for 'config')
1261 Symbol object for 'config' if found, else None
1263 sym = kconf.syms.get(imply_config)
1265 for sel, cond in (sym.selects + sym.implies):
1270 def check_imply_rule(kconf, config, imply_config):
1271 """Check if we can add an 'imply' option
1273 This finds imply_config in the Kconfig and looks to see if it is possible
1274 to add an 'imply' for 'config' to that part of the Kconfig.
1277 kconf: Kconfiglib.Kconfig object
1278 config: Name of config to check (without CONFIG_ prefix)
1279 imply_config: Implying config (without CONFIG_ prefix) which may or
1280 may not have an 'imply' for 'config')
1284 filename of Kconfig file containing imply_config, or None if none
1285 line number within the Kconfig file, or 0 if none
1286 message indicating the result
1288 sym = kconf.syms.get(imply_config)
1290 return 'cannot find sym'
1293 return '%d locations' % len(nodes)
1294 fname, linenum = nodes[0].filename, nodes[0].linern
1296 if cwd and fname.startswith(cwd):
1297 fname = fname[len(cwd) + 1:]
1298 file_line = ' at %s:%d' % (fname, linenum)
1299 with open(fname) as fd:
1300 data = fd.read().splitlines()
1301 if data[linenum - 1] != 'config %s' % imply_config:
1302 return None, 0, 'bad sym format %s%s' % (data[linenum], file_line)
1303 return fname, linenum, 'adding%s' % file_line
1305 def add_imply_rule(config, fname, linenum):
1306 """Add a new 'imply' option to a Kconfig
1309 config: config option to add an imply for (without CONFIG_ prefix)
1310 fname: Kconfig filename to update
1311 linenum: Line number to place the 'imply' before
1314 Message indicating the result
1316 file_line = ' at %s:%d' % (fname, linenum)
1317 data = open(fname).read().splitlines()
1320 for offset, line in enumerate(data[linenum:]):
1321 if line.strip().startswith('help') or not line:
1322 data.insert(linenum + offset, '\timply %s' % config)
1323 with open(fname, 'w') as fd:
1324 fd.write('\n'.join(data) + '\n')
1325 return 'added%s' % file_line
1327 return 'could not insert%s'
1329 (IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
1333 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
1334 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
1335 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
1337 IMPLY_NON_ARCH_BOARD,
1338 'Allow Kconfig options outside arch/ and /board/ to imply'],
1341 def do_imply_config(config_list, add_imply, imply_flags, skip_added,
1342 check_kconfig=True, find_superset=False):
1343 """Find CONFIG options which imply those in the list
1345 Some CONFIG options can be implied by others and this can help to reduce
1346 the size of the defconfig files. For example, CONFIG_X86 implies
1347 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
1348 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
1349 each of the x86 defconfig files.
1351 This function uses the moveconfig database to find such options. It
1352 displays a list of things that could possibly imply those in the list.
1353 The algorithm ignores any that start with CONFIG_TARGET since these
1354 typically refer to only a few defconfigs (often one). It also does not
1355 display a config with less than 5 defconfigs.
1357 The algorithm works using sets. For each target config in config_list:
1358 - Get the set 'defconfigs' which use that target config
1359 - For each config (from a list of all configs):
1360 - Get the set 'imply_defconfig' of defconfigs which use that config
1362 - If imply_defconfigs contains anything not in defconfigs then
1363 this config does not imply the target config
1366 config_list: List of CONFIG options to check (each a string)
1367 add_imply: Automatically add an 'imply' for each config.
1368 imply_flags: Flags which control which implying configs are allowed
1370 skip_added: Don't show options which already have an imply added.
1371 check_kconfig: Check if implied symbols already have an 'imply' or
1372 'select' for the target config, and show this information if so.
1373 find_superset: True to look for configs which are a superset of those
1374 already found. So for example if CONFIG_EXYNOS5 implies an option,
1375 but CONFIG_EXYNOS covers a larger set of defconfigs and also
1376 implies that option, this will drop the former in favour of the
1377 latter. In practice this option has not proved very used.
1379 Note the terminoloy:
1380 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
1381 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
1383 kconf = KconfigScanner().conf if check_kconfig else None
1384 if add_imply and add_imply != 'all':
1385 add_imply = add_imply.split()
1387 # key is defconfig name, value is dict of (CONFIG_xxx, value)
1390 # Holds a dict containing the set of defconfigs that contain each config
1391 # key is config, value is set of defconfigs using that config
1392 defconfig_db = collections.defaultdict(set)
1394 # Set of all config options we have seen
1397 # Set of all defconfigs we have seen
1398 all_defconfigs = set()
1400 # Read in the database
1402 with open(CONFIG_DATABASE) as fd:
1403 for line in fd.readlines():
1404 line = line.rstrip()
1405 if not line: # Separator between defconfigs
1406 config_db[defconfig] = configs
1407 all_defconfigs.add(defconfig)
1409 elif line[0] == ' ': # CONFIG line
1410 config, value = line.strip().split('=', 1)
1411 configs[config] = value
1412 defconfig_db[config].add(defconfig)
1413 all_configs.add(config)
1414 else: # New defconfig
1417 # Work through each target config option in tern, independently
1418 for config in config_list:
1419 defconfigs = defconfig_db.get(config)
1421 print('%s not found in any defconfig' % config)
1424 # Get the set of defconfigs without this one (since a config cannot
1426 non_defconfigs = all_defconfigs - defconfigs
1427 num_defconfigs = len(defconfigs)
1428 print('%s found in %d/%d defconfigs' % (config, num_defconfigs,
1431 # This will hold the results: key=config, value=defconfigs containing it
1433 rest_configs = all_configs - set([config])
1435 # Look at every possible config, except the target one
1436 for imply_config in rest_configs:
1437 if 'ERRATUM' in imply_config:
1439 if not (imply_flags & IMPLY_CMD):
1440 if 'CONFIG_CMD' in imply_config:
1442 if not (imply_flags & IMPLY_TARGET):
1443 if 'CONFIG_TARGET' in imply_config:
1446 # Find set of defconfigs that have this config
1447 imply_defconfig = defconfig_db[imply_config]
1449 # Get the intersection of this with defconfigs containing the
1451 common_defconfigs = imply_defconfig & defconfigs
1453 # Get the set of defconfigs containing this config which DO NOT
1454 # also contain the taret config. If this set is non-empty it means
1455 # that this config affects other defconfigs as well as (possibly)
1456 # the ones affected by the target config. This means it implies
1457 # things we don't want to imply.
1458 not_common_defconfigs = imply_defconfig & non_defconfigs
1459 if not_common_defconfigs:
1462 # If there are common defconfigs, imply_config may be useful
1463 if common_defconfigs:
1466 for prev in list(imply_configs.keys()):
1467 prev_count = len(imply_configs[prev])
1468 count = len(common_defconfigs)
1469 if (prev_count > count and
1470 (imply_configs[prev] & common_defconfigs ==
1471 common_defconfigs)):
1472 # skip imply_config because prev is a superset
1475 elif count > prev_count:
1476 # delete prev because imply_config is a superset
1477 del imply_configs[prev]
1479 imply_configs[imply_config] = common_defconfigs
1481 # Now we have a dict imply_configs of configs which imply each config
1482 # The value of each dict item is the set of defconfigs containing that
1483 # config. Rank them so that we print the configs that imply the largest
1484 # number of defconfigs first.
1485 ranked_iconfigs = sorted(imply_configs,
1486 key=lambda k: len(imply_configs[k]), reverse=True)
1489 add_list = collections.defaultdict(list)
1490 for iconfig in ranked_iconfigs:
1491 num_common = len(imply_configs[iconfig])
1493 # Don't bother if there are less than 5 defconfigs affected.
1494 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
1496 missing = defconfigs - imply_configs[iconfig]
1497 missing_str = ', '.join(missing) if missing else 'all'
1501 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1502 iconfig[CONFIG_LEN:])
1507 fname, linenum = nodes[0].filename, nodes[0].linenr
1508 if cwd and fname.startswith(cwd):
1509 fname = fname[len(cwd) + 1:]
1510 kconfig_info = '%s:%d' % (fname, linenum)
1514 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
1519 fname, linenum = nodes[0].filename, nodes[0].linenr
1520 if cwd and fname.startswith(cwd):
1521 fname = fname[len(cwd) + 1:]
1522 in_arch_board = not sym or (fname.startswith('arch') or
1523 fname.startswith('board'))
1524 if (not in_arch_board and
1525 not (imply_flags & IMPLY_NON_ARCH_BOARD)):
1528 if add_imply and (add_imply == 'all' or
1529 iconfig in add_imply):
1530 fname, linenum, kconfig_info = (check_imply_rule(kconf,
1531 config[CONFIG_LEN:], iconfig[CONFIG_LEN:]))
1533 add_list[fname].append(linenum)
1535 if show and kconfig_info != 'skip':
1536 print('%5d : %-30s%-25s %s' % (num_common, iconfig.ljust(30),
1537 kconfig_info, missing_str))
1539 # Having collected a list of things to add, now we add them. We process
1540 # each file from the largest line number to the smallest so that
1541 # earlier additions do not affect our line numbers. E.g. if we added an
1542 # imply at line 20 it would change the position of each line after
1544 for fname, linenums in add_list.items():
1545 for linenum in sorted(linenums, reverse=True):
1546 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
1551 cpu_count = multiprocessing.cpu_count()
1552 except NotImplementedError:
1555 parser = optparse.OptionParser()
1557 parser.add_option('-a', '--add-imply', type='string', default='',
1558 help='comma-separated list of CONFIG options to add '
1559 "an 'imply' statement to for the CONFIG in -i")
1560 parser.add_option('-A', '--skip-added', action='store_true', default=False,
1561 help="don't show options which are already marked as "
1563 parser.add_option('-b', '--build-db', action='store_true', default=False,
1564 help='build a CONFIG database')
1565 parser.add_option('-c', '--color', action='store_true', default=False,
1566 help='display the log in color')
1567 parser.add_option('-C', '--commit', action='store_true', default=False,
1568 help='Create a git commit for the operation')
1569 parser.add_option('-d', '--defconfigs', type='string',
1570 help='a file containing a list of defconfigs to move, '
1571 "one per line (for example 'snow_defconfig') "
1572 "or '-' to read from stdin")
1573 parser.add_option('-i', '--imply', action='store_true', default=False,
1574 help='find options which imply others')
1575 parser.add_option('-I', '--imply-flags', type='string', default='',
1576 help="control the -i option ('help' for help")
1577 parser.add_option('-n', '--dry-run', action='store_true', default=False,
1578 help='perform a trial run (show log with no changes)')
1579 parser.add_option('-e', '--exit-on-error', action='store_true',
1581 help='exit immediately on any error')
1582 parser.add_option('-s', '--force-sync', action='store_true', default=False,
1583 help='force sync by savedefconfig')
1584 parser.add_option('-S', '--spl', action='store_true', default=False,
1585 help='parse config options defined for SPL build')
1586 parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1587 action='store_true', default=False,
1588 help='only cleanup the headers')
1589 parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1590 help='the number of jobs to run simultaneously')
1591 parser.add_option('-r', '--git-ref', type='string',
1592 help='the git ref to clone for building the autoconf.mk')
1593 parser.add_option('-y', '--yes', action='store_true', default=False,
1594 help="respond 'yes' to any prompts")
1595 parser.add_option('-v', '--verbose', action='store_true', default=False,
1596 help='show any build errors as boards are built')
1597 parser.usage += ' CONFIG ...'
1599 (options, configs) = parser.parse_args()
1601 if len(configs) == 0 and not any((options.force_sync, options.build_db,
1603 parser.print_usage()
1606 # prefix the option name with CONFIG_ if missing
1607 configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1608 for config in configs ]
1610 check_top_directory()
1614 if options.imply_flags == 'all':
1617 elif options.imply_flags:
1618 for flag in options.imply_flags.split(','):
1619 bad = flag not in IMPLY_FLAGS
1621 print("Invalid flag '%s'" % flag)
1622 if flag == 'help' or bad:
1623 print("Imply flags: (separate with ',')")
1624 for name, info in IMPLY_FLAGS.items():
1625 print(' %-15s: %s' % (name, info[1]))
1626 parser.print_usage()
1628 imply_flags |= IMPLY_FLAGS[flag][0]
1630 do_imply_config(configs, options.add_imply, imply_flags,
1635 db_queue = queue.Queue()
1636 t = DatabaseThread(config_db, db_queue)
1640 if not options.cleanup_headers_only:
1641 check_clean_directory()
1643 toolchains = toolchain.Toolchains()
1644 toolchains.GetSettings()
1645 toolchains.Scan(verbose=False)
1646 move_config(toolchains, configs, options, db_queue)
1650 cleanup_headers(configs, options)
1651 cleanup_extra_options(configs, options)
1652 cleanup_whitelist(configs, options)
1653 cleanup_readme(configs, options)
1656 subprocess.call(['git', 'add', '-u'])
1658 msg = 'Convert %s %sto Kconfig' % (configs[0],
1659 'et al ' if len(configs) > 1 else '')
1660 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1661 '\n '.join(configs))
1663 msg = 'configs: Resync with savedefconfig'
1664 msg += '\n\nRsync all defconfig files using moveconfig.py'
1665 subprocess.call(['git', 'commit', '-s', '-m', msg])
1667 if options.build_db:
1668 with open(CONFIG_DATABASE, 'w') as fd:
1669 for defconfig, configs in config_db.items():
1670 fd.write('%s\n' % defconfig)
1671 for config in sorted(configs.keys()):
1672 fd.write(' %s=%s\n' % (config, configs[config]))
1675 if __name__ == '__main__':