]> Git Repo - u-boot.git/blob - tools/qconfig.py
qconfig: Allow searching for CONFIG values
[u-boot.git] / tools / qconfig.py
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: GPL-2.0+
3
4 """Build and query a Kconfig database for boards.
5
6 See doc/develop/qconfig.rst for documentation.
7
8 Author: Masahiro Yamada <[email protected]>
9 Author: Simon Glass <[email protected]>
10 """
11
12 from argparse import ArgumentParser
13 import collections
14 from contextlib import ExitStack
15 import doctest
16 import filecmp
17 import fnmatch
18 import glob
19 import multiprocessing
20 import os
21 import queue
22 import re
23 import shutil
24 import subprocess
25 import sys
26 import tempfile
27 import threading
28 import time
29 import unittest
30
31 from buildman import bsettings
32 from buildman import kconfiglib
33 from buildman import toolchain
34 from u_boot_pylib import terminal
35
36 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
37 SLEEP_TIME=0.03
38
39 STATE_IDLE = 0
40 STATE_DEFCONFIG = 1
41 STATE_AUTOCONF = 2
42 STATE_SAVEDEFCONFIG = 3
43
44 AUTO_CONF_PATH = 'include/config/auto.conf'
45 CONFIG_DATABASE = 'qconfig.db'
46 FAILED_LIST = 'qconfig.failed'
47
48 CONFIG_LEN = len('CONFIG_')
49
50 SIZES = {
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,
67     'SZ_4G':  0x100000000
68 }
69
70 RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
71
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', ]
81
82 SPL_PREFIXES = ['SPL_', 'TPL_', 'VPL_', 'TOOLS_']
83
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.')
90
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'")
96
97 def get_make_cmd():
98     """Get the command name of GNU Make.
99
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.
103     """
104     with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc:
105         ret = proc.communicate()
106         if proc.returncode:
107             sys.exit('GNU Make not found')
108     return ret[0].rstrip()
109
110 def get_matched_defconfig(line):
111     """Get the defconfig files that match a pattern
112
113     Args:
114         line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
115             'k2*_defconfig'. If no directory is provided, 'configs/' is
116             prepended
117
118     Returns:
119         list of str: a list of matching defconfig files
120     """
121     dirname = os.path.dirname(line)
122     if dirname:
123         pattern = line
124     else:
125         pattern = os.path.join('configs', line)
126     return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
127
128 def get_matched_defconfigs(defconfigs_file):
129     """Get all the defconfig files that match the patterns in a file.
130
131     Args:
132         defconfigs_file (str): File containing a list of defconfigs to process,
133             or '-' to read the list from stdin
134
135     Returns:
136         list of str: A list of paths to defconfig files, with no duplicates
137     """
138     defconfigs = []
139     with ExitStack() as stack:
140         if defconfigs_file == '-':
141             inf = sys.stdin
142             defconfigs_file = 'stdin'
143         else:
144             inf = stack.enter_context(open(defconfigs_file, encoding='utf-8'))
145         for i, line in enumerate(inf):
146             line = line.strip()
147             if not line:
148                 continue # skip blank lines silently
149             if ' ' in line:
150                 line = line.split(' ')[0]  # handle 'git log' input
151             matched = get_matched_defconfig(line)
152             if not matched:
153                 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'",
154                       file=sys.stderr)
155
156             defconfigs += matched
157
158     # use set() to drop multiple matching
159     return [defconfig[len('configs') + 1:]  for defconfig in set(defconfigs)]
160
161 def get_all_defconfigs():
162     """Get all the defconfig files under the configs/ directory.
163
164     Returns:
165         list of str: List of paths to defconfig files
166     """
167     defconfigs = []
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))
172
173     return defconfigs
174
175 def write_file(fname, data):
176     """Write data to a file
177
178     Args:
179         fname (str): Filename to write to
180         data (list of str): Lines to write (with or without trailing newline);
181             or str to write
182     """
183     with open(fname, 'w', encoding='utf-8') as out:
184         if isinstance(data, list):
185             for line in data:
186                 print(line.rstrip('\n'), file=out)
187         else:
188             out.write(data)
189
190 def read_file(fname, as_lines=True, skip_unicode=False):
191     """Read a file and return the contents
192
193     Args:
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
197
198     Returns:
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
201             occurred
202
203     Raises:
204         UnicodeDecodeError: Unicode error occurred when reading
205     """
206     with open(fname, encoding='utf-8') as inf:
207         try:
208             if as_lines:
209                 return [line.rstrip('\n') for line in inf.readlines()]
210             return inf.read()
211         except UnicodeDecodeError as exc:
212             if not skip_unicode:
213                 raise
214             print(f"Failed on file '{fname}: {exc}")
215             return None
216
217
218 ### classes ###
219 class Progress:
220     """Progress Indicator"""
221
222     def __init__(self, col, total):
223         """Create a new progress indicator.
224
225         Args:
226             col (terminal.Color): Colour-output class
227             total (int): A number of defconfig files to process.
228
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
232         """
233         self.col = col
234         self.total = total
235
236         self.current = 0
237         self.good = 0
238         self.failed = None
239         self.failure_msg = None
240
241     def inc(self, success):
242         """Increment the number of processed defconfig files.
243
244         Args:
245             success (bool): True if processing succeeded
246         """
247         self.good += success
248         self.current += 1
249
250     def show(self):
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='')
259         sys.stdout.flush()
260
261     def completed(self):
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 ''
265
266
267 def scan_kconfig():
268     """Scan all the Kconfig files and create a Config object
269
270     Returns:
271         Kconfig object
272     """
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()
279
280
281 # pylint: disable=R0903
282 class KconfigParser:
283     """A parser of .config and include/autoconf.mk."""
284
285     re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
286     re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
287
288     def __init__(self, args, build_dir):
289         """Create a new parser.
290
291         Args:
292           args (Namespace): program arguments
293           build_dir: Build directory.
294         """
295         self.args = args
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',
299                                          'autoconf.mk')
300         self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
301         self.defconfig = os.path.join(build_dir, 'defconfig')
302
303     def get_arch(self):
304         """Parse .config file and return the architecture.
305
306         Returns:
307           Architecture name (e.g. 'arm').
308         """
309         arch = ''
310         cpu = ''
311         for line in read_file(self.dotconfig):
312             m_arch = self.re_arch.match(line)
313             if m_arch:
314                 arch = m_arch.group(1)
315                 continue
316             m_cpu = self.re_cpu.match(line)
317             if m_cpu:
318                 cpu = m_cpu.group(1)
319
320         if not arch:
321             return None
322
323         # fix-up for aarch64
324         if arch == 'arm' and cpu == 'armv8':
325             arch = 'aarch64'
326
327         return arch
328
329
330 class DatabaseThread(threading.Thread):
331     """This thread processes results from Slot threads.
332
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.
335     """
336     def __init__(self, config_db, db_queue):
337         """Set up a new result thread
338
339         Args:
340             builder: Builder which will be sent each result
341         """
342         threading.Thread.__init__(self)
343         self.config_db = config_db
344         self.db_queue= db_queue
345
346     def run(self):
347         """Called to start up the result thread.
348
349         We collect the next result job and pass it on to the build.
350         """
351         while True:
352             defconfig, configs = self.db_queue.get()
353             self.config_db[defconfig] = configs
354             self.db_queue.task_done()
355
356
357 class Slot:
358
359     """A slot to store a subprocess.
360
361     Each instance of this class handles one subprocess.
362     This class is useful to control multiple threads
363     for faster processing.
364     """
365
366     def __init__(self, toolchains, args, progress, devnull, make_cmd,
367                  reference_src_dir, db_queue):
368         """Create a new process slot.
369
370         Args:
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
377                              source tree.
378           db_queue: output queue to write config info for the database
379           col (terminal.Color): Colour object
380         """
381         self.toolchains = toolchains
382         self.args = args
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
394         self.log = []
395         self.current_src_dir = None
396         self.proc = None
397
398     def __del__(self):
399         """Delete the working directory
400
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.
405
406         If the subprocess is still running, wait until it finishes.
407         """
408         if self.state != STATE_IDLE:
409             while self.proc.poll() is None:
410                 pass
411         shutil.rmtree(self.build_dir)
412
413     def add(self, defconfig):
414         """Assign a new subprocess for defconfig and add it to the slot.
415
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).
419
420         Args:
421           defconfig (str): defconfig name.
422
423         Returns:
424           Return True on success or False on failure
425         """
426         if self.state != STATE_IDLE:
427             return False
428
429         self.defconfig = defconfig
430         self.log = []
431         self.current_src_dir = self.reference_src_dir
432         self.do_defconfig()
433         return True
434
435     def poll(self):
436         """Check the status of the subprocess and handle it as needed.
437
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
446         to the idle state.
447
448         Returns:
449           Return True if the subprocess is terminated, False otherwise
450         """
451         if self.state == STATE_IDLE:
452             return True
453
454         if self.proc.poll() is None:
455             return False
456
457         if self.proc.poll() != 0:
458             self.handle_error()
459         elif self.state == STATE_DEFCONFIG:
460             if self.reference_src_dir and not self.current_src_dir:
461                 self.do_savedefconfig()
462             else:
463                 self.do_autoconf()
464         elif self.state == STATE_AUTOCONF:
465             if self.current_src_dir:
466                 self.current_src_dir = None
467                 self.do_defconfig()
468             elif self.args.build_db:
469                 self.do_build_db()
470             else:
471                 self.do_savedefconfig()
472         elif self.state == STATE_SAVEDEFCONFIG:
473             self.update_defconfig()
474         else:
475             sys.exit('Internal Error. This should not happen.')
476
477         return self.state == STATE_IDLE
478
479     def handle_error(self):
480         """Handle error cases."""
481
482         self.log.append(self.col.build(self.col.RED, 'Failed to process',
483                                        bright=True))
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))
487         self.finish(False)
488
489     def do_defconfig(self):
490         """Run 'make <board>_defconfig' to create the .config file."""
491
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
499
500     def do_autoconf(self):
501         """Run 'make AUTO_CONF_PATH'."""
502
503         arch = self.parser.get_arch()
504         try:
505             tchain = self.toolchains.Select(arch)
506         except ValueError:
507             self.log.append(self.col.build(
508                 self.col.YELLOW,
509                 f"Tool chain for '{arch}' is missing: do nothing"))
510             self.finish(False)
511             return
512         env = tchain.MakeEnvironment(False)
513
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
522
523     def do_build_db(self):
524         """Add the board to the database"""
525         configs = {}
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])
531         self.finish(True)
532
533     def do_savedefconfig(self):
534         """Update the .config and run 'make savedefconfig'."""
535         if not self.args.force_sync:
536             self.finish(True)
537             return
538
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
545
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)
551
552         if updated:
553             self.log.append(
554                 self.col.build(self.col.BLUE, 'defconfig updated', bright=True))
555
556         if not self.args.dry_run and updated:
557             shutil.move(new_defconfig, orig_defconfig)
558         self.finish(True)
559
560     def finish(self, success):
561         """Display log along with progress and go to the idle state.
562
563         Args:
564           success (bool): Should be True when the defconfig was processed
565                    successfully, or False when it fails.
566         """
567         # output at least 30 characters to hide the "* defconfigs out of *".
568         name = self.defconfig[:-len('_defconfig')]
569         if self.log:
570
571             # Put the first log line on the first line
572             log = name.ljust(20) + ' ' + self.log[0]
573
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))
579
580         if not success:
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)
586
587         self.progress.inc(success)
588         self.progress.show()
589         self.state = STATE_IDLE
590
591     def get_failed_boards(self):
592         """Returns a set of failed boards (defconfigs) in this slot.
593         """
594         return self.failed_boards
595
596 class Slots:
597     """Controller of the array of subprocess slots."""
598
599     def __init__(self, toolchains, args, progress, reference_src_dir, db_queue):
600         """Create a new slots controller.
601
602         Args:
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
609         """
610         self.args = args
611         self.slots = []
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))
619
620     def add(self, defconfig):
621         """Add a new subprocess if a vacant slot is found.
622
623         Args:
624           defconfig (str): defconfig name to be put into.
625
626         Returns:
627           Return True on success or False on failure
628         """
629         for slot in self.slots:
630             if slot.add(defconfig):
631                 return True
632         return False
633
634     def available(self):
635         """Check if there is a vacant slot.
636
637         Returns:
638           Return True if at lease one vacant slot is found, False otherwise.
639         """
640         for slot in self.slots:
641             if slot.poll():
642                 return True
643         return False
644
645     def empty(self):
646         """Check if all slots are vacant.
647
648         Returns:
649           Return True if all the slots are vacant, False otherwise.
650         """
651         ret = True
652         for slot in self.slots:
653             if not slot.poll():
654                 ret = False
655         return ret
656
657     def write_failed_boards(self):
658         """Show the results of processing"""
659         boards = set()
660
661         for slot in self.slots:
662             boards |= slot.get_failed_boards()
663
664         if boards:
665             boards = '\n'.join(sorted(boards)) + '\n'
666             write_file(FAILED_LIST, boards)
667
668
669 class ReferenceSource:
670
671     """Reference source against which original configs should be parsed."""
672
673     def __init__(self, commit):
674         """Create a reference source directory based on a specified commit.
675
676         Args:
677           commit: commit to git-clone
678         """
679         self.src_dir = tempfile.mkdtemp()
680         print('Cloning git repo to a separate work directory...')
681         subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
682                                 cwd=self.src_dir)
683         rev = subprocess.check_output(['git', 'rev-parse', '--short',
684                                        commit]).strip()
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)
688
689     def __del__(self):
690         """Delete the reference source directory
691
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.
696         """
697         shutil.rmtree(self.src_dir)
698
699     def get_dir(self):
700         """Return the absolute path to the reference source directory."""
701
702         return self.src_dir
703
704 def move_config(args):
705     """Build database or sync config options to defconfig files.
706
707     Args:
708         args (Namespace): Program arguments
709
710     Returns:
711         tuple:
712             config_db (dict of configs for each defconfig):
713                 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
714                 value: dict:
715                     key: CONFIG option
716                     value: Value of option
717             Progress: Progress indicator
718     """
719     config_db = {}
720     db_queue = queue.Queue()
721     dbt = DatabaseThread(config_db, db_queue)
722     dbt.daemon = True
723     dbt.start()
724
725     check_clean_directory()
726     bsettings.setup('')
727
728     # Get toolchains to use
729     toolchains = toolchain.Toolchains()
730     toolchains.GetSettings()
731     toolchains.Scan(verbose=False)
732
733     if args.git_ref:
734         reference_src = ReferenceSource(args.git_ref)
735         reference_src_dir = reference_src.get_dir()
736     else:
737         reference_src_dir = None
738
739     if args.defconfigs:
740         defconfigs = get_matched_defconfigs(args.defconfigs)
741     else:
742         defconfigs = get_all_defconfigs()
743
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)
748
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)
757
758     # wait until all the subprocesses finish
759     while not slots.empty():
760         time.sleep(SLEEP_TIME)
761
762     slots.write_failed_boards()
763     db_queue.join()
764     progress.completed()
765     return config_db, progress
766
767 def find_kconfig_rules(kconf, config, imply_config):
768     """Check whether a config has a 'select' or 'imply' keyword
769
770     Args:
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')
775
776     Returns:
777         Symbol object for 'config' if found, else None
778     """
779     sym = kconf.syms.get(imply_config)
780     if sym:
781         for sel, _ in (sym.selects + sym.implies):
782             if sel.name == config:
783                 return sym
784     return None
785
786 def check_imply_rule(kconf, imply_config):
787     """Check if we can add an 'imply' option
788
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.
791
792     Args:
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')
796
797     Returns:
798         tuple:
799             str: filename of Kconfig file containing imply_config, or None if
800                 none
801             int: line number within the Kconfig file, or 0 if none
802             str: message indicating the result
803     """
804     sym = kconf.syms.get(imply_config)
805     if not sym:
806         return 'cannot find sym'
807     nodes = sym.nodes
808     if len(nodes) != 1:
809         return f'{len(nodes)} locations'
810     node = nodes[0]
811     fname, linenum = node.filename, node.linenr
812     cwd = os.getcwd()
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}'
820
821 def add_imply_rule(config, fname, linenum):
822     """Add a new 'imply' option to a Kconfig
823
824     Args:
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
828
829     Returns:
830         Message indicating the result
831     """
832     file_line = f' at {fname}:{linenum}'
833     data = read_file(fname)
834     linenum -= 1
835
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}'
841
842     return 'could not insert%s'
843
844 (IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
845     1, 2, 4, 8)
846
847 IMPLY_FLAGS = {
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'],
851     'non-arch-board': [
852         IMPLY_NON_ARCH_BOARD,
853         'Allow Kconfig options outside arch/ and /board/ to imply'],
854 }
855
856
857 def read_database():
858     """Read in the config database
859
860     Returns:
861         tuple:
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"
866                 value: dict:
867                     key: CONFIG option
868                     value: Value of option
869             dict of defconfigs for each config:
870                 key: CONFIG option
871                 value: set of boards using that option
872
873     """
874     configs = {}
875
876     # key is defconfig name, value is dict of (CONFIG_xxx, value)
877     config_db = {}
878
879     # Set of all config options we have seen
880     all_configs = set()
881
882     # Set of all defconfigs we have seen
883     all_defconfigs = set()
884
885     defconfig_db = collections.defaultdict(set)
886     defconfig = None
887     for line in read_file(CONFIG_DATABASE):
888         line = line.rstrip()
889         if not line:  # Separator between defconfigs
890             config_db[defconfig] = configs
891             all_defconfigs.add(defconfig)
892             configs = {}
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
899             defconfig = line
900
901     return all_configs, all_defconfigs, config_db, defconfig_db
902
903
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
907
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.
913
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.
919
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
924             -
925             - If imply_defconfigs contains anything not in defconfigs then
926               this config does not imply the target config
927
928     Args:
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
932            (IMPLY_...)
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.
941
942     Note the terminoloy:
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')
945     """
946     kconf = scan_kconfig() if check_kconfig else None
947     if add_imply and add_imply != 'all':
948         add_imply = add_imply.split(',')
949
950     all_configs, all_defconfigs, _, defconfig_db = read_database()
951
952     # Work through each target config option in turn, independently
953     for config in config_list:
954         defconfigs = defconfig_db.get(config)
955         if not defconfigs:
956             print(f'{config} not found in any defconfig')
957             continue
958
959         # Get the set of defconfigs without this one (since a config cannot
960         # imply itself)
961         non_defconfigs = all_defconfigs - defconfigs
962         num_defconfigs = len(defconfigs)
963         print(f'{config} found in {num_defconfigs}/{len(all_configs)} defconfigs')
964
965         # This will hold the results: key=config, value=defconfigs containing it
966         imply_configs = {}
967         rest_configs = all_configs - set([config])
968
969         # Look at every possible config, except the target one
970         for imply_config in rest_configs:
971             if 'ERRATUM' in imply_config:
972                 continue
973             if not imply_flags & IMPLY_CMD:
974                 if 'CONFIG_CMD' in imply_config:
975                     continue
976             if not imply_flags & IMPLY_TARGET:
977                 if 'CONFIG_TARGET' in imply_config:
978                     continue
979
980             # Find set of defconfigs that have this config
981             imply_defconfig = defconfig_db[imply_config]
982
983             # Get the intersection of this with defconfigs containing the
984             # target config
985             common_defconfigs = imply_defconfig & defconfigs
986
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:
994                 continue
995
996             # If there are common defconfigs, imply_config may be useful
997             if common_defconfigs:
998                 skip = False
999                 if find_superset:
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
1007                             skip = True
1008                             break
1009                         if count > prev_count:
1010                             # delete prev because imply_config is a superset
1011                             del imply_configs[prev]
1012                 if not skip:
1013                     imply_configs[imply_config] = common_defconfigs
1014
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)
1021         kconfig_info = ''
1022         cwd = os.getcwd()
1023         add_list = collections.defaultdict(list)
1024         for iconfig in ranked_iconfigs:
1025             num_common = len(imply_configs[iconfig])
1026
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):
1029                 continue
1030             missing = defconfigs - imply_configs[iconfig]
1031             missing_str = ', '.join(missing) if missing else 'all'
1032             missing_str = ''
1033             show = True
1034             if kconf:
1035                 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1036                                          iconfig[CONFIG_LEN:])
1037                 kconfig_info = ''
1038                 if sym:
1039                     nodes = sym.nodes
1040                     if len(nodes) == 1:
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}'
1045                         if skip_added:
1046                             show = False
1047                 else:
1048                     sym = kconf.syms.get(iconfig[CONFIG_LEN:])
1049                     fname = ''
1050                     if sym:
1051                         nodes = sym.nodes
1052                         if len(nodes) == 1:
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):
1060                         continue
1061
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:]))
1066                         if fname:
1067                             add_list[fname].append(linenum)
1068
1069             if show and kconfig_info != 'skip':
1070                 print(f'{num_common:5} : '
1071                       f'{iconfig.ljust(30)}{kconfig_info.ljust(25)} {missing_str}')
1072
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
1077         # that.
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)
1081
1082 def defconfig_matches(configs, re_match, re_val):
1083     """Check if any CONFIG option matches a regex
1084
1085     The match must be complete, i.e. from the start to end of the CONFIG option.
1086
1087     Args:
1088         configs (dict): Dict of CONFIG options:
1089             key: CONFIG option
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)
1093
1094     Returns:
1095         bool: True if any CONFIG matches the regex
1096     """
1097     for cfg, val in configs.items():
1098         if re_match.fullmatch(cfg):
1099             if not re_val or re_val.fullmatch(val):
1100                 return True
1101     return False
1102
1103 def do_find_config(config_list):
1104     """Find boards with a given combination of CONFIGs
1105
1106     Args:
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
1112     Returns:
1113         int: exit code (0 for success)
1114     """
1115     _, all_defconfigs, config_db, _ = read_database()
1116
1117     # Start with all defconfigs
1118     out = all_defconfigs
1119
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
1123         cfg = item
1124         want = True
1125         if cfg[0] == '~':
1126             want = False
1127             cfg = cfg[1:]
1128         val = None
1129         re_val = None
1130         if '=' in cfg:
1131             cfg, val = cfg.split('=', maxsplit=1)
1132             re_val = re.compile(val)
1133
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
1137         in_list = out
1138         out = set()
1139         re_match = re.compile(cfg)
1140         for defc in in_list:
1141             has_cfg = defconfig_matches(config_db[defc], re_match, re_val)
1142             if has_cfg == want:
1143                 out.add(defc)
1144     print(f'{len(out)} matches')
1145     print(' '.join(item.split('_defconfig')[0] for item in out))
1146     return 0
1147
1148
1149 def prefix_config(cfg):
1150     """Prefix a config with CONFIG_ if needed
1151
1152     This handles ~ operator, which indicates that the CONFIG should be disabled
1153
1154     >>> prefix_config('FRED')
1155     'CONFIG_FRED'
1156     >>> prefix_config('CONFIG_FRED')
1157     'CONFIG_FRED'
1158     >>> prefix_config('~FRED')
1159     '~CONFIG_FRED'
1160     >>> prefix_config('~CONFIG_FRED')
1161     '~CONFIG_FRED'
1162     >>> prefix_config('A123')
1163     'CONFIG_A123'
1164     """
1165     oper = ''
1166     if cfg[0] == '~':
1167         oper = cfg[0]
1168         cfg = cfg[1:]
1169     if not cfg.startswith('CONFIG_'):
1170         cfg = 'CONFIG_' + cfg
1171     return oper + cfg
1172
1173
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_]*)\)')
1178
1179 class ConfigUse:
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
1183
1184         Args:
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
1189         """
1190         self.cfg = cfg
1191         self.is_spl = is_spl
1192         self.fname = fname
1193         self.rest = rest
1194
1195     def __hash__(self):
1196         return hash((self.cfg, self.is_spl))
1197
1198 def scan_makefiles(fnames):
1199     """Scan Makefiles looking for Kconfig options
1200
1201     Looks for uses of CONFIG options in Makefiles
1202
1203     Args:
1204         fnames (list of tuple):
1205             str: Makefile filename where the option was found
1206             str: Line of the Makefile
1207
1208     Returns:
1209         tuple:
1210             dict: all_uses
1211                 key (ConfigUse): object
1212                 value (list of str): matching lines
1213             dict: Uses by filename
1214                 key (str): filename
1215                 value (set of ConfigUse): uses in that filename
1216
1217     >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1218     (None, 'FRED')
1219     >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_)MARY').groups()
1220     ('$(SPL_)', 'MARY')
1221     >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1222     ('$(SPL_TPL_)', 'MARY')
1223     """
1224     all_uses = collections.defaultdict(list)
1225     fname_uses = {}
1226     for fname, rest in fnames:
1227         m_iter = RE_MK_CONFIGS.finditer(rest)
1228         for mat in m_iter:
1229             real_opt = mat.group(2)
1230             if real_opt == '':
1231                 continue
1232             is_spl = False
1233             if mat.group(1):
1234                 is_spl = True
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
1241
1242
1243 def scan_src_files(fnames):
1244     """Scan source files (other than Makefiles) looking for Kconfig options
1245
1246     Looks for uses of CONFIG options
1247
1248     Args:
1249         fnames (list of tuple):
1250             str: Makefile filename where the option was found
1251             str: Line of the Makefile
1252
1253     Returns:
1254         tuple:
1255             dict: all_uses
1256                 key (ConfigUse): object
1257                 value (list of str): matching lines
1258             dict: Uses by filename
1259                 key (str): filename
1260                 value (set of ConfigUse): uses in that filename
1261
1262     >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1263     ('FRED',)
1264     >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1265     ('MARY',)
1266     >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1267     ('OF_PLATDATA',)
1268     """
1269     fname = None
1270     rest = None
1271
1272     def add_uses(m_iter, is_spl):
1273         for mat in m_iter:
1274             real_opt = mat.group(1)
1275             if real_opt == '':
1276                 continue
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)
1282
1283     all_uses = collections.defaultdict(list)
1284     fname_uses = {}
1285     for fname, rest in fnames:
1286         m_iter = RE_C_CONFIGS.finditer(rest)
1287         add_uses(m_iter, False)
1288
1289         m_iter2 = RE_CONFIG_IS.finditer(rest)
1290         add_uses(m_iter2, True)
1291
1292     return all_uses, fname_uses
1293
1294
1295 MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1296
1297 def do_scan_source(path, do_update):
1298     """Scan the source tree for Kconfig inconsistencies
1299
1300     Args:
1301         path (str): Path to source tree
1302         do_update (bool) : True to write to scripts/kconf_... files
1303     """
1304     def is_not_proper(name):
1305         for prefix in SPL_PREFIXES:
1306             if name.startswith(prefix):
1307                 return name[len(prefix):]
1308         return False
1309
1310     def check_not_found(all_uses, spl_mode):
1311         """Check for Kconfig options mentioned in the source but not in Kconfig
1312
1313         Args:
1314             all_uses (dict):
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
1323
1324         Returns:
1325             dict:
1326                 key (str): CONFIG name (without 'CONFIG_' prefix
1327                 value (list of ConfigUse): List of uses of this CONFIG
1328         """
1329         # Make sure we know about all the options
1330         not_found = collections.defaultdict(list)
1331         for use, _ in all_uses.items():
1332             name = use.cfg
1333             if name in IGNORE_SYMS:
1334                 continue
1335             check = True
1336
1337             if spl_mode == MODE_SPL:
1338                 check = use.is_spl
1339
1340                 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1341                 # find at least one SPL symbol
1342                 if use.is_spl:
1343                     for prefix in SPL_PREFIXES:
1344                         try_name = prefix + name
1345                         sym = kconf.syms.get(try_name)
1346                         if sym:
1347                             break
1348                     if not sym:
1349                         not_found[f'SPL_{name}'].append(use)
1350                     continue
1351             elif spl_mode == MODE_PROPER:
1352                 # Try to find the Proper version of this symbol, i.e. without
1353                 # the SPL_ prefix
1354                 proper_name = is_not_proper(name)
1355                 if proper_name:
1356                     name = proper_name
1357                 elif not use.is_spl:
1358                     check = False
1359             else: # MODE_NORMAL
1360                 sym = kconf.syms.get(name)
1361                 if not sym:
1362                     proper_name = is_not_proper(name)
1363                     if proper_name:
1364                         name = proper_name
1365                     sym = kconf.syms.get(name)
1366                 if not sym:
1367                     for prefix in SPL_PREFIXES:
1368                         try_name = prefix + name
1369                         sym = kconf.syms.get(try_name)
1370                         if sym:
1371                             break
1372                 if not sym:
1373                     not_found[name].append(use)
1374                 continue
1375
1376             sym = kconf.syms.get(name)
1377             if not sym and check:
1378                 not_found[name].append(use)
1379         return not_found
1380
1381     def show_uses(uses):
1382         """Show a list of uses along with their filename and code snippet
1383
1384         Args:
1385             uses (dict):
1386                 key (str): CONFIG name (without 'CONFIG_' prefix
1387                 value (list of ConfigUse): List of uses of this CONFIG
1388         """
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()}')
1393
1394
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('^([^:]*):(.*)')
1403     src_list = []
1404     mk_list = []
1405     for line in lines:
1406         linestr = line.decode('utf-8')
1407         m_fname = re_fname.search(linestr)
1408         if not m_fname:
1409             continue
1410         fname, rest = m_fname.groups()
1411         dirname, leaf = os.path.split(fname)
1412         root, ext = os.path.splitext(leaf)
1413         if ext == '.autoconf':
1414             pass
1415         elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1416                      '.env', '.tmpl']:
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']:
1421             pass
1422         elif 'Kconfig' in root or 'Kbuild' in root:
1423             pass
1424         elif 'README' in root:
1425             pass
1426         elif dirname in ['configs']:
1427             pass
1428         elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1429             pass
1430         else:
1431             print(f'Not sure how to handle file {fname}')
1432
1433     # Scan the Makefiles
1434     all_uses, _ = scan_makefiles(mk_list)
1435
1436     spl_not_found = set()
1437     proper_not_found = set()
1438
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)
1443
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()}
1448
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()}
1453
1454     # Scan the source code
1455     all_uses, _ = scan_src_files(src_list)
1456
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)
1461
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()}
1466
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()}
1471
1472     print('\nCONFIG options used as SPL but without an SPL_ variant:')
1473     for item in sorted(spl_not_found):
1474         print(f'   {item}')
1475
1476     print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1477     for item in sorted(proper_not_found):
1478         print(f'   {item}')
1479
1480     # Write out the updated information
1481     if do_update:
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',
1485                   file=out)
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',
1491                   file=out)
1492             for item in sorted(proper_not_found):
1493                 print(item, file=out)
1494     return 0
1495
1496
1497 def parse_args():
1498     """Parse the program arguments
1499
1500     Returns:
1501         tuple:
1502             argparse.ArgumentParser: parser
1503             argparse.Namespace: Parsed arguments
1504     """
1505     try:
1506         cpu_count = multiprocessing.cpu_count()
1507     except NotImplementedError:
1508         cpu_count = 1
1509
1510     epilog = '''Move config options from headers to defconfig files. See
1511 doc/develop/moveconfig.rst for documentation.'''
1512
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 "
1520                       'implying others')
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',
1532                       default=False,
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='*')
1561
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()
1566         sys.exit(1)
1567
1568     return parser, args
1569
1570
1571 def imply(args):
1572     """Handle checking for flags which imply others
1573
1574     Args:
1575         args (argparse.Namespace): Program arguments
1576
1577     Returns:
1578         int: exit code (0 for success)
1579     """
1580     imply_flags = 0
1581     if args.imply_flags == 'all':
1582         imply_flags = -1
1583
1584     elif args.imply_flags:
1585         for flag in args.imply_flags.split(','):
1586             bad = flag not in IMPLY_FLAGS
1587             if bad:
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]}')
1593                 return 1
1594             imply_flags |= IMPLY_FLAGS[flag][0]
1595
1596     do_imply_config(args.configs, args.add_imply, imply_flags, args.skip_added)
1597     return 0
1598
1599
1600 def add_commit(configs):
1601     """Add a commit indicating which CONFIG options were converted
1602
1603     Args:
1604         configs (list of str) List of CONFIG_... options to process
1605     """
1606     subprocess.call(['git', 'add', '-u'])
1607     if configs:
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))
1612     else:
1613         msg = 'configs: Resync with savedefconfig'
1614         msg += '\n\nRsync all defconfig files using moveconfig.py'
1615     subprocess.call(['git', 'commit', '-s', '-m', msg])
1616
1617
1618 def write_db(config_db, progress):
1619     """Write the database to a file
1620
1621     Args:
1622         config_db (dict of dict): configs for each defconfig
1623             key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
1624             value: dict:
1625                 key: CONFIG option
1626                 value: Value of option
1627         progress (Progress): Progress indicator.
1628
1629     Returns:
1630         int: exit code (0 for success)
1631     """
1632     col = progress.col
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')
1638             outf.write('\n')
1639     print(col.build(
1640         col.RED if progress.failed else col.GREEN,
1641         f'{progress.failure_msg}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1642     return 0
1643
1644
1645 def move_done(progress):
1646     """Write a message indicating that the move is done
1647
1648     Args:
1649         progress (Progress): Progress indicator.
1650
1651     Returns:
1652         int: exit code (0 for success)
1653     """
1654     col = progress.col
1655     if progress.failed:
1656         print(col.build(col.RED, f'{progress.failure_msg}see {FAILED_LIST}', True))
1657     else:
1658         # Add enough spaces to overwrite the progress indicator
1659         print(col.build(
1660             col.GREEN, f'{progress.total} processed        ', bright=True))
1661     return 0
1662
1663 def do_tests():
1664     """Run doctests and unit tests (so far there are no unit tests)"""
1665     sys.argv = [sys.argv[0]]
1666     fail, _ = doctest.testmod()
1667     if fail:
1668         return 1
1669     unittest.main()
1670     return 0
1671
1672
1673 def main():
1674     """Main program"""
1675     parser, args = parse_args()
1676     check_top_directory()
1677
1678     # prefix the option name with CONFIG_ if missing
1679     args.configs = [prefix_config(cfg) for cfg in args.configs]
1680
1681     if args.test:
1682         return do_tests()
1683     if args.scan_source:
1684         return do_scan_source(os.getcwd(), args.update)
1685     if args.imply:
1686         if imply(args):
1687             parser.print_usage()
1688             sys.exit(1)
1689         return 0
1690     if args.find:
1691         return do_find_config(args.configs)
1692
1693     config_db, progress = move_config(args)
1694
1695     if args.commit:
1696         add_commit(args.configs)
1697
1698     if args.build_db:
1699         return write_db(config_db, progress)
1700     return move_done(progress)
1701
1702
1703 if __name__ == '__main__':
1704     sys.exit(main())
This page took 0.131382 seconds and 4 git commands to generate.