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', ]
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):
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)
1113 int: exit code (0 for success)
1115 _, all_defconfigs, config_db, _ = read_database()
1117 # Start with all defconfigs
1118 out = all_defconfigs
1120 # Work through each config in turn
1121 for item in config_list:
1122 # Get the real config name and whether we want this config or not
1131 cfg, val = cfg.split('=', maxsplit=1)
1132 re_val = re.compile(val)
1134 # Search everything that is still in the running. If it has a config
1135 # that we want, or doesn't have one that we don't, add it into the
1136 # running for the next stage
1139 re_match = re.compile(cfg)
1140 for defc in in_list:
1141 has_cfg = defconfig_matches(config_db[defc], re_match, re_val)
1144 print(f'{len(out)} matches')
1145 print(' '.join(item.split('_defconfig')[0] for item in out))
1149 def prefix_config(cfg):
1150 """Prefix a config with CONFIG_ if needed
1152 This handles ~ operator, which indicates that the CONFIG should be disabled
1154 >>> prefix_config('FRED')
1156 >>> prefix_config('CONFIG_FRED')
1158 >>> prefix_config('~FRED')
1160 >>> prefix_config('~CONFIG_FRED')
1162 >>> prefix_config('A123')
1169 if not cfg.startswith('CONFIG_'):
1170 cfg = 'CONFIG_' + cfg
1174 RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1175 RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1176 RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1177 RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
1180 """Tracks whether a config relates to SPL or not"""
1181 def __init__(self, cfg, is_spl, fname, rest):
1182 """Set up a new ConfigUse
1185 cfg (str): CONFIG option, without any CONFIG_ or SPL_ prefix
1186 is_spl (bool): True if this option relates to SPL
1187 fname (str): Makefile filename where the CONFIG option was found
1188 rest (str): Line of the Makefile
1191 self.is_spl = is_spl
1196 return hash((self.cfg, self.is_spl))
1198 def scan_makefiles(fnames):
1199 """Scan Makefiles looking for Kconfig options
1201 Looks for uses of CONFIG options in Makefiles
1204 fnames (list of tuple):
1205 str: Makefile filename where the option was found
1206 str: Line of the Makefile
1211 key (ConfigUse): object
1212 value (list of str): matching lines
1213 dict: Uses by filename
1215 value (set of ConfigUse): uses in that filename
1217 >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1219 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1221 >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1222 ('$(SPL_TPL_)', 'MARY')
1224 all_uses = collections.defaultdict(list)
1226 for fname, rest in fnames:
1227 m_iter = RE_MK_CONFIGS.finditer(rest)
1229 real_opt = mat.group(2)
1235 use = ConfigUse(real_opt, is_spl, fname, rest)
1236 if fname not in fname_uses:
1237 fname_uses[fname] = set()
1238 fname_uses[fname].add(use)
1239 all_uses[use].append(rest)
1240 return all_uses, fname_uses
1243 def scan_src_files(fnames):
1244 """Scan source files (other than Makefiles) looking for Kconfig options
1246 Looks for uses of CONFIG options
1249 fnames (list of tuple):
1250 str: Makefile filename where the option was found
1251 str: Line of the Makefile
1256 key (ConfigUse): object
1257 value (list of str): matching lines
1258 dict: Uses by filename
1260 value (set of ConfigUse): uses in that filename
1262 >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1264 >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1266 >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1272 def add_uses(m_iter, is_spl):
1274 real_opt = mat.group(1)
1277 use = ConfigUse(real_opt, is_spl, fname, rest)
1278 if fname not in fname_uses:
1279 fname_uses[fname] = set()
1280 fname_uses[fname].add(use)
1281 all_uses[use].append(rest)
1283 all_uses = collections.defaultdict(list)
1285 for fname, rest in fnames:
1286 m_iter = RE_C_CONFIGS.finditer(rest)
1287 add_uses(m_iter, False)
1289 m_iter2 = RE_CONFIG_IS.finditer(rest)
1290 add_uses(m_iter2, True)
1292 return all_uses, fname_uses
1295 MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1297 def do_scan_source(path, do_update):
1298 """Scan the source tree for Kconfig inconsistencies
1301 path (str): Path to source tree
1302 do_update (bool) : True to write to scripts/kconf_... files
1304 def is_not_proper(name):
1305 for prefix in SPL_PREFIXES:
1306 if name.startswith(prefix):
1307 return name[len(prefix):]
1310 def check_not_found(all_uses, spl_mode):
1311 """Check for Kconfig options mentioned in the source but not in Kconfig
1315 key (ConfigUse): object
1316 value (list of str): matching lines
1317 spl_mode (int): If MODE_SPL, look at source code which implies
1318 an SPL_ option, but for which there is none;
1319 for MOD_PROPER, look at source code which implies a Proper
1320 option (i.e. use of CONFIG_IS_ENABLED() or $(SPL_) or
1321 $(SPL_TPL_) but for which there none;
1322 if MODE_NORMAL, ignore SPL
1326 key (str): CONFIG name (without 'CONFIG_' prefix
1327 value (list of ConfigUse): List of uses of this CONFIG
1329 # Make sure we know about all the options
1330 not_found = collections.defaultdict(list)
1331 for use, _ in all_uses.items():
1333 if name in IGNORE_SYMS:
1337 if spl_mode == MODE_SPL:
1340 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1341 # find at least one SPL symbol
1343 for prefix in SPL_PREFIXES:
1344 try_name = prefix + name
1345 sym = kconf.syms.get(try_name)
1349 not_found[f'SPL_{name}'].append(use)
1351 elif spl_mode == MODE_PROPER:
1352 # Try to find the Proper version of this symbol, i.e. without
1354 proper_name = is_not_proper(name)
1357 elif not use.is_spl:
1360 sym = kconf.syms.get(name)
1362 proper_name = is_not_proper(name)
1365 sym = kconf.syms.get(name)
1367 for prefix in SPL_PREFIXES:
1368 try_name = prefix + name
1369 sym = kconf.syms.get(try_name)
1373 not_found[name].append(use)
1376 sym = kconf.syms.get(name)
1377 if not sym and check:
1378 not_found[name].append(use)
1381 def show_uses(uses):
1382 """Show a list of uses along with their filename and code snippet
1386 key (str): CONFIG name (without 'CONFIG_' prefix
1387 value (list of ConfigUse): List of uses of this CONFIG
1389 for name in sorted(uses):
1390 print(f'{name}: ', end='')
1391 for i, use in enumerate(uses[name]):
1392 print(f'{" " if i else ""}{use.fname}: {use.rest.strip()}')
1395 print('Scanning Kconfig')
1396 kconf = scan_kconfig()
1397 print(f'Scanning source in {path}')
1398 args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1399 with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
1400 out, _ = proc.communicate()
1401 lines = out.splitlines()
1402 re_fname = re.compile('^([^:]*):(.*)')
1406 linestr = line.decode('utf-8')
1407 m_fname = re_fname.search(linestr)
1410 fname, rest = m_fname.groups()
1411 dirname, leaf = os.path.split(fname)
1412 root, ext = os.path.splitext(leaf)
1413 if ext == '.autoconf':
1415 elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1417 src_list.append([fname, rest])
1418 elif 'Makefile' in root or ext == '.mk':
1419 mk_list.append([fname, rest])
1420 elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1422 elif 'Kconfig' in root or 'Kbuild' in root:
1424 elif 'README' in root:
1426 elif dirname in ['configs']:
1428 elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1431 print(f'Not sure how to handle file {fname}')
1433 # Scan the Makefiles
1434 all_uses, _ = scan_makefiles(mk_list)
1436 spl_not_found = set()
1437 proper_not_found = set()
1439 # Make sure we know about all the options
1440 print('\nCONFIG options present in Makefiles but not Kconfig:')
1441 not_found = check_not_found(all_uses, MODE_NORMAL)
1442 show_uses(not_found)
1444 print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1445 not_found = check_not_found(all_uses, MODE_SPL)
1446 show_uses(not_found)
1447 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
1449 print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1450 not_found = check_not_found(all_uses, MODE_PROPER)
1451 show_uses(not_found)
1452 proper_not_found |= {not_found.keys()}
1454 # Scan the source code
1455 all_uses, _ = scan_src_files(src_list)
1457 # Make sure we know about all the options
1458 print('\nCONFIG options present in source but not Kconfig:')
1459 not_found = check_not_found(all_uses, MODE_NORMAL)
1460 show_uses(not_found)
1462 print('\nCONFIG options present in source but not Kconfig (SPL):')
1463 not_found = check_not_found(all_uses, MODE_SPL)
1464 show_uses(not_found)
1465 spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
1467 print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1468 not_found = check_not_found(all_uses, MODE_PROPER)
1469 show_uses(not_found)
1470 proper_not_found |= {not_found.keys()}
1472 print('\nCONFIG options used as SPL but without an SPL_ variant:')
1473 for item in sorted(spl_not_found):
1476 print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1477 for item in sorted(proper_not_found):
1480 # Write out the updated information
1482 with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1483 encoding='utf-8') as out:
1484 print('# These options should not be enabled in SPL builds\n',
1486 for item in sorted(spl_not_found):
1487 print(item, file=out)
1488 with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1489 encoding='utf-8') as out:
1490 print('# These options should not be enabled in Proper builds\n',
1492 for item in sorted(proper_not_found):
1493 print(item, file=out)
1498 """Parse the program arguments
1502 argparse.ArgumentParser: parser
1503 argparse.Namespace: Parsed arguments
1506 cpu_count = multiprocessing.cpu_count()
1507 except NotImplementedError:
1510 epilog = '''Move config options from headers to defconfig files. See
1511 doc/develop/moveconfig.rst for documentation.'''
1513 parser = ArgumentParser(epilog=epilog)
1514 # Add arguments here
1515 parser.add_argument('-a', '--add-imply', type=str, default='',
1516 help='comma-separated list of CONFIG options to add '
1517 "an 'imply' statement to for the CONFIG in -i")
1518 parser.add_argument('-A', '--skip-added', action='store_true', default=False,
1519 help="don't show options which are already marked as "
1521 parser.add_argument('-b', '--build-db', action='store_true', default=False,
1522 help='build a CONFIG database')
1523 parser.add_argument('-C', '--commit', action='store_true', default=False,
1524 help='Create a git commit for the operation')
1525 parser.add_argument('--nocolour', action='store_true', default=False,
1526 help="don't display the log in colour")
1527 parser.add_argument('-d', '--defconfigs', type=str,
1528 help='a file containing a list of defconfigs to move, '
1529 "one per line (for example 'snow_defconfig') "
1530 "or '-' to read from stdin")
1531 parser.add_argument('-e', '--exit-on-error', action='store_true',
1533 help='exit immediately on any error')
1534 parser.add_argument('-f', '--find', action='store_true', default=False,
1535 help='Find boards with a given config combination')
1536 parser.add_argument('-i', '--imply', action='store_true', default=False,
1537 help='find options which imply others')
1538 parser.add_argument('-I', '--imply-flags', type=str, default='',
1539 help="control the -i option ('help' for help")
1540 parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
1541 help='the number of jobs to run simultaneously')
1542 parser.add_argument('-n', '--dry-run', action='store_true', default=False,
1543 help='perform a trial run (show log with no changes)')
1544 parser.add_argument('-r', '--git-ref', type=str,
1545 help='the git ref to clone for building the autoconf.mk')
1546 parser.add_argument('-s', '--force-sync', action='store_true', default=False,
1547 help='force sync by savedefconfig')
1548 parser.add_argument('-S', '--spl', action='store_true', default=False,
1549 help='parse config options defined for SPL build')
1550 parser.add_argument('--scan-source', action='store_true', default=False,
1551 help='scan source for uses of CONFIG options')
1552 parser.add_argument('-t', '--test', action='store_true', default=False,
1553 help='run unit tests')
1554 parser.add_argument('-y', '--yes', action='store_true', default=False,
1555 help="respond 'yes' to any prompts")
1556 parser.add_argument('-u', '--update', action='store_true', default=False,
1557 help="update scripts/ files (use with --scan-source)")
1558 parser.add_argument('-v', '--verbose', action='store_true', default=False,
1559 help='show any build errors as boards are built')
1560 parser.add_argument('configs', nargs='*')
1562 args = parser.parse_args()
1563 if not any((args.force_sync, args.build_db, args.imply, args.find,
1564 args.scan_source, args.test)):
1565 parser.print_usage()
1572 """Handle checking for flags which imply others
1575 args (argparse.Namespace): Program arguments
1578 int: exit code (0 for success)
1581 if args.imply_flags == 'all':
1584 elif args.imply_flags:
1585 for flag in args.imply_flags.split(','):
1586 bad = flag not in IMPLY_FLAGS
1588 print(f"Invalid flag '{flag}'")
1589 if flag == 'help' or bad:
1590 print("Imply flags: (separate with ',')")
1591 for name, info in IMPLY_FLAGS.items():
1592 print(f' {name:-15s}: {info[1]}')
1594 imply_flags |= IMPLY_FLAGS[flag][0]
1596 do_imply_config(args.configs, args.add_imply, imply_flags, args.skip_added)
1600 def add_commit(configs):
1601 """Add a commit indicating which CONFIG options were converted
1604 configs (list of str) List of CONFIG_... options to process
1606 subprocess.call(['git', 'add', '-u'])
1608 part = 'et al ' if len(configs) > 1 else ''
1609 msg = f'Convert {configs[0]} {part}to Kconfig'
1610 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' %
1611 '\n '.join(configs))
1613 msg = 'configs: Resync with savedefconfig'
1614 msg += '\n\nRsync all defconfig files using moveconfig.py'
1615 subprocess.call(['git', 'commit', '-s', '-m', msg])
1618 def write_db(config_db, progress):
1619 """Write the database to a file
1622 config_db (dict of dict): configs for each defconfig
1623 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
1626 value: Value of option
1627 progress (Progress): Progress indicator.
1630 int: exit code (0 for success)
1633 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
1634 for defconfig, configs in config_db.items():
1635 outf.write(f'{defconfig}\n')
1636 for config in sorted(configs.keys()):
1637 outf.write(f' {config}={configs[config]}\n')
1640 col.RED if progress.failed else col.GREEN,
1641 f'{progress.failure_msg}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1645 def move_done(progress):
1646 """Write a message indicating that the move is done
1649 progress (Progress): Progress indicator.
1652 int: exit code (0 for success)
1656 print(col.build(col.RED, f'{progress.failure_msg}see {FAILED_LIST}', True))
1658 # Add enough spaces to overwrite the progress indicator
1660 col.GREEN, f'{progress.total} processed ', bright=True))
1664 """Run doctests and unit tests (so far there are no unit tests)"""
1665 sys.argv = [sys.argv[0]]
1666 fail, _ = doctest.testmod()
1675 parser, args = parse_args()
1676 check_top_directory()
1678 # prefix the option name with CONFIG_ if missing
1679 args.configs = [prefix_config(cfg) for cfg in args.configs]
1683 if args.scan_source:
1684 return do_scan_source(os.getcwd(), args.update)
1687 parser.print_usage()
1691 return do_find_config(args.configs)
1693 config_db, progress = move_config(args)
1696 add_commit(args.configs)
1699 return write_db(config_db, progress)
1700 return move_done(progress)
1703 if __name__ == '__main__':