binman: Add BintoolPacker class to bintool
[platform/kernel/u-boot.git] / tools / moveconfig.py
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: GPL-2.0+
3 #
4 # Author: Masahiro Yamada <yamada.masahiro@socionext.com>
5 #
6
7 """
8 Move config options from headers to defconfig files.
9
10 See doc/develop/moveconfig.rst for documentation.
11 """
12
13 from argparse import ArgumentParser
14 import asteval
15 import collections
16 from contextlib import ExitStack
17 import copy
18 import difflib
19 import doctest
20 import filecmp
21 import fnmatch
22 import glob
23 import multiprocessing
24 import os
25 import queue
26 import re
27 import shutil
28 import subprocess
29 import sys
30 import tempfile
31 import threading
32 import time
33 import unittest
34
35 from buildman import bsettings
36 from buildman import kconfiglib
37 from buildman import toolchain
38
39 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
40 SLEEP_TIME=0.03
41
42 STATE_IDLE = 0
43 STATE_DEFCONFIG = 1
44 STATE_AUTOCONF = 2
45 STATE_SAVEDEFCONFIG = 3
46
47 ACTION_MOVE = 0
48 ACTION_NO_ENTRY = 1
49 ACTION_NO_ENTRY_WARN = 2
50 ACTION_NO_CHANGE = 3
51
52 COLOR_BLACK        = '0;30'
53 COLOR_RED          = '0;31'
54 COLOR_GREEN        = '0;32'
55 COLOR_BROWN        = '0;33'
56 COLOR_BLUE         = '0;34'
57 COLOR_PURPLE       = '0;35'
58 COLOR_CYAN         = '0;36'
59 COLOR_LIGHT_GRAY   = '0;37'
60 COLOR_DARK_GRAY    = '1;30'
61 COLOR_LIGHT_RED    = '1;31'
62 COLOR_LIGHT_GREEN  = '1;32'
63 COLOR_YELLOW       = '1;33'
64 COLOR_LIGHT_BLUE   = '1;34'
65 COLOR_LIGHT_PURPLE = '1;35'
66 COLOR_LIGHT_CYAN   = '1;36'
67 COLOR_WHITE        = '1;37'
68
69 AUTO_CONF_PATH = 'include/config/auto.conf'
70 CONFIG_DATABASE = 'moveconfig.db'
71
72 CONFIG_LEN = len('CONFIG_')
73
74 SIZES = {
75     'SZ_1':    0x00000001, 'SZ_2':    0x00000002,
76     'SZ_4':    0x00000004, 'SZ_8':    0x00000008,
77     'SZ_16':   0x00000010, 'SZ_32':   0x00000020,
78     'SZ_64':   0x00000040, 'SZ_128':  0x00000080,
79     'SZ_256':  0x00000100, 'SZ_512':  0x00000200,
80     'SZ_1K':   0x00000400, 'SZ_2K':   0x00000800,
81     'SZ_4K':   0x00001000, 'SZ_8K':   0x00002000,
82     'SZ_16K':  0x00004000, 'SZ_32K':  0x00008000,
83     'SZ_64K':  0x00010000, 'SZ_128K': 0x00020000,
84     'SZ_256K': 0x00040000, 'SZ_512K': 0x00080000,
85     'SZ_1M':   0x00100000, 'SZ_2M':   0x00200000,
86     'SZ_4M':   0x00400000, 'SZ_8M':   0x00800000,
87     'SZ_16M':  0x01000000, 'SZ_32M':  0x02000000,
88     'SZ_64M':  0x04000000, 'SZ_128M': 0x08000000,
89     'SZ_256M': 0x10000000, 'SZ_512M': 0x20000000,
90     'SZ_1G':   0x40000000, 'SZ_2G':   0x80000000,
91     'SZ_4G':  0x100000000
92 }
93
94 RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig')
95
96 ### helper functions ###
97 def check_top_directory():
98     """Exit if we are not at the top of source directory."""
99     for fname in 'README', 'Licenses':
100         if not os.path.exists(fname):
101             sys.exit('Please run at the top of source directory.')
102
103 def check_clean_directory():
104     """Exit if the source tree is not clean."""
105     for fname in '.config', 'include/config':
106         if os.path.exists(fname):
107             sys.exit("source tree is not clean, please run 'make mrproper'")
108
109 def get_make_cmd():
110     """Get the command name of GNU Make.
111
112     U-Boot needs GNU Make for building, but the command name is not
113     necessarily "make". (for example, "gmake" on FreeBSD).
114     Returns the most appropriate command name on your system.
115     """
116     with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc:
117         ret = proc.communicate()
118         if proc.returncode:
119             sys.exit('GNU Make not found')
120     return ret[0].rstrip()
121
122 def get_matched_defconfig(line):
123     """Get the defconfig files that match a pattern
124
125     Args:
126         line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or
127             'k2*_defconfig'. If no directory is provided, 'configs/' is
128             prepended
129
130     Returns:
131         list of str: a list of matching defconfig files
132     """
133     dirname = os.path.dirname(line)
134     if dirname:
135         pattern = line
136     else:
137         pattern = os.path.join('configs', line)
138     return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
139
140 def get_matched_defconfigs(defconfigs_file):
141     """Get all the defconfig files that match the patterns in a file.
142
143     Args:
144         defconfigs_file (str): File containing a list of defconfigs to process,
145             or '-' to read the list from stdin
146
147     Returns:
148         list of str: A list of paths to defconfig files, with no duplicates
149     """
150     defconfigs = []
151     with ExitStack() as stack:
152         if defconfigs_file == '-':
153             inf = sys.stdin
154             defconfigs_file = 'stdin'
155         else:
156             inf = stack.enter_context(open(defconfigs_file, encoding='utf-8'))
157         for i, line in enumerate(inf):
158             line = line.strip()
159             if not line:
160                 continue # skip blank lines silently
161             if ' ' in line:
162                 line = line.split(' ')[0]  # handle 'git log' input
163             matched = get_matched_defconfig(line)
164             if not matched:
165                 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'",
166                       file=sys.stderr)
167
168             defconfigs += matched
169
170     # use set() to drop multiple matching
171     return [defconfig[len('configs') + 1:]  for defconfig in set(defconfigs)]
172
173 def get_all_defconfigs():
174     """Get all the defconfig files under the configs/ directory.
175
176     Returns:
177         list of str: List of paths to defconfig files
178     """
179     defconfigs = []
180     for (dirpath, _, filenames) in os.walk('configs'):
181         dirpath = dirpath[len('configs') + 1:]
182         for filename in fnmatch.filter(filenames, '*_defconfig'):
183             defconfigs.append(os.path.join(dirpath, filename))
184
185     return defconfigs
186
187 def color_text(color_enabled, color, string):
188     """Return colored string."""
189     if color_enabled:
190         # LF should not be surrounded by the escape sequence.
191         # Otherwise, additional whitespace or line-feed might be printed.
192         return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
193                            for s in string.split('\n') ])
194     return string
195
196 def show_diff(alines, blines, file_path, color_enabled):
197     """Show unidified diff.
198
199     Args:
200        alines (list of str): A list of lines (before)
201        blines (list of str): A list of lines (after)
202        file_path (str): Path to the file
203        color_enabled (bool): Display the diff in color
204     """
205     diff = difflib.unified_diff(alines, blines,
206                                 fromfile=os.path.join('a', file_path),
207                                 tofile=os.path.join('b', file_path))
208
209     for line in diff:
210         if line.startswith('-') and not line.startswith('--'):
211             print(color_text(color_enabled, COLOR_RED, line))
212         elif line.startswith('+') and not line.startswith('++'):
213             print(color_text(color_enabled, COLOR_GREEN, line))
214         else:
215             print(line)
216
217 def extend_matched_lines(lines, matched, pre_patterns, post_patterns,
218                          extend_pre, extend_post):
219     """Extend matched lines if desired patterns are found before/after already
220     matched lines.
221
222     Args:
223       lines (list of str): list of lines handled.
224       matched (list of int): list of line numbers that have been already
225           matched (will be updated by this function)
226       pre_patterns (list of re.Pattern): list of regular expression that should
227           be matched as preamble
228       post_patterns (list of re.Pattern): list of regular expression that should
229           be matched as postamble
230       extend_pre (bool): Add the line number of matched preamble to the matched
231           list
232       extend_post (bool): Add the line number of matched postamble to the
233           matched list
234     """
235     extended_matched = []
236
237     j = matched[0]
238
239     for i in matched:
240         if i == 0 or i < j:
241             continue
242         j = i
243         while j in matched:
244             j += 1
245         if j >= len(lines):
246             break
247
248         for pat in pre_patterns:
249             if pat.search(lines[i - 1]):
250                 break
251         else:
252             # not matched
253             continue
254
255         for pat in post_patterns:
256             if pat.search(lines[j]):
257                 break
258         else:
259             # not matched
260             continue
261
262         if extend_pre:
263             extended_matched.append(i - 1)
264         if extend_post:
265             extended_matched.append(j)
266
267     matched += extended_matched
268     matched.sort()
269
270 def confirm(args, prompt):
271     """Ask the user to confirm something
272
273     Args:
274         args (Namespace ): program arguments
275
276     Returns:
277         bool: True to confirm, False to cancel/stop
278     """
279     if not args.yes:
280         while True:
281             choice = input(f'{prompt} [y/n]: ')
282             choice = choice.lower()
283             print(choice)
284             if choice in ('y', 'n'):
285                 break
286
287         if choice == 'n':
288             return False
289
290     return True
291
292 def write_file(fname, data):
293     """Write data to a file
294
295     Args:
296         fname (str): Filename to write to
297         data (list of str): Lines to write (with or without trailing newline);
298             or str to write
299     """
300     with open(fname, 'w', encoding='utf-8') as out:
301         if isinstance(data, list):
302             for line in data:
303                 print(line.rstrip('\n'), file=out)
304         else:
305             out.write(data)
306
307 def read_file(fname, as_lines=True, skip_unicode=False):
308     """Read a file and return the contents
309
310     Args:
311         fname (str): Filename to read from
312         as_lines: Return file contents as a list of lines
313         skip_unicode (bool): True to report unicode errors and continue
314
315     Returns:
316         iter of str: List of ;ines from the file with newline removed; str if
317             as_lines is False with newlines intact; or None if a unicode error
318             occurred
319
320     Raises:
321         UnicodeDecodeError: Unicode error occurred when reading
322     """
323     with open(fname, encoding='utf-8') as inf:
324         try:
325             if as_lines:
326                 return [line.rstrip('\n') for line in inf.readlines()]
327             else:
328                 return inf.read()
329         except UnicodeDecodeError as e:
330             if not skip_unicode:
331                 raise
332             print("Failed on file %s': %s" % (fname, e))
333             return None
334
335 def cleanup_empty_blocks(header_path, args):
336     """Clean up empty conditional blocks
337
338     Args:
339       header_path (str): path to the cleaned file.
340       args (Namespace): program arguments
341     """
342     pattern = re.compile(r'^\s*#\s*if.*$\n^\s*#\s*endif.*$\n*', flags=re.M)
343     data = read_file(header_path, as_lines=False, skip_unicode=True)
344     if data is None:
345         return
346
347     new_data = pattern.sub('\n', data)
348
349     show_diff(data.splitlines(True), new_data.splitlines(True), header_path,
350               args.color)
351
352     if args.dry_run:
353         return
354
355     if new_data != data:
356         write_file(header_path, new_data)
357
358 def cleanup_one_header(header_path, patterns, args):
359     """Clean regex-matched lines away from a file.
360
361     Args:
362       header_path: path to the cleaned file.
363       patterns: list of regex patterns.  Any lines matching to these
364                 patterns are deleted.
365       args (Namespace): program arguments
366     """
367     lines = read_file(header_path, skip_unicode=True)
368     if lines is None:
369         return
370
371     matched = []
372     for i, line in enumerate(lines):
373         if i - 1 in matched and lines[i - 1].endswith('\\'):
374             matched.append(i)
375             continue
376         for pattern in patterns:
377             if pattern.search(line):
378                 matched.append(i)
379                 break
380
381     if not matched:
382         return
383
384     # remove empty #ifdef ... #endif, successive blank lines
385     pattern_if = re.compile(r'#\s*if(def|ndef)?\b') #  #if, #ifdef, #ifndef
386     pattern_elif = re.compile(r'#\s*el(if|se)\b')   #  #elif, #else
387     pattern_endif = re.compile(r'#\s*endif\b')      #  #endif
388     pattern_blank = re.compile(r'^\s*$')            #  empty line
389
390     while True:
391         old_matched = copy.copy(matched)
392         extend_matched_lines(lines, matched, [pattern_if],
393                              [pattern_endif], True, True)
394         extend_matched_lines(lines, matched, [pattern_elif],
395                              [pattern_elif, pattern_endif], True, False)
396         extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
397                              [pattern_blank], False, True)
398         extend_matched_lines(lines, matched, [pattern_blank],
399                              [pattern_elif, pattern_endif], True, False)
400         extend_matched_lines(lines, matched, [pattern_blank],
401                              [pattern_blank], True, False)
402         if matched == old_matched:
403             break
404
405     tolines = copy.copy(lines)
406
407     for i in reversed(matched):
408         tolines.pop(i)
409
410     show_diff(lines, tolines, header_path, args.color)
411
412     if args.dry_run:
413         return
414
415     write_file(header_path, tolines)
416
417 def cleanup_headers(configs, args):
418     """Delete config defines from board headers.
419
420     Args:
421       configs: A list of CONFIGs to remove.
422       args (Namespace): program arguments
423     """
424     if not confirm(args, 'Clean up headers?'):
425         return
426
427     patterns = []
428     for config in configs:
429         patterns.append(re.compile(r'#\s*define\s+%s\b' % config))
430         patterns.append(re.compile(r'#\s*undef\s+%s\b' % config))
431
432     for dir in 'include', 'arch', 'board':
433         for (dirpath, dirnames, filenames) in os.walk(dir):
434             if dirpath == os.path.join('include', 'generated'):
435                 continue
436             for filename in filenames:
437                 if not filename.endswith(('~', '.dts', '.dtsi', '.bin',
438                                           '.elf','.aml','.dat')):
439                     header_path = os.path.join(dirpath, filename)
440                     # This file contains UTF-16 data and no CONFIG symbols
441                     if header_path == 'include/video_font_data.h':
442                         continue
443                     cleanup_one_header(header_path, patterns, args)
444                     cleanup_empty_blocks(header_path, args)
445
446 def cleanup_whitelist(configs, args):
447     """Delete config whitelist entries
448
449     Args:
450       configs: A list of CONFIGs to remove.
451       args (Namespace): program arguments
452     """
453     if not confirm(args, 'Clean up whitelist entries?'):
454         return
455
456     lines = read_file(os.path.join('scripts', 'config_whitelist.txt'))
457
458     lines = [x for x in lines if x.strip() not in configs]
459
460     write_file(os.path.join('scripts', 'config_whitelist.txt'), lines)
461
462 def find_matching(patterns, line):
463     for pat in patterns:
464         if pat.search(line):
465             return True
466     return False
467
468 def cleanup_readme(configs, args):
469     """Delete config description in README
470
471     Args:
472       configs: A list of CONFIGs to remove.
473       args (Namespace): program arguments
474     """
475     if not confirm(args, 'Clean up README?'):
476         return
477
478     patterns = []
479     for config in configs:
480         patterns.append(re.compile(r'^\s+%s' % config))
481
482     lines = read_file('README')
483
484     found = False
485     newlines = []
486     for line in lines:
487         if not found:
488             found = find_matching(patterns, line)
489             if found:
490                 continue
491
492         if found and re.search(r'^\s+CONFIG', line):
493             found = False
494
495         if not found:
496             newlines.append(line)
497
498     write_file('README', newlines)
499
500 def try_expand(line):
501     """If value looks like an expression, try expanding it
502     Otherwise just return the existing value
503     """
504     if line.find('=') == -1:
505         return line
506
507     try:
508         aeval = asteval.Interpreter( usersyms=SIZES, minimal=True )
509         cfg, val = re.split("=", line)
510         val= val.strip('\"')
511         if re.search(r'[*+-/]|<<|SZ_+|\(([^\)]+)\)', val):
512             newval = hex(aeval(val))
513             print('\tExpanded expression %s to %s' % (val, newval))
514             return cfg+'='+newval
515     except:
516         print('\tFailed to expand expression in %s' % line)
517
518     return line
519
520
521 ### classes ###
522 class Progress:
523
524     """Progress Indicator"""
525
526     def __init__(self, total):
527         """Create a new progress indicator.
528
529         Args:
530           total: A number of defconfig files to process.
531         """
532         self.current = 0
533         self.total = total
534
535     def inc(self):
536         """Increment the number of processed defconfig files."""
537
538         self.current += 1
539
540     def show(self):
541         """Display the progress."""
542         print(' %d defconfigs out of %d\r' % (self.current, self.total), end=' ')
543         sys.stdout.flush()
544
545
546 class KconfigScanner:
547     """Kconfig scanner."""
548
549     def __init__(self):
550         """Scan all the Kconfig files and create a Config object."""
551         # Define environment variables referenced from Kconfig
552         os.environ['srctree'] = os.getcwd()
553         os.environ['UBOOTVERSION'] = 'dummy'
554         os.environ['KCONFIG_OBJDIR'] = ''
555         self.conf = kconfiglib.Kconfig()
556
557
558 class KconfigParser:
559
560     """A parser of .config and include/autoconf.mk."""
561
562     re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
563     re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
564
565     def __init__(self, configs, args, build_dir):
566         """Create a new parser.
567
568         Args:
569           configs: A list of CONFIGs to move.
570           args (Namespace): program arguments
571           build_dir: Build directory.
572         """
573         self.configs = configs
574         self.args = args
575         self.dotconfig = os.path.join(build_dir, '.config')
576         self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
577         self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
578                                          'autoconf.mk')
579         self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
580         self.defconfig = os.path.join(build_dir, 'defconfig')
581
582     def get_arch(self):
583         """Parse .config file and return the architecture.
584
585         Returns:
586           Architecture name (e.g. 'arm').
587         """
588         arch = ''
589         cpu = ''
590         for line in read_file(self.dotconfig):
591             m = self.re_arch.match(line)
592             if m:
593                 arch = m.group(1)
594                 continue
595             m = self.re_cpu.match(line)
596             if m:
597                 cpu = m.group(1)
598
599         if not arch:
600             return None
601
602         # fix-up for aarch64
603         if arch == 'arm' and cpu == 'armv8':
604             arch = 'aarch64'
605
606         return arch
607
608     def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
609         """Parse .config, defconfig, include/autoconf.mk for one config.
610
611         This function looks for the config options in the lines from
612         defconfig, .config, and include/autoconf.mk in order to decide
613         which action should be taken for this defconfig.
614
615         Args:
616           config: CONFIG name to parse.
617           dotconfig_lines: lines from the .config file.
618           autoconf_lines: lines from the include/autoconf.mk file.
619
620         Returns:
621           A tupple of the action for this defconfig and the line
622           matched for the config.
623         """
624         not_set = '# %s is not set' % config
625
626         for line in autoconf_lines:
627             line = line.rstrip()
628             if line.startswith(config + '='):
629                 new_val = line
630                 break
631         else:
632             new_val = not_set
633
634         new_val = try_expand(new_val)
635
636         for line in dotconfig_lines:
637             line = line.rstrip()
638             if line.startswith(config + '=') or line == not_set:
639                 old_val = line
640                 break
641         else:
642             if new_val == not_set:
643                 return (ACTION_NO_ENTRY, config)
644             else:
645                 return (ACTION_NO_ENTRY_WARN, config)
646
647         # If this CONFIG is neither bool nor trisate
648         if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
649             # tools/scripts/define2mk.sed changes '1' to 'y'.
650             # This is a problem if the CONFIG is int type.
651             # Check the type in Kconfig and handle it correctly.
652             if new_val[-2:] == '=y':
653                 new_val = new_val[:-1] + '1'
654
655         return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
656                 new_val)
657
658     def update_dotconfig(self):
659         """Parse files for the config options and update the .config.
660
661         This function parses the generated .config and include/autoconf.mk
662         searching the target options.
663         Move the config option(s) to the .config as needed.
664
665         Args:
666           defconfig: defconfig name.
667
668         Returns:
669           Return a tuple of (updated flag, log string).
670           The "updated flag" is True if the .config was updated, False
671           otherwise.  The "log string" shows what happend to the .config.
672         """
673
674         results = []
675         updated = False
676         suspicious = False
677         rm_files = [self.config_autoconf, self.autoconf]
678
679         if self.args.spl:
680             if os.path.exists(self.spl_autoconf):
681                 autoconf_path = self.spl_autoconf
682                 rm_files.append(self.spl_autoconf)
683             else:
684                 for f in rm_files:
685                     os.remove(f)
686                 return (updated, suspicious,
687                         color_text(self.args.color, COLOR_BROWN,
688                                    "SPL is not enabled.  Skipped.") + '\n')
689         else:
690             autoconf_path = self.autoconf
691
692         dotconfig_lines = read_file(self.dotconfig)
693
694         autoconf_lines = read_file(autoconf_path)
695
696         for config in self.configs:
697             result = self.parse_one_config(config, dotconfig_lines,
698                                            autoconf_lines)
699             results.append(result)
700
701         log = ''
702
703         for (action, value) in results:
704             if action == ACTION_MOVE:
705                 actlog = "Move '%s'" % value
706                 log_color = COLOR_LIGHT_GREEN
707             elif action == ACTION_NO_ENTRY:
708                 actlog = '%s is not defined in Kconfig.  Do nothing.' % value
709                 log_color = COLOR_LIGHT_BLUE
710             elif action == ACTION_NO_ENTRY_WARN:
711                 actlog = '%s is not defined in Kconfig (suspicious).  Do nothing.' % value
712                 log_color = COLOR_YELLOW
713                 suspicious = True
714             elif action == ACTION_NO_CHANGE:
715                 actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
716                          % value
717                 log_color = COLOR_LIGHT_PURPLE
718             else:
719                 sys.exit('Internal Error. This should not happen.')
720
721             log += color_text(self.args.color, log_color, actlog) + '\n'
722
723         with open(self.dotconfig, 'a', encoding='utf-8') as out:
724             for (action, value) in results:
725                 if action == ACTION_MOVE:
726                     out.write(value + '\n')
727                     updated = True
728
729         self.results = results
730         for f in rm_files:
731             os.remove(f)
732
733         return (updated, suspicious, log)
734
735     def check_defconfig(self):
736         """Check the defconfig after savedefconfig
737
738         Returns:
739           Return additional log if moved CONFIGs were removed again by
740           'make savedefconfig'.
741         """
742
743         log = ''
744
745         defconfig_lines = read_file(self.defconfig)
746
747         for (action, value) in self.results:
748             if action != ACTION_MOVE:
749                 continue
750             if not value in defconfig_lines:
751                 log += color_text(self.args.color, COLOR_YELLOW,
752                                   "'%s' was removed by savedefconfig.\n" %
753                                   value)
754
755         return log
756
757
758 class DatabaseThread(threading.Thread):
759     """This thread processes results from Slot threads.
760
761     It collects the data in the master config directary. There is only one
762     result thread, and this helps to serialise the build output.
763     """
764     def __init__(self, config_db, db_queue):
765         """Set up a new result thread
766
767         Args:
768             builder: Builder which will be sent each result
769         """
770         threading.Thread.__init__(self)
771         self.config_db = config_db
772         self.db_queue= db_queue
773
774     def run(self):
775         """Called to start up the result thread.
776
777         We collect the next result job and pass it on to the build.
778         """
779         while True:
780             defconfig, configs = self.db_queue.get()
781             self.config_db[defconfig] = configs
782             self.db_queue.task_done()
783
784
785 class Slot:
786
787     """A slot to store a subprocess.
788
789     Each instance of this class handles one subprocess.
790     This class is useful to control multiple threads
791     for faster processing.
792     """
793
794     def __init__(self, toolchains, configs, args, progress, devnull,
795                  make_cmd, reference_src_dir, db_queue):
796         """Create a new process slot.
797
798         Args:
799           toolchains: Toolchains object containing toolchains.
800           configs: A list of CONFIGs to move.
801           args: Program arguments
802           progress: A progress indicator.
803           devnull: A file object of '/dev/null'.
804           make_cmd: command name of GNU Make.
805           reference_src_dir: Determine the true starting config state from this
806                              source tree.
807           db_queue: output queue to write config info for the database
808         """
809         self.toolchains = toolchains
810         self.args = args
811         self.progress = progress
812         self.build_dir = tempfile.mkdtemp()
813         self.devnull = devnull
814         self.make_cmd = (make_cmd, 'O=' + self.build_dir)
815         self.reference_src_dir = reference_src_dir
816         self.db_queue = db_queue
817         self.parser = KconfigParser(configs, args, self.build_dir)
818         self.state = STATE_IDLE
819         self.failed_boards = set()
820         self.suspicious_boards = set()
821
822     def __del__(self):
823         """Delete the working directory
824
825         This function makes sure the temporary directory is cleaned away
826         even if Python suddenly dies due to error.  It should be done in here
827         because it is guaranteed the destructor is always invoked when the
828         instance of the class gets unreferenced.
829
830         If the subprocess is still running, wait until it finishes.
831         """
832         if self.state != STATE_IDLE:
833             while self.ps.poll() == None:
834                 pass
835         shutil.rmtree(self.build_dir)
836
837     def add(self, defconfig):
838         """Assign a new subprocess for defconfig and add it to the slot.
839
840         If the slot is vacant, create a new subprocess for processing the
841         given defconfig and add it to the slot.  Just returns False if
842         the slot is occupied (i.e. the current subprocess is still running).
843
844         Args:
845           defconfig: defconfig name.
846
847         Returns:
848           Return True on success or False on failure
849         """
850         if self.state != STATE_IDLE:
851             return False
852
853         self.defconfig = defconfig
854         self.log = ''
855         self.current_src_dir = self.reference_src_dir
856         self.do_defconfig()
857         return True
858
859     def poll(self):
860         """Check the status of the subprocess and handle it as needed.
861
862         Returns True if the slot is vacant (i.e. in idle state).
863         If the configuration is successfully finished, assign a new
864         subprocess to build include/autoconf.mk.
865         If include/autoconf.mk is generated, invoke the parser to
866         parse the .config and the include/autoconf.mk, moving
867         config options to the .config as needed.
868         If the .config was updated, run "make savedefconfig" to sync
869         it, update the original defconfig, and then set the slot back
870         to the idle state.
871
872         Returns:
873           Return True if the subprocess is terminated, False otherwise
874         """
875         if self.state == STATE_IDLE:
876             return True
877
878         if self.ps.poll() == None:
879             return False
880
881         if self.ps.poll() != 0:
882             self.handle_error()
883         elif self.state == STATE_DEFCONFIG:
884             if self.reference_src_dir and not self.current_src_dir:
885                 self.do_savedefconfig()
886             else:
887                 self.do_autoconf()
888         elif self.state == STATE_AUTOCONF:
889             if self.current_src_dir:
890                 self.current_src_dir = None
891                 self.do_defconfig()
892             elif self.args.build_db:
893                 self.do_build_db()
894             else:
895                 self.do_savedefconfig()
896         elif self.state == STATE_SAVEDEFCONFIG:
897             self.update_defconfig()
898         else:
899             sys.exit('Internal Error. This should not happen.')
900
901         return True if self.state == STATE_IDLE else False
902
903     def handle_error(self):
904         """Handle error cases."""
905
906         self.log += color_text(self.args.color, COLOR_LIGHT_RED,
907                                'Failed to process.\n')
908         if self.args.verbose:
909             self.log += color_text(self.args.color, COLOR_LIGHT_CYAN,
910                                    self.ps.stderr.read().decode())
911         self.finish(False)
912
913     def do_defconfig(self):
914         """Run 'make <board>_defconfig' to create the .config file."""
915
916         cmd = list(self.make_cmd)
917         cmd.append(self.defconfig)
918         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
919                                    stderr=subprocess.PIPE,
920                                    cwd=self.current_src_dir)
921         self.state = STATE_DEFCONFIG
922
923     def do_autoconf(self):
924         """Run 'make AUTO_CONF_PATH'."""
925
926         arch = self.parser.get_arch()
927         try:
928             toolchain = self.toolchains.Select(arch)
929         except ValueError:
930             self.log += color_text(self.args.color, COLOR_YELLOW,
931                     "Tool chain for '%s' is missing.  Do nothing.\n" % arch)
932             self.finish(False)
933             return
934         env = toolchain.MakeEnvironment(False)
935
936         cmd = list(self.make_cmd)
937         cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
938         cmd.append(AUTO_CONF_PATH)
939         self.ps = subprocess.Popen(cmd, stdout=self.devnull, env=env,
940                                    stderr=subprocess.PIPE,
941                                    cwd=self.current_src_dir)
942         self.state = STATE_AUTOCONF
943
944     def do_build_db(self):
945         """Add the board to the database"""
946         configs = {}
947         for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)):
948             if line.startswith('CONFIG'):
949                 config, value = line.split('=', 1)
950                 configs[config] = value.rstrip()
951         self.db_queue.put([self.defconfig, configs])
952         self.finish(True)
953
954     def do_savedefconfig(self):
955         """Update the .config and run 'make savedefconfig'."""
956
957         (updated, suspicious, log) = self.parser.update_dotconfig()
958         if suspicious:
959             self.suspicious_boards.add(self.defconfig)
960         self.log += log
961
962         if not self.args.force_sync and not updated:
963             self.finish(True)
964             return
965         if updated:
966             self.log += color_text(self.args.color, COLOR_LIGHT_GREEN,
967                                    'Syncing by savedefconfig...\n')
968         else:
969             self.log += 'Syncing by savedefconfig (forced by option)...\n'
970
971         cmd = list(self.make_cmd)
972         cmd.append('savedefconfig')
973         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
974                                    stderr=subprocess.PIPE)
975         self.state = STATE_SAVEDEFCONFIG
976
977     def update_defconfig(self):
978         """Update the input defconfig and go back to the idle state."""
979
980         log = self.parser.check_defconfig()
981         if log:
982             self.suspicious_boards.add(self.defconfig)
983             self.log += log
984         orig_defconfig = os.path.join('configs', self.defconfig)
985         new_defconfig = os.path.join(self.build_dir, 'defconfig')
986         updated = not filecmp.cmp(orig_defconfig, new_defconfig)
987
988         if updated:
989             self.log += color_text(self.args.color, COLOR_LIGHT_BLUE,
990                                    'defconfig was updated.\n')
991
992         if not self.args.dry_run and updated:
993             shutil.move(new_defconfig, orig_defconfig)
994         self.finish(True)
995
996     def finish(self, success):
997         """Display log along with progress and go to the idle state.
998
999         Args:
1000           success: Should be True when the defconfig was processed
1001                    successfully, or False when it fails.
1002         """
1003         # output at least 30 characters to hide the "* defconfigs out of *".
1004         log = self.defconfig.ljust(30) + '\n'
1005
1006         log += '\n'.join([ '    ' + s for s in self.log.split('\n') ])
1007         # Some threads are running in parallel.
1008         # Print log atomically to not mix up logs from different threads.
1009         print(log, file=(sys.stdout if success else sys.stderr))
1010
1011         if not success:
1012             if self.args.exit_on_error:
1013                 sys.exit('Exit on error.')
1014             # If --exit-on-error flag is not set, skip this board and continue.
1015             # Record the failed board.
1016             self.failed_boards.add(self.defconfig)
1017
1018         self.progress.inc()
1019         self.progress.show()
1020         self.state = STATE_IDLE
1021
1022     def get_failed_boards(self):
1023         """Returns a set of failed boards (defconfigs) in this slot.
1024         """
1025         return self.failed_boards
1026
1027     def get_suspicious_boards(self):
1028         """Returns a set of boards (defconfigs) with possible misconversion.
1029         """
1030         return self.suspicious_boards - self.failed_boards
1031
1032 class Slots:
1033
1034     """Controller of the array of subprocess slots."""
1035
1036     def __init__(self, toolchains, configs, args, progress,
1037                  reference_src_dir, db_queue):
1038         """Create a new slots controller.
1039
1040         Args:
1041           toolchains: Toolchains object containing toolchains.
1042           configs: A list of CONFIGs to move.
1043           args: Program arguments
1044           progress: A progress indicator.
1045           reference_src_dir: Determine the true starting config state from this
1046                              source tree.
1047           db_queue: output queue to write config info for the database
1048         """
1049         self.args = args
1050         self.slots = []
1051         devnull = subprocess.DEVNULL
1052         make_cmd = get_make_cmd()
1053         for i in range(args.jobs):
1054             self.slots.append(Slot(toolchains, configs, args, progress,
1055                                    devnull, make_cmd, reference_src_dir,
1056                                    db_queue))
1057
1058     def add(self, defconfig):
1059         """Add a new subprocess if a vacant slot is found.
1060
1061         Args:
1062           defconfig: defconfig name to be put into.
1063
1064         Returns:
1065           Return True on success or False on failure
1066         """
1067         for slot in self.slots:
1068             if slot.add(defconfig):
1069                 return True
1070         return False
1071
1072     def available(self):
1073         """Check if there is a vacant slot.
1074
1075         Returns:
1076           Return True if at lease one vacant slot is found, False otherwise.
1077         """
1078         for slot in self.slots:
1079             if slot.poll():
1080                 return True
1081         return False
1082
1083     def empty(self):
1084         """Check if all slots are vacant.
1085
1086         Returns:
1087           Return True if all the slots are vacant, False otherwise.
1088         """
1089         ret = True
1090         for slot in self.slots:
1091             if not slot.poll():
1092                 ret = False
1093         return ret
1094
1095     def show_failed_boards(self):
1096         """Display all of the failed boards (defconfigs)."""
1097         boards = set()
1098         output_file = 'moveconfig.failed'
1099
1100         for slot in self.slots:
1101             boards |= slot.get_failed_boards()
1102
1103         if boards:
1104             boards = '\n'.join(boards) + '\n'
1105             msg = 'The following boards were not processed due to error:\n'
1106             msg += boards
1107             msg += '(the list has been saved in %s)\n' % output_file
1108             print(color_text(self.args.color, COLOR_LIGHT_RED,
1109                                             msg), file=sys.stderr)
1110
1111             write_file(output_file, boards)
1112
1113     def show_suspicious_boards(self):
1114         """Display all boards (defconfigs) with possible misconversion."""
1115         boards = set()
1116         output_file = 'moveconfig.suspicious'
1117
1118         for slot in self.slots:
1119             boards |= slot.get_suspicious_boards()
1120
1121         if boards:
1122             boards = '\n'.join(boards) + '\n'
1123             msg = 'The following boards might have been converted incorrectly.\n'
1124             msg += 'It is highly recommended to check them manually:\n'
1125             msg += boards
1126             msg += '(the list has been saved in %s)\n' % output_file
1127             print(color_text(self.args.color, COLOR_YELLOW,
1128                                             msg), file=sys.stderr)
1129
1130             write_file(output_file, boards)
1131
1132 class ReferenceSource:
1133
1134     """Reference source against which original configs should be parsed."""
1135
1136     def __init__(self, commit):
1137         """Create a reference source directory based on a specified commit.
1138
1139         Args:
1140           commit: commit to git-clone
1141         """
1142         self.src_dir = tempfile.mkdtemp()
1143         print('Cloning git repo to a separate work directory...')
1144         subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1145                                 cwd=self.src_dir)
1146         print("Checkout '%s' to build the original autoconf.mk." % \
1147             subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip())
1148         subprocess.check_output(['git', 'checkout', commit],
1149                                 stderr=subprocess.STDOUT, cwd=self.src_dir)
1150
1151     def __del__(self):
1152         """Delete the reference source directory
1153
1154         This function makes sure the temporary directory is cleaned away
1155         even if Python suddenly dies due to error.  It should be done in here
1156         because it is guaranteed the destructor is always invoked when the
1157         instance of the class gets unreferenced.
1158         """
1159         shutil.rmtree(self.src_dir)
1160
1161     def get_dir(self):
1162         """Return the absolute path to the reference source directory."""
1163
1164         return self.src_dir
1165
1166 def move_config(toolchains, configs, args, db_queue):
1167     """Move config options to defconfig files.
1168
1169     Args:
1170       configs: A list of CONFIGs to move.
1171       args: Program arguments
1172     """
1173     if len(configs) == 0:
1174         if args.force_sync:
1175             print('No CONFIG is specified. You are probably syncing defconfigs.', end=' ')
1176         elif args.build_db:
1177             print('Building %s database' % CONFIG_DATABASE)
1178         else:
1179             print('Neither CONFIG nor --force-sync is specified. Nothing will happen.', end=' ')
1180     else:
1181         print('Move ' + ', '.join(configs), end=' ')
1182     print('(jobs: %d)\n' % args.jobs)
1183
1184     if args.git_ref:
1185         reference_src = ReferenceSource(args.git_ref)
1186         reference_src_dir = reference_src.get_dir()
1187     else:
1188         reference_src_dir = None
1189
1190     if args.defconfigs:
1191         defconfigs = get_matched_defconfigs(args.defconfigs)
1192     else:
1193         defconfigs = get_all_defconfigs()
1194
1195     progress = Progress(len(defconfigs))
1196     slots = Slots(toolchains, configs, args, progress, reference_src_dir,
1197                   db_queue)
1198
1199     # Main loop to process defconfig files:
1200     #  Add a new subprocess into a vacant slot.
1201     #  Sleep if there is no available slot.
1202     for defconfig in defconfigs:
1203         while not slots.add(defconfig):
1204             while not slots.available():
1205                 # No available slot: sleep for a while
1206                 time.sleep(SLEEP_TIME)
1207
1208     # wait until all the subprocesses finish
1209     while not slots.empty():
1210         time.sleep(SLEEP_TIME)
1211
1212     print('')
1213     slots.show_failed_boards()
1214     slots.show_suspicious_boards()
1215
1216 def find_kconfig_rules(kconf, config, imply_config):
1217     """Check whether a config has a 'select' or 'imply' keyword
1218
1219     Args:
1220         kconf: Kconfiglib.Kconfig object
1221         config: Name of config to check (without CONFIG_ prefix)
1222         imply_config: Implying config (without CONFIG_ prefix) which may or
1223             may not have an 'imply' for 'config')
1224
1225     Returns:
1226         Symbol object for 'config' if found, else None
1227     """
1228     sym = kconf.syms.get(imply_config)
1229     if sym:
1230         for sel, cond in (sym.selects + sym.implies):
1231             if sel.name == config:
1232                 return sym
1233     return None
1234
1235 def check_imply_rule(kconf, config, imply_config):
1236     """Check if we can add an 'imply' option
1237
1238     This finds imply_config in the Kconfig and looks to see if it is possible
1239     to add an 'imply' for 'config' to that part of the Kconfig.
1240
1241     Args:
1242         kconf: Kconfiglib.Kconfig object
1243         config: Name of config to check (without CONFIG_ prefix)
1244         imply_config: Implying config (without CONFIG_ prefix) which may or
1245             may not have an 'imply' for 'config')
1246
1247     Returns:
1248         tuple:
1249             filename of Kconfig file containing imply_config, or None if none
1250             line number within the Kconfig file, or 0 if none
1251             message indicating the result
1252     """
1253     sym = kconf.syms.get(imply_config)
1254     if not sym:
1255         return 'cannot find sym'
1256     nodes = sym.nodes
1257     if len(nodes) != 1:
1258         return '%d locations' % len(nodes)
1259     node = nodes[0]
1260     fname, linenum = node.filename, node.linenr
1261     cwd = os.getcwd()
1262     if cwd and fname.startswith(cwd):
1263         fname = fname[len(cwd) + 1:]
1264     file_line = ' at %s:%d' % (fname, linenum)
1265     data = read_file(fname)
1266     if data[linenum - 1] != 'config %s' % imply_config:
1267         return None, 0, 'bad sym format %s%s' % (data[linenum], file_line)
1268     return fname, linenum, 'adding%s' % file_line
1269
1270 def add_imply_rule(config, fname, linenum):
1271     """Add a new 'imply' option to a Kconfig
1272
1273     Args:
1274         config: config option to add an imply for (without CONFIG_ prefix)
1275         fname: Kconfig filename to update
1276         linenum: Line number to place the 'imply' before
1277
1278     Returns:
1279         Message indicating the result
1280     """
1281     file_line = ' at %s:%d' % (fname, linenum)
1282     data = read_file(fname)
1283     linenum -= 1
1284
1285     for offset, line in enumerate(data[linenum:]):
1286         if line.strip().startswith('help') or not line:
1287             data.insert(linenum + offset, '\timply %s' % config)
1288             write_file(fname, data)
1289             return 'added%s' % file_line
1290
1291     return 'could not insert%s'
1292
1293 (IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = (
1294     1, 2, 4, 8)
1295
1296 IMPLY_FLAGS = {
1297     'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'],
1298     'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'],
1299     'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'],
1300     'non-arch-board': [
1301         IMPLY_NON_ARCH_BOARD,
1302         'Allow Kconfig options outside arch/ and /board/ to imply'],
1303 }
1304
1305
1306 def read_database():
1307     """Read in the config database
1308
1309     Returns:
1310         tuple:
1311             set of all config options seen (each a str)
1312             set of all defconfigs seen (each a str)
1313             dict of configs for each defconfig:
1314                 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig"
1315                 value: dict:
1316                     key: CONFIG option
1317                     value: Value of option
1318             dict of defconfigs for each config:
1319                 key: CONFIG option
1320                 value: set of boards using that option
1321
1322     """
1323     configs = {}
1324
1325     # key is defconfig name, value is dict of (CONFIG_xxx, value)
1326     config_db = {}
1327
1328     # Set of all config options we have seen
1329     all_configs = set()
1330
1331     # Set of all defconfigs we have seen
1332     all_defconfigs = set()
1333
1334     defconfig_db = collections.defaultdict(set)
1335     for line in read_file(CONFIG_DATABASE):
1336         line = line.rstrip()
1337         if not line:  # Separator between defconfigs
1338             config_db[defconfig] = configs
1339             all_defconfigs.add(defconfig)
1340             configs = {}
1341         elif line[0] == ' ':  # CONFIG line
1342             config, value = line.strip().split('=', 1)
1343             configs[config] = value
1344             defconfig_db[config].add(defconfig)
1345             all_configs.add(config)
1346         else:  # New defconfig
1347             defconfig = line
1348
1349     return all_configs, all_defconfigs, config_db, defconfig_db
1350
1351
1352 def do_imply_config(config_list, add_imply, imply_flags, skip_added,
1353                     check_kconfig=True, find_superset=False):
1354     """Find CONFIG options which imply those in the list
1355
1356     Some CONFIG options can be implied by others and this can help to reduce
1357     the size of the defconfig files. For example, CONFIG_X86 implies
1358     CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and
1359     all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to
1360     each of the x86 defconfig files.
1361
1362     This function uses the moveconfig database to find such options. It
1363     displays a list of things that could possibly imply those in the list.
1364     The algorithm ignores any that start with CONFIG_TARGET since these
1365     typically refer to only a few defconfigs (often one). It also does not
1366     display a config with less than 5 defconfigs.
1367
1368     The algorithm works using sets. For each target config in config_list:
1369         - Get the set 'defconfigs' which use that target config
1370         - For each config (from a list of all configs):
1371             - Get the set 'imply_defconfig' of defconfigs which use that config
1372             -
1373             - If imply_defconfigs contains anything not in defconfigs then
1374               this config does not imply the target config
1375
1376     Params:
1377         config_list: List of CONFIG options to check (each a string)
1378         add_imply: Automatically add an 'imply' for each config.
1379         imply_flags: Flags which control which implying configs are allowed
1380            (IMPLY_...)
1381         skip_added: Don't show options which already have an imply added.
1382         check_kconfig: Check if implied symbols already have an 'imply' or
1383             'select' for the target config, and show this information if so.
1384         find_superset: True to look for configs which are a superset of those
1385             already found. So for example if CONFIG_EXYNOS5 implies an option,
1386             but CONFIG_EXYNOS covers a larger set of defconfigs and also
1387             implies that option, this will drop the former in favour of the
1388             latter. In practice this option has not proved very used.
1389
1390     Note the terminoloy:
1391         config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM')
1392         defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig')
1393     """
1394     kconf = KconfigScanner().conf if check_kconfig else None
1395     if add_imply and add_imply != 'all':
1396         add_imply = add_imply.split(',')
1397
1398     all_configs, all_defconfigs, config_db, defconfig_db = read_database()
1399
1400     # Work through each target config option in turn, independently
1401     for config in config_list:
1402         defconfigs = defconfig_db.get(config)
1403         if not defconfigs:
1404             print('%s not found in any defconfig' % config)
1405             continue
1406
1407         # Get the set of defconfigs without this one (since a config cannot
1408         # imply itself)
1409         non_defconfigs = all_defconfigs - defconfigs
1410         num_defconfigs = len(defconfigs)
1411         print('%s found in %d/%d defconfigs' % (config, num_defconfigs,
1412                                                 len(all_configs)))
1413
1414         # This will hold the results: key=config, value=defconfigs containing it
1415         imply_configs = {}
1416         rest_configs = all_configs - set([config])
1417
1418         # Look at every possible config, except the target one
1419         for imply_config in rest_configs:
1420             if 'ERRATUM' in imply_config:
1421                 continue
1422             if not imply_flags & IMPLY_CMD:
1423                 if 'CONFIG_CMD' in imply_config:
1424                     continue
1425             if not imply_flags & IMPLY_TARGET:
1426                 if 'CONFIG_TARGET' in imply_config:
1427                     continue
1428
1429             # Find set of defconfigs that have this config
1430             imply_defconfig = defconfig_db[imply_config]
1431
1432             # Get the intersection of this with defconfigs containing the
1433             # target config
1434             common_defconfigs = imply_defconfig & defconfigs
1435
1436             # Get the set of defconfigs containing this config which DO NOT
1437             # also contain the taret config. If this set is non-empty it means
1438             # that this config affects other defconfigs as well as (possibly)
1439             # the ones affected by the target config. This means it implies
1440             # things we don't want to imply.
1441             not_common_defconfigs = imply_defconfig & non_defconfigs
1442             if not_common_defconfigs:
1443                 continue
1444
1445             # If there are common defconfigs, imply_config may be useful
1446             if common_defconfigs:
1447                 skip = False
1448                 if find_superset:
1449                     for prev in list(imply_configs.keys()):
1450                         prev_count = len(imply_configs[prev])
1451                         count = len(common_defconfigs)
1452                         if (prev_count > count and
1453                             (imply_configs[prev] & common_defconfigs ==
1454                             common_defconfigs)):
1455                             # skip imply_config because prev is a superset
1456                             skip = True
1457                             break
1458                         elif count > prev_count:
1459                             # delete prev because imply_config is a superset
1460                             del imply_configs[prev]
1461                 if not skip:
1462                     imply_configs[imply_config] = common_defconfigs
1463
1464         # Now we have a dict imply_configs of configs which imply each config
1465         # The value of each dict item is the set of defconfigs containing that
1466         # config. Rank them so that we print the configs that imply the largest
1467         # number of defconfigs first.
1468         ranked_iconfigs = sorted(imply_configs,
1469                             key=lambda k: len(imply_configs[k]), reverse=True)
1470         kconfig_info = ''
1471         cwd = os.getcwd()
1472         add_list = collections.defaultdict(list)
1473         for iconfig in ranked_iconfigs:
1474             num_common = len(imply_configs[iconfig])
1475
1476             # Don't bother if there are less than 5 defconfigs affected.
1477             if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5):
1478                 continue
1479             missing = defconfigs - imply_configs[iconfig]
1480             missing_str = ', '.join(missing) if missing else 'all'
1481             missing_str = ''
1482             show = True
1483             if kconf:
1484                 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:],
1485                                          iconfig[CONFIG_LEN:])
1486                 kconfig_info = ''
1487                 if sym:
1488                     nodes = sym.nodes
1489                     if len(nodes) == 1:
1490                         fname, linenum = nodes[0].filename, nodes[0].linenr
1491                         if cwd and fname.startswith(cwd):
1492                             fname = fname[len(cwd) + 1:]
1493                         kconfig_info = '%s:%d' % (fname, linenum)
1494                         if skip_added:
1495                             show = False
1496                 else:
1497                     sym = kconf.syms.get(iconfig[CONFIG_LEN:])
1498                     fname = ''
1499                     if sym:
1500                         nodes = sym.nodes
1501                         if len(nodes) == 1:
1502                             fname, linenum = nodes[0].filename, nodes[0].linenr
1503                             if cwd and fname.startswith(cwd):
1504                                 fname = fname[len(cwd) + 1:]
1505                     in_arch_board = not sym or (fname.startswith('arch') or
1506                                                 fname.startswith('board'))
1507                     if (not in_arch_board and
1508                         not imply_flags & IMPLY_NON_ARCH_BOARD):
1509                         continue
1510
1511                     if add_imply and (add_imply == 'all' or
1512                                       iconfig in add_imply):
1513                         fname, linenum, kconfig_info = (check_imply_rule(kconf,
1514                                 config[CONFIG_LEN:], iconfig[CONFIG_LEN:]))
1515                         if fname:
1516                             add_list[fname].append(linenum)
1517
1518             if show and kconfig_info != 'skip':
1519                 print('%5d : %-30s%-25s %s' % (num_common, iconfig.ljust(30),
1520                                               kconfig_info, missing_str))
1521
1522         # Having collected a list of things to add, now we add them. We process
1523         # each file from the largest line number to the smallest so that
1524         # earlier additions do not affect our line numbers. E.g. if we added an
1525         # imply at line 20 it would change the position of each line after
1526         # that.
1527         for fname, linenums in add_list.items():
1528             for linenum in sorted(linenums, reverse=True):
1529                 add_imply_rule(config[CONFIG_LEN:], fname, linenum)
1530
1531 def defconfig_matches(configs, re_match):
1532     """Check if any CONFIG option matches a regex
1533
1534     The match must be complete, i.e. from the start to end of the CONFIG option.
1535
1536     Args:
1537         configs (dict): Dict of CONFIG options:
1538             key: CONFIG option
1539             value: Value of option
1540         re_match (re.Pattern): Match to check
1541
1542     Returns:
1543         bool: True if any CONFIG matches the regex
1544     """
1545     for cfg in configs:
1546         if re_match.fullmatch(cfg):
1547             return True
1548     return False
1549
1550 def do_find_config(config_list):
1551     """Find boards with a given combination of CONFIGs
1552
1553     Params:
1554         config_list: List of CONFIG options to check (each a regex consisting
1555             of a config option, with or without a CONFIG_ prefix. If an option
1556             is preceded by a tilde (~) then it must be false, otherwise it must
1557             be true)
1558     """
1559     all_configs, all_defconfigs, config_db, defconfig_db = read_database()
1560
1561     # Get the whitelist
1562     adhoc_configs = set(read_file('scripts/config_whitelist.txt'))
1563
1564     # Start with all defconfigs
1565     out = all_defconfigs
1566
1567     # Work through each config in turn
1568     adhoc = []
1569     for item in config_list:
1570         # Get the real config name and whether we want this config or not
1571         cfg = item
1572         want = True
1573         if cfg[0] == '~':
1574             want = False
1575             cfg = cfg[1:]
1576
1577         if cfg in adhoc_configs:
1578             adhoc.append(cfg)
1579             continue
1580
1581         # Search everything that is still in the running. If it has a config
1582         # that we want, or doesn't have one that we don't, add it into the
1583         # running for the next stage
1584         in_list = out
1585         out = set()
1586         re_match = re.compile(cfg)
1587         for defc in in_list:
1588             has_cfg = defconfig_matches(config_db[defc], re_match)
1589             if has_cfg == want:
1590                 out.add(defc)
1591     if adhoc:
1592         print(f"Error: Not in Kconfig: %s" % ' '.join(adhoc))
1593     else:
1594         print(f'{len(out)} matches')
1595         print(' '.join(item.split('_defconfig')[0] for item in out))
1596
1597
1598 def prefix_config(cfg):
1599     """Prefix a config with CONFIG_ if needed
1600
1601     This handles ~ operator, which indicates that the CONFIG should be disabled
1602
1603     >>> prefix_config('FRED')
1604     'CONFIG_FRED'
1605     >>> prefix_config('CONFIG_FRED')
1606     'CONFIG_FRED'
1607     >>> prefix_config('~FRED')
1608     '~CONFIG_FRED'
1609     >>> prefix_config('~CONFIG_FRED')
1610     '~CONFIG_FRED'
1611     >>> prefix_config('A123')
1612     'CONFIG_A123'
1613     """
1614     op = ''
1615     if cfg[0] == '~':
1616         op = cfg[0]
1617         cfg = cfg[1:]
1618     if not cfg.startswith('CONFIG_'):
1619         cfg = 'CONFIG_' + cfg
1620     return op + cfg
1621
1622
1623 def main():
1624     try:
1625         cpu_count = multiprocessing.cpu_count()
1626     except NotImplementedError:
1627         cpu_count = 1
1628
1629     epilog = '''Move config options from headers to defconfig files. See
1630 doc/develop/moveconfig.rst for documentation.'''
1631
1632     parser = ArgumentParser(epilog=epilog)
1633     # Add arguments here
1634     parser.add_argument('-a', '--add-imply', type=str, default='',
1635                       help='comma-separated list of CONFIG options to add '
1636                       "an 'imply' statement to for the CONFIG in -i")
1637     parser.add_argument('-A', '--skip-added', action='store_true', default=False,
1638                       help="don't show options which are already marked as "
1639                       'implying others')
1640     parser.add_argument('-b', '--build-db', action='store_true', default=False,
1641                       help='build a CONFIG database')
1642     parser.add_argument('-c', '--color', action='store_true', default=False,
1643                       help='display the log in color')
1644     parser.add_argument('-C', '--commit', action='store_true', default=False,
1645                       help='Create a git commit for the operation')
1646     parser.add_argument('-d', '--defconfigs', type=str,
1647                       help='a file containing a list of defconfigs to move, '
1648                       "one per line (for example 'snow_defconfig') "
1649                       "or '-' to read from stdin")
1650     parser.add_argument('-e', '--exit-on-error', action='store_true',
1651                       default=False,
1652                       help='exit immediately on any error')
1653     parser.add_argument('-f', '--find', action='store_true', default=False,
1654                       help='Find boards with a given config combination')
1655     parser.add_argument('-H', '--headers-only', dest='cleanup_headers_only',
1656                       action='store_true', default=False,
1657                       help='only cleanup the headers')
1658     parser.add_argument('-i', '--imply', action='store_true', default=False,
1659                       help='find options which imply others')
1660     parser.add_argument('-I', '--imply-flags', type=str, default='',
1661                       help="control the -i option ('help' for help")
1662     parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
1663                       help='the number of jobs to run simultaneously')
1664     parser.add_argument('-n', '--dry-run', action='store_true', default=False,
1665                       help='perform a trial run (show log with no changes)')
1666     parser.add_argument('-r', '--git-ref', type=str,
1667                       help='the git ref to clone for building the autoconf.mk')
1668     parser.add_argument('-s', '--force-sync', action='store_true', default=False,
1669                       help='force sync by savedefconfig')
1670     parser.add_argument('-S', '--spl', action='store_true', default=False,
1671                       help='parse config options defined for SPL build')
1672     parser.add_argument('-t', '--test', action='store_true', default=False,
1673                       help='run unit tests')
1674     parser.add_argument('-y', '--yes', action='store_true', default=False,
1675                       help="respond 'yes' to any prompts")
1676     parser.add_argument('-v', '--verbose', action='store_true', default=False,
1677                       help='show any build errors as boards are built')
1678     parser.add_argument('configs', nargs='*')
1679
1680     args = parser.parse_args()
1681     configs = args.configs
1682
1683     if args.test:
1684         sys.argv = [sys.argv[0]]
1685         fail, count = doctest.testmod()
1686         if fail:
1687             return 1
1688         unittest.main()
1689
1690     if not any((len(configs), args.force_sync, args.build_db, args.imply,
1691                 args.find)):
1692         parser.print_usage()
1693         sys.exit(1)
1694
1695     # prefix the option name with CONFIG_ if missing
1696     configs = [prefix_config(cfg) for cfg in configs]
1697
1698     check_top_directory()
1699
1700     if args.imply:
1701         imply_flags = 0
1702         if args.imply_flags == 'all':
1703             imply_flags = -1
1704
1705         elif args.imply_flags:
1706             for flag in args.imply_flags.split(','):
1707                 bad = flag not in IMPLY_FLAGS
1708                 if bad:
1709                     print("Invalid flag '%s'" % flag)
1710                 if flag == 'help' or bad:
1711                     print("Imply flags: (separate with ',')")
1712                     for name, info in IMPLY_FLAGS.items():
1713                         print(' %-15s: %s' % (name, info[1]))
1714                     parser.print_usage()
1715                     sys.exit(1)
1716                 imply_flags |= IMPLY_FLAGS[flag][0]
1717
1718         do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
1719         return
1720
1721     if args.find:
1722         do_find_config(configs)
1723         return
1724
1725     config_db = {}
1726     db_queue = queue.Queue()
1727     t = DatabaseThread(config_db, db_queue)
1728     t.setDaemon(True)
1729     t.start()
1730
1731     if not args.cleanup_headers_only:
1732         check_clean_directory()
1733         bsettings.Setup('')
1734         toolchains = toolchain.Toolchains()
1735         toolchains.GetSettings()
1736         toolchains.Scan(verbose=False)
1737         move_config(toolchains, configs, args, db_queue)
1738         db_queue.join()
1739
1740     if configs:
1741         cleanup_headers(configs, args)
1742         cleanup_whitelist(configs, args)
1743         cleanup_readme(configs, args)
1744
1745     if args.commit:
1746         subprocess.call(['git', 'add', '-u'])
1747         if configs:
1748             msg = 'Convert %s %sto Kconfig' % (configs[0],
1749                     'et al ' if len(configs) > 1 else '')
1750             msg += ('\n\nThis converts the following to Kconfig:\n   %s\n' %
1751                     '\n   '.join(configs))
1752         else:
1753             msg = 'configs: Resync with savedefconfig'
1754             msg += '\n\nRsync all defconfig files using moveconfig.py'
1755         subprocess.call(['git', 'commit', '-s', '-m', msg])
1756
1757     if args.build_db:
1758         with open(CONFIG_DATABASE, 'w', encoding='utf-8') as fd:
1759             for defconfig, configs in config_db.items():
1760                 fd.write('%s\n' % defconfig)
1761                 for config in sorted(configs.keys()):
1762                     fd.write('   %s=%s\n' % (config, configs[config]))
1763                 fd.write('\n')
1764
1765 if __name__ == '__main__':
1766     sys.exit(main())