moveconfig: Show the config name rather than the defconfig
[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
1610 def do_find_config(config_list):
1611     """Find boards with a given combination of CONFIGs
1612
1613     Params:
1614         config_list: List of CONFIG options to check (each a string consisting
1615             of a config option, with or without a CONFIG_ prefix. If an option
1616             is preceded by a tilde (~) then it must be false, otherwise it must
1617             be true)
1618     """
1619     all_configs, all_defconfigs, config_db, defconfig_db = read_database()
1620
1621     # Get the whitelist
1622     adhoc_configs = set(read_file('scripts/config_whitelist.txt'))
1623
1624     # Start with all defconfigs
1625     out = all_defconfigs
1626
1627     # Work through each config in turn
1628     adhoc = []
1629     for item in config_list:
1630         # Get the real config name and whether we want this config or not
1631         cfg = item
1632         want = True
1633         if cfg[0] == '~':
1634             want = False
1635             cfg = cfg[1:]
1636
1637         if cfg in adhoc_configs:
1638             adhoc.append(cfg)
1639             continue
1640
1641         # Search everything that is still in the running. If it has a config
1642         # that we want, or doesn't have one that we don't, add it into the
1643         # running for the next stage
1644         in_list = out
1645         out = set()
1646         for defc in in_list:
1647             has_cfg = cfg in config_db[defc]
1648             if has_cfg == want:
1649                 out.add(defc)
1650     if adhoc:
1651         print(f"Error: Not in Kconfig: %s" % ' '.join(adhoc))
1652     else:
1653         print(f'{len(out)} matches')
1654         print(' '.join([remove_defconfig(item) for item in out]))
1655
1656
1657 def prefix_config(cfg):
1658     """Prefix a config with CONFIG_ if needed
1659
1660     This handles ~ operator, which indicates that the CONFIG should be disabled
1661
1662     >>> prefix_config('FRED')
1663     'CONFIG_FRED'
1664     >>> prefix_config('CONFIG_FRED')
1665     'CONFIG_FRED'
1666     >>> prefix_config('~FRED')
1667     '~CONFIG_FRED'
1668     >>> prefix_config('~CONFIG_FRED')
1669     '~CONFIG_FRED'
1670     >>> prefix_config('A123')
1671     'CONFIG_A123'
1672     """
1673     op = ''
1674     if cfg[0] == '~':
1675         op = cfg[0]
1676         cfg = cfg[1:]
1677     if not cfg.startswith('CONFIG_'):
1678         cfg = 'CONFIG_' + cfg
1679     return op + cfg
1680
1681
1682 def main():
1683     try:
1684         cpu_count = multiprocessing.cpu_count()
1685     except NotImplementedError:
1686         cpu_count = 1
1687
1688     epilog = '''Move config options from headers to defconfig files. See
1689 doc/develop/moveconfig.rst for documentation.'''
1690
1691     parser = ArgumentParser(epilog=epilog)
1692     # Add arguments here
1693     parser.add_argument('-a', '--add-imply', type=str, default='',
1694                       help='comma-separated list of CONFIG options to add '
1695                       "an 'imply' statement to for the CONFIG in -i")
1696     parser.add_argument('-A', '--skip-added', action='store_true', default=False,
1697                       help="don't show options which are already marked as "
1698                       'implying others')
1699     parser.add_argument('-b', '--build-db', action='store_true', default=False,
1700                       help='build a CONFIG database')
1701     parser.add_argument('-c', '--color', action='store_true', default=False,
1702                       help='display the log in color')
1703     parser.add_argument('-C', '--commit', action='store_true', default=False,
1704                       help='Create a git commit for the operation')
1705     parser.add_argument('-d', '--defconfigs', type=str,
1706                       help='a file containing a list of defconfigs to move, '
1707                       "one per line (for example 'snow_defconfig') "
1708                       "or '-' to read from stdin")
1709     parser.add_argument('-e', '--exit-on-error', action='store_true',
1710                       default=False,
1711                       help='exit immediately on any error')
1712     parser.add_argument('-f', '--find', action='store_true', default=False,
1713                       help='Find boards with a given config combination')
1714     parser.add_argument('-H', '--headers-only', dest='cleanup_headers_only',
1715                       action='store_true', default=False,
1716                       help='only cleanup the headers')
1717     parser.add_argument('-i', '--imply', action='store_true', default=False,
1718                       help='find options which imply others')
1719     parser.add_argument('-I', '--imply-flags', type=str, default='',
1720                       help="control the -i option ('help' for help")
1721     parser.add_argument('-j', '--jobs', type=int, default=cpu_count,
1722                       help='the number of jobs to run simultaneously')
1723     parser.add_argument('-n', '--dry-run', action='store_true', default=False,
1724                       help='perform a trial run (show log with no changes)')
1725     parser.add_argument('-r', '--git-ref', type=str,
1726                       help='the git ref to clone for building the autoconf.mk')
1727     parser.add_argument('-s', '--force-sync', action='store_true', default=False,
1728                       help='force sync by savedefconfig')
1729     parser.add_argument('-S', '--spl', action='store_true', default=False,
1730                       help='parse config options defined for SPL build')
1731     parser.add_argument('-t', '--test', action='store_true', default=False,
1732                       help='run unit tests')
1733     parser.add_argument('-y', '--yes', action='store_true', default=False,
1734                       help="respond 'yes' to any prompts")
1735     parser.add_argument('-v', '--verbose', action='store_true', default=False,
1736                       help='show any build errors as boards are built')
1737     parser.add_argument('configs', nargs='*')
1738
1739     args = parser.parse_args()
1740     configs = args.configs
1741
1742     if args.test:
1743         sys.argv = [sys.argv[0]]
1744         fail, count = doctest.testmod()
1745         if fail:
1746             return 1
1747         unittest.main()
1748
1749     if not any((len(configs), args.force_sync, args.build_db, args.imply,
1750                 args.find)):
1751         parser.print_usage()
1752         sys.exit(1)
1753
1754     # prefix the option name with CONFIG_ if missing
1755     configs = [prefix_config(cfg) for cfg in configs]
1756
1757     check_top_directory()
1758
1759     if args.imply:
1760         imply_flags = 0
1761         if args.imply_flags == 'all':
1762             imply_flags = -1
1763
1764         elif args.imply_flags:
1765             for flag in args.imply_flags.split(','):
1766                 bad = flag not in IMPLY_FLAGS
1767                 if bad:
1768                     print("Invalid flag '%s'" % flag)
1769                 if flag == 'help' or bad:
1770                     print("Imply flags: (separate with ',')")
1771                     for name, info in IMPLY_FLAGS.items():
1772                         print(' %-15s: %s' % (name, info[1]))
1773                     parser.print_usage()
1774                     sys.exit(1)
1775                 imply_flags |= IMPLY_FLAGS[flag][0]
1776
1777         do_imply_config(configs, args.add_imply, imply_flags, args.skip_added)
1778         return
1779
1780     if args.find:
1781         do_find_config(configs)
1782         return
1783
1784     config_db = {}
1785     db_queue = queue.Queue()
1786     t = DatabaseThread(config_db, db_queue)
1787     t.setDaemon(True)
1788     t.start()
1789
1790     if not args.cleanup_headers_only:
1791         check_clean_directory()
1792         bsettings.Setup('')
1793         toolchains = toolchain.Toolchains()
1794         toolchains.GetSettings()
1795         toolchains.Scan(verbose=False)
1796         move_config(toolchains, configs, args, db_queue)
1797         db_queue.join()
1798
1799     if configs:
1800         cleanup_headers(configs, args)
1801         cleanup_extra_options(configs, args)
1802         cleanup_whitelist(configs, args)
1803         cleanup_readme(configs, args)
1804
1805     if args.commit:
1806         subprocess.call(['git', 'add', '-u'])
1807         if configs:
1808             msg = 'Convert %s %sto Kconfig' % (configs[0],
1809                     'et al ' if len(configs) > 1 else '')
1810             msg += ('\n\nThis converts the following to Kconfig:\n   %s\n' %
1811                     '\n   '.join(configs))
1812         else:
1813             msg = 'configs: Resync with savedefconfig'
1814             msg += '\n\nRsync all defconfig files using moveconfig.py'
1815         subprocess.call(['git', 'commit', '-s', '-m', msg])
1816
1817     if args.build_db:
1818         with open(CONFIG_DATABASE, 'w', encoding='utf-8') as fd:
1819             for defconfig, configs in config_db.items():
1820                 fd.write('%s\n' % defconfig)
1821                 for config in sorted(configs.keys()):
1822                     fd.write('   %s=%s\n' % (config, configs[config]))
1823                 fd.write('\n')
1824
1825 if __name__ == '__main__':
1826     sys.exit(main())