]> Git Repo - J-u-boot.git/blob - tools/qconfig.py
global: Rename SPL_ to XPL_
[J-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', 'XPL_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, list_format):
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         list_format (bool): True to write in 'list' format, one board name per
1112             line
1113
1114     Returns:
1115         int: exit code (0 for success)
1116     """
1117     _, all_defconfigs, config_db, _ = read_database()
1118
1119     # Start with all defconfigs
1120     out = all_defconfigs
1121
1122     # Work through each config in turn
1123     for item in config_list:
1124         # Get the real config name and whether we want this config or not
1125         cfg = item
1126         want = True
1127         if cfg[0] == '~':
1128             want = False
1129             cfg = cfg[1:]
1130         val = None
1131         re_val = None
1132         if '=' in cfg:
1133             cfg, val = cfg.split('=', maxsplit=1)
1134             re_val = re.compile(val)
1135
1136         # Search everything that is still in the running. If it has a config
1137         # that we want, or doesn't have one that we don't, add it into the
1138         # running for the next stage
1139         in_list = out
1140         out = set()
1141         re_match = re.compile(cfg)
1142         for defc in in_list:
1143             has_cfg = defconfig_matches(config_db[defc], re_match, re_val)
1144             if has_cfg == want:
1145                 out.add(defc)
1146     if not list_format:
1147         print(f'{len(out)} matches')
1148     sep = '\n' if list_format else ' '
1149     print(sep.join(item.split('_defconfig')[0] for item in sorted(list(out))))
1150     return 0
1151
1152
1153 def prefix_config(cfg):
1154     """Prefix a config with CONFIG_ if needed
1155
1156     This handles ~ operator, which indicates that the CONFIG should be disabled
1157
1158     >>> prefix_config('FRED')
1159     'CONFIG_FRED'
1160     >>> prefix_config('CONFIG_FRED')
1161     'CONFIG_FRED'
1162     >>> prefix_config('~FRED')
1163     '~CONFIG_FRED'
1164     >>> prefix_config('~CONFIG_FRED')
1165     '~CONFIG_FRED'
1166     >>> prefix_config('A123')
1167     'CONFIG_A123'
1168     """
1169     oper = ''
1170     if cfg[0] == '~':
1171         oper = cfg[0]
1172         cfg = cfg[1:]
1173     if not cfg.startswith('CONFIG_'):
1174         cfg = 'CONFIG_' + cfg
1175     return oper + cfg
1176
1177
1178 RE_MK_CONFIGS = re.compile(r'CONFIG_(\$\(SPL_(?:TPL_)?\))?([A-Za-z0-9_]*)')
1179 RE_IFDEF = re.compile(r'(ifdef|ifndef)')
1180 RE_C_CONFIGS = re.compile(r'CONFIG_([A-Za-z0-9_]*)')
1181 RE_CONFIG_IS = re.compile(r'CONFIG_IS_ENABLED\(([A-Za-z0-9_]*)\)')
1182
1183 class ConfigUse:
1184     """Tracks whether a config relates to SPL or not"""
1185     def __init__(self, cfg, is_spl, fname, rest):
1186         """Set up a new ConfigUse
1187
1188         Args:
1189             cfg (str): CONFIG option, without any CONFIG_ or SPL_ prefix
1190             is_spl (bool): True if this option relates to SPL
1191             fname (str): Makefile filename where the CONFIG option was found
1192             rest (str): Line of the Makefile
1193         """
1194         self.cfg = cfg
1195         self.is_spl = is_spl
1196         self.fname = fname
1197         self.rest = rest
1198
1199     def __hash__(self):
1200         return hash((self.cfg, self.is_spl))
1201
1202 def scan_makefiles(fnames):
1203     """Scan Makefiles looking for Kconfig options
1204
1205     Looks for uses of CONFIG options in Makefiles
1206
1207     Args:
1208         fnames (list of tuple):
1209             str: Makefile filename where the option was found
1210             str: Line of the Makefile
1211
1212     Returns:
1213         tuple:
1214             dict: all_uses
1215                 key (ConfigUse): object
1216                 value (list of str): matching lines
1217             dict: Uses by filename
1218                 key (str): filename
1219                 value (set of ConfigUse): uses in that filename
1220
1221     >>> RE_MK_CONFIGS.search('CONFIG_FRED').groups()
1222     (None, 'FRED')
1223     >>> RE_MK_CONFIGS.search('CONFIG_$(XPL_)MARY').groups()
1224     ('$(XPL_)', 'MARY')
1225     >>> RE_MK_CONFIGS.search('CONFIG_$(SPL_TPL_)MARY').groups()
1226     ('$(SPL_TPL_)', 'MARY')
1227     """
1228     all_uses = collections.defaultdict(list)
1229     fname_uses = {}
1230     for fname, rest in fnames:
1231         m_iter = RE_MK_CONFIGS.finditer(rest)
1232         for mat in m_iter:
1233             real_opt = mat.group(2)
1234             if real_opt == '':
1235                 continue
1236             is_spl = False
1237             if mat.group(1):
1238                 is_spl = True
1239             use = ConfigUse(real_opt, is_spl, fname, rest)
1240             if fname not in fname_uses:
1241                 fname_uses[fname] = set()
1242             fname_uses[fname].add(use)
1243             all_uses[use].append(rest)
1244     return all_uses, fname_uses
1245
1246
1247 def scan_src_files(fnames):
1248     """Scan source files (other than Makefiles) looking for Kconfig options
1249
1250     Looks for uses of CONFIG options
1251
1252     Args:
1253         fnames (list of tuple):
1254             str: Makefile filename where the option was found
1255             str: Line of the Makefile
1256
1257     Returns:
1258         tuple:
1259             dict: all_uses
1260                 key (ConfigUse): object
1261                 value (list of str): matching lines
1262             dict: Uses by filename
1263                 key (str): filename
1264                 value (set of ConfigUse): uses in that filename
1265
1266     >>> RE_C_CONFIGS.search('CONFIG_FRED').groups()
1267     ('FRED',)
1268     >>> RE_CONFIG_IS.search('CONFIG_IS_ENABLED(MARY)').groups()
1269     ('MARY',)
1270     >>> RE_CONFIG_IS.search('#if CONFIG_IS_ENABLED(OF_PLATDATA)').groups()
1271     ('OF_PLATDATA',)
1272     """
1273     fname = None
1274     rest = None
1275
1276     def add_uses(m_iter, is_spl):
1277         for mat in m_iter:
1278             real_opt = mat.group(1)
1279             if real_opt == '':
1280                 continue
1281             use = ConfigUse(real_opt, is_spl, fname, rest)
1282             if fname not in fname_uses:
1283                 fname_uses[fname] = set()
1284             fname_uses[fname].add(use)
1285             all_uses[use].append(rest)
1286
1287     all_uses = collections.defaultdict(list)
1288     fname_uses = {}
1289     for fname, rest in fnames:
1290         m_iter = RE_C_CONFIGS.finditer(rest)
1291         add_uses(m_iter, False)
1292
1293         m_iter2 = RE_CONFIG_IS.finditer(rest)
1294         add_uses(m_iter2, True)
1295
1296     return all_uses, fname_uses
1297
1298
1299 MODE_NORMAL, MODE_SPL, MODE_PROPER = range(3)
1300
1301 def do_scan_source(path, do_update):
1302     """Scan the source tree for Kconfig inconsistencies
1303
1304     Args:
1305         path (str): Path to source tree
1306         do_update (bool) : True to write to scripts/kconf_... files
1307     """
1308     def is_not_proper(name):
1309         for prefix in SPL_PREFIXES:
1310             if name.startswith(prefix):
1311                 return name[len(prefix):]
1312         return False
1313
1314     def check_not_found(all_uses, spl_mode):
1315         """Check for Kconfig options mentioned in the source but not in Kconfig
1316
1317         Args:
1318             all_uses (dict):
1319                 key (ConfigUse): object
1320                 value (list of str): matching lines
1321             spl_mode (int): If MODE_SPL, look at source code which implies
1322                 an SPL_ option, but for which there is none;
1323                 for MOD_PROPER, look at source code which implies a Proper
1324                 option (i.e. use of CONFIG_IS_ENABLED() or $(XPL_) or
1325                 $(SPL_TPL_) but for which there none;
1326                 if MODE_NORMAL, ignore SPL
1327
1328         Returns:
1329             dict:
1330                 key (str): CONFIG name (without 'CONFIG_' prefix
1331                 value (list of ConfigUse): List of uses of this CONFIG
1332         """
1333         # Make sure we know about all the options
1334         not_found = collections.defaultdict(list)
1335         for use, _ in all_uses.items():
1336             name = use.cfg
1337             if name in IGNORE_SYMS:
1338                 continue
1339             check = True
1340
1341             if spl_mode == MODE_SPL:
1342                 check = use.is_spl
1343
1344                 # If it is an SPL symbol, try prepending all SPL_ prefixes to
1345                 # find at least one SPL symbol
1346                 if use.is_spl:
1347                     for prefix in SPL_PREFIXES:
1348                         try_name = prefix + name
1349                         sym = kconf.syms.get(try_name)
1350                         if sym:
1351                             break
1352                     if not sym:
1353                         not_found[f'SPL_{name}'].append(use)
1354                     continue
1355             elif spl_mode == MODE_PROPER:
1356                 # Try to find the Proper version of this symbol, i.e. without
1357                 # the SPL_ prefix
1358                 proper_name = is_not_proper(name)
1359                 if proper_name:
1360                     name = proper_name
1361                 elif not use.is_spl:
1362                     check = False
1363             else: # MODE_NORMAL
1364                 sym = kconf.syms.get(name)
1365                 if not sym:
1366                     proper_name = is_not_proper(name)
1367                     if proper_name:
1368                         name = proper_name
1369                     sym = kconf.syms.get(name)
1370                 if not sym:
1371                     for prefix in SPL_PREFIXES:
1372                         try_name = prefix + name
1373                         sym = kconf.syms.get(try_name)
1374                         if sym:
1375                             break
1376                 if not sym:
1377                     not_found[name].append(use)
1378                 continue
1379
1380             sym = kconf.syms.get(name)
1381             if not sym and check:
1382                 not_found[name].append(use)
1383         return not_found
1384
1385     def show_uses(uses):
1386         """Show a list of uses along with their filename and code snippet
1387
1388         Args:
1389             uses (dict):
1390                 key (str): CONFIG name (without 'CONFIG_' prefix
1391                 value (list of ConfigUse): List of uses of this CONFIG
1392         """
1393         for name in sorted(uses):
1394             print(f'{name}: ', end='')
1395             for i, use in enumerate(uses[name]):
1396                 print(f'{"   " if i else ""}{use.fname}: {use.rest.strip()}')
1397
1398
1399     print('Scanning Kconfig')
1400     kconf = scan_kconfig()
1401     print(f'Scanning source in {path}')
1402     args = ['git', 'grep', '-E', r'IS_ENABLED|\bCONFIG']
1403     with subprocess.Popen(args, stdout=subprocess.PIPE) as proc:
1404         out, _ = proc.communicate()
1405     lines = out.splitlines()
1406     re_fname = re.compile('^([^:]*):(.*)')
1407     src_list = []
1408     mk_list = []
1409     for line in lines:
1410         linestr = line.decode('utf-8')
1411         m_fname = re_fname.search(linestr)
1412         if not m_fname:
1413             continue
1414         fname, rest = m_fname.groups()
1415         dirname, leaf = os.path.split(fname)
1416         root, ext = os.path.splitext(leaf)
1417         if ext == '.autoconf':
1418             pass
1419         elif ext in ['.c', '.h', '.S', '.lds', '.dts', '.dtsi', '.asl', '.cfg',
1420                      '.env', '.tmpl']:
1421             src_list.append([fname, rest])
1422         elif 'Makefile' in root or ext == '.mk':
1423             mk_list.append([fname, rest])
1424         elif ext in ['.yml', '.sh', '.py', '.awk', '.pl', '.rst', '', '.sed']:
1425             pass
1426         elif 'Kconfig' in root or 'Kbuild' in root:
1427             pass
1428         elif 'README' in root:
1429             pass
1430         elif dirname in ['configs']:
1431             pass
1432         elif dirname.startswith('doc') or dirname.startswith('scripts/kconfig'):
1433             pass
1434         else:
1435             print(f'Not sure how to handle file {fname}')
1436
1437     # Scan the Makefiles
1438     all_uses, _ = scan_makefiles(mk_list)
1439
1440     spl_not_found = set()
1441     proper_not_found = set()
1442
1443     # Make sure we know about all the options
1444     print('\nCONFIG options present in Makefiles but not Kconfig:')
1445     not_found = check_not_found(all_uses, MODE_NORMAL)
1446     show_uses(not_found)
1447
1448     print('\nCONFIG options present in Makefiles but not Kconfig (SPL):')
1449     not_found = check_not_found(all_uses, MODE_SPL)
1450     show_uses(not_found)
1451     spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
1452
1453     print('\nCONFIG options used as Proper in Makefiles but without a non-SPL_ variant:')
1454     not_found = check_not_found(all_uses, MODE_PROPER)
1455     show_uses(not_found)
1456     proper_not_found |= {not_found.keys()}
1457
1458     # Scan the source code
1459     all_uses, _ = scan_src_files(src_list)
1460
1461     # Make sure we know about all the options
1462     print('\nCONFIG options present in source but not Kconfig:')
1463     not_found = check_not_found(all_uses, MODE_NORMAL)
1464     show_uses(not_found)
1465
1466     print('\nCONFIG options present in source but not Kconfig (SPL):')
1467     not_found = check_not_found(all_uses, MODE_SPL)
1468     show_uses(not_found)
1469     spl_not_found |= {is_not_proper(key) or key for key in not_found.keys()}
1470
1471     print('\nCONFIG options used as Proper in source but without a non-SPL_ variant:')
1472     not_found = check_not_found(all_uses, MODE_PROPER)
1473     show_uses(not_found)
1474     proper_not_found |= {not_found.keys()}
1475
1476     print('\nCONFIG options used as SPL but without an SPL_ variant:')
1477     for item in sorted(spl_not_found):
1478         print(f'   {item}')
1479
1480     print('\nCONFIG options used as Proper but without a non-SPL_ variant:')
1481     for item in sorted(proper_not_found):
1482         print(f'   {item}')
1483
1484     # Write out the updated information
1485     if do_update:
1486         with open(os.path.join(path, 'scripts', 'conf_nospl'), 'w',
1487                   encoding='utf-8') as out:
1488             print('# These options should not be enabled in SPL builds\n',
1489                   file=out)
1490             for item in sorted(spl_not_found):
1491                 print(item, file=out)
1492         with open(os.path.join(path, 'scripts', 'conf_noproper'), 'w',
1493                   encoding='utf-8') as out:
1494             print('# These options should not be enabled in Proper builds\n',
1495                   file=out)
1496             for item in sorted(proper_not_found):
1497                 print(item, file=out)
1498     return 0
1499
1500
1501 def parse_args():
1502     """Parse the program arguments
1503
1504     Returns:
1505         tuple:
1506             argparse.ArgumentParser: parser
1507             argparse.Namespace: Parsed arguments
1508     """
1509     try:
1510         cpu_count = multiprocessing.cpu_count()
1511     except NotImplementedError:
1512         cpu_count = 1
1513
1514     epilog = '''Move config options from headers to defconfig files. See
1515 doc/develop/moveconfig.rst for documentation.'''
1516
1517     parser = ArgumentParser(epilog=epilog)
1518     # Add arguments here
1519     parser.add_argument('-a', '--add-imply', type=str, default='',
1520                       help='comma-separated list of CONFIG options to add '
1521                       "an 'imply' statement to for the CONFIG in -i")
1522     parser.add_argument('-A', '--skip-added', action='store_true', default=False,
1523                       help="don't show options which are already marked as "
1524                       'implying others')
1525     parser.add_argument('-b', '--build-db', action='store_true', default=False,
1526                       help='build a CONFIG database')
1527     parser.add_argument('-C', '--commit', action='store_true', default=False,
1528                       help='Create a git commit for the operation')
1529     parser.add_argument('--nocolour', action='store_true', default=False,
1530                       help="don't display the log in colour")
1531     parser.add_argument('-d', '--defconfigs', type=str,
1532                       help='a file containing a list of defconfigs to move, '
1533                       "one per line (for example 'snow_defconfig') "
1534                       "or '-' to read from stdin")
1535     parser.add_argument('-e', '--exit-on-error', action='store_true',
1536                       default=False,
1537                       help='exit immediately on any error')
1538     parser.add_argument('-f', '--find', action='store_true', default=False,
1539                       help='Find boards with a given config combination')
1540     parser.add_argument('-i', '--imply', action='store_true', default=False,
1541                       help='find options which imply others')
1542     parser.add_argument('-l', '--list', action='store_true', default=False,
1543                       help='Show a sorted list of board names, one per line')
1544     parser.add_argument('-I', '--imply-flags', type=str, default='',
1545                       help="control the -i option ('help' for help")
1546     parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
1547                       help='the number of jobs to run simultaneously')
1548     parser.add_argument('-n', '--dry-run', action='store_true', default=False,
1549                       help='perform a trial run (show log with no changes)')
1550     parser.add_argument('-r', '--git-ref', type=str,
1551                       help='the git ref to clone for building the autoconf.mk')
1552     parser.add_argument('-s', '--force-sync', action='store_true', default=False,
1553                       help='force sync by savedefconfig')
1554     parser.add_argument('-S', '--spl', action='store_true', default=False,
1555                       help='parse config options defined for SPL build')
1556     parser.add_argument('--scan-source', action='store_true', default=False,
1557                       help='scan source for uses of CONFIG options')
1558     parser.add_argument('-t', '--test', action='store_true', default=False,
1559                       help='run unit tests')
1560     parser.add_argument('-y', '--yes', action='store_true', default=False,
1561                       help="respond 'yes' to any prompts")
1562     parser.add_argument('-u', '--update', action='store_true', default=False,
1563                       help="update scripts/ files (use with --scan-source)")
1564     parser.add_argument('-v', '--verbose', action='store_true', default=False,
1565                       help='show any build errors as boards are built')
1566     parser.add_argument('configs', nargs='*')
1567
1568     args = parser.parse_args()
1569     if not any((args.force_sync, args.build_db, args.imply, args.find,
1570                 args.scan_source, args.test)):
1571         parser.print_usage()
1572         sys.exit(1)
1573
1574     return parser, args
1575
1576
1577 def imply(args):
1578     """Handle checking for flags which imply others
1579
1580     Args:
1581         args (argparse.Namespace): Program arguments
1582
1583     Returns:
1584         int: exit code (0 for success)
1585     """
1586     imply_flags = 0
1587     if args.imply_flags == 'all':
1588         imply_flags = -1
1589
1590     elif args.imply_flags:
1591         for flag in args.imply_flags.split(','):
1592             bad = flag not in IMPLY_FLAGS
1593             if bad:
1594                 print(f"Invalid flag '{flag}'")
1595             if flag == 'help' or bad:
1596                 print("Imply flags: (separate with ',')")
1597                 for name, info in IMPLY_FLAGS.items():
1598                     print(f' {name.ljust(15)}: {info[1]}')
1599                 return 1
1600             imply_flags |= IMPLY_FLAGS[flag][0]
1601
1602     do_imply_config(args.configs, args.add_imply, imply_flags, args.skip_added)
1603     return 0
1604
1605
1606 def add_commit(configs):
1607     """Add a commit indicating which CONFIG options were converted
1608
1609     Args:
1610         configs (list of str) List of CONFIG_... options to process
1611     """
1612     subprocess.call(['git', 'add', '-u'])
1613     if configs:
1614         part = 'et al ' if len(configs) > 1 else ''
1615         msg = f'Convert {configs[0]} {part}to Kconfig'
1616         msg += ('\n\nThis converts the following to Kconfig:\n   %s\n' %
1617                 '\n   '.join(configs))
1618     else:
1619         msg = 'configs: Resync with savedefconfig'
1620         msg += '\n\nRsync all defconfig files using moveconfig.py'
1621     subprocess.call(['git', 'commit', '-s', '-m', msg])
1622
1623
1624 def write_db(config_db, progress):
1625     """Write the database to a file
1626
1627     Args:
1628         config_db (dict of dict): configs for each defconfig
1629             key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
1630             value: dict:
1631                 key: CONFIG option
1632                 value: Value of option
1633         progress (Progress): Progress indicator.
1634
1635     Returns:
1636         int: exit code (0 for success)
1637     """
1638     col = progress.col
1639     with open(CONFIG_DATABASE, 'w', encoding='utf-8') as outf:
1640         for defconfig, configs in config_db.items():
1641             outf.write(f'{defconfig}\n')
1642             for config in sorted(configs.keys()):
1643                 outf.write(f'   {config}={configs[config]}\n')
1644             outf.write('\n')
1645     print(col.build(
1646         col.RED if progress.failed else col.GREEN,
1647         f'{progress.failure_msg}{len(config_db)} boards written to {CONFIG_DATABASE}'))
1648     return 0
1649
1650
1651 def move_done(progress):
1652     """Write a message indicating that the move is done
1653
1654     Args:
1655         progress (Progress): Progress indicator.
1656
1657     Returns:
1658         int: exit code (0 for success)
1659     """
1660     col = progress.col
1661     if progress.failed:
1662         print(col.build(col.RED, f'{progress.failure_msg}see {FAILED_LIST}', True))
1663     else:
1664         # Add enough spaces to overwrite the progress indicator
1665         print(col.build(
1666             col.GREEN, f'{progress.total} processed        ', bright=True))
1667     return 0
1668
1669 def do_tests():
1670     """Run doctests and unit tests (so far there are no unit tests)"""
1671     sys.argv = [sys.argv[0]]
1672     fail, _ = doctest.testmod()
1673     if fail:
1674         return 1
1675     unittest.main()
1676     return 0
1677
1678
1679 def main():
1680     """Main program"""
1681     parser, args = parse_args()
1682     check_top_directory()
1683
1684     # prefix the option name with CONFIG_ if missing
1685     args.configs = [prefix_config(cfg) for cfg in args.configs]
1686
1687     if args.test:
1688         return do_tests()
1689     if args.scan_source:
1690         return do_scan_source(os.getcwd(), args.update)
1691     if args.imply:
1692         if imply(args):
1693             parser.print_usage()
1694             sys.exit(1)
1695         return 0
1696     if args.find:
1697         return do_find_config(args.configs, args.list)
1698
1699     config_db, progress = move_config(args)
1700
1701     if args.commit:
1702         add_commit(args.configs)
1703
1704     if args.build_db:
1705         return write_db(config_db, progress)
1706     return move_done(progress)
1707
1708
1709 if __name__ == '__main__':
1710     sys.exit(main())
This page took 0.123432 seconds and 4 git commands to generate.