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