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