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