3 # Author: Masahiro Yamada <yamada.masahiro@socionext.com>
5 # SPDX-License-Identifier: GPL-2.0+
9 Move config options from headers to defconfig files.
11 Since Kconfig was introduced to U-Boot, we have worked on moving
12 config options from headers to Kconfig (defconfig).
14 This tool intends to help this tremendous work.
20 First, you must edit the Kconfig to add the menu entries for the configs
23 And then run this tool giving CONFIG names you want to move.
24 For example, if you want to move CONFIG_CMD_USB and CONFIG_SYS_TEXT_BASE,
25 simply type as follows:
27 $ tools/moveconfig.py CONFIG_CMD_USB CONFIG_SYS_TEXT_BASE
29 The tool walks through all the defconfig files and move the given CONFIGs.
31 The log is also displayed on the terminal.
33 The log is printed for each defconfig as follows:
41 <defconfig_name> is the name of the defconfig.
43 <action*> shows what the tool did for that defconfig.
44 It looks like one of the following:
47 This config option was moved to the defconfig
49 - CONFIG_... is not defined in Kconfig. Do nothing.
50 The entry for this CONFIG was not found in Kconfig. The option is not
51 defined in the config header, either. So, this case can be just skipped.
53 - CONFIG_... is not defined in Kconfig (suspicious). Do nothing.
54 This option is defined in the config header, but its entry was not found
56 There are two common cases:
57 - You forgot to create an entry for the CONFIG before running
58 this tool, or made a typo in a CONFIG passed to this tool.
59 - The entry was hidden due to unmet 'depends on'.
60 The tool does not know if the result is reasonable, so please check it
63 - 'CONFIG_...' is the same as the define in Kconfig. Do nothing.
64 The define in the config header matched the one in Kconfig.
65 We do not need to touch it.
67 - Compiler is missing. Do nothing.
68 The compiler specified for this architecture was not found
69 in your PATH environment.
70 (If -e option is passed, the tool exits immediately.)
73 An error occurred during processing this defconfig. Skipped.
74 (If -e option is passed, the tool exits immediately on error.)
76 Finally, you will be asked, Clean up headers? [y/n]:
78 If you say 'y' here, the unnecessary config defines are removed
79 from the config headers (include/configs/*.h).
80 It just uses the regex method, so you should not rely on it.
81 Just in case, please do 'git diff' to see what happened.
87 This tool runs configuration and builds include/autoconf.mk for every
88 defconfig. The config options defined in Kconfig appear in the .config
89 file (unless they are hidden because of unmet dependency.)
90 On the other hand, the config options defined by board headers are seen
91 in include/autoconf.mk. The tool looks for the specified options in both
92 of them to decide the appropriate action for the options. If the given
93 config option is found in the .config, but its value does not match the
94 one from the board header, the config option in the .config is replaced
95 with the define in the board header. Then, the .config is synced by
96 "make savedefconfig" and the defconfig is updated with it.
98 For faster processing, this tool handles multi-threading. It creates
99 separate build directories where the out-of-tree build is run. The
100 temporary build directories are automatically created and deleted as
101 needed. The number of threads are chosen based on the number of the CPU
102 cores of your system although you can change it via -j (--jobs) option.
108 Appropriate toolchain are necessary to generate include/autoconf.mk
109 for all the architectures supported by U-Boot. Most of them are available
110 at the kernel.org site, some are not provided by kernel.org.
112 The default per-arch CROSS_COMPILE used by this tool is specified by
113 the list below, CROSS_COMPILE. You may wish to update the list to
114 use your own. Instead of modifying the list directly, you can give
115 them via environments.
122 Surround each portion of the log with escape sequences to display it
123 in color on the terminal.
126 Create a git commit with the changes when the operation is complete. A
127 standard commit message is used which may need to be edited.
130 Specify a file containing a list of defconfigs to move. The defconfig
131 files can be given with shell-style wildcards.
134 Perform a trial run that does not make any changes. It is useful to
135 see what is going to happen before one actually runs it.
138 Exit immediately if Make exits with a non-zero status while processing
142 Do "make savedefconfig" forcibly for all the defconfig files.
143 If not specified, "make savedefconfig" only occurs for cases
144 where at least one CONFIG was moved.
147 Look for moved config options in spl/include/autoconf.mk instead of
148 include/autoconf.mk. This is useful for moving options for SPL build
149 because SPL related options (mostly prefixed with CONFIG_SPL_) are
150 sometimes blocked by CONFIG_SPL_BUILD ifdef conditionals.
153 Only cleanup the headers; skip the defconfig processing
156 Specify the number of threads to run simultaneously. If not specified,
157 the number of threads is the same as the number of CPU cores.
160 Specify the git ref to clone for building the autoconf.mk. If unspecified
161 use the CWD. This is useful for when changes to the Kconfig affect the
162 default values and you want to capture the state of the defconfig from
163 before that change was in effect. If in doubt, specify a ref pre-Kconfig
164 changes (use HEAD if Kconfig changes are not committed). Worst case it will
165 take a bit longer to run, but will always do the right thing.
168 Show any build errors as boards are built
171 Instead of prompting, automatically go ahead with all operations. This
172 includes cleaning up headers and CONFIG_SYS_EXTRA_OPTIONS.
174 To see the complete list of supported options, run
176 $ tools/moveconfig.py -h
185 import multiprocessing
195 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
198 # Here is the list of cross-tools I use.
199 # Most of them are available at kernel.org
200 # (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the following:
201 # arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases
202 # nds32: http://osdk.andestech.com/packages/nds32le-linux-glibc-v1.tgz
203 # nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545
204 # sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu
207 'aarch64': 'aarch64-linux-',
208 'arm': 'arm-unknown-linux-gnueabi-',
209 'avr32': 'avr32-linux-',
210 'm68k': 'm68k-linux-',
211 'microblaze': 'microblaze-linux-',
212 'mips': 'mips-linux-',
213 'nds32': 'nds32le-linux-',
214 'nios2': 'nios2-linux-gnu-',
215 'powerpc': 'powerpc-linux-',
216 'sh': 'sh-linux-gnu-',
217 'x86': 'i386-linux-',
218 'xtensa': 'xtensa-linux-'
224 STATE_SAVEDEFCONFIG = 3
228 ACTION_NO_ENTRY_WARN = 2
236 COLOR_PURPLE = '0;35'
238 COLOR_LIGHT_GRAY = '0;37'
239 COLOR_DARK_GRAY = '1;30'
240 COLOR_LIGHT_RED = '1;31'
241 COLOR_LIGHT_GREEN = '1;32'
242 COLOR_YELLOW = '1;33'
243 COLOR_LIGHT_BLUE = '1;34'
244 COLOR_LIGHT_PURPLE = '1;35'
245 COLOR_LIGHT_CYAN = '1;36'
248 ### helper functions ###
250 """Get the file object of '/dev/null' device."""
252 devnull = subprocess.DEVNULL # py3k
253 except AttributeError:
254 devnull = open(os.devnull, 'wb')
257 def check_top_directory():
258 """Exit if we are not at the top of source directory."""
259 for f in ('README', 'Licenses'):
260 if not os.path.exists(f):
261 sys.exit('Please run at the top of source directory.')
263 def check_clean_directory():
264 """Exit if the source tree is not clean."""
265 for f in ('.config', 'include/config'):
266 if os.path.exists(f):
267 sys.exit("source tree is not clean, please run 'make mrproper'")
270 """Get the command name of GNU Make.
272 U-Boot needs GNU Make for building, but the command name is not
273 necessarily "make". (for example, "gmake" on FreeBSD).
274 Returns the most appropriate command name on your system.
276 process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
277 ret = process.communicate()
278 if process.returncode:
279 sys.exit('GNU Make not found')
280 return ret[0].rstrip()
282 def get_matched_defconfigs(defconfigs_file):
283 """Get all the defconfig files that match the patterns in a file."""
285 for i, line in enumerate(open(defconfigs_file)):
288 continue # skip blank lines silently
289 pattern = os.path.join('configs', line)
290 matched = glob.glob(pattern) + glob.glob(pattern + '_defconfig')
292 print >> sys.stderr, "warning: %s:%d: no defconfig matched '%s'" % \
293 (defconfigs_file, i + 1, line)
295 defconfigs += matched
297 # use set() to drop multiple matching
298 return [ defconfig[len('configs') + 1:] for defconfig in set(defconfigs) ]
300 def get_all_defconfigs():
301 """Get all the defconfig files under the configs/ directory."""
303 for (dirpath, dirnames, filenames) in os.walk('configs'):
304 dirpath = dirpath[len('configs') + 1:]
305 for filename in fnmatch.filter(filenames, '*_defconfig'):
306 defconfigs.append(os.path.join(dirpath, filename))
310 def color_text(color_enabled, color, string):
311 """Return colored string."""
313 # LF should not be surrounded by the escape sequence.
314 # Otherwise, additional whitespace or line-feed might be printed.
315 return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
316 for s in string.split('\n') ])
320 def show_diff(a, b, file_path, color_enabled):
321 """Show unidified diff.
324 a: A list of lines (before)
325 b: A list of lines (after)
326 file_path: Path to the file
327 color_enabled: Display the diff in color
330 diff = difflib.unified_diff(a, b,
331 fromfile=os.path.join('a', file_path),
332 tofile=os.path.join('b', file_path))
335 if line[0] == '-' and line[1] != '-':
336 print color_text(color_enabled, COLOR_RED, line),
337 elif line[0] == '+' and line[1] != '+':
338 print color_text(color_enabled, COLOR_GREEN, line),
342 def update_cross_compile(color_enabled):
343 """Update per-arch CROSS_COMPILE via environment variables
345 The default CROSS_COMPILE values are available
346 in the CROSS_COMPILE list above.
348 You can override them via environment variables
349 CROSS_COMPILE_{ARCH}.
351 For example, if you want to override toolchain prefixes
352 for ARM and PowerPC, you can do as follows in your shell:
354 export CROSS_COMPILE_ARM=...
355 export CROSS_COMPILE_POWERPC=...
357 Then, this function checks if specified compilers really exist in your
362 for arch in os.listdir('arch'):
363 if os.path.exists(os.path.join('arch', arch, 'Makefile')):
366 # arm64 is a special case
367 archs.append('aarch64')
370 env = 'CROSS_COMPILE_' + arch.upper()
371 cross_compile = os.environ.get(env)
372 if not cross_compile:
373 cross_compile = CROSS_COMPILE.get(arch, '')
375 for path in os.environ["PATH"].split(os.pathsep):
376 gcc_path = os.path.join(path, cross_compile + 'gcc')
377 if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
380 print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
381 'warning: %sgcc: not found in PATH. %s architecture boards will be skipped'
382 % (cross_compile, arch))
385 CROSS_COMPILE[arch] = cross_compile
387 def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre,
389 """Extend matched lines if desired patterns are found before/after already
393 lines: A list of lines handled.
394 matched: A list of line numbers that have been already matched.
395 (will be updated by this function)
396 pre_patterns: A list of regular expression that should be matched as
398 post_patterns: A list of regular expression that should be matched as
400 extend_pre: Add the line number of matched preamble to the matched list.
401 extend_post: Add the line number of matched postamble to the matched list.
403 extended_matched = []
416 for p in pre_patterns:
417 if p.search(lines[i - 1]):
423 for p in post_patterns:
424 if p.search(lines[j]):
431 extended_matched.append(i - 1)
433 extended_matched.append(j)
435 matched += extended_matched
438 def confirm(options, prompt):
441 choice = raw_input('{} [y/n]: '.format(prompt))
442 choice = choice.lower()
444 if choice == 'y' or choice == 'n':
452 def cleanup_one_header(header_path, patterns, options):
453 """Clean regex-matched lines away from a file.
456 header_path: path to the cleaned file.
457 patterns: list of regex patterns. Any lines matching to these
458 patterns are deleted.
459 options: option flags.
461 with open(header_path) as f:
462 lines = f.readlines()
465 for i, line in enumerate(lines):
466 if i - 1 in matched and lines[i - 1][-2:] == '\\\n':
469 for pattern in patterns:
470 if pattern.search(line):
477 # remove empty #ifdef ... #endif, successive blank lines
478 pattern_if = re.compile(r'#\s*if(def|ndef)?\W') # #if, #ifdef, #ifndef
479 pattern_elif = re.compile(r'#\s*el(if|se)\W') # #elif, #else
480 pattern_endif = re.compile(r'#\s*endif\W') # #endif
481 pattern_blank = re.compile(r'^\s*$') # empty line
484 old_matched = copy.copy(matched)
485 extend_matched_lines(lines, matched, [pattern_if],
486 [pattern_endif], True, True)
487 extend_matched_lines(lines, matched, [pattern_elif],
488 [pattern_elif, pattern_endif], True, False)
489 extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
490 [pattern_blank], False, True)
491 extend_matched_lines(lines, matched, [pattern_blank],
492 [pattern_elif, pattern_endif], True, False)
493 extend_matched_lines(lines, matched, [pattern_blank],
494 [pattern_blank], True, False)
495 if matched == old_matched:
498 tolines = copy.copy(lines)
500 for i in reversed(matched):
503 show_diff(lines, tolines, header_path, options.color)
508 with open(header_path, 'w') as f:
512 def cleanup_headers(configs, options):
513 """Delete config defines from board headers.
516 configs: A list of CONFIGs to remove.
517 options: option flags.
519 if not confirm(options, 'Clean up headers?'):
523 for config in configs:
524 patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
525 patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
527 for dir in 'include', 'arch', 'board':
528 for (dirpath, dirnames, filenames) in os.walk(dir):
529 if dirpath == os.path.join('include', 'generated'):
531 for filename in filenames:
532 if not fnmatch.fnmatch(filename, '*~'):
533 cleanup_one_header(os.path.join(dirpath, filename),
536 def cleanup_one_extra_option(defconfig_path, configs, options):
537 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
540 defconfig_path: path to the cleaned defconfig file.
541 configs: A list of CONFIGs to remove.
542 options: option flags.
545 start = 'CONFIG_SYS_EXTRA_OPTIONS="'
548 with open(defconfig_path) as f:
549 lines = f.readlines()
551 for i, line in enumerate(lines):
552 if line.startswith(start) and line.endswith(end):
555 # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
558 old_tokens = line[len(start):-len(end)].split(',')
561 for token in old_tokens:
562 pos = token.find('=')
563 if not (token[:pos] if pos >= 0 else token) in configs:
564 new_tokens.append(token)
566 if new_tokens == old_tokens:
569 tolines = copy.copy(lines)
572 tolines[i] = start + ','.join(new_tokens) + end
576 show_diff(lines, tolines, defconfig_path, options.color)
581 with open(defconfig_path, 'w') as f:
585 def cleanup_extra_options(configs, options):
586 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
589 configs: A list of CONFIGs to remove.
590 options: option flags.
592 if not confirm(options, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'):
595 configs = [ config[len('CONFIG_'):] for config in configs ]
597 defconfigs = get_all_defconfigs()
599 for defconfig in defconfigs:
600 cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
603 def cleanup_whitelist(configs, options):
604 """Delete config whitelist entries
607 configs: A list of CONFIGs to remove.
608 options: option flags.
610 if not confirm(options, 'Clean up whitelist entries?'):
613 with open(os.path.join('scripts', 'config_whitelist.txt')) as f:
614 lines = f.readlines()
616 lines = [x for x in lines if x.strip() not in configs]
618 with open(os.path.join('scripts', 'config_whitelist.txt'), 'w') as f:
619 f.write(''.join(lines))
625 """Progress Indicator"""
627 def __init__(self, total):
628 """Create a new progress indicator.
631 total: A number of defconfig files to process.
637 """Increment the number of processed defconfig files."""
642 """Display the progress."""
643 print ' %d defconfigs out of %d\r' % (self.current, self.total),
648 """A parser of .config and include/autoconf.mk."""
650 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
651 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
653 def __init__(self, configs, options, build_dir):
654 """Create a new parser.
657 configs: A list of CONFIGs to move.
658 options: option flags.
659 build_dir: Build directory.
661 self.configs = configs
662 self.options = options
663 self.dotconfig = os.path.join(build_dir, '.config')
664 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
665 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
667 self.config_autoconf = os.path.join(build_dir, 'include', 'config',
669 self.defconfig = os.path.join(build_dir, 'defconfig')
671 def get_cross_compile(self):
672 """Parse .config file and return CROSS_COMPILE.
675 A string storing the compiler prefix for the architecture.
676 Return a NULL string for architectures that do not require
677 compiler prefix (Sandbox and native build is the case).
678 Return None if the specified compiler is missing in your PATH.
679 Caller should distinguish '' and None.
683 for line in open(self.dotconfig):
684 m = self.re_arch.match(line)
688 m = self.re_cpu.match(line)
696 if arch == 'arm' and cpu == 'armv8':
699 return CROSS_COMPILE.get(arch, None)
701 def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
702 """Parse .config, defconfig, include/autoconf.mk for one config.
704 This function looks for the config options in the lines from
705 defconfig, .config, and include/autoconf.mk in order to decide
706 which action should be taken for this defconfig.
709 config: CONFIG name to parse.
710 dotconfig_lines: lines from the .config file.
711 autoconf_lines: lines from the include/autoconf.mk file.
714 A tupple of the action for this defconfig and the line
715 matched for the config.
717 not_set = '# %s is not set' % config
719 for line in autoconf_lines:
721 if line.startswith(config + '='):
727 for line in dotconfig_lines:
729 if line.startswith(config + '=') or line == not_set:
733 if new_val == not_set:
734 return (ACTION_NO_ENTRY, config)
736 return (ACTION_NO_ENTRY_WARN, config)
738 # If this CONFIG is neither bool nor trisate
739 if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
740 # tools/scripts/define2mk.sed changes '1' to 'y'.
741 # This is a problem if the CONFIG is int type.
742 # Check the type in Kconfig and handle it correctly.
743 if new_val[-2:] == '=y':
744 new_val = new_val[:-1] + '1'
746 return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
749 def update_dotconfig(self):
750 """Parse files for the config options and update the .config.
752 This function parses the generated .config and include/autoconf.mk
753 searching the target options.
754 Move the config option(s) to the .config as needed.
757 defconfig: defconfig name.
760 Return a tuple of (updated flag, log string).
761 The "updated flag" is True if the .config was updated, False
762 otherwise. The "log string" shows what happend to the .config.
768 rm_files = [self.config_autoconf, self.autoconf]
771 if os.path.exists(self.spl_autoconf):
772 autoconf_path = self.spl_autoconf
773 rm_files.append(self.spl_autoconf)
777 return (updated, suspicious,
778 color_text(self.options.color, COLOR_BROWN,
779 "SPL is not enabled. Skipped.") + '\n')
781 autoconf_path = self.autoconf
783 with open(self.dotconfig) as f:
784 dotconfig_lines = f.readlines()
786 with open(autoconf_path) as f:
787 autoconf_lines = f.readlines()
789 for config in self.configs:
790 result = self.parse_one_config(config, dotconfig_lines,
792 results.append(result)
796 for (action, value) in results:
797 if action == ACTION_MOVE:
798 actlog = "Move '%s'" % value
799 log_color = COLOR_LIGHT_GREEN
800 elif action == ACTION_NO_ENTRY:
801 actlog = "%s is not defined in Kconfig. Do nothing." % value
802 log_color = COLOR_LIGHT_BLUE
803 elif action == ACTION_NO_ENTRY_WARN:
804 actlog = "%s is not defined in Kconfig (suspicious). Do nothing." % value
805 log_color = COLOR_YELLOW
807 elif action == ACTION_NO_CHANGE:
808 actlog = "'%s' is the same as the define in Kconfig. Do nothing." \
810 log_color = COLOR_LIGHT_PURPLE
811 elif action == ACTION_SPL_NOT_EXIST:
812 actlog = "SPL is not enabled for this defconfig. Skip."
813 log_color = COLOR_PURPLE
815 sys.exit("Internal Error. This should not happen.")
817 log += color_text(self.options.color, log_color, actlog) + '\n'
819 with open(self.dotconfig, 'a') as f:
820 for (action, value) in results:
821 if action == ACTION_MOVE:
822 f.write(value + '\n')
825 self.results = results
829 return (updated, suspicious, log)
831 def check_defconfig(self):
832 """Check the defconfig after savedefconfig
835 Return additional log if moved CONFIGs were removed again by
836 'make savedefconfig'.
841 with open(self.defconfig) as f:
842 defconfig_lines = f.readlines()
844 for (action, value) in self.results:
845 if action != ACTION_MOVE:
847 if not value + '\n' in defconfig_lines:
848 log += color_text(self.options.color, COLOR_YELLOW,
849 "'%s' was removed by savedefconfig.\n" %
856 """A slot to store a subprocess.
858 Each instance of this class handles one subprocess.
859 This class is useful to control multiple threads
860 for faster processing.
863 def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir):
864 """Create a new process slot.
867 configs: A list of CONFIGs to move.
868 options: option flags.
869 progress: A progress indicator.
870 devnull: A file object of '/dev/null'.
871 make_cmd: command name of GNU Make.
872 reference_src_dir: Determine the true starting config state from this
875 self.options = options
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.parser = KconfigParser(configs, options, self.build_dir)
882 self.state = STATE_IDLE
883 self.failed_boards = set()
884 self.suspicious_boards = set()
887 """Delete the working directory
889 This function makes sure the temporary directory is cleaned away
890 even if Python suddenly dies due to error. It should be done in here
891 because it is guaranteed the destructor is always invoked when the
892 instance of the class gets unreferenced.
894 If the subprocess is still running, wait until it finishes.
896 if self.state != STATE_IDLE:
897 while self.ps.poll() == None:
899 shutil.rmtree(self.build_dir)
901 def add(self, defconfig):
902 """Assign a new subprocess for defconfig and add it to the slot.
904 If the slot is vacant, create a new subprocess for processing the
905 given defconfig and add it to the slot. Just returns False if
906 the slot is occupied (i.e. the current subprocess is still running).
909 defconfig: defconfig name.
912 Return True on success or False on failure
914 if self.state != STATE_IDLE:
917 self.defconfig = defconfig
919 self.current_src_dir = self.reference_src_dir
924 """Check the status of the subprocess and handle it as needed.
926 Returns True if the slot is vacant (i.e. in idle state).
927 If the configuration is successfully finished, assign a new
928 subprocess to build include/autoconf.mk.
929 If include/autoconf.mk is generated, invoke the parser to
930 parse the .config and the include/autoconf.mk, moving
931 config options to the .config as needed.
932 If the .config was updated, run "make savedefconfig" to sync
933 it, update the original defconfig, and then set the slot back
937 Return True if the subprocess is terminated, False otherwise
939 if self.state == STATE_IDLE:
942 if self.ps.poll() == None:
945 if self.ps.poll() != 0:
947 elif self.state == STATE_DEFCONFIG:
948 if self.reference_src_dir and not self.current_src_dir:
949 self.do_savedefconfig()
952 elif self.state == STATE_AUTOCONF:
953 if self.current_src_dir:
954 self.current_src_dir = None
957 self.do_savedefconfig()
958 elif self.state == STATE_SAVEDEFCONFIG:
959 self.update_defconfig()
961 sys.exit("Internal Error. This should not happen.")
963 return True if self.state == STATE_IDLE else False
965 def handle_error(self):
966 """Handle error cases."""
968 self.log += color_text(self.options.color, COLOR_LIGHT_RED,
969 "Failed to process.\n")
970 if self.options.verbose:
971 self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
972 self.ps.stderr.read())
975 def do_defconfig(self):
976 """Run 'make <board>_defconfig' to create the .config file."""
978 cmd = list(self.make_cmd)
979 cmd.append(self.defconfig)
980 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
981 stderr=subprocess.PIPE,
982 cwd=self.current_src_dir)
983 self.state = STATE_DEFCONFIG
985 def do_autoconf(self):
986 """Run 'make include/config/auto.conf'."""
988 self.cross_compile = self.parser.get_cross_compile()
989 if self.cross_compile is None:
990 self.log += color_text(self.options.color, COLOR_YELLOW,
991 "Compiler is missing. Do nothing.\n")
995 cmd = list(self.make_cmd)
996 if self.cross_compile:
997 cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
998 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
999 cmd.append('include/config/auto.conf')
1000 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1001 stderr=subprocess.PIPE,
1002 cwd=self.current_src_dir)
1003 self.state = STATE_AUTOCONF
1005 def do_savedefconfig(self):
1006 """Update the .config and run 'make savedefconfig'."""
1008 (updated, suspicious, log) = self.parser.update_dotconfig()
1010 self.suspicious_boards.add(self.defconfig)
1013 if not self.options.force_sync and not updated:
1017 self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
1018 "Syncing by savedefconfig...\n")
1020 self.log += "Syncing by savedefconfig (forced by option)...\n"
1022 cmd = list(self.make_cmd)
1023 cmd.append('savedefconfig')
1024 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1025 stderr=subprocess.PIPE)
1026 self.state = STATE_SAVEDEFCONFIG
1028 def update_defconfig(self):
1029 """Update the input defconfig and go back to the idle state."""
1031 log = self.parser.check_defconfig()
1033 self.suspicious_boards.add(self.defconfig)
1035 orig_defconfig = os.path.join('configs', self.defconfig)
1036 new_defconfig = os.path.join(self.build_dir, 'defconfig')
1037 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
1040 self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
1041 "defconfig was updated.\n")
1043 if not self.options.dry_run and updated:
1044 shutil.move(new_defconfig, orig_defconfig)
1047 def finish(self, success):
1048 """Display log along with progress and go to the idle state.
1051 success: Should be True when the defconfig was processed
1052 successfully, or False when it fails.
1054 # output at least 30 characters to hide the "* defconfigs out of *".
1055 log = self.defconfig.ljust(30) + '\n'
1057 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ])
1058 # Some threads are running in parallel.
1059 # Print log atomically to not mix up logs from different threads.
1060 print >> (sys.stdout if success else sys.stderr), log
1063 if self.options.exit_on_error:
1064 sys.exit("Exit on error.")
1065 # If --exit-on-error flag is not set, skip this board and continue.
1066 # Record the failed board.
1067 self.failed_boards.add(self.defconfig)
1070 self.progress.show()
1071 self.state = STATE_IDLE
1073 def get_failed_boards(self):
1074 """Returns a set of failed boards (defconfigs) in this slot.
1076 return self.failed_boards
1078 def get_suspicious_boards(self):
1079 """Returns a set of boards (defconfigs) with possible misconversion.
1081 return self.suspicious_boards - self.failed_boards
1085 """Controller of the array of subprocess slots."""
1087 def __init__(self, configs, options, progress, reference_src_dir):
1088 """Create a new slots controller.
1091 configs: A list of CONFIGs to move.
1092 options: option flags.
1093 progress: A progress indicator.
1094 reference_src_dir: Determine the true starting config state from this
1097 self.options = options
1099 devnull = get_devnull()
1100 make_cmd = get_make_cmd()
1101 for i in range(options.jobs):
1102 self.slots.append(Slot(configs, options, progress, devnull,
1103 make_cmd, reference_src_dir))
1105 def add(self, defconfig):
1106 """Add a new subprocess if a vacant slot is found.
1109 defconfig: defconfig name to be put into.
1112 Return True on success or False on failure
1114 for slot in self.slots:
1115 if slot.add(defconfig):
1119 def available(self):
1120 """Check if there is a vacant slot.
1123 Return True if at lease one vacant slot is found, False otherwise.
1125 for slot in self.slots:
1131 """Check if all slots are vacant.
1134 Return True if all the slots are vacant, False otherwise.
1137 for slot in self.slots:
1142 def show_failed_boards(self):
1143 """Display all of the failed boards (defconfigs)."""
1145 output_file = 'moveconfig.failed'
1147 for slot in self.slots:
1148 boards |= slot.get_failed_boards()
1151 boards = '\n'.join(boards) + '\n'
1152 msg = "The following boards were not processed due to error:\n"
1154 msg += "(the list has been saved in %s)\n" % output_file
1155 print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
1158 with open(output_file, 'w') as f:
1161 def show_suspicious_boards(self):
1162 """Display all boards (defconfigs) with possible misconversion."""
1164 output_file = 'moveconfig.suspicious'
1166 for slot in self.slots:
1167 boards |= slot.get_suspicious_boards()
1170 boards = '\n'.join(boards) + '\n'
1171 msg = "The following boards might have been converted incorrectly.\n"
1172 msg += "It is highly recommended to check them manually:\n"
1174 msg += "(the list has been saved in %s)\n" % output_file
1175 print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
1178 with open(output_file, 'w') as f:
1181 class ReferenceSource:
1183 """Reference source against which original configs should be parsed."""
1185 def __init__(self, commit):
1186 """Create a reference source directory based on a specified commit.
1189 commit: commit to git-clone
1191 self.src_dir = tempfile.mkdtemp()
1192 print "Cloning git repo to a separate work directory..."
1193 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1195 print "Checkout '%s' to build the original autoconf.mk." % \
1196 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
1197 subprocess.check_output(['git', 'checkout', commit],
1198 stderr=subprocess.STDOUT, cwd=self.src_dir)
1201 """Delete the reference source directory
1203 This function makes sure the temporary directory is cleaned away
1204 even if Python suddenly dies due to error. It should be done in here
1205 because it is guaranteed the destructor is always invoked when the
1206 instance of the class gets unreferenced.
1208 shutil.rmtree(self.src_dir)
1211 """Return the absolute path to the reference source directory."""
1215 def move_config(configs, options):
1216 """Move config options to defconfig files.
1219 configs: A list of CONFIGs to move.
1220 options: option flags
1222 if len(configs) == 0:
1223 if options.force_sync:
1224 print 'No CONFIG is specified. You are probably syncing defconfigs.',
1226 print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
1228 print 'Move ' + ', '.join(configs),
1229 print '(jobs: %d)\n' % options.jobs
1232 reference_src = ReferenceSource(options.git_ref)
1233 reference_src_dir = reference_src.get_dir()
1235 reference_src_dir = None
1237 if options.defconfigs:
1238 defconfigs = get_matched_defconfigs(options.defconfigs)
1240 defconfigs = get_all_defconfigs()
1242 progress = Progress(len(defconfigs))
1243 slots = Slots(configs, options, progress, reference_src_dir)
1245 # Main loop to process defconfig files:
1246 # Add a new subprocess into a vacant slot.
1247 # Sleep if there is no available slot.
1248 for defconfig in defconfigs:
1249 while not slots.add(defconfig):
1250 while not slots.available():
1251 # No available slot: sleep for a while
1252 time.sleep(SLEEP_TIME)
1254 # wait until all the subprocesses finish
1255 while not slots.empty():
1256 time.sleep(SLEEP_TIME)
1259 slots.show_failed_boards()
1260 slots.show_suspicious_boards()
1264 cpu_count = multiprocessing.cpu_count()
1265 except NotImplementedError:
1268 parser = optparse.OptionParser()
1270 parser.add_option('-c', '--color', action='store_true', default=False,
1271 help='display the log in color')
1272 parser.add_option('-C', '--commit', action='store_true', default=False,
1273 help='Create a git commit for the operation')
1274 parser.add_option('-d', '--defconfigs', type='string',
1275 help='a file containing a list of defconfigs to move')
1276 parser.add_option('-n', '--dry-run', action='store_true', default=False,
1277 help='perform a trial run (show log with no changes)')
1278 parser.add_option('-e', '--exit-on-error', action='store_true',
1280 help='exit immediately on any error')
1281 parser.add_option('-s', '--force-sync', action='store_true', default=False,
1282 help='force sync by savedefconfig')
1283 parser.add_option('-S', '--spl', action='store_true', default=False,
1284 help='parse config options defined for SPL build')
1285 parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1286 action='store_true', default=False,
1287 help='only cleanup the headers')
1288 parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1289 help='the number of jobs to run simultaneously')
1290 parser.add_option('-r', '--git-ref', type='string',
1291 help='the git ref to clone for building the autoconf.mk')
1292 parser.add_option('-y', '--yes', action='store_true', default=False,
1293 help="respond 'yes' to any prompts")
1294 parser.add_option('-v', '--verbose', action='store_true', default=False,
1295 help='show any build errors as boards are built')
1296 parser.usage += ' CONFIG ...'
1298 (options, configs) = parser.parse_args()
1300 if len(configs) == 0 and not options.force_sync:
1301 parser.print_usage()
1304 # prefix the option name with CONFIG_ if missing
1305 configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1306 for config in configs ]
1308 check_top_directory()
1310 if not options.cleanup_headers_only:
1311 check_clean_directory()
1312 update_cross_compile(options.color)
1313 move_config(configs, options)
1316 cleanup_headers(configs, options)
1317 cleanup_extra_options(configs, options)
1318 cleanup_whitelist(configs, options)
1321 subprocess.call(['git', 'add', '-u'])
1323 msg = 'Convert %s %sto Kconfig' % (configs[0],
1324 'et al ' if len(configs) > 1 else '')
1325 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1326 '\n '.join(configs))
1328 msg = 'configs: Resync with savedefconfig'
1329 msg += '\n\nRsync all defconfig files using moveconfig.py'
1330 subprocess.call(['git', 'commit', '-s', '-m', msg])
1332 if __name__ == '__main__':