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