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.
121 To sync only X86 defconfigs:
123 ./tools/moveconfig.py -s -d <(grep -l X86 configs/*)
127 grep -l X86 configs/* | ./tools/moveconfig.py -s -d -
129 To process CONFIG_CMD_FPGAD only for a subset of configs based on path match:
131 ls configs/{hrcon*,iocon*,strider*} | \
132 ./tools/moveconfig.py -Cy CONFIG_CMD_FPGAD -d -
135 Finding implied CONFIGs
136 -----------------------
138 Some CONFIG options can be implied by others and this can help to reduce
139 the size of the defconfig files. For example, CONFIG_X86 implies
140 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
141 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
142 each of the x86 defconfig files.
144 This tool can help find such configs. To use it, first build a database:
146 ./tools/moveconfig.py -b
148 Then try to query it:
150 ./tools/moveconfig.py -i CONFIG_CMD_IRQ
151 CONFIG_CMD_IRQ found in 311/2384 defconfigs
152 44 : CONFIG_SYS_FSL_ERRATUM_IFC_A002769
153 41 : CONFIG_SYS_FSL_ERRATUM_A007075
154 31 : CONFIG_SYS_FSL_DDR_VER_44
155 28 : CONFIG_ARCH_P1010
156 28 : CONFIG_SYS_FSL_ERRATUM_P1010_A003549
157 28 : CONFIG_SYS_FSL_ERRATUM_SEC_A003571
158 28 : CONFIG_SYS_FSL_ERRATUM_IFC_A003399
159 25 : CONFIG_SYS_FSL_ERRATUM_A008044
160 22 : CONFIG_ARCH_P1020
161 21 : CONFIG_SYS_FSL_DDR_VER_46
162 20 : CONFIG_MAX_PIRQ_LINKS
163 20 : CONFIG_HPET_ADDRESS
165 20 : CONFIG_PCIE_ECAM_SIZE
166 20 : CONFIG_IRQ_SLOT_COUNT
167 20 : CONFIG_I8259_PIC
168 20 : CONFIG_CPU_ADDR_BITS
170 20 : CONFIG_SYS_FSL_ERRATUM_A005871
171 20 : CONFIG_PCIE_ECAM_BASE
172 20 : CONFIG_X86_TSC_TIMER
173 20 : CONFIG_I8254_TIMER
174 20 : CONFIG_CMD_GETTIME
175 19 : CONFIG_SYS_FSL_ERRATUM_A005812
176 18 : CONFIG_X86_RUN_32BIT
177 17 : CONFIG_CMD_CHIP_CONFIG
180 This shows a list of config options which might imply CONFIG_CMD_EEPROM along
181 with how many defconfigs they cover. From this you can see that CONFIG_X86
182 implies CONFIG_CMD_EEPROM. Therefore, instead of adding CONFIG_CMD_EEPROM to
183 the defconfig of every x86 board, you could add a single imply line to the
187 bool "x86 architecture"
191 That will cover 20 defconfigs. Many of the options listed are not suitable as
192 they are not related. E.g. it would be odd for CONFIG_CMD_GETTIME to imply
195 Using this search you can reduce the size of moveconfig patches.
202 Surround each portion of the log with escape sequences to display it
203 in color on the terminal.
206 Create a git commit with the changes when the operation is complete. A
207 standard commit message is used which may need to be edited.
210 Specify a file containing a list of defconfigs to move. The defconfig
211 files can be given with shell-style wildcards. Use '-' to read from stdin.
214 Perform a trial run that does not make any changes. It is useful to
215 see what is going to happen before one actually runs it.
218 Exit immediately if Make exits with a non-zero status while processing
222 Do "make savedefconfig" forcibly for all the defconfig files.
223 If not specified, "make savedefconfig" only occurs for cases
224 where at least one CONFIG was moved.
227 Look for moved config options in spl/include/autoconf.mk instead of
228 include/autoconf.mk. This is useful for moving options for SPL build
229 because SPL related options (mostly prefixed with CONFIG_SPL_) are
230 sometimes blocked by CONFIG_SPL_BUILD ifdef conditionals.
233 Only cleanup the headers; skip the defconfig processing
236 Specify the number of threads to run simultaneously. If not specified,
237 the number of threads is the same as the number of CPU cores.
240 Specify the git ref to clone for building the autoconf.mk. If unspecified
241 use the CWD. This is useful for when changes to the Kconfig affect the
242 default values and you want to capture the state of the defconfig from
243 before that change was in effect. If in doubt, specify a ref pre-Kconfig
244 changes (use HEAD if Kconfig changes are not committed). Worst case it will
245 take a bit longer to run, but will always do the right thing.
248 Show any build errors as boards are built
251 Instead of prompting, automatically go ahead with all operations. This
252 includes cleaning up headers, CONFIG_SYS_EXTRA_OPTIONS, the config whitelist
255 To see the complete list of supported options, run
257 $ tools/moveconfig.py -h
267 import multiprocessing
279 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
282 # Here is the list of cross-tools I use.
283 # Most of them are available at kernel.org
284 # (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the following:
285 # arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases
286 # nds32: http://osdk.andestech.com/packages/nds32le-linux-glibc-v1.tgz
287 # nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545
288 # sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu
291 'aarch64': 'aarch64-linux-',
292 'arm': 'arm-unknown-linux-gnueabi-',
293 'm68k': 'm68k-linux-',
294 'microblaze': 'microblaze-linux-',
295 'mips': 'mips-linux-',
296 'nds32': 'nds32le-linux-',
297 'nios2': 'nios2-linux-gnu-',
298 'powerpc': 'powerpc-linux-',
299 'sh': 'sh-linux-gnu-',
300 'x86': 'i386-linux-',
301 'xtensa': 'xtensa-linux-'
307 STATE_SAVEDEFCONFIG = 3
311 ACTION_NO_ENTRY_WARN = 2
319 COLOR_PURPLE = '0;35'
321 COLOR_LIGHT_GRAY = '0;37'
322 COLOR_DARK_GRAY = '1;30'
323 COLOR_LIGHT_RED = '1;31'
324 COLOR_LIGHT_GREEN = '1;32'
325 COLOR_YELLOW = '1;33'
326 COLOR_LIGHT_BLUE = '1;34'
327 COLOR_LIGHT_PURPLE = '1;35'
328 COLOR_LIGHT_CYAN = '1;36'
331 AUTO_CONF_PATH = 'include/config/auto.conf'
332 CONFIG_DATABASE = 'moveconfig.db'
335 ### helper functions ###
337 """Get the file object of '/dev/null' device."""
339 devnull = subprocess.DEVNULL # py3k
340 except AttributeError:
341 devnull = open(os.devnull, 'wb')
344 def check_top_directory():
345 """Exit if we are not at the top of source directory."""
346 for f in ('README', 'Licenses'):
347 if not os.path.exists(f):
348 sys.exit('Please run at the top of source directory.')
350 def check_clean_directory():
351 """Exit if the source tree is not clean."""
352 for f in ('.config', 'include/config'):
353 if os.path.exists(f):
354 sys.exit("source tree is not clean, please run 'make mrproper'")
357 """Get the command name of GNU Make.
359 U-Boot needs GNU Make for building, but the command name is not
360 necessarily "make". (for example, "gmake" on FreeBSD).
361 Returns the most appropriate command name on your system.
363 process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
364 ret = process.communicate()
365 if process.returncode:
366 sys.exit('GNU Make not found')
367 return ret[0].rstrip()
369 def get_matched_defconfig(line):
370 """Get the defconfig files that match a pattern
373 line: Path or filename to match, e.g. 'configs/snow_defconfig' or
374 'k2*_defconfig'. If no directory is provided, 'configs/' is
378 a list of matching defconfig files
380 dirname = os.path.dirname(line)
384 pattern = os.path.join('configs', line)
385 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
387 def get_matched_defconfigs(defconfigs_file):
388 """Get all the defconfig files that match the patterns in a file.
391 defconfigs_file: File containing a list of defconfigs to process, or
392 '-' to read the list from stdin
395 A list of paths to defconfig files, with no duplicates
398 if defconfigs_file == '-':
400 defconfigs_file = 'stdin'
402 fd = open(defconfigs_file)
403 for i, line in enumerate(fd):
406 continue # skip blank lines silently
407 matched = get_matched_defconfig(line)
409 print >> sys.stderr, "warning: %s:%d: no defconfig matched '%s'" % \
410 (defconfigs_file, i + 1, line)
412 defconfigs += matched
414 # use set() to drop multiple matching
415 return [ defconfig[len('configs') + 1:] for defconfig in set(defconfigs) ]
417 def get_all_defconfigs():
418 """Get all the defconfig files under the configs/ directory."""
420 for (dirpath, dirnames, filenames) in os.walk('configs'):
421 dirpath = dirpath[len('configs') + 1:]
422 for filename in fnmatch.filter(filenames, '*_defconfig'):
423 defconfigs.append(os.path.join(dirpath, filename))
427 def color_text(color_enabled, color, string):
428 """Return colored string."""
430 # LF should not be surrounded by the escape sequence.
431 # Otherwise, additional whitespace or line-feed might be printed.
432 return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
433 for s in string.split('\n') ])
437 def show_diff(a, b, file_path, color_enabled):
438 """Show unidified diff.
441 a: A list of lines (before)
442 b: A list of lines (after)
443 file_path: Path to the file
444 color_enabled: Display the diff in color
447 diff = difflib.unified_diff(a, b,
448 fromfile=os.path.join('a', file_path),
449 tofile=os.path.join('b', file_path))
452 if line[0] == '-' and line[1] != '-':
453 print color_text(color_enabled, COLOR_RED, line),
454 elif line[0] == '+' and line[1] != '+':
455 print color_text(color_enabled, COLOR_GREEN, line),
459 def update_cross_compile(color_enabled):
460 """Update per-arch CROSS_COMPILE via environment variables
462 The default CROSS_COMPILE values are available
463 in the CROSS_COMPILE list above.
465 You can override them via environment variables
466 CROSS_COMPILE_{ARCH}.
468 For example, if you want to override toolchain prefixes
469 for ARM and PowerPC, you can do as follows in your shell:
471 export CROSS_COMPILE_ARM=...
472 export CROSS_COMPILE_POWERPC=...
474 Then, this function checks if specified compilers really exist in your
479 for arch in os.listdir('arch'):
480 if os.path.exists(os.path.join('arch', arch, 'Makefile')):
483 # arm64 is a special case
484 archs.append('aarch64')
487 env = 'CROSS_COMPILE_' + arch.upper()
488 cross_compile = os.environ.get(env)
489 if not cross_compile:
490 cross_compile = CROSS_COMPILE.get(arch, '')
492 for path in os.environ["PATH"].split(os.pathsep):
493 gcc_path = os.path.join(path, cross_compile + 'gcc')
494 if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
497 print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
498 'warning: %sgcc: not found in PATH. %s architecture boards will be skipped'
499 % (cross_compile, arch))
502 CROSS_COMPILE[arch] = cross_compile
504 def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre,
506 """Extend matched lines if desired patterns are found before/after already
510 lines: A list of lines handled.
511 matched: A list of line numbers that have been already matched.
512 (will be updated by this function)
513 pre_patterns: A list of regular expression that should be matched as
515 post_patterns: A list of regular expression that should be matched as
517 extend_pre: Add the line number of matched preamble to the matched list.
518 extend_post: Add the line number of matched postamble to the matched list.
520 extended_matched = []
533 for p in pre_patterns:
534 if p.search(lines[i - 1]):
540 for p in post_patterns:
541 if p.search(lines[j]):
548 extended_matched.append(i - 1)
550 extended_matched.append(j)
552 matched += extended_matched
555 def confirm(options, prompt):
558 choice = raw_input('{} [y/n]: '.format(prompt))
559 choice = choice.lower()
561 if choice == 'y' or choice == 'n':
569 def cleanup_one_header(header_path, patterns, options):
570 """Clean regex-matched lines away from a file.
573 header_path: path to the cleaned file.
574 patterns: list of regex patterns. Any lines matching to these
575 patterns are deleted.
576 options: option flags.
578 with open(header_path) as f:
579 lines = f.readlines()
582 for i, line in enumerate(lines):
583 if i - 1 in matched and lines[i - 1][-2:] == '\\\n':
586 for pattern in patterns:
587 if pattern.search(line):
594 # remove empty #ifdef ... #endif, successive blank lines
595 pattern_if = re.compile(r'#\s*if(def|ndef)?\W') # #if, #ifdef, #ifndef
596 pattern_elif = re.compile(r'#\s*el(if|se)\W') # #elif, #else
597 pattern_endif = re.compile(r'#\s*endif\W') # #endif
598 pattern_blank = re.compile(r'^\s*$') # empty line
601 old_matched = copy.copy(matched)
602 extend_matched_lines(lines, matched, [pattern_if],
603 [pattern_endif], True, True)
604 extend_matched_lines(lines, matched, [pattern_elif],
605 [pattern_elif, pattern_endif], True, False)
606 extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
607 [pattern_blank], False, True)
608 extend_matched_lines(lines, matched, [pattern_blank],
609 [pattern_elif, pattern_endif], True, False)
610 extend_matched_lines(lines, matched, [pattern_blank],
611 [pattern_blank], True, False)
612 if matched == old_matched:
615 tolines = copy.copy(lines)
617 for i in reversed(matched):
620 show_diff(lines, tolines, header_path, options.color)
625 with open(header_path, 'w') as f:
629 def cleanup_headers(configs, options):
630 """Delete config defines from board headers.
633 configs: A list of CONFIGs to remove.
634 options: option flags.
636 if not confirm(options, 'Clean up headers?'):
640 for config in configs:
641 patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
642 patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
644 for dir in 'include', 'arch', 'board':
645 for (dirpath, dirnames, filenames) in os.walk(dir):
646 if dirpath == os.path.join('include', 'generated'):
648 for filename in filenames:
649 if not fnmatch.fnmatch(filename, '*~'):
650 cleanup_one_header(os.path.join(dirpath, filename),
653 def cleanup_one_extra_option(defconfig_path, configs, options):
654 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
657 defconfig_path: path to the cleaned defconfig file.
658 configs: A list of CONFIGs to remove.
659 options: option flags.
662 start = 'CONFIG_SYS_EXTRA_OPTIONS="'
665 with open(defconfig_path) as f:
666 lines = f.readlines()
668 for i, line in enumerate(lines):
669 if line.startswith(start) and line.endswith(end):
672 # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
675 old_tokens = line[len(start):-len(end)].split(',')
678 for token in old_tokens:
679 pos = token.find('=')
680 if not (token[:pos] if pos >= 0 else token) in configs:
681 new_tokens.append(token)
683 if new_tokens == old_tokens:
686 tolines = copy.copy(lines)
689 tolines[i] = start + ','.join(new_tokens) + end
693 show_diff(lines, tolines, defconfig_path, options.color)
698 with open(defconfig_path, 'w') as f:
702 def cleanup_extra_options(configs, options):
703 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
706 configs: A list of CONFIGs to remove.
707 options: option flags.
709 if not confirm(options, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'):
712 configs = [ config[len('CONFIG_'):] for config in configs ]
714 defconfigs = get_all_defconfigs()
716 for defconfig in defconfigs:
717 cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
720 def cleanup_whitelist(configs, options):
721 """Delete config whitelist entries
724 configs: A list of CONFIGs to remove.
725 options: option flags.
727 if not confirm(options, 'Clean up whitelist entries?'):
730 with open(os.path.join('scripts', 'config_whitelist.txt')) as f:
731 lines = f.readlines()
733 lines = [x for x in lines if x.strip() not in configs]
735 with open(os.path.join('scripts', 'config_whitelist.txt'), 'w') as f:
736 f.write(''.join(lines))
738 def find_matching(patterns, line):
744 def cleanup_readme(configs, options):
745 """Delete config description in README
748 configs: A list of CONFIGs to remove.
749 options: option flags.
751 if not confirm(options, 'Clean up README?'):
755 for config in configs:
756 patterns.append(re.compile(r'^\s+%s' % config))
758 with open('README') as f:
759 lines = f.readlines()
765 found = find_matching(patterns, line)
769 if found and re.search(r'^\s+CONFIG', line):
773 newlines.append(line)
775 with open('README', 'w') as f:
776 f.write(''.join(newlines))
782 """Progress Indicator"""
784 def __init__(self, total):
785 """Create a new progress indicator.
788 total: A number of defconfig files to process.
794 """Increment the number of processed defconfig files."""
799 """Display the progress."""
800 print ' %d defconfigs out of %d\r' % (self.current, self.total),
805 """A parser of .config and include/autoconf.mk."""
807 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
808 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
810 def __init__(self, configs, options, build_dir):
811 """Create a new parser.
814 configs: A list of CONFIGs to move.
815 options: option flags.
816 build_dir: Build directory.
818 self.configs = configs
819 self.options = options
820 self.dotconfig = os.path.join(build_dir, '.config')
821 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
822 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
824 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
825 self.defconfig = os.path.join(build_dir, 'defconfig')
827 def get_cross_compile(self):
828 """Parse .config file and return CROSS_COMPILE.
831 A string storing the compiler prefix for the architecture.
832 Return a NULL string for architectures that do not require
833 compiler prefix (Sandbox and native build is the case).
834 Return None if the specified compiler is missing in your PATH.
835 Caller should distinguish '' and None.
839 for line in open(self.dotconfig):
840 m = self.re_arch.match(line)
844 m = self.re_cpu.match(line)
852 if arch == 'arm' and cpu == 'armv8':
855 return CROSS_COMPILE.get(arch, None)
857 def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
858 """Parse .config, defconfig, include/autoconf.mk for one config.
860 This function looks for the config options in the lines from
861 defconfig, .config, and include/autoconf.mk in order to decide
862 which action should be taken for this defconfig.
865 config: CONFIG name to parse.
866 dotconfig_lines: lines from the .config file.
867 autoconf_lines: lines from the include/autoconf.mk file.
870 A tupple of the action for this defconfig and the line
871 matched for the config.
873 not_set = '# %s is not set' % config
875 for line in autoconf_lines:
877 if line.startswith(config + '='):
883 for line in dotconfig_lines:
885 if line.startswith(config + '=') or line == not_set:
889 if new_val == not_set:
890 return (ACTION_NO_ENTRY, config)
892 return (ACTION_NO_ENTRY_WARN, config)
894 # If this CONFIG is neither bool nor trisate
895 if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
896 # tools/scripts/define2mk.sed changes '1' to 'y'.
897 # This is a problem if the CONFIG is int type.
898 # Check the type in Kconfig and handle it correctly.
899 if new_val[-2:] == '=y':
900 new_val = new_val[:-1] + '1'
902 return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
905 def update_dotconfig(self):
906 """Parse files for the config options and update the .config.
908 This function parses the generated .config and include/autoconf.mk
909 searching the target options.
910 Move the config option(s) to the .config as needed.
913 defconfig: defconfig name.
916 Return a tuple of (updated flag, log string).
917 The "updated flag" is True if the .config was updated, False
918 otherwise. The "log string" shows what happend to the .config.
924 rm_files = [self.config_autoconf, self.autoconf]
927 if os.path.exists(self.spl_autoconf):
928 autoconf_path = self.spl_autoconf
929 rm_files.append(self.spl_autoconf)
933 return (updated, suspicious,
934 color_text(self.options.color, COLOR_BROWN,
935 "SPL is not enabled. Skipped.") + '\n')
937 autoconf_path = self.autoconf
939 with open(self.dotconfig) as f:
940 dotconfig_lines = f.readlines()
942 with open(autoconf_path) as f:
943 autoconf_lines = f.readlines()
945 for config in self.configs:
946 result = self.parse_one_config(config, dotconfig_lines,
948 results.append(result)
952 for (action, value) in results:
953 if action == ACTION_MOVE:
954 actlog = "Move '%s'" % value
955 log_color = COLOR_LIGHT_GREEN
956 elif action == ACTION_NO_ENTRY:
957 actlog = "%s is not defined in Kconfig. Do nothing." % value
958 log_color = COLOR_LIGHT_BLUE
959 elif action == ACTION_NO_ENTRY_WARN:
960 actlog = "%s is not defined in Kconfig (suspicious). Do nothing." % value
961 log_color = COLOR_YELLOW
963 elif action == ACTION_NO_CHANGE:
964 actlog = "'%s' is the same as the define in Kconfig. Do nothing." \
966 log_color = COLOR_LIGHT_PURPLE
967 elif action == ACTION_SPL_NOT_EXIST:
968 actlog = "SPL is not enabled for this defconfig. Skip."
969 log_color = COLOR_PURPLE
971 sys.exit("Internal Error. This should not happen.")
973 log += color_text(self.options.color, log_color, actlog) + '\n'
975 with open(self.dotconfig, 'a') as f:
976 for (action, value) in results:
977 if action == ACTION_MOVE:
978 f.write(value + '\n')
981 self.results = results
985 return (updated, suspicious, log)
987 def check_defconfig(self):
988 """Check the defconfig after savedefconfig
991 Return additional log if moved CONFIGs were removed again by
992 'make savedefconfig'.
997 with open(self.defconfig) as f:
998 defconfig_lines = f.readlines()
1000 for (action, value) in self.results:
1001 if action != ACTION_MOVE:
1003 if not value + '\n' in defconfig_lines:
1004 log += color_text(self.options.color, COLOR_YELLOW,
1005 "'%s' was removed by savedefconfig.\n" %
1011 class DatabaseThread(threading.Thread):
1012 """This thread processes results from Slot threads.
1014 It collects the data in the master config directary. There is only one
1015 result thread, and this helps to serialise the build output.
1017 def __init__(self, config_db, db_queue):
1018 """Set up a new result thread
1021 builder: Builder which will be sent each result
1023 threading.Thread.__init__(self)
1024 self.config_db = config_db
1025 self.db_queue= db_queue
1028 """Called to start up the result thread.
1030 We collect the next result job and pass it on to the build.
1033 defconfig, configs = self.db_queue.get()
1034 self.config_db[defconfig] = configs
1035 self.db_queue.task_done()
1040 """A slot to store a subprocess.
1042 Each instance of this class handles one subprocess.
1043 This class is useful to control multiple threads
1044 for faster processing.
1047 def __init__(self, configs, options, progress, devnull, make_cmd,
1048 reference_src_dir, db_queue):
1049 """Create a new process slot.
1052 configs: A list of CONFIGs to move.
1053 options: option flags.
1054 progress: A progress indicator.
1055 devnull: A file object of '/dev/null'.
1056 make_cmd: command name of GNU Make.
1057 reference_src_dir: Determine the true starting config state from this
1059 db_queue: output queue to write config info for the database
1061 self.options = options
1062 self.progress = progress
1063 self.build_dir = tempfile.mkdtemp()
1064 self.devnull = devnull
1065 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
1066 self.reference_src_dir = reference_src_dir
1067 self.db_queue = db_queue
1068 self.parser = KconfigParser(configs, options, self.build_dir)
1069 self.state = STATE_IDLE
1070 self.failed_boards = set()
1071 self.suspicious_boards = set()
1074 """Delete the working directory
1076 This function makes sure the temporary directory is cleaned away
1077 even if Python suddenly dies due to error. It should be done in here
1078 because it is guaranteed the destructor is always invoked when the
1079 instance of the class gets unreferenced.
1081 If the subprocess is still running, wait until it finishes.
1083 if self.state != STATE_IDLE:
1084 while self.ps.poll() == None:
1086 shutil.rmtree(self.build_dir)
1088 def add(self, defconfig):
1089 """Assign a new subprocess for defconfig and add it to the slot.
1091 If the slot is vacant, create a new subprocess for processing the
1092 given defconfig and add it to the slot. Just returns False if
1093 the slot is occupied (i.e. the current subprocess is still running).
1096 defconfig: defconfig name.
1099 Return True on success or False on failure
1101 if self.state != STATE_IDLE:
1104 self.defconfig = defconfig
1106 self.current_src_dir = self.reference_src_dir
1111 """Check the status of the subprocess and handle it as needed.
1113 Returns True if the slot is vacant (i.e. in idle state).
1114 If the configuration is successfully finished, assign a new
1115 subprocess to build include/autoconf.mk.
1116 If include/autoconf.mk is generated, invoke the parser to
1117 parse the .config and the include/autoconf.mk, moving
1118 config options to the .config as needed.
1119 If the .config was updated, run "make savedefconfig" to sync
1120 it, update the original defconfig, and then set the slot back
1124 Return True if the subprocess is terminated, False otherwise
1126 if self.state == STATE_IDLE:
1129 if self.ps.poll() == None:
1132 if self.ps.poll() != 0:
1134 elif self.state == STATE_DEFCONFIG:
1135 if self.reference_src_dir and not self.current_src_dir:
1136 self.do_savedefconfig()
1139 elif self.state == STATE_AUTOCONF:
1140 if self.current_src_dir:
1141 self.current_src_dir = None
1143 elif self.options.build_db:
1146 self.do_savedefconfig()
1147 elif self.state == STATE_SAVEDEFCONFIG:
1148 self.update_defconfig()
1150 sys.exit("Internal Error. This should not happen.")
1152 return True if self.state == STATE_IDLE else False
1154 def handle_error(self):
1155 """Handle error cases."""
1157 self.log += color_text(self.options.color, COLOR_LIGHT_RED,
1158 "Failed to process.\n")
1159 if self.options.verbose:
1160 self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
1161 self.ps.stderr.read())
1164 def do_defconfig(self):
1165 """Run 'make <board>_defconfig' to create the .config file."""
1167 cmd = list(self.make_cmd)
1168 cmd.append(self.defconfig)
1169 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1170 stderr=subprocess.PIPE,
1171 cwd=self.current_src_dir)
1172 self.state = STATE_DEFCONFIG
1174 def do_autoconf(self):
1175 """Run 'make AUTO_CONF_PATH'."""
1177 self.cross_compile = self.parser.get_cross_compile()
1178 if self.cross_compile is None:
1179 self.log += color_text(self.options.color, COLOR_YELLOW,
1180 "Compiler is missing. Do nothing.\n")
1184 cmd = list(self.make_cmd)
1185 if self.cross_compile:
1186 cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
1187 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
1188 cmd.append(AUTO_CONF_PATH)
1189 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1190 stderr=subprocess.PIPE,
1191 cwd=self.current_src_dir)
1192 self.state = STATE_AUTOCONF
1194 def do_build_db(self):
1195 """Add the board to the database"""
1197 with open(os.path.join(self.build_dir, AUTO_CONF_PATH)) as fd:
1198 for line in fd.readlines():
1199 if line.startswith('CONFIG'):
1200 config, value = line.split('=', 1)
1201 configs[config] = value.rstrip()
1202 self.db_queue.put([self.defconfig, configs])
1205 def do_savedefconfig(self):
1206 """Update the .config and run 'make savedefconfig'."""
1208 (updated, suspicious, log) = self.parser.update_dotconfig()
1210 self.suspicious_boards.add(self.defconfig)
1213 if not self.options.force_sync and not updated:
1217 self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
1218 "Syncing by savedefconfig...\n")
1220 self.log += "Syncing by savedefconfig (forced by option)...\n"
1222 cmd = list(self.make_cmd)
1223 cmd.append('savedefconfig')
1224 self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1225 stderr=subprocess.PIPE)
1226 self.state = STATE_SAVEDEFCONFIG
1228 def update_defconfig(self):
1229 """Update the input defconfig and go back to the idle state."""
1231 log = self.parser.check_defconfig()
1233 self.suspicious_boards.add(self.defconfig)
1235 orig_defconfig = os.path.join('configs', self.defconfig)
1236 new_defconfig = os.path.join(self.build_dir, 'defconfig')
1237 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
1240 self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
1241 "defconfig was updated.\n")
1243 if not self.options.dry_run and updated:
1244 shutil.move(new_defconfig, orig_defconfig)
1247 def finish(self, success):
1248 """Display log along with progress and go to the idle state.
1251 success: Should be True when the defconfig was processed
1252 successfully, or False when it fails.
1254 # output at least 30 characters to hide the "* defconfigs out of *".
1255 log = self.defconfig.ljust(30) + '\n'
1257 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ])
1258 # Some threads are running in parallel.
1259 # Print log atomically to not mix up logs from different threads.
1260 print >> (sys.stdout if success else sys.stderr), log
1263 if self.options.exit_on_error:
1264 sys.exit("Exit on error.")
1265 # If --exit-on-error flag is not set, skip this board and continue.
1266 # Record the failed board.
1267 self.failed_boards.add(self.defconfig)
1270 self.progress.show()
1271 self.state = STATE_IDLE
1273 def get_failed_boards(self):
1274 """Returns a set of failed boards (defconfigs) in this slot.
1276 return self.failed_boards
1278 def get_suspicious_boards(self):
1279 """Returns a set of boards (defconfigs) with possible misconversion.
1281 return self.suspicious_boards - self.failed_boards
1285 """Controller of the array of subprocess slots."""
1287 def __init__(self, configs, options, progress, reference_src_dir, db_queue):
1288 """Create a new slots controller.
1291 configs: A list of CONFIGs to move.
1292 options: option flags.
1293 progress: A progress indicator.
1294 reference_src_dir: Determine the true starting config state from this
1296 db_queue: output queue to write config info for the database
1298 self.options = options
1300 devnull = get_devnull()
1301 make_cmd = get_make_cmd()
1302 for i in range(options.jobs):
1303 self.slots.append(Slot(configs, options, progress, devnull,
1304 make_cmd, reference_src_dir, db_queue))
1306 def add(self, defconfig):
1307 """Add a new subprocess if a vacant slot is found.
1310 defconfig: defconfig name to be put into.
1313 Return True on success or False on failure
1315 for slot in self.slots:
1316 if slot.add(defconfig):
1320 def available(self):
1321 """Check if there is a vacant slot.
1324 Return True if at lease one vacant slot is found, False otherwise.
1326 for slot in self.slots:
1332 """Check if all slots are vacant.
1335 Return True if all the slots are vacant, False otherwise.
1338 for slot in self.slots:
1343 def show_failed_boards(self):
1344 """Display all of the failed boards (defconfigs)."""
1346 output_file = 'moveconfig.failed'
1348 for slot in self.slots:
1349 boards |= slot.get_failed_boards()
1352 boards = '\n'.join(boards) + '\n'
1353 msg = "The following boards were not processed due to error:\n"
1355 msg += "(the list has been saved in %s)\n" % output_file
1356 print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
1359 with open(output_file, 'w') as f:
1362 def show_suspicious_boards(self):
1363 """Display all boards (defconfigs) with possible misconversion."""
1365 output_file = 'moveconfig.suspicious'
1367 for slot in self.slots:
1368 boards |= slot.get_suspicious_boards()
1371 boards = '\n'.join(boards) + '\n'
1372 msg = "The following boards might have been converted incorrectly.\n"
1373 msg += "It is highly recommended to check them manually:\n"
1375 msg += "(the list has been saved in %s)\n" % output_file
1376 print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
1379 with open(output_file, 'w') as f:
1382 class ReferenceSource:
1384 """Reference source against which original configs should be parsed."""
1386 def __init__(self, commit):
1387 """Create a reference source directory based on a specified commit.
1390 commit: commit to git-clone
1392 self.src_dir = tempfile.mkdtemp()
1393 print "Cloning git repo to a separate work directory..."
1394 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1396 print "Checkout '%s' to build the original autoconf.mk." % \
1397 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
1398 subprocess.check_output(['git', 'checkout', commit],
1399 stderr=subprocess.STDOUT, cwd=self.src_dir)
1402 """Delete the reference source directory
1404 This function makes sure the temporary directory is cleaned away
1405 even if Python suddenly dies due to error. It should be done in here
1406 because it is guaranteed the destructor is always invoked when the
1407 instance of the class gets unreferenced.
1409 shutil.rmtree(self.src_dir)
1412 """Return the absolute path to the reference source directory."""
1416 def move_config(configs, options, db_queue):
1417 """Move config options to defconfig files.
1420 configs: A list of CONFIGs to move.
1421 options: option flags
1423 if len(configs) == 0:
1424 if options.force_sync:
1425 print 'No CONFIG is specified. You are probably syncing defconfigs.',
1426 elif options.build_db:
1427 print 'Building %s database' % CONFIG_DATABASE
1429 print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
1431 print 'Move ' + ', '.join(configs),
1432 print '(jobs: %d)\n' % options.jobs
1435 reference_src = ReferenceSource(options.git_ref)
1436 reference_src_dir = reference_src.get_dir()
1438 reference_src_dir = None
1440 if options.defconfigs:
1441 defconfigs = get_matched_defconfigs(options.defconfigs)
1443 defconfigs = get_all_defconfigs()
1445 progress = Progress(len(defconfigs))
1446 slots = Slots(configs, options, progress, reference_src_dir, db_queue)
1448 # Main loop to process defconfig files:
1449 # Add a new subprocess into a vacant slot.
1450 # Sleep if there is no available slot.
1451 for defconfig in defconfigs:
1452 while not slots.add(defconfig):
1453 while not slots.available():
1454 # No available slot: sleep for a while
1455 time.sleep(SLEEP_TIME)
1457 # wait until all the subprocesses finish
1458 while not slots.empty():
1459 time.sleep(SLEEP_TIME)
1462 slots.show_failed_boards()
1463 slots.show_suspicious_boards()
1465 def imply_config(config_list, find_superset=False):
1466 """Find CONFIG options which imply those in the list
1468 Some CONFIG options can be implied by others and this can help to reduce
1469 the size of the defconfig files. For example, CONFIG_X86 implies
1470 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
1471 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
1472 each of the x86 defconfig files.
1474 This function uses the moveconfig database to find such options. It
1475 displays a list of things that could possibly imply those in the list.
1476 The algorithm ignores any that start with CONFIG_TARGET since these
1477 typically refer to only a few defconfigs (often one). It also does not
1478 display a config with less than 5 defconfigs.
1480 The algorithm works using sets. For each target config in config_list:
1481 - Get the set 'defconfigs' which use that target config
1482 - For each config (from a list of all configs):
1483 - Get the set 'imply_defconfig' of defconfigs which use that config
1485 - If imply_defconfigs contains anything not in defconfigs then
1486 this config does not imply the target config
1489 config_list: List of CONFIG options to check (each a string)
1490 find_superset: True to look for configs which are a superset of those
1491 already found. So for example if CONFIG_EXYNOS5 implies an option,
1492 but CONFIG_EXYNOS covers a larger set of defconfigs and also
1493 implies that option, this will drop the former in favour of the
1494 latter. In practice this option has not proved very used.
1496 Note the terminoloy:
1497 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
1498 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
1500 # key is defconfig name, value is dict of (CONFIG_xxx, value)
1503 # Holds a dict containing the set of defconfigs that contain each config
1504 # key is config, value is set of defconfigs using that config
1505 defconfig_db = collections.defaultdict(set)
1507 # Set of all config options we have seen
1510 # Set of all defconfigs we have seen
1511 all_defconfigs = set()
1513 # Read in the database
1515 with open(CONFIG_DATABASE) as fd:
1516 for line in fd.readlines():
1517 line = line.rstrip()
1518 if not line: # Separator between defconfigs
1519 config_db[defconfig] = configs
1520 all_defconfigs.add(defconfig)
1522 elif line[0] == ' ': # CONFIG line
1523 config, value = line.strip().split('=', 1)
1524 configs[config] = value
1525 defconfig_db[config].add(defconfig)
1526 all_configs.add(config)
1527 else: # New defconfig
1530 # Work through each target config option in tern, independently
1531 for config in config_list:
1532 defconfigs = defconfig_db.get(config)
1534 print '%s not found in any defconfig' % config
1537 # Get the set of defconfigs without this one (since a config cannot
1539 non_defconfigs = all_defconfigs - defconfigs
1540 num_defconfigs = len(defconfigs)
1541 print '%s found in %d/%d defconfigs' % (config, num_defconfigs,
1544 # This will hold the results: key=config, value=defconfigs containing it
1546 rest_configs = all_configs - set([config])
1548 # Look at every possible config, except the target one
1549 for imply_config in rest_configs:
1550 if 'CONFIG_TARGET' in imply_config:
1553 # Find set of defconfigs that have this config
1554 imply_defconfig = defconfig_db[imply_config]
1556 # Get the intersection of this with defconfigs containing the
1558 common_defconfigs = imply_defconfig & defconfigs
1560 # Get the set of defconfigs containing this config which DO NOT
1561 # also contain the taret config. If this set is non-empty it means
1562 # that this config affects other defconfigs as well as (possibly)
1563 # the ones affected by the target config. This means it implies
1564 # things we don't want to imply.
1565 not_common_defconfigs = imply_defconfig & non_defconfigs
1566 if not_common_defconfigs:
1569 # If there are common defconfigs, imply_config may be useful
1570 if common_defconfigs:
1573 for prev in imply_configs.keys():
1574 prev_count = len(imply_configs[prev])
1575 count = len(common_defconfigs)
1576 if (prev_count > count and
1577 (imply_configs[prev] & common_defconfigs ==
1578 common_defconfigs)):
1579 # skip imply_config because prev is a superset
1582 elif count > prev_count:
1583 # delete prev because imply_config is a superset
1584 del imply_configs[prev]
1586 imply_configs[imply_config] = common_defconfigs
1588 # Now we have a dict imply_configs of configs which imply each config
1589 # The value of each dict item is the set of defconfigs containing that
1590 # config. Rank them so that we print the configs that imply the largest
1591 # number of defconfigs first.
1592 ranked_configs = sorted(imply_configs,
1593 key=lambda k: len(imply_configs[k]), reverse=True)
1594 for config in ranked_configs:
1595 num_common = len(imply_configs[config])
1597 # Don't bother if there are less than 5 defconfigs affected.
1600 missing = defconfigs - imply_configs[config]
1601 missing_str = ', '.join(missing) if missing else 'all'
1603 print ' %d : %-30s%s' % (num_common, config.ljust(30),
1609 cpu_count = multiprocessing.cpu_count()
1610 except NotImplementedError:
1613 parser = optparse.OptionParser()
1615 parser.add_option('-b', '--build-db', action='store_true', default=False,
1616 help='build a CONFIG database')
1617 parser.add_option('-c', '--color', action='store_true', default=False,
1618 help='display the log in color')
1619 parser.add_option('-C', '--commit', action='store_true', default=False,
1620 help='Create a git commit for the operation')
1621 parser.add_option('-d', '--defconfigs', type='string',
1622 help='a file containing a list of defconfigs to move, '
1623 "one per line (for example 'snow_defconfig') "
1624 "or '-' to read from stdin")
1625 parser.add_option('-i', '--imply', action='store_true', default=False,
1626 help='find options which imply others')
1627 parser.add_option('-n', '--dry-run', action='store_true', default=False,
1628 help='perform a trial run (show log with no changes)')
1629 parser.add_option('-e', '--exit-on-error', action='store_true',
1631 help='exit immediately on any error')
1632 parser.add_option('-s', '--force-sync', action='store_true', default=False,
1633 help='force sync by savedefconfig')
1634 parser.add_option('-S', '--spl', action='store_true', default=False,
1635 help='parse config options defined for SPL build')
1636 parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1637 action='store_true', default=False,
1638 help='only cleanup the headers')
1639 parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1640 help='the number of jobs to run simultaneously')
1641 parser.add_option('-r', '--git-ref', type='string',
1642 help='the git ref to clone for building the autoconf.mk')
1643 parser.add_option('-y', '--yes', action='store_true', default=False,
1644 help="respond 'yes' to any prompts")
1645 parser.add_option('-v', '--verbose', action='store_true', default=False,
1646 help='show any build errors as boards are built')
1647 parser.usage += ' CONFIG ...'
1649 (options, configs) = parser.parse_args()
1651 if len(configs) == 0 and not any((options.force_sync, options.build_db,
1653 parser.print_usage()
1656 # prefix the option name with CONFIG_ if missing
1657 configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1658 for config in configs ]
1660 check_top_directory()
1663 imply_config(configs)
1667 db_queue = Queue.Queue()
1668 t = DatabaseThread(config_db, db_queue)
1672 if not options.cleanup_headers_only:
1673 check_clean_directory()
1674 update_cross_compile(options.color)
1675 move_config(configs, options, db_queue)
1679 cleanup_headers(configs, options)
1680 cleanup_extra_options(configs, options)
1681 cleanup_whitelist(configs, options)
1682 cleanup_readme(configs, options)
1685 subprocess.call(['git', 'add', '-u'])
1687 msg = 'Convert %s %sto Kconfig' % (configs[0],
1688 'et al ' if len(configs) > 1 else '')
1689 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1690 '\n '.join(configs))
1692 msg = 'configs: Resync with savedefconfig'
1693 msg += '\n\nRsync all defconfig files using moveconfig.py'
1694 subprocess.call(['git', 'commit', '-s', '-m', msg])
1696 if options.build_db:
1697 with open(CONFIG_DATABASE, 'w') as fd:
1698 for defconfig, configs in config_db.iteritems():
1699 print >>fd, '%s' % defconfig
1700 for config in sorted(configs.keys()):
1701 print >>fd, ' %s=%s' % (config, configs[config])
1704 if __name__ == '__main__':