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 'm68k': 'm68k-linux-',
210 'microblaze': 'microblaze-linux-',
211 'mips': 'mips-linux-',
212 'nds32': 'nds32le-linux-',
213 'nios2': 'nios2-linux-gnu-',
214 'powerpc': 'powerpc-linux-',
215 'sh': 'sh-linux-gnu-',
216 'x86': 'i386-linux-',
217 'xtensa': 'xtensa-linux-'
223 STATE_SAVEDEFCONFIG = 3
227 ACTION_NO_ENTRY_WARN = 2
235 COLOR_PURPLE = '0;35'
237 COLOR_LIGHT_GRAY = '0;37'
238 COLOR_DARK_GRAY = '1;30'
239 COLOR_LIGHT_RED = '1;31'
240 COLOR_LIGHT_GREEN = '1;32'
241 COLOR_YELLOW = '1;33'
242 COLOR_LIGHT_BLUE = '1;34'
243 COLOR_LIGHT_PURPLE = '1;35'
244 COLOR_LIGHT_CYAN = '1;36'
247 ### helper functions ###
249 """Get the file object of '/dev/null' device."""
251 devnull = subprocess.DEVNULL # py3k
252 except AttributeError:
253 devnull = open(os.devnull, 'wb')
256 def check_top_directory():
257 """Exit if we are not at the top of source directory."""
258 for f in ('README', 'Licenses'):
259 if not os.path.exists(f):
260 sys.exit('Please run at the top of source directory.')
262 def check_clean_directory():
263 """Exit if the source tree is not clean."""
264 for f in ('.config', 'include/config'):
265 if os.path.exists(f):
266 sys.exit("source tree is not clean, please run 'make mrproper'")
269 """Get the command name of GNU Make.
271 U-Boot needs GNU Make for building, but the command name is not
272 necessarily "make". (for example, "gmake" on FreeBSD).
273 Returns the most appropriate command name on your system.
275 process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
276 ret = process.communicate()
277 if process.returncode:
278 sys.exit('GNU Make not found')
279 return ret[0].rstrip()
281 def get_matched_defconfig(line):
282 """Get the defconfig files that match a pattern
285 line: Path or filename to match, e.g. 'configs/snow_defconfig' or
286 'k2*_defconfig'. If no directory is provided, 'configs/' is
290 a list of matching defconfig files
292 dirname = os.path.dirname(line)
296 pattern = os.path.join('configs', line)
297 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
299 def get_matched_defconfigs(defconfigs_file):
300 """Get all the defconfig files that match the patterns in a file.
303 defconfigs_file: File containing a list of defconfigs to process, or
304 '-' to read the list from stdin
307 A list of paths to defconfig files, with no duplicates
310 if defconfigs_file == '-':
312 defconfigs_file = 'stdin'
314 fd = open(defconfigs_file)
315 for i, line in enumerate(fd):
318 continue # skip blank lines silently
319 matched = get_matched_defconfig(line)
321 print >> sys.stderr, "warning: %s:%d: no defconfig matched '%s'" % \
322 (defconfigs_file, i + 1, line)
324 defconfigs += matched
326 # use set() to drop multiple matching
327 return [ defconfig[len('configs') + 1:] for defconfig in set(defconfigs) ]
329 def get_all_defconfigs():
330 """Get all the defconfig files under the configs/ directory."""
332 for (dirpath, dirnames, filenames) in os.walk('configs'):
333 dirpath = dirpath[len('configs') + 1:]
334 for filename in fnmatch.filter(filenames, '*_defconfig'):
335 defconfigs.append(os.path.join(dirpath, filename))
339 def color_text(color_enabled, color, string):
340 """Return colored string."""
342 # LF should not be surrounded by the escape sequence.
343 # Otherwise, additional whitespace or line-feed might be printed.
344 return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
345 for s in string.split('\n') ])
349 def show_diff(a, b, file_path, color_enabled):
350 """Show unidified diff.
353 a: A list of lines (before)
354 b: A list of lines (after)
355 file_path: Path to the file
356 color_enabled: Display the diff in color
359 diff = difflib.unified_diff(a, b,
360 fromfile=os.path.join('a', file_path),
361 tofile=os.path.join('b', file_path))
364 if line[0] == '-' and line[1] != '-':
365 print color_text(color_enabled, COLOR_RED, line),
366 elif line[0] == '+' and line[1] != '+':
367 print color_text(color_enabled, COLOR_GREEN, line),
371 def update_cross_compile(color_enabled):
372 """Update per-arch CROSS_COMPILE via environment variables
374 The default CROSS_COMPILE values are available
375 in the CROSS_COMPILE list above.
377 You can override them via environment variables
378 CROSS_COMPILE_{ARCH}.
380 For example, if you want to override toolchain prefixes
381 for ARM and PowerPC, you can do as follows in your shell:
383 export CROSS_COMPILE_ARM=...
384 export CROSS_COMPILE_POWERPC=...
386 Then, this function checks if specified compilers really exist in your
391 for arch in os.listdir('arch'):
392 if os.path.exists(os.path.join('arch', arch, 'Makefile')):
395 # arm64 is a special case
396 archs.append('aarch64')
399 env = 'CROSS_COMPILE_' + arch.upper()
400 cross_compile = os.environ.get(env)
401 if not cross_compile:
402 cross_compile = CROSS_COMPILE.get(arch, '')
404 for path in os.environ["PATH"].split(os.pathsep):
405 gcc_path = os.path.join(path, cross_compile + 'gcc')
406 if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
409 print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
410 'warning: %sgcc: not found in PATH. %s architecture boards will be skipped'
411 % (cross_compile, arch))
414 CROSS_COMPILE[arch] = cross_compile
416 def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre,
418 """Extend matched lines if desired patterns are found before/after already
422 lines: A list of lines handled.
423 matched: A list of line numbers that have been already matched.
424 (will be updated by this function)
425 pre_patterns: A list of regular expression that should be matched as
427 post_patterns: A list of regular expression that should be matched as
429 extend_pre: Add the line number of matched preamble to the matched list.
430 extend_post: Add the line number of matched postamble to the matched list.
432 extended_matched = []
445 for p in pre_patterns:
446 if p.search(lines[i - 1]):
452 for p in post_patterns:
453 if p.search(lines[j]):
460 extended_matched.append(i - 1)
462 extended_matched.append(j)
464 matched += extended_matched
467 def confirm(options, prompt):
470 choice = raw_input('{} [y/n]: '.format(prompt))
471 choice = choice.lower()
473 if choice == 'y' or choice == 'n':
481 def cleanup_one_header(header_path, patterns, options):
482 """Clean regex-matched lines away from a file.
485 header_path: path to the cleaned file.
486 patterns: list of regex patterns. Any lines matching to these
487 patterns are deleted.
488 options: option flags.
490 with open(header_path) as f:
491 lines = f.readlines()
494 for i, line in enumerate(lines):
495 if i - 1 in matched and lines[i - 1][-2:] == '\\\n':
498 for pattern in patterns:
499 if pattern.search(line):
506 # remove empty #ifdef ... #endif, successive blank lines
507 pattern_if = re.compile(r'#\s*if(def|ndef)?\W') # #if, #ifdef, #ifndef
508 pattern_elif = re.compile(r'#\s*el(if|se)\W') # #elif, #else
509 pattern_endif = re.compile(r'#\s*endif\W') # #endif
510 pattern_blank = re.compile(r'^\s*$') # empty line
513 old_matched = copy.copy(matched)
514 extend_matched_lines(lines, matched, [pattern_if],
515 [pattern_endif], True, True)
516 extend_matched_lines(lines, matched, [pattern_elif],
517 [pattern_elif, pattern_endif], True, False)
518 extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
519 [pattern_blank], False, True)
520 extend_matched_lines(lines, matched, [pattern_blank],
521 [pattern_elif, pattern_endif], True, False)
522 extend_matched_lines(lines, matched, [pattern_blank],
523 [pattern_blank], True, False)
524 if matched == old_matched:
527 tolines = copy.copy(lines)
529 for i in reversed(matched):
532 show_diff(lines, tolines, header_path, options.color)
537 with open(header_path, 'w') as f:
541 def cleanup_headers(configs, options):
542 """Delete config defines from board headers.
545 configs: A list of CONFIGs to remove.
546 options: option flags.
548 if not confirm(options, 'Clean up headers?'):
552 for config in configs:
553 patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
554 patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
556 for dir in 'include', 'arch', 'board':
557 for (dirpath, dirnames, filenames) in os.walk(dir):
558 if dirpath == os.path.join('include', 'generated'):
560 for filename in filenames:
561 if not fnmatch.fnmatch(filename, '*~'):
562 cleanup_one_header(os.path.join(dirpath, filename),
565 def cleanup_one_extra_option(defconfig_path, configs, options):
566 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
569 defconfig_path: path to the cleaned defconfig file.
570 configs: A list of CONFIGs to remove.
571 options: option flags.
574 start = 'CONFIG_SYS_EXTRA_OPTIONS="'
577 with open(defconfig_path) as f:
578 lines = f.readlines()
580 for i, line in enumerate(lines):
581 if line.startswith(start) and line.endswith(end):
584 # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
587 old_tokens = line[len(start):-len(end)].split(',')
590 for token in old_tokens:
591 pos = token.find('=')
592 if not (token[:pos] if pos >= 0 else token) in configs:
593 new_tokens.append(token)
595 if new_tokens == old_tokens:
598 tolines = copy.copy(lines)
601 tolines[i] = start + ','.join(new_tokens) + end
605 show_diff(lines, tolines, defconfig_path, options.color)
610 with open(defconfig_path, 'w') as f:
614 def cleanup_extra_options(configs, options):
615 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
618 configs: A list of CONFIGs to remove.
619 options: option flags.
621 if not confirm(options, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'):
624 configs = [ config[len('CONFIG_'):] for config in configs ]
626 defconfigs = get_all_defconfigs()
628 for defconfig in defconfigs:
629 cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
632 def cleanup_whitelist(configs, options):
633 """Delete config whitelist entries
636 configs: A list of CONFIGs to remove.
637 options: option flags.
639 if not confirm(options, 'Clean up whitelist entries?'):
642 with open(os.path.join('scripts', 'config_whitelist.txt')) as f:
643 lines = f.readlines()
645 lines = [x for x in lines if x.strip() not in configs]
647 with open(os.path.join('scripts', 'config_whitelist.txt'), 'w') as f:
648 f.write(''.join(lines))
650 def find_matching(patterns, line):
656 def cleanup_readme(configs, options):
657 """Delete config description in README
660 configs: A list of CONFIGs to remove.
661 options: option flags.
663 if not confirm(options, 'Clean up README?'):
667 for config in configs:
668 patterns.append(re.compile(r'^\s+%s' % config))
670 with open('README') as f:
671 lines = f.readlines()
677 found = find_matching(patterns, line)
681 if found and re.search(r'^\s+CONFIG', line):
685 newlines.append(line)
687 with open('README', 'w') as f:
688 f.write(''.join(newlines))
694 """Progress Indicator"""
696 def __init__(self, total):
697 """Create a new progress indicator.
700 total: A number of defconfig files to process.
706 """Increment the number of processed defconfig files."""
711 """Display the progress."""
712 print ' %d defconfigs out of %d\r' % (self.current, self.total),
717 """A parser of .config and include/autoconf.mk."""
719 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
720 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
722 def __init__(self, configs, options, build_dir):
723 """Create a new parser.
726 configs: A list of CONFIGs to move.
727 options: option flags.
728 build_dir: Build directory.
730 self.configs = configs
731 self.options = options
732 self.dotconfig = os.path.join(build_dir, '.config')
733 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
734 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
736 self.config_autoconf = os.path.join(build_dir, 'include', 'config',
738 self.defconfig = os.path.join(build_dir, 'defconfig')
740 def get_cross_compile(self):
741 """Parse .config file and return CROSS_COMPILE.
744 A string storing the compiler prefix for the architecture.
745 Return a NULL string for architectures that do not require
746 compiler prefix (Sandbox and native build is the case).
747 Return None if the specified compiler is missing in your PATH.
748 Caller should distinguish '' and None.
752 for line in open(self.dotconfig):
753 m = self.re_arch.match(line)
757 m = self.re_cpu.match(line)
765 if arch == 'arm' and cpu == 'armv8':
768 return CROSS_COMPILE.get(arch, None)
770 def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
771 """Parse .config, defconfig, include/autoconf.mk for one config.
773 This function looks for the config options in the lines from
774 defconfig, .config, and include/autoconf.mk in order to decide
775 which action should be taken for this defconfig.
778 config: CONFIG name to parse.
779 dotconfig_lines: lines from the .config file.
780 autoconf_lines: lines from the include/autoconf.mk file.
783 A tupple of the action for this defconfig and the line
784 matched for the config.
786 not_set = '# %s is not set' % config
788 for line in autoconf_lines:
790 if line.startswith(config + '='):
796 for line in dotconfig_lines:
798 if line.startswith(config + '=') or line == not_set:
802 if new_val == not_set:
803 return (ACTION_NO_ENTRY, config)
805 return (ACTION_NO_ENTRY_WARN, config)
807 # If this CONFIG is neither bool nor trisate
808 if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
809 # tools/scripts/define2mk.sed changes '1' to 'y'.
810 # This is a problem if the CONFIG is int type.
811 # Check the type in Kconfig and handle it correctly.
812 if new_val[-2:] == '=y':
813 new_val = new_val[:-1] + '1'
815 return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
818 def update_dotconfig(self):
819 """Parse files for the config options and update the .config.
821 This function parses the generated .config and include/autoconf.mk
822 searching the target options.
823 Move the config option(s) to the .config as needed.
826 defconfig: defconfig name.
829 Return a tuple of (updated flag, log string).
830 The "updated flag" is True if the .config was updated, False
831 otherwise. The "log string" shows what happend to the .config.
837 rm_files = [self.config_autoconf, self.autoconf]
840 if os.path.exists(self.spl_autoconf):
841 autoconf_path = self.spl_autoconf
842 rm_files.append(self.spl_autoconf)
846 return (updated, suspicious,
847 color_text(self.options.color, COLOR_BROWN,
848 "SPL is not enabled. Skipped.") + '\n')
850 autoconf_path = self.autoconf
852 with open(self.dotconfig) as f:
853 dotconfig_lines = f.readlines()
855 with open(autoconf_path) as f:
856 autoconf_lines = f.readlines()
858 for config in self.configs:
859 result = self.parse_one_config(config, dotconfig_lines,
861 results.append(result)
865 for (action, value) in results:
866 if action == ACTION_MOVE:
867 actlog = "Move '%s'" % value
868 log_color = COLOR_LIGHT_GREEN
869 elif action == ACTION_NO_ENTRY:
870 actlog = "%s is not defined in Kconfig. Do nothing." % value
871 log_color = COLOR_LIGHT_BLUE
872 elif action == ACTION_NO_ENTRY_WARN:
873 actlog = "%s is not defined in Kconfig (suspicious). Do nothing." % value
874 log_color = COLOR_YELLOW
876 elif action == ACTION_NO_CHANGE:
877 actlog = "'%s' is the same as the define in Kconfig. Do nothing." \
879 log_color = COLOR_LIGHT_PURPLE
880 elif action == ACTION_SPL_NOT_EXIST:
881 actlog = "SPL is not enabled for this defconfig. Skip."
882 log_color = COLOR_PURPLE
884 sys.exit("Internal Error. This should not happen.")
886 log += color_text(self.options.color, log_color, actlog) + '\n'
888 with open(self.dotconfig, 'a') as f:
889 for (action, value) in results:
890 if action == ACTION_MOVE:
891 f.write(value + '\n')
894 self.results = results
898 return (updated, suspicious, log)
900 def check_defconfig(self):
901 """Check the defconfig after savedefconfig
904 Return additional log if moved CONFIGs were removed again by
905 'make savedefconfig'.
910 with open(self.defconfig) as f:
911 defconfig_lines = f.readlines()
913 for (action, value) in self.results:
914 if action != ACTION_MOVE:
916 if not value + '\n' in defconfig_lines:
917 log += color_text(self.options.color, COLOR_YELLOW,
918 "'%s' was removed by savedefconfig.\n" %
925 """A slot to store a subprocess.
927 Each instance of this class handles one subprocess.
928 This class is useful to control multiple threads
929 for faster processing.
932 def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir):
933 """Create a new process slot.
936 configs: A list of CONFIGs to move.
937 options: option flags.
938 progress: A progress indicator.
939 devnull: A file object of '/dev/null'.
940 make_cmd: command name of GNU Make.
941 reference_src_dir: Determine the true starting config state from this
944 self.options = options
945 self.progress = progress
946 self.build_dir = tempfile.mkdtemp()
947 self.devnull = devnull
948 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
949 self.reference_src_dir = reference_src_dir
950 self.parser = KconfigParser(configs, options, self.build_dir)
951 self.state = STATE_IDLE
952 self.failed_boards = set()
953 self.suspicious_boards = set()
956 """Delete the working directory
958 This function makes sure the temporary directory is cleaned away
959 even if Python suddenly dies due to error. It should be done in here
960 because it is guaranteed the destructor is always invoked when the
961 instance of the class gets unreferenced.
963 If the subprocess is still running, wait until it finishes.
965 if self.state != STATE_IDLE:
966 while self.ps.poll() == None:
968 shutil.rmtree(self.build_dir)
970 def add(self, defconfig):
971 """Assign a new subprocess for defconfig and add it to the slot.
973 If the slot is vacant, create a new subprocess for processing the
974 given defconfig and add it to the slot. Just returns False if
975 the slot is occupied (i.e. the current subprocess is still running).
978 defconfig: defconfig name.
981 Return True on success or False on failure
983 if self.state != STATE_IDLE:
986 self.defconfig = defconfig
988 self.current_src_dir = self.reference_src_dir
993 """Check the status of the subprocess and handle it as needed.
995 Returns True if the slot is vacant (i.e. in idle state).
996 If the configuration is successfully finished, assign a new
997 subprocess to build include/autoconf.mk.
998 If include/autoconf.mk is generated, invoke the parser to
999 parse the .config and the include/autoconf.mk, moving
1000 config options to the .config as needed.
1001 If the .config was updated, run "make savedefconfig" to sync
1002 it, update the original defconfig, and then set the slot back
1006 Return True if the subprocess is terminated, False otherwise
1008 if self.state == STATE_IDLE:
1011 if self.ps.poll() == None:
1014 if self.ps.poll() != 0:
1016 elif self.state == STATE_DEFCONFIG:
1017 if self.reference_src_dir and not self.current_src_dir:
1018 self.do_savedefconfig()
1021 elif self.state == STATE_AUTOCONF:
1022 if self.current_src_dir:
1023 self.current_src_dir = None
1026 self.do_savedefconfig()
1027 elif self.state == STATE_SAVEDEFCONFIG:
1028 self.update_defconfig()
1030 sys.exit("Internal Error. This should not happen.")
1032 return True if self.state == STATE_IDLE else False
1034 def handle_error(self):
1035 """Handle error cases."""
1037 self.log += color_text(self.options.color, COLOR_LIGHT_RED,
1038 "Failed to process.\n")
1039 if self.options.verbose:
1040 self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
1041 self.ps.stderr.read())
1044 def do_defconfig(self):
1045 """Run 'make <board>_defconfig' to create the .config file."""
1047 cmd = list(self.make_cmd)
1048 cmd.append(self.defconfig)
1049 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1050 stderr=subprocess.PIPE,
1051 cwd=self.current_src_dir)
1052 self.state = STATE_DEFCONFIG
1054 def do_autoconf(self):
1055 """Run 'make include/config/auto.conf'."""
1057 self.cross_compile = self.parser.get_cross_compile()
1058 if self.cross_compile is None:
1059 self.log += color_text(self.options.color, COLOR_YELLOW,
1060 "Compiler is missing. Do nothing.\n")
1064 cmd = list(self.make_cmd)
1065 if self.cross_compile:
1066 cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
1067 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
1068 cmd.append('include/config/auto.conf')
1069 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1070 stderr=subprocess.PIPE,
1071 cwd=self.current_src_dir)
1072 self.state = STATE_AUTOCONF
1074 def do_savedefconfig(self):
1075 """Update the .config and run 'make savedefconfig'."""
1077 (updated, suspicious, log) = self.parser.update_dotconfig()
1079 self.suspicious_boards.add(self.defconfig)
1082 if not self.options.force_sync and not updated:
1086 self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
1087 "Syncing by savedefconfig...\n")
1089 self.log += "Syncing by savedefconfig (forced by option)...\n"
1091 cmd = list(self.make_cmd)
1092 cmd.append('savedefconfig')
1093 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1094 stderr=subprocess.PIPE)
1095 self.state = STATE_SAVEDEFCONFIG
1097 def update_defconfig(self):
1098 """Update the input defconfig and go back to the idle state."""
1100 log = self.parser.check_defconfig()
1102 self.suspicious_boards.add(self.defconfig)
1104 orig_defconfig = os.path.join('configs', self.defconfig)
1105 new_defconfig = os.path.join(self.build_dir, 'defconfig')
1106 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
1109 self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
1110 "defconfig was updated.\n")
1112 if not self.options.dry_run and updated:
1113 shutil.move(new_defconfig, orig_defconfig)
1116 def finish(self, success):
1117 """Display log along with progress and go to the idle state.
1120 success: Should be True when the defconfig was processed
1121 successfully, or False when it fails.
1123 # output at least 30 characters to hide the "* defconfigs out of *".
1124 log = self.defconfig.ljust(30) + '\n'
1126 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ])
1127 # Some threads are running in parallel.
1128 # Print log atomically to not mix up logs from different threads.
1129 print >> (sys.stdout if success else sys.stderr), log
1132 if self.options.exit_on_error:
1133 sys.exit("Exit on error.")
1134 # If --exit-on-error flag is not set, skip this board and continue.
1135 # Record the failed board.
1136 self.failed_boards.add(self.defconfig)
1139 self.progress.show()
1140 self.state = STATE_IDLE
1142 def get_failed_boards(self):
1143 """Returns a set of failed boards (defconfigs) in this slot.
1145 return self.failed_boards
1147 def get_suspicious_boards(self):
1148 """Returns a set of boards (defconfigs) with possible misconversion.
1150 return self.suspicious_boards - self.failed_boards
1154 """Controller of the array of subprocess slots."""
1156 def __init__(self, configs, options, progress, reference_src_dir):
1157 """Create a new slots controller.
1160 configs: A list of CONFIGs to move.
1161 options: option flags.
1162 progress: A progress indicator.
1163 reference_src_dir: Determine the true starting config state from this
1166 self.options = options
1168 devnull = get_devnull()
1169 make_cmd = get_make_cmd()
1170 for i in range(options.jobs):
1171 self.slots.append(Slot(configs, options, progress, devnull,
1172 make_cmd, reference_src_dir))
1174 def add(self, defconfig):
1175 """Add a new subprocess if a vacant slot is found.
1178 defconfig: defconfig name to be put into.
1181 Return True on success or False on failure
1183 for slot in self.slots:
1184 if slot.add(defconfig):
1188 def available(self):
1189 """Check if there is a vacant slot.
1192 Return True if at lease one vacant slot is found, False otherwise.
1194 for slot in self.slots:
1200 """Check if all slots are vacant.
1203 Return True if all the slots are vacant, False otherwise.
1206 for slot in self.slots:
1211 def show_failed_boards(self):
1212 """Display all of the failed boards (defconfigs)."""
1214 output_file = 'moveconfig.failed'
1216 for slot in self.slots:
1217 boards |= slot.get_failed_boards()
1220 boards = '\n'.join(boards) + '\n'
1221 msg = "The following boards were not processed due to error:\n"
1223 msg += "(the list has been saved in %s)\n" % output_file
1224 print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
1227 with open(output_file, 'w') as f:
1230 def show_suspicious_boards(self):
1231 """Display all boards (defconfigs) with possible misconversion."""
1233 output_file = 'moveconfig.suspicious'
1235 for slot in self.slots:
1236 boards |= slot.get_suspicious_boards()
1239 boards = '\n'.join(boards) + '\n'
1240 msg = "The following boards might have been converted incorrectly.\n"
1241 msg += "It is highly recommended to check them manually:\n"
1243 msg += "(the list has been saved in %s)\n" % output_file
1244 print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
1247 with open(output_file, 'w') as f:
1250 class ReferenceSource:
1252 """Reference source against which original configs should be parsed."""
1254 def __init__(self, commit):
1255 """Create a reference source directory based on a specified commit.
1258 commit: commit to git-clone
1260 self.src_dir = tempfile.mkdtemp()
1261 print "Cloning git repo to a separate work directory..."
1262 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1264 print "Checkout '%s' to build the original autoconf.mk." % \
1265 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
1266 subprocess.check_output(['git', 'checkout', commit],
1267 stderr=subprocess.STDOUT, cwd=self.src_dir)
1270 """Delete the reference source directory
1272 This function makes sure the temporary directory is cleaned away
1273 even if Python suddenly dies due to error. It should be done in here
1274 because it is guaranteed the destructor is always invoked when the
1275 instance of the class gets unreferenced.
1277 shutil.rmtree(self.src_dir)
1280 """Return the absolute path to the reference source directory."""
1284 def move_config(configs, options):
1285 """Move config options to defconfig files.
1288 configs: A list of CONFIGs to move.
1289 options: option flags
1291 if len(configs) == 0:
1292 if options.force_sync:
1293 print 'No CONFIG is specified. You are probably syncing defconfigs.',
1295 print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
1297 print 'Move ' + ', '.join(configs),
1298 print '(jobs: %d)\n' % options.jobs
1301 reference_src = ReferenceSource(options.git_ref)
1302 reference_src_dir = reference_src.get_dir()
1304 reference_src_dir = None
1306 if options.defconfigs:
1307 defconfigs = get_matched_defconfigs(options.defconfigs)
1309 defconfigs = get_all_defconfigs()
1311 progress = Progress(len(defconfigs))
1312 slots = Slots(configs, options, progress, reference_src_dir)
1314 # Main loop to process defconfig files:
1315 # Add a new subprocess into a vacant slot.
1316 # Sleep if there is no available slot.
1317 for defconfig in defconfigs:
1318 while not slots.add(defconfig):
1319 while not slots.available():
1320 # No available slot: sleep for a while
1321 time.sleep(SLEEP_TIME)
1323 # wait until all the subprocesses finish
1324 while not slots.empty():
1325 time.sleep(SLEEP_TIME)
1328 slots.show_failed_boards()
1329 slots.show_suspicious_boards()
1333 cpu_count = multiprocessing.cpu_count()
1334 except NotImplementedError:
1337 parser = optparse.OptionParser()
1339 parser.add_option('-c', '--color', action='store_true', default=False,
1340 help='display the log in color')
1341 parser.add_option('-C', '--commit', action='store_true', default=False,
1342 help='Create a git commit for the operation')
1343 parser.add_option('-d', '--defconfigs', type='string',
1344 help='a file containing a list of defconfigs to move, '
1345 "one per line (for example 'snow_defconfig') "
1346 "or '-' to read from stdin")
1347 parser.add_option('-n', '--dry-run', action='store_true', default=False,
1348 help='perform a trial run (show log with no changes)')
1349 parser.add_option('-e', '--exit-on-error', action='store_true',
1351 help='exit immediately on any error')
1352 parser.add_option('-s', '--force-sync', action='store_true', default=False,
1353 help='force sync by savedefconfig')
1354 parser.add_option('-S', '--spl', action='store_true', default=False,
1355 help='parse config options defined for SPL build')
1356 parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1357 action='store_true', default=False,
1358 help='only cleanup the headers')
1359 parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1360 help='the number of jobs to run simultaneously')
1361 parser.add_option('-r', '--git-ref', type='string',
1362 help='the git ref to clone for building the autoconf.mk')
1363 parser.add_option('-y', '--yes', action='store_true', default=False,
1364 help="respond 'yes' to any prompts")
1365 parser.add_option('-v', '--verbose', action='store_true', default=False,
1366 help='show any build errors as boards are built')
1367 parser.usage += ' CONFIG ...'
1369 (options, configs) = parser.parse_args()
1371 if len(configs) == 0 and not options.force_sync:
1372 parser.print_usage()
1375 # prefix the option name with CONFIG_ if missing
1376 configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1377 for config in configs ]
1379 check_top_directory()
1381 if not options.cleanup_headers_only:
1382 check_clean_directory()
1383 update_cross_compile(options.color)
1384 move_config(configs, options)
1387 cleanup_headers(configs, options)
1388 cleanup_extra_options(configs, options)
1389 cleanup_whitelist(configs, options)
1390 cleanup_readme(configs, options)
1393 subprocess.call(['git', 'add', '-u'])
1395 msg = 'Convert %s %sto Kconfig' % (configs[0],
1396 'et al ' if len(configs) > 1 else '')
1397 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1398 '\n '.join(configs))
1400 msg = 'configs: Resync with savedefconfig'
1401 msg += '\n\nRsync all defconfig files using moveconfig.py'
1402 subprocess.call(['git', 'commit', '-s', '-m', msg])
1404 if __name__ == '__main__':