2 # SPDX-License-Identifier: GPL-2.0+
4 """Build and query a Kconfig database for boards.
6 See doc/develop/qconfig.rst for documentation.
12 from argparse import ArgumentParser
14 from contextlib import ExitStack
19 import multiprocessing
31 from buildman import bsettings
32 from buildman import kconfiglib
33 from buildman import toolchain
34 from u_boot_pylib import terminal
36 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
42 STATE_SAVEDEFCONFIG = 3
44 AUTO_CONF_PATH = 'include/config/auto.conf'
45 CONFIG_DATABASE = 'qconfig.db'
46 FAILED_LIST = 'qconfig.failed'
48 CONFIG_LEN = len('CONFIG_')
51 'SZ_1': 0x00000001, 'SZ_2': 0x00000002,
52 'SZ_4': 0x00000004, 'SZ_8': 0x00000008,
53 'SZ_16': 0x00000010, 'SZ_32': 0x00000020,
54 'SZ_64': 0x00000040, 'SZ_128': 0x00000080,
55 'SZ_256': 0x00000100, 'SZ_512': 0x00000200,
56 'SZ_1K': 0x00000400, 'SZ_2K': 0x00000800,
57 'SZ_4K': 0x00001000, 'SZ_8K': 0x00002000,
58 'SZ_16K': 0x00004000, 'SZ_32K': 0x00008000,
59 'SZ_64K': 0x00010000, 'SZ_128K': 0x00020000,
60 'SZ_256K': 0x00040000, 'SZ_512K': 0x00080000,
61 'SZ_1M': 0x00100000, 'SZ_2M': 0x00200000,
62 'SZ_4M': 0x00400000, 'SZ_8M': 0x00800000,
63 'SZ_16M': 0x01000000, 'SZ_32M': 0x02000000,
64 'SZ_64M': 0x04000000, 'SZ_128M': 0x08000000,
65 'SZ_256M': 0x10000000, 'SZ_512M': 0x20000000,
66 'SZ_1G': 0x40000000, 'SZ_2G': 0x80000000,
70 RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
72 # CONFIG symbols present in the build system (from Linux) but not actually used
73 # in U-Boot; KCONFIG symbols
74 IGNORE_SYMS = ['DEBUG_SECTION_MISMATCH', 'FTRACE_MCOUNT_RECORD', 'GCOV_KERNEL',
75 'GCOV_PROFILE_ALL', 'KALLSYMS', 'KASAN', 'MODVERSIONS', 'SHELL',
76 'TPL_BUILD', 'VPL_BUILD', 'IS_ENABLED', 'FOO', 'IF_ENABLED_INT',
77 'IS_ENABLED_', 'IS_ENABLED_1', 'IS_ENABLED_2', 'IS_ENABLED_3',
78 'SPL_', 'TPL_', 'SPL_FOO', 'TPL_FOO', 'TOOLS_FOO',
79 'ACME', 'SPL_ACME', 'TPL_ACME', 'TRACE_BRANCH_PROFILING',
80 'VAL', '_UNDEFINED', 'SPL_BUILD', 'XPL_BUILD', ]
82 SPL_PREFIXES = ['SPL_', 'TPL_', 'VPL_', 'TOOLS_']
84 ### helper functions ###
85 def check_top_directory():
86 """Exit if we are not at the top of source directory."""
87 for fname in 'README', 'Licenses':
88 if not os.path.exists(fname):
89 sys.exit('Please run at the top of source directory.')
91 def check_clean_directory():
92 """Exit if the source tree is not clean."""
93 for fname in '.config', 'include/config':
94 if os.path.exists(fname):
95 sys.exit("source tree is not clean, please run 'make mrproper'")
98 """Get the command name of GNU Make.
100 U-Boot needs GNU Make for building, but the command name is not
101 necessarily "make". (for example, "gmake" on FreeBSD).
102 Returns the most appropriate command name on your system.
104 with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc:
105 ret = proc.communicate()
107 sys.exit('GNU Make not found')
108 return ret[0].rstrip()
110 def get_matched_defconfig(line):
111 """Get the defconfig files that match a pattern
114 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
115 'k2*_defconfig'. If no directory is provided, 'configs/' is
119 list of str: a list of matching defconfig files
121 dirname = os.path.dirname(line)
125 pattern = os.path.join('configs', line)
126 return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
128 def get_matched_defconfigs(defconfigs_file):
129 """Get all the defconfig files that match the patterns in a file.
132 defconfigs_file (str): File containing a list of defconfigs to process,
133 or '-' to read the list from stdin
136 list of str: A list of paths to defconfig files, with no duplicates
139 with ExitStack() as stack:
140 if defconfigs_file == '-':
142 defconfigs_file = 'stdin'
144 inf = stack.enter_context(open(defconfigs_file, encoding='utf-8'))
145 for i, line in enumerate(inf):
148 continue # skip blank lines silently
150 line = line.split(' ')[0] # handle 'git log' input
151 matched = get_matched_defconfig(line)
153 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'",
156 defconfigs += matched
158 # use set() to drop multiple matching
159 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)]
161 def get_all_defconfigs():
162 """Get all the defconfig files under the configs/ directory.
165 list of str: List of paths to defconfig files
168 for (dirpath, _, filenames) in os.walk('configs'):
169 dirpath = dirpath[len('configs') + 1:]
170 for filename in fnmatch.filter(filenames, '*_defconfig'):
171 defconfigs.append(os.path.join(dirpath, filename))
175 def write_file(fname, data):
176 """Write data to a file
179 fname (str): Filename to write to
180 data (list of str): Lines to write (with or without trailing newline);
183 with open(fname, 'w', encoding='utf-8') as out:
184 if isinstance(data, list):
186 print(line.rstrip('\n'), file=out)
190 def read_file(fname, as_lines=True, skip_unicode=False):
191 """Read a file and return the contents
194 fname (str): Filename to read from
195 as_lines (bool): Return file contents as a list of lines
196 skip_unicode (bool): True to report unicode errors and continue
199 iter of str: List of ;ines from the file with newline removed; str if
200 as_lines is False with newlines intact; or None if a unicode error
204 UnicodeDecodeError: Unicode error occurred when reading
206 with open(fname, encoding='utf-8') as inf:
209 return [line.rstrip('\n') for line in inf.readlines()]
211 except UnicodeDecodeError as exc:
214 print(f"Failed on file '{fname}: {exc}")
220 """Progress Indicator"""
222 def __init__(self, col, total):
223 """Create a new progress indicator.
226 col (terminal.Color): Colour-output class
227 total (int): A number of defconfig files to process.
229 current (int): Number of boards processed so far
230 failed (int): Number of failed boards
231 failure_msg (str): Message indicating number of failures, '' if none
239 self.failure_msg = None
241 def inc(self, success):
242 """Increment the number of processed defconfig files.
245 success (bool): True if processing succeeded
251 """Display the progress."""
252 if self.current != self.total:
253 line = self.col.build(self.col.GREEN, f'{self.good:5d}')
254 line += self.col.build(self.col.RED,
255 f'{self.current - self.good:5d}')
256 line += self.col.build(self.col.MAGENTA,
257 f'/{self.total - self.current}')
258 print(f'{line} \r', end='')
262 """Set up extra properties when completed"""
263 self.failed = self.total - self.good
264 self.failure_msg = f'{self.failed} failed, ' if self.failed else ''
268 """Scan all the Kconfig files and create a Config object
273 # Define environment variables referenced from Kconfig
274 os.environ['srctree'] = os.getcwd()
275 os.environ['UBOOTVERSION'] = 'dummy'
276 os.environ['KCONFIG_OBJDIR'] = ''
277 os.environ['CC'] = 'gcc'
278 return kconfiglib.Kconfig()
281 # pylint: disable=R0903
283 """A parser of .config and include/autoconf.mk."""
285 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
286 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
288 def __init__(self, args, build_dir):
289 """Create a new parser.
292 args (Namespace): program arguments
293 build_dir: Build directory.
296 self.dotconfig = os.path.join(build_dir, '.config')
297 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
298 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
300 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
301 self.defconfig = os.path.join(build_dir, 'defconfig')
304 """Parse .config file and return the architecture.
307 Architecture name (e.g. 'arm').
311 for line in read_file(self.dotconfig):
312 m_arch = self.re_arch.match(line)
314 arch = m_arch.group(1)
316 m_cpu = self.re_cpu.match(line)
324 if arch == 'arm' and cpu == 'armv8':
330 class DatabaseThread(threading.Thread):
331 """This thread processes results from Slot threads.
333 It collects the data in the master config directary. There is only one
334 result thread, and this helps to serialise the build output.
336 def __init__(self, config_db, db_queue):
337 """Set up a new result thread
340 builder: Builder which will be sent each result
342 threading.Thread.__init__(self)
343 self.config_db = config_db
344 self.db_queue= db_queue
347 """Called to start up the result thread.
349 We collect the next result job and pass it on to the build.
352 defconfig, configs = self.db_queue.get()
353 self.config_db[defconfig] = configs
354 self.db_queue.task_done()
359 """A slot to store a subprocess.
361 Each instance of this class handles one subprocess.
362 This class is useful to control multiple threads
363 for faster processing.
366 def __init__(self, toolchains, args, progress, devnull, make_cmd,
367 reference_src_dir, db_queue):
368 """Create a new process slot.
371 toolchains: Toolchains object containing toolchains.
372 args: Program arguments
373 progress: A progress indicator.
374 devnull: A file object of '/dev/null'.
375 make_cmd: command name of GNU Make.
376 reference_src_dir: Determine the true starting config state from this
378 db_queue: output queue to write config info for the database
379 col (terminal.Color): Colour object
381 self.toolchains = toolchains
383 self.progress = progress
384 self.build_dir = tempfile.mkdtemp()
385 self.devnull = devnull
386 self.make_cmd = (make_cmd, 'O=' + self.build_dir)
387 self.reference_src_dir = reference_src_dir
388 self.db_queue = db_queue
389 self.col = progress.col
390 self.parser = KconfigParser(args, self.build_dir)
391 self.state = STATE_IDLE
392 self.failed_boards = set()
393 self.defconfig = None
395 self.current_src_dir = None
399 """Delete the working directory
401 This function makes sure the temporary directory is cleaned away
402 even if Python suddenly dies due to error. It should be done in here
403 because it is guaranteed the destructor is always invoked when the
404 instance of the class gets unreferenced.
406 If the subprocess is still running, wait until it finishes.
408 if self.state != STATE_IDLE:
409 while self.proc.poll() is None:
411 shutil.rmtree(self.build_dir)
413 def add(self, defconfig):
414 """Assign a new subprocess for defconfig and add it to the slot.
416 If the slot is vacant, create a new subprocess for processing the
417 given defconfig and add it to the slot. Just returns False if
418 the slot is occupied (i.e. the current subprocess is still running).
421 defconfig (str): defconfig name.
424 Return True on success or False on failure
426 if self.state != STATE_IDLE:
429 self.defconfig = defconfig
431 self.current_src_dir = self.reference_src_dir
436 """Check the status of the subprocess and handle it as needed.
438 Returns True if the slot is vacant (i.e. in idle state).
439 If the configuration is successfully finished, assign a new
440 subprocess to build include/autoconf.mk.
441 If include/autoconf.mk is generated, invoke the parser to
442 parse the .config and the include/autoconf.mk, moving
443 config options to the .config as needed.
444 If the .config was updated, run "make savedefconfig" to sync
445 it, update the original defconfig, and then set the slot back
449 Return True if the subprocess is terminated, False otherwise
451 if self.state == STATE_IDLE:
454 if self.proc.poll() is None:
457 if self.proc.poll() != 0:
459 elif self.state == STATE_DEFCONFIG:
460 if self.reference_src_dir and not self.current_src_dir:
461 self.do_savedefconfig()
464 elif self.state == STATE_AUTOCONF:
465 if self.current_src_dir:
466 self.current_src_dir = None
468 elif self.args.build_db:
471 self.do_savedefconfig()
472 elif self.state == STATE_SAVEDEFCONFIG:
473 self.update_defconfig()
475 sys.exit('Internal Error. This should not happen.')
477 return self.state == STATE_IDLE
479 def handle_error(self):
480 """Handle error cases."""
482 self.log.append(self.col.build(self.col.RED, 'Failed to process',
484 if self.args.verbose:
485 for line in self.proc.stderr.read().decode().splitlines():
486 self.log.append(self.col.build(self.col.CYAN, line, True))
489 def do_defconfig(self):
490 """Run 'make <board>_defconfig' to create the .config file."""
492 cmd = list(self.make_cmd)
493 cmd.append(self.defconfig)
494 # pylint: disable=R1732
495 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
496 stderr=subprocess.PIPE,
497 cwd=self.current_src_dir)
498 self.state = STATE_DEFCONFIG
500 def do_autoconf(self):
501 """Run 'make AUTO_CONF_PATH'."""
503 arch = self.parser.get_arch()
505 tchain = self.toolchains.Select(arch)
507 self.log.append(self.col.build(
509 f"Tool chain for '{arch}' is missing: do nothing"))
512 env = tchain.MakeEnvironment(False)
514 cmd = list(self.make_cmd)
515 cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
516 cmd.append(AUTO_CONF_PATH)
517 # pylint: disable=R1732
518 self.proc = subprocess.Popen(cmd, stdout=self.devnull, env=env,
519 stderr=subprocess.PIPE,
520 cwd=self.current_src_dir)
521 self.state = STATE_AUTOCONF
523 def do_build_db(self):
524 """Add the board to the database"""
526 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
527 if line.startswith('CONFIG'):
528 config, value = line.split('=', 1)
529 configs[config] = value.rstrip()
530 self.db_queue.put([self.defconfig, configs])
533 def do_savedefconfig(self):
534 """Update the .config and run 'make savedefconfig'."""
535 if not self.args.force_sync:
539 cmd = list(self.make_cmd)
540 cmd.append('savedefconfig')
541 # pylint: disable=R1732
542 self.proc = subprocess.Popen(cmd, stdout=self.devnull,
543 stderr=subprocess.PIPE)
544 self.state = STATE_SAVEDEFCONFIG
546 def update_defconfig(self):
547 """Update the input defconfig and go back to the idle state."""
548 orig_defconfig = os.path.join('configs', self.defconfig)
549 new_defconfig = os.path.join(self.build_dir, 'defconfig')
550 updated = not filecmp.cmp(orig_defconfig, new_defconfig)
554 self.col.build(self.col.BLUE, 'defconfig updated', bright=True))
556 if not self.args.dry_run and updated:
557 shutil.move(new_defconfig, orig_defconfig)
560 def finish(self, success):
561 """Display log along with progress and go to the idle state.
564 success (bool): Should be True when the defconfig was processed
565 successfully, or False when it fails.
567 # output at least 30 characters to hide the "* defconfigs out of *".
568 name = self.defconfig[:-len('_defconfig')]
571 # Put the first log line on the first line
572 log = name.ljust(20) + ' ' + self.log[0]
574 if len(self.log) > 1:
575 log += '\n' + '\n'.join([' ' + s for s in self.log[1:]])
576 # Some threads are running in parallel.
577 # Print log atomically to not mix up logs from different threads.
578 print(log, file=(sys.stdout if success else sys.stderr))
581 if self.args.exit_on_error:
582 sys.exit('Exit on error.')
583 # If --exit-on-error flag is not set, skip this board and continue.
584 # Record the failed board.
585 self.failed_boards.add(name)
587 self.progress.inc(success)
589 self.state = STATE_IDLE
591 def get_failed_boards(self):
592 """Returns a set of failed boards (defconfigs) in this slot.
594 return self.failed_boards
597 """Controller of the array of subprocess slots."""
599 def __init__(self, toolchains, args, progress, reference_src_dir, db_queue):
600 """Create a new slots controller.
603 toolchains (Toolchains): Toolchains object containing toolchains
604 args (Namespace): Program arguments
605 progress (Progress): A progress indicator.
606 reference_src_dir (str): Determine the true starting config state
607 from this source tree (None for none)
608 db_queue (Queue): output queue to write config info for the database
612 self.progress = progress
613 self.col = progress.col
614 devnull = subprocess.DEVNULL
615 make_cmd = get_make_cmd()
616 for _ in range(args.jobs):
617 self.slots.append(Slot(toolchains, args, progress, devnull,
618 make_cmd, reference_src_dir, db_queue))
620 def add(self, defconfig):
621 """Add a new subprocess if a vacant slot is found.
624 defconfig (str): defconfig name to be put into.
627 Return True on success or False on failure
629 for slot in self.slots:
630 if slot.add(defconfig):
635 """Check if there is a vacant slot.
638 Return True if at lease one vacant slot is found, False otherwise.
640 for slot in self.slots:
646 """Check if all slots are vacant.
649 Return True if all the slots are vacant, False otherwise.
652 for slot in self.slots:
657 def write_failed_boards(self):
658 """Show the results of processing"""
661 for slot in self.slots:
662 boards |= slot.get_failed_boards()
665 boards = '\n'.join(sorted(boards)) + '\n'
666 write_file(FAILED_LIST, boards)
669 class ReferenceSource:
671 """Reference source against which original configs should be parsed."""
673 def __init__(self, commit):
674 """Create a reference source directory based on a specified commit.
677 commit: commit to git-clone
679 self.src_dir = tempfile.mkdtemp()
680 print('Cloning git repo to a separate work directory...')
681 subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
683 rev = subprocess.check_output(['git', 'rev-parse', '--short',
685 print(f"Checkout '{rev}' to build the original autoconf.mk.")
686 subprocess.check_output(['git', 'checkout', commit],
687 stderr=subprocess.STDOUT, cwd=self.src_dir)
690 """Delete the reference source directory
692 This function makes sure the temporary directory is cleaned away
693 even if Python suddenly dies due to error. It should be done in here
694 because it is guaranteed the destructor is always invoked when the
695 instance of the class gets unreferenced.
697 shutil.rmtree(self.src_dir)
700 """Return the absolute path to the reference source directory."""
704 def move_config(args):
705 """Build database or sync config options to defconfig files.
708 args (Namespace): Program arguments
712 config_db (dict of configs for each defconfig):
713 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
716 value: Value of option
717 Progress: Progress indicator
720 db_queue = queue.Queue()
721 dbt = DatabaseThread(config_db, db_queue)
725 check_clean_directory()
728 # Get toolchains to use
729 toolchains = toolchain.Toolchains()
730 toolchains.GetSettings()
731 toolchains.Scan(verbose=False)
734 reference_src = ReferenceSource(args.git_ref)
735 reference_src_dir = reference_src.get_dir()
737 reference_src_dir = None
740 defconfigs = get_matched_defconfigs(args.defconfigs)
742 defconfigs = get_all_defconfigs()
744 col = terminal.Color(terminal.COLOR_NEVER if args.nocolour
745 else terminal.COLOR_IF_TERMINAL)
746 progress = Progress(col, len(defconfigs))
747 slots = Slots(toolchains, args, progress, reference_src_dir, db_queue)
749 # Main loop to process defconfig files:
750 # Add a new subprocess into a vacant slot.
751 # Sleep if there is no available slot.
752 for defconfig in defconfigs:
753 while not slots.add(defconfig):
754 while not slots.available():
755 # No available slot: sleep for a while
756 time.sleep(SLEEP_TIME)
758 # wait until all the subprocesses finish
759 while not slots.empty():
760 time.sleep(SLEEP_TIME)
762 slots.write_failed_boards()
765 return config_db, progress
767 def find_kconfig_rules(kconf, config, imply_config):
768 """Check whether a config has a 'select' or 'imply' keyword
771 kconf (Kconfiglib.Kconfig): Kconfig object
772 config (str): Name of config to check (without CONFIG_ prefix)
773 imply_config (str): Implying config (without CONFIG_ prefix) which may
774 or may not have an 'imply' for 'config')
777 Symbol object for 'config' if found, else None
779 sym = kconf.syms.get(imply_config)
781 for sel, _ in (sym.selects + sym.implies):
782 if sel.name == config:
786 def check_imply_rule(kconf, imply_config):
787 """Check if we can add an 'imply' option
789 This finds imply_config in the Kconfig and looks to see if it is possible
790 to add an 'imply' for 'config' to that part of the Kconfig.
793 kconf (Kconfiglib.Kconfig): Kconfig object
794 imply_config (str): Implying config (without CONFIG_ prefix) which may
795 or may not have an 'imply' for 'config')
799 str: filename of Kconfig file containing imply_config, or None if
801 int: line number within the Kconfig file, or 0 if none
802 str: message indicating the result
804 sym = kconf.syms.get(imply_config)
806 return 'cannot find sym'
809 return f'{len(nodes)} locations'
811 fname, linenum = node.filename, node.linenr
813 if cwd and fname.startswith(cwd):
814 fname = fname[len(cwd) + 1:]
815 file_line = f' at {fname}:{linenum}'
816 data = read_file(fname)
817 if data[linenum - 1] != f'config {imply_config}':
818 return None, 0, f'bad sym format {data[linenum]}{file_line})'
819 return fname, linenum, f'adding{file_line}'
821 def add_imply_rule(config, fname, linenum):
822 """Add a new 'imply' option to a Kconfig
825 config (str): config option to add an imply for (without CONFIG_ prefix)
826 fname (str): Kconfig filename to update
827 linenum (int): Line number to place the 'imply' before
830 Message indicating the result
832 file_line = f' at {fname}:{linenum}'
833 data = read_file(fname)
836 for offset, line in enumerate(data[linenum:]):
837 if line.strip().startswith('help') or not line:
838 data.insert(linenum + offset, f'\timply {config}')
839 write_file(fname, data)
840 return f'added{file_line}'
842 return 'could not insert%s'
844 (IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
848 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
849 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
850 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
852 IMPLY_NON_ARCH_BOARD,
853 'Allow Kconfig options outside arch/ and /board/ to imply'],
858 """Read in the config database
862 set of all config options seen (each a str)
863 set of all defconfigs seen (each a str)
864 dict of configs for each defconfig:
865 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
868 value: Value of option
869 dict of defconfigs for each config:
871 value: set of boards using that option
876 # key is defconfig name, value is dict of (CONFIG_xxx, value)
879 # Set of all config options we have seen
882 # Set of all defconfigs we have seen
883 all_defconfigs = set()
885 defconfig_db = collections.defaultdict(set)
887 for line in read_file(CONFIG_DATABASE):
889 if not line: # Separator between defconfigs
890 config_db[defconfig] = configs
891 all_defconfigs.add(defconfig)
893 elif line[0] == ' ': # CONFIG line
894 config, value = line.strip().split('=', 1)
895 configs[config] = value
896 defconfig_db[config].add(defconfig)
897 all_configs.add(config)
898 else: # New defconfig
901 return all_configs, all_defconfigs, config_db, defconfig_db
904 def do_imply_config(config_list, add_imply, imply_flags, skip_added,
905 check_kconfig=True, find_superset=False):
906 """Find CONFIG options which imply those in the list
908 Some CONFIG options can be implied by others and this can help to reduce
909 the size of the defconfig files. For example, CONFIG_X86 implies
910 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
911 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
912 each of the x86 defconfig files.
914 This function uses the qconfig database to find such options. It
915 displays a list of things that could possibly imply those in the list.
916 The algorithm ignores any that start with CONFIG_TARGET since these
917 typically refer to only a few defconfigs (often one). It also does not
918 display a config with less than 5 defconfigs.
920 The algorithm works using sets. For each target config in config_list:
921 - Get the set 'defconfigs' which use that target config
922 - For each config (from a list of all configs):
923 - Get the set 'imply_defconfig' of defconfigs which use that config
925 - If imply_defconfigs contains anything not in defconfigs then
926 this config does not imply the target config
929 config_list (list of str): List of CONFIG options to check
930 add_imply (bool): Automatically add an 'imply' for each config.
931 imply_flags (int): Flags which control which implying configs are allowed
933 skip_added (bool): Don't show options which already have an imply added.
934 check_kconfig (bool): Check if implied symbols already have an 'imply' or
935 'select' for the target config, and show this information if so.
936 find_superset (bool): True to look for configs which are a superset of those
937 already found. So for example if CONFIG_EXYNOS5 implies an option,
938 but CONFIG_EXYNOS covers a larger set of defconfigs and also
939 implies that option, this will drop the former in favour of the
940 latter. In practice this option has not proved very used.
943 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
944 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
946 kconf = scan_kconfig() if check_kconfig else None
947 if add_imply and add_imply != 'all':
948 add_imply = add_imply.split(',')
950 all_configs, all_defconfigs, _, defconfig_db = read_database()
952 # Work through each target config option in turn, independently
953 for config in config_list:
954 defconfigs = defconfig_db.get(config)
956 print(f'{config} not found in any defconfig')
959 # Get the set of defconfigs without this one (since a config cannot
961 non_defconfigs = all_defconfigs - defconfigs
962 num_defconfigs = len(defconfigs)
963 print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
965 # This will hold the results: key=config, value=defconfigs containing it
967 rest_configs = all_configs - set([config])
969 # Look at every possible config, except the target one
970 for imply_config in rest_configs:
971 if 'ERRATUM' in imply_config:
973 if not imply_flags & IMPLY_CMD:
974 if 'CONFIG_CMD' in imply_config:
976 if not imply_flags & IMPLY_TARGET:
977 if 'CONFIG_TARGET' in imply_config:
980 # Find set of defconfigs that have this config
981 imply_defconfig = defconfig_db[imply_config]
983 # Get the intersection of this with defconfigs containing the
985 common_defconfigs = imply_defconfig & defconfigs
987 # Get the set of defconfigs containing this config which DO NOT
988 # also contain the taret config. If this set is non-empty it means
989 # that this config affects other defconfigs as well as (possibly)
990 # the ones affected by the target config. This means it implies
991 # things we don't want to imply.
992 not_common_defconfigs = imply_defconfig & non_defconfigs
993 if not_common_defconfigs:
996 # If there are common defconfigs, imply_config may be useful
997 if common_defconfigs:
1000 for prev in list(imply_configs.keys()):
1001 prev_count = len(imply_configs[prev])
1002 count = len(common_defconfigs)
1003 if (prev_count > count and
1004 (imply_configs[prev] & common_defconfigs ==
1005 common_defconfigs)):
1006 # skip imply_config because prev is a superset
1009 if count > prev_count:
1010 # delete prev because imply_config is a superset
1011 del imply_configs[prev]
1013 imply_configs[imply_config] = common_defconfigs
1015 # Now we have a dict imply_configs of configs which imply each config
1016 # The value of each dict item is the set of defconfigs containing that
1017 # config. Rank them so that we print the configs that imply the largest
1018 # number of defconfigs first.
1019 ranked_iconfigs = sorted(imply_configs,
1020 key=lambda k: len(imply_configs[k]), reverse=True)
1023 add_list = collections.defaultdict(list)
1024 for iconfig in ranked_iconfigs:
1025 num_common = len(imply_configs[iconfig])
1027 # Don't bother if there are less than 5 defconfigs affected.
1028 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
1030 missing = defconfigs - imply_configs[iconfig]
1031 missing_str = ', '.join(missing) if missing else 'all'
1035 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1036 iconfig[CONFIG_LEN:])
1041 fname, linenum = nodes[0].filename, nodes[0].linenr
1042 if cwd and fname.startswith(cwd):
1043 fname = fname[len(cwd) + 1:]
1044 kconfig_info = f'{fname}:{linenum}'
1048 sym = kconf.syms.get(iconfig[CONFIG_LEN:])
1053 fname, linenum = nodes[0].filename, nodes[0].linenr
1054 if cwd and fname.startswith(cwd):
1055 fname = fname[len(cwd) + 1:]
1056 in_arch_board = not sym or (fname.startswith('arch') or
1057 fname.startswith('board'))
1058 if (not in_arch_board and
1059 not imply_flags & IMPLY_NON_ARCH_BOARD):
1062 if add_imply and (add_imply == 'all' or
1063 iconfig in add_imply):
1064 fname, linenum, kconfig_info = (check_imply_rule(kconf,
1065 iconfig[CONFIG_LEN:]))
1067 add_list[fname].append(linenum)
1069 if show and kconfig_info != 'skip':
1070 print(f'{num_common:5} : '
1071 f'{iconfig.ljust(30)}{kconfig_info.ljust(25)} {missing_str}')
1073 # Having collected a list of things to add, now we add them. We process
1074 # each file from the largest line number to the smallest so that
1075 # earlier additions do not affect our line numbers. E.g. if we added an
1076 # imply at line 20 it would change the position of each line after
1078 for fname, linenums in add_list.items():
1079 for linenum in sorted(linenums, reverse=True):
1080 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
1082 def defconfig_matches(configs, re_match, re_val):
1083 """Check if any CONFIG option matches a regex
1085 The match must be complete, i.e. from the start to end of the CONFIG option.
1088 configs (dict): Dict of CONFIG options:
1090 value: Value of option
1091 re_match (re.Pattern): Match to check
1092 re_val (re.Pattern): Regular expression to check against value (or None)
1095 bool: True if any CONFIG matches the regex
1097 for cfg, val in configs.items():
1098 if re_match.fullmatch(cfg):
1099 if not re_val or re_val.fullmatch(val):
1103 def do_find_config(config_list, list_format):
1104 """Find boards with a given combination of CONFIGs
1107 config_list (list of str): List of CONFIG options to check (each a regex
1108 consisting of a config option, with or without a CONFIG_ prefix. If
1109 an option is preceded by a tilde (~) then it must be false,
1110 otherwise it must be true)
1111 list_format (bool): True to write in 'list' format, one board name per
1115 int: exit code (0 for success)
1117 _, all_defconfigs, config_db, _ = read_database()
1119 # Start with all defconfigs
1120 out = all_defconfigs
1122 # Work through each config in turn
1123 for item in config_list:
1124 # Get the real config name and whether we want this config or not
1133 cfg, val = cfg.split('=', maxsplit=1)
1134 re_val = re.compile(val)
1136 # Search everything that is still in the running. If it has a config
1137 # that we want, or doesn't have one that we don't, add it into the
1138 # running for the next stage
1141 re_match = re.compile(cfg)
1142 for defc in in_list:
1143 has_cfg = defconfig_matches(config_db[defc], re_match, re_val)
1147 print(f'{len(out)} matches')
1148 sep = '\n' if list_format else ' '
1149 print(sep.join(item.split('_defconfig')[0] for item in sorted(list(out))))
1153 def prefix_config(cfg):
1154 """Prefix a config with CONFIG_ if needed
1156 This handles ~ operator, which indicates that the CONFIG should be disabled
1158 >>> prefix_config('FRED')
1160 >>> prefix_config('CONFIG_FRED')
1162 >>> prefix_config('~FRED')
1164 >>> prefix_config('~CONFIG_FRED')
1166 >>> prefix_config('A123')
1173 if not cfg.startswith('CONFIG_'):
1174 cfg = 'CONFIG_' + cfg
1178 RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1179 RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1180 RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1181 RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
1184 """Tracks whether a config relates to SPL or not"""
1185 def __init__(self, cfg, is_spl, fname, rest):
1186 """Set up a new ConfigUse
1189 cfg (str): CONFIG option, without any CONFIG_ or SPL_ prefix
1190 is_spl (bool): True if this option relates to SPL
1191 fname (str): Makefile filename where the CONFIG option was found
1192 rest (str): Line of the Makefile
1195 self.is_spl = is_spl
1200 return hash((self.cfg, self.is_spl))
1202 def scan_makefiles(fnames):
1203 """Scan Makefiles looking for Kconfig options
1205 Looks for uses of CONFIG options in Makefiles
1208 fnames (list of tuple):
1209 str: Makefile filename where the option was found
1210 str: Line of the Makefile
1215 key (ConfigUse): object
1216 value (list of str): matching lines
1217 dict: Uses by filename
1219 value (set of ConfigUse): uses in that filename
1221 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1223 >>> RE_MK_CONFIGS.search('CONFIG_$(XPL_)MARY').groups()
1225 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1226 ('$(SPL_TPL_)', 'MARY')
1228 all_uses = collections.defaultdict(list)
1230 for fname, rest in fnames:
1231 m_iter = RE_MK_CONFIGS.finditer(rest)
1233 real_opt = mat.group(2)
1239 use = ConfigUse(real_opt, is_spl, fname, rest)
1240 if fname not in fname_uses:
1241 fname_uses[fname] = set()
1242 fname_uses[fname].add(use)
1243 all_uses[use].append(rest)
1244 return all_uses, fname_uses
1247 def scan_src_files(fnames):
1248 """Scan source files (other than Makefiles) looking for Kconfig options
1250 Looks for uses of CONFIG options
1253 fnames (list of tuple):
1254 str: Makefile filename where the option was found
1255 str: Line of the Makefile
1260 key (ConfigUse): object
1261 value (list of str): matching lines
1262 dict: Uses by filename
1264 value (set of ConfigUse): uses in that filename
1266 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1268 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1270 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1276 def add_uses(m_iter, is_spl):
1278 real_opt = mat.group(1)
1281 use = ConfigUse(real_opt, is_spl, fname, rest)
1282 if fname not in fname_uses:
1283 fname_uses[fname] = set()
1284 fname_uses[fname].add(use)
1285 all_uses[use].append(rest)
1287 all_uses = collections.defaultdict(list)
1289 for fname, rest in fnames:
1290 m_iter = RE_C_CONFIGS.finditer(rest)
1291 add_uses(m_iter, False)
1293 m_iter2 = RE_CONFIG_IS.finditer(rest)
1294 add_uses(m_iter2, True)
1296 return all_uses, fname_uses
1299 MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1301 def do_scan_source(path, do_update):
1302 """Scan the source tree for Kconfig inconsistencies
1305 path (str): Path to source tree
1306 do_update (bool) : True to write to scripts/kconf_... files
1308 def is_not_proper(name):
1309 for prefix in SPL_PREFIXES:
1310 if name.startswith(prefix):
1311 return name[len(prefix):]
1314 def check_not_found(all_uses, spl_mode):
1315 """Check for Kconfig options mentioned in the source but not in Kconfig
1319 key (ConfigUse): object
1320 value (list of str): matching lines
1321 spl_mode (int): If MODE_SPL, look at source code which implies
1322 an SPL_ option, but for which there is none;
1323 for MOD_PROPER, look at source code which implies a Proper
1324 option (i.e. use of CONFIG_IS_ENABLED() or $(XPL_) or
1325 $(SPL_TPL_) but for which there none;
1326 if MODE_NORMAL, ignore SPL
1330 key (str): CONFIG name (without 'CONFIG_' prefix
1331 value (list of ConfigUse): List of uses of this CONFIG
1333 # Make sure we know about all the options
1334 not_found = collections.defaultdict(list)
1335 for use, _ in all_uses.items():
1337 if name in IGNORE_SYMS:
1341 if spl_mode == MODE_SPL:
1344 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1345 # find at least one SPL symbol
1347 for prefix in SPL_PREFIXES:
1348 try_name = prefix + name
1349 sym = kconf.syms.get(try_name)
1353 not_found[f'SPL_{name}'].append(use)
1355 elif spl_mode == MODE_PROPER:
1356 # Try to find the Proper version of this symbol, i.e. without
1358 proper_name = is_not_proper(name)
1361 elif not use.is_spl:
1364 sym = kconf.syms.get(name)
1366 proper_name = is_not_proper(name)
1369 sym = kconf.syms.get(name)
1371 for prefix in SPL_PREFIXES:
1372 try_name = prefix + name
1373 sym = kconf.syms.get(try_name)
1377 not_found[name].append(use)
1380 sym = kconf.syms.get(name)
1381 if not sym and check:
1382 not_found[name].append(use)
1385 def show_uses(uses):
1386 """Show a list of uses along with their filename and code snippet
1390 key (str): CONFIG name (without 'CONFIG_' prefix
1391 value (list of ConfigUse): List of uses of this CONFIG
1393 for name in sorted(uses):
1394 print(f'{name}: ', end='')
1395 for i, use in enumerate(uses[name]):
1396 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1399 print('Scanning Kconfig')
1400 kconf = scan_kconfig()
1401 print(f'Scanning source in {path}')
1402 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1403 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
1404 out, _ = proc.communicate()
1405 lines = out.splitlines()
1406 re_fname = re.compile('^([^:]*):(.*)')
1410 linestr = line.decode('utf-8')
1411 m_fname = re_fname.search(linestr)
1414 fname, rest = m_fname.groups()
1415 dirname, leaf = os.path.split(fname)
1416 root, ext = os.path.splitext(leaf)
1417 if ext == '.autoconf':
1419 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1421 src_list.append([fname, rest])
1422 elif 'Makefile' in root or ext == '.mk':
1423 mk_list.append([fname, rest])
1424 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1426 elif 'Kconfig' in root or 'Kbuild' in root:
1428 elif 'README' in root:
1430 elif dirname in ['configs']:
1432 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1435 print(f'Not sure how to handle file {fname}')
1437 # Scan the Makefiles
1438 all_uses, _ = scan_makefiles(mk_list)
1440 spl_not_found = set()
1441 proper_not_found = set()
1443 # Make sure we know about all the options
1444 print('\nCONFIG options present in Makefiles but not Kconfig:')
1445 not_found = check_not_found(all_uses, MODE_NORMAL)
1446 show_uses(not_found)
1448 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1449 not_found = check_not_found(all_uses, MODE_SPL)
1450 show_uses(not_found)
1451 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
1453 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1454 not_found = check_not_found(all_uses, MODE_PROPER)
1455 show_uses(not_found)
1456 proper_not_found |= {not_found.keys()}
1458 # Scan the source code
1459 all_uses, _ = scan_src_files(src_list)
1461 # Make sure we know about all the options
1462 print('\nCONFIG options present in source but not Kconfig:')
1463 not_found = check_not_found(all_uses, MODE_NORMAL)
1464 show_uses(not_found)
1466 print('\nCONFIG options present in source but not Kconfig (SPL):')
1467 not_found = check_not_found(all_uses, MODE_SPL)
1468 show_uses(not_found)
1469 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
1471 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1472 not_found = check_not_found(all_uses, MODE_PROPER)
1473 show_uses(not_found)
1474 proper_not_found |= {not_found.keys()}
1476 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1477 for item in sorted(spl_not_found):
1480 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1481 for item in sorted(proper_not_found):
1484 # Write out the updated information
1486 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1487 encoding='utf-8') as out:
1488 print('# These options should not be enabled in SPL builds\n',
1490 for item in sorted(spl_not_found):
1491 print(item, file=out)
1492 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1493 encoding='utf-8') as out:
1494 print('# These options should not be enabled in Proper builds\n',
1496 for item in sorted(proper_not_found):
1497 print(item, file=out)
1502 """Parse the program arguments
1506 argparse.ArgumentParser: parser
1507 argparse.Namespace: Parsed arguments
1510 cpu_count = multiprocessing.cpu_count()
1511 except NotImplementedError:
1514 epilog = '''Move config options from headers to defconfig files. See
1515 doc/develop/moveconfig.rst for documentation.'''
1517 parser = ArgumentParser(epilog=epilog)
1518 # Add arguments here
1519 parser.add_argument('-a', '--add-imply', type=str, default='',
1520 help='comma-separated list of CONFIG options to add '
1521 "an 'imply' statement to for the CONFIG in -i")
1522 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
1523 help="don't show options which are already marked as "
1525 parser.add_argument('-b', '--build-db', action='store_true', default=False,
1526 help='build a CONFIG database')
1527 parser.add_argument('-C', '--commit', action='store_true', default=False,
1528 help='Create a git commit for the operation')
1529 parser.add_argument('--nocolour', action='store_true', default=False,
1530 help="don't display the log in colour")
1531 parser.add_argument('-d', '--defconfigs', type=str,
1532 help='a file containing a list of defconfigs to move, '
1533 "one per line (for example 'snow_defconfig') "
1534 "or '-' to read from stdin")
1535 parser.add_argument('-e', '--exit-on-error', action='store_true',
1537 help='exit immediately on any error')
1538 parser.add_argument('-f', '--find', action='store_true', default=False,
1539 help='Find boards with a given config combination')
1540 parser.add_argument('-i', '--imply', action='store_true', default=False,
1541 help='find options which imply others')
1542 parser.add_argument('-l', '--list', action='store_true', default=False,
1543 help='Show a sorted list of board names, one per line')
1544 parser.add_argument('-I', '--imply-flags', type=str, default='',
1545 help="control the -i option ('help' for help")
1546 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
1547 help='the number of jobs to run simultaneously')
1548 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
1549 help='perform a trial run (show log with no changes)')
1550 parser.add_argument('-r', '--git-ref', type=str,
1551 help='the git ref to clone for building the autoconf.mk')
1552 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
1553 help='force sync by savedefconfig')
1554 parser.add_argument('-S', '--spl', action='store_true', default=False,
1555 help='parse config options defined for SPL build')
1556 parser.add_argument('--scan-source', action='store_true', default=False,
1557 help='scan source for uses of CONFIG options')
1558 parser.add_argument('-t', '--test', action='store_true', default=False,
1559 help='run unit tests')
1560 parser.add_argument('-y', '--yes', action='store_true', default=False,
1561 help="respond 'yes' to any prompts")
1562 parser.add_argument('-u', '--update', action='store_true', default=False,
1563 help="update scripts/ files (use with --scan-source)")
1564 parser.add_argument('-v', '--verbose', action='store_true', default=False,
1565 help='show any build errors as boards are built')
1566 parser.add_argument('configs', nargs='*')
1568 args = parser.parse_args()
1569 if not any((args.force_sync, args.build_db, args.imply, args.find,
1570 args.scan_source, args.test)):
1571 parser.print_usage()
1578 """Handle checking for flags which imply others
1581 args (argparse.Namespace): Program arguments
1584 int: exit code (0 for success)
1587 if args.imply_flags == 'all':
1590 elif args.imply_flags:
1591 for flag in args.imply_flags.split(','):
1592 bad = flag not in IMPLY_FLAGS
1594 print(f"Invalid flag '{flag}'")
1595 if flag == 'help' or bad:
1596 print("Imply flags: (separate with ',')")
1597 for name, info in IMPLY_FLAGS.items():
1598 print(f' {name.ljust(15)}: {info[1]}')
1600 imply_flags |= IMPLY_FLAGS[flag][0]
1602 do_imply_config(args.configs, args.add_imply, imply_flags, args.skip_added)
1606 def add_commit(configs):
1607 """Add a commit indicating which CONFIG options were converted
1610 configs (list of str) List of CONFIG_... options to process
1612 subprocess.call(['git', 'add', '-u'])
1614 part = 'et al ' if len(configs) > 1 else ''
1615 msg = f'Convert {configs[0]} {part}to Kconfig'
1616 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1617 '\n '.join(configs))
1619 msg = 'configs: Resync with savedefconfig'
1620 msg += '\n\nRsync all defconfig files using moveconfig.py'
1621 subprocess.call(['git', 'commit', '-s', '-m', msg])
1624 def write_db(config_db, progress):
1625 """Write the database to a file
1628 config_db (dict of dict): configs for each defconfig
1629 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
1632 value: Value of option
1633 progress (Progress): Progress indicator.
1636 int: exit code (0 for success)
1639 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
1640 for defconfig, configs in config_db.items():
1641 outf.write(f'{defconfig}\n')
1642 for config in sorted(configs.keys()):
1643 outf.write(f' {config}={configs[config]}\n')
1646 col.RED if progress.failed else col.GREEN,
1647 f'{progress.failure_msg}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1651 def move_done(progress):
1652 """Write a message indicating that the move is done
1655 progress (Progress): Progress indicator.
1658 int: exit code (0 for success)
1662 print(col.build(col.RED, f'{progress.failure_msg}see {FAILED_LIST}', True))
1664 # Add enough spaces to overwrite the progress indicator
1666 col.GREEN, f'{progress.total} processed ', bright=True))
1670 """Run doctests and unit tests (so far there are no unit tests)"""
1671 sys.argv = [sys.argv[0]]
1672 fail, _ = doctest.testmod()
1681 parser, args = parse_args()
1682 check_top_directory()
1684 # prefix the option name with CONFIG_ if missing
1685 args.configs = [prefix_config(cfg) for cfg in args.configs]
1689 if args.scan_source:
1690 return do_scan_source(os.getcwd(), args.update)
1693 parser.print_usage()
1697 return do_find_config(args.configs, args.list)
1699 config_db, progress = move_config(args)
1702 add_commit(args.configs)
1705 return write_db(config_db, progress)
1706 return move_done(progress)
1709 if __name__ == '__main__':