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