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