]> git.sur5r.net Git - u-boot/blob - tools/moveconfig.py
moveconfig: Support building a simple config database
[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 Available options
136 -----------------
137
138  -c, --color
139    Surround each portion of the log with escape sequences to display it
140    in color on the terminal.
141
142  -C, --commit
143    Create a git commit with the changes when the operation is complete. A
144    standard commit message is used which may need to be edited.
145
146  -d, --defconfigs
147   Specify a file containing a list of defconfigs to move.  The defconfig
148   files can be given with shell-style wildcards. Use '-' to read from stdin.
149
150  -n, --dry-run
151    Perform a trial run that does not make any changes.  It is useful to
152    see what is going to happen before one actually runs it.
153
154  -e, --exit-on-error
155    Exit immediately if Make exits with a non-zero status while processing
156    a defconfig file.
157
158  -s, --force-sync
159    Do "make savedefconfig" forcibly for all the defconfig files.
160    If not specified, "make savedefconfig" only occurs for cases
161    where at least one CONFIG was moved.
162
163  -S, --spl
164    Look for moved config options in spl/include/autoconf.mk instead of
165    include/autoconf.mk.  This is useful for moving options for SPL build
166    because SPL related options (mostly prefixed with CONFIG_SPL_) are
167    sometimes blocked by CONFIG_SPL_BUILD ifdef conditionals.
168
169  -H, --headers-only
170    Only cleanup the headers; skip the defconfig processing
171
172  -j, --jobs
173    Specify the number of threads to run simultaneously.  If not specified,
174    the number of threads is the same as the number of CPU cores.
175
176  -r, --git-ref
177    Specify the git ref to clone for building the autoconf.mk. If unspecified
178    use the CWD. This is useful for when changes to the Kconfig affect the
179    default values and you want to capture the state of the defconfig from
180    before that change was in effect. If in doubt, specify a ref pre-Kconfig
181    changes (use HEAD if Kconfig changes are not committed). Worst case it will
182    take a bit longer to run, but will always do the right thing.
183
184  -v, --verbose
185    Show any build errors as boards are built
186
187  -y, --yes
188    Instead of prompting, automatically go ahead with all operations. This
189    includes cleaning up headers, CONFIG_SYS_EXTRA_OPTIONS, the config whitelist
190    and the README.
191
192 To see the complete list of supported options, run
193
194   $ tools/moveconfig.py -h
195
196 """
197
198 import copy
199 import difflib
200 import filecmp
201 import fnmatch
202 import glob
203 import multiprocessing
204 import optparse
205 import os
206 import Queue
207 import re
208 import shutil
209 import subprocess
210 import sys
211 import tempfile
212 import threading
213 import time
214
215 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
216 SLEEP_TIME=0.03
217
218 # Here is the list of cross-tools I use.
219 # Most of them are available at kernel.org
220 # (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the following:
221 # arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases
222 # nds32: http://osdk.andestech.com/packages/nds32le-linux-glibc-v1.tgz
223 # nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545
224 # sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu
225 CROSS_COMPILE = {
226     'arc': 'arc-linux-',
227     'aarch64': 'aarch64-linux-',
228     'arm': 'arm-unknown-linux-gnueabi-',
229     'm68k': 'm68k-linux-',
230     'microblaze': 'microblaze-linux-',
231     'mips': 'mips-linux-',
232     'nds32': 'nds32le-linux-',
233     'nios2': 'nios2-linux-gnu-',
234     'powerpc': 'powerpc-linux-',
235     'sh': 'sh-linux-gnu-',
236     'x86': 'i386-linux-',
237     'xtensa': 'xtensa-linux-'
238 }
239
240 STATE_IDLE = 0
241 STATE_DEFCONFIG = 1
242 STATE_AUTOCONF = 2
243 STATE_SAVEDEFCONFIG = 3
244
245 ACTION_MOVE = 0
246 ACTION_NO_ENTRY = 1
247 ACTION_NO_ENTRY_WARN = 2
248 ACTION_NO_CHANGE = 3
249
250 COLOR_BLACK        = '0;30'
251 COLOR_RED          = '0;31'
252 COLOR_GREEN        = '0;32'
253 COLOR_BROWN        = '0;33'
254 COLOR_BLUE         = '0;34'
255 COLOR_PURPLE       = '0;35'
256 COLOR_CYAN         = '0;36'
257 COLOR_LIGHT_GRAY   = '0;37'
258 COLOR_DARK_GRAY    = '1;30'
259 COLOR_LIGHT_RED    = '1;31'
260 COLOR_LIGHT_GREEN  = '1;32'
261 COLOR_YELLOW       = '1;33'
262 COLOR_LIGHT_BLUE   = '1;34'
263 COLOR_LIGHT_PURPLE = '1;35'
264 COLOR_LIGHT_CYAN   = '1;36'
265 COLOR_WHITE        = '1;37'
266
267 AUTO_CONF_PATH = 'include/config/auto.conf'
268 CONFIG_DATABASE = 'moveconfig.db'
269
270
271 ### helper functions ###
272 def get_devnull():
273     """Get the file object of '/dev/null' device."""
274     try:
275         devnull = subprocess.DEVNULL # py3k
276     except AttributeError:
277         devnull = open(os.devnull, 'wb')
278     return devnull
279
280 def check_top_directory():
281     """Exit if we are not at the top of source directory."""
282     for f in ('README', 'Licenses'):
283         if not os.path.exists(f):
284             sys.exit('Please run at the top of source directory.')
285
286 def check_clean_directory():
287     """Exit if the source tree is not clean."""
288     for f in ('.config', 'include/config'):
289         if os.path.exists(f):
290             sys.exit("source tree is not clean, please run 'make mrproper'")
291
292 def get_make_cmd():
293     """Get the command name of GNU Make.
294
295     U-Boot needs GNU Make for building, but the command name is not
296     necessarily "make". (for example, "gmake" on FreeBSD).
297     Returns the most appropriate command name on your system.
298     """
299     process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
300     ret = process.communicate()
301     if process.returncode:
302         sys.exit('GNU Make not found')
303     return ret[0].rstrip()
304
305 def get_matched_defconfig(line):
306     """Get the defconfig files that match a pattern
307
308     Args:
309         line: Path or filename to match, e.g. 'configs/snow_defconfig' or
310             'k2*_defconfig'. If no directory is provided, 'configs/' is
311             prepended
312
313     Returns:
314         a list of matching defconfig files
315     """
316     dirname = os.path.dirname(line)
317     if dirname:
318         pattern = line
319     else:
320         pattern = os.path.join('configs', line)
321     return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
322
323 def get_matched_defconfigs(defconfigs_file):
324     """Get all the defconfig files that match the patterns in a file.
325
326     Args:
327         defconfigs_file: File containing a list of defconfigs to process, or
328             '-' to read the list from stdin
329
330     Returns:
331         A list of paths to defconfig files, with no duplicates
332     """
333     defconfigs = []
334     if defconfigs_file == '-':
335         fd = sys.stdin
336         defconfigs_file = 'stdin'
337     else:
338         fd = open(defconfigs_file)
339     for i, line in enumerate(fd):
340         line = line.strip()
341         if not line:
342             continue # skip blank lines silently
343         matched = get_matched_defconfig(line)
344         if not matched:
345             print >> sys.stderr, "warning: %s:%d: no defconfig matched '%s'" % \
346                                                  (defconfigs_file, i + 1, line)
347
348         defconfigs += matched
349
350     # use set() to drop multiple matching
351     return [ defconfig[len('configs') + 1:]  for defconfig in set(defconfigs) ]
352
353 def get_all_defconfigs():
354     """Get all the defconfig files under the configs/ directory."""
355     defconfigs = []
356     for (dirpath, dirnames, filenames) in os.walk('configs'):
357         dirpath = dirpath[len('configs') + 1:]
358         for filename in fnmatch.filter(filenames, '*_defconfig'):
359             defconfigs.append(os.path.join(dirpath, filename))
360
361     return defconfigs
362
363 def color_text(color_enabled, color, string):
364     """Return colored string."""
365     if color_enabled:
366         # LF should not be surrounded by the escape sequence.
367         # Otherwise, additional whitespace or line-feed might be printed.
368         return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
369                            for s in string.split('\n') ])
370     else:
371         return string
372
373 def show_diff(a, b, file_path, color_enabled):
374     """Show unidified diff.
375
376     Arguments:
377       a: A list of lines (before)
378       b: A list of lines (after)
379       file_path: Path to the file
380       color_enabled: Display the diff in color
381     """
382
383     diff = difflib.unified_diff(a, b,
384                                 fromfile=os.path.join('a', file_path),
385                                 tofile=os.path.join('b', file_path))
386
387     for line in diff:
388         if line[0] == '-' and line[1] != '-':
389             print color_text(color_enabled, COLOR_RED, line),
390         elif line[0] == '+' and line[1] != '+':
391             print color_text(color_enabled, COLOR_GREEN, line),
392         else:
393             print line,
394
395 def update_cross_compile(color_enabled):
396     """Update per-arch CROSS_COMPILE via environment variables
397
398     The default CROSS_COMPILE values are available
399     in the CROSS_COMPILE list above.
400
401     You can override them via environment variables
402     CROSS_COMPILE_{ARCH}.
403
404     For example, if you want to override toolchain prefixes
405     for ARM and PowerPC, you can do as follows in your shell:
406
407     export CROSS_COMPILE_ARM=...
408     export CROSS_COMPILE_POWERPC=...
409
410     Then, this function checks if specified compilers really exist in your
411     PATH environment.
412     """
413     archs = []
414
415     for arch in os.listdir('arch'):
416         if os.path.exists(os.path.join('arch', arch, 'Makefile')):
417             archs.append(arch)
418
419     # arm64 is a special case
420     archs.append('aarch64')
421
422     for arch in archs:
423         env = 'CROSS_COMPILE_' + arch.upper()
424         cross_compile = os.environ.get(env)
425         if not cross_compile:
426             cross_compile = CROSS_COMPILE.get(arch, '')
427
428         for path in os.environ["PATH"].split(os.pathsep):
429             gcc_path = os.path.join(path, cross_compile + 'gcc')
430             if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
431                 break
432         else:
433             print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
434                  'warning: %sgcc: not found in PATH.  %s architecture boards will be skipped'
435                                             % (cross_compile, arch))
436             cross_compile = None
437
438         CROSS_COMPILE[arch] = cross_compile
439
440 def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre,
441                          extend_post):
442     """Extend matched lines if desired patterns are found before/after already
443     matched lines.
444
445     Arguments:
446       lines: A list of lines handled.
447       matched: A list of line numbers that have been already matched.
448                (will be updated by this function)
449       pre_patterns: A list of regular expression that should be matched as
450                     preamble.
451       post_patterns: A list of regular expression that should be matched as
452                      postamble.
453       extend_pre: Add the line number of matched preamble to the matched list.
454       extend_post: Add the line number of matched postamble to the matched list.
455     """
456     extended_matched = []
457
458     j = matched[0]
459
460     for i in matched:
461         if i == 0 or i < j:
462             continue
463         j = i
464         while j in matched:
465             j += 1
466         if j >= len(lines):
467             break
468
469         for p in pre_patterns:
470             if p.search(lines[i - 1]):
471                 break
472         else:
473             # not matched
474             continue
475
476         for p in post_patterns:
477             if p.search(lines[j]):
478                 break
479         else:
480             # not matched
481             continue
482
483         if extend_pre:
484             extended_matched.append(i - 1)
485         if extend_post:
486             extended_matched.append(j)
487
488     matched += extended_matched
489     matched.sort()
490
491 def confirm(options, prompt):
492     if not options.yes:
493         while True:
494             choice = raw_input('{} [y/n]: '.format(prompt))
495             choice = choice.lower()
496             print choice
497             if choice == 'y' or choice == 'n':
498                 break
499
500         if choice == 'n':
501             return False
502
503     return True
504
505 def cleanup_one_header(header_path, patterns, options):
506     """Clean regex-matched lines away from a file.
507
508     Arguments:
509       header_path: path to the cleaned file.
510       patterns: list of regex patterns.  Any lines matching to these
511                 patterns are deleted.
512       options: option flags.
513     """
514     with open(header_path) as f:
515         lines = f.readlines()
516
517     matched = []
518     for i, line in enumerate(lines):
519         if i - 1 in matched and lines[i - 1][-2:] == '\\\n':
520             matched.append(i)
521             continue
522         for pattern in patterns:
523             if pattern.search(line):
524                 matched.append(i)
525                 break
526
527     if not matched:
528         return
529
530     # remove empty #ifdef ... #endif, successive blank lines
531     pattern_if = re.compile(r'#\s*if(def|ndef)?\W') #  #if, #ifdef, #ifndef
532     pattern_elif = re.compile(r'#\s*el(if|se)\W')   #  #elif, #else
533     pattern_endif = re.compile(r'#\s*endif\W')      #  #endif
534     pattern_blank = re.compile(r'^\s*$')            #  empty line
535
536     while True:
537         old_matched = copy.copy(matched)
538         extend_matched_lines(lines, matched, [pattern_if],
539                              [pattern_endif], True, True)
540         extend_matched_lines(lines, matched, [pattern_elif],
541                              [pattern_elif, pattern_endif], True, False)
542         extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
543                              [pattern_blank], False, True)
544         extend_matched_lines(lines, matched, [pattern_blank],
545                              [pattern_elif, pattern_endif], True, False)
546         extend_matched_lines(lines, matched, [pattern_blank],
547                              [pattern_blank], True, False)
548         if matched == old_matched:
549             break
550
551     tolines = copy.copy(lines)
552
553     for i in reversed(matched):
554         tolines.pop(i)
555
556     show_diff(lines, tolines, header_path, options.color)
557
558     if options.dry_run:
559         return
560
561     with open(header_path, 'w') as f:
562         for line in tolines:
563             f.write(line)
564
565 def cleanup_headers(configs, options):
566     """Delete config defines from board headers.
567
568     Arguments:
569       configs: A list of CONFIGs to remove.
570       options: option flags.
571     """
572     if not confirm(options, 'Clean up headers?'):
573         return
574
575     patterns = []
576     for config in configs:
577         patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
578         patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
579
580     for dir in 'include', 'arch', 'board':
581         for (dirpath, dirnames, filenames) in os.walk(dir):
582             if dirpath == os.path.join('include', 'generated'):
583                 continue
584             for filename in filenames:
585                 if not fnmatch.fnmatch(filename, '*~'):
586                     cleanup_one_header(os.path.join(dirpath, filename),
587                                        patterns, options)
588
589 def cleanup_one_extra_option(defconfig_path, configs, options):
590     """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
591
592     Arguments:
593       defconfig_path: path to the cleaned defconfig file.
594       configs: A list of CONFIGs to remove.
595       options: option flags.
596     """
597
598     start = 'CONFIG_SYS_EXTRA_OPTIONS="'
599     end = '"\n'
600
601     with open(defconfig_path) as f:
602         lines = f.readlines()
603
604     for i, line in enumerate(lines):
605         if line.startswith(start) and line.endswith(end):
606             break
607     else:
608         # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
609         return
610
611     old_tokens = line[len(start):-len(end)].split(',')
612     new_tokens = []
613
614     for token in old_tokens:
615         pos = token.find('=')
616         if not (token[:pos] if pos >= 0 else token) in configs:
617             new_tokens.append(token)
618
619     if new_tokens == old_tokens:
620         return
621
622     tolines = copy.copy(lines)
623
624     if new_tokens:
625         tolines[i] = start + ','.join(new_tokens) + end
626     else:
627         tolines.pop(i)
628
629     show_diff(lines, tolines, defconfig_path, options.color)
630
631     if options.dry_run:
632         return
633
634     with open(defconfig_path, 'w') as f:
635         for line in tolines:
636             f.write(line)
637
638 def cleanup_extra_options(configs, options):
639     """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
640
641     Arguments:
642       configs: A list of CONFIGs to remove.
643       options: option flags.
644     """
645     if not confirm(options, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'):
646         return
647
648     configs = [ config[len('CONFIG_'):] for config in configs ]
649
650     defconfigs = get_all_defconfigs()
651
652     for defconfig in defconfigs:
653         cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
654                                  options)
655
656 def cleanup_whitelist(configs, options):
657     """Delete config whitelist entries
658
659     Arguments:
660       configs: A list of CONFIGs to remove.
661       options: option flags.
662     """
663     if not confirm(options, 'Clean up whitelist entries?'):
664         return
665
666     with open(os.path.join('scripts', 'config_whitelist.txt')) as f:
667         lines = f.readlines()
668
669     lines = [x for x in lines if x.strip() not in configs]
670
671     with open(os.path.join('scripts', 'config_whitelist.txt'), 'w') as f:
672         f.write(''.join(lines))
673
674 def find_matching(patterns, line):
675     for pat in patterns:
676         if pat.search(line):
677             return True
678     return False
679
680 def cleanup_readme(configs, options):
681     """Delete config description in README
682
683     Arguments:
684       configs: A list of CONFIGs to remove.
685       options: option flags.
686     """
687     if not confirm(options, 'Clean up README?'):
688         return
689
690     patterns = []
691     for config in configs:
692         patterns.append(re.compile(r'^\s+%s' % config))
693
694     with open('README') as f:
695         lines = f.readlines()
696
697     found = False
698     newlines = []
699     for line in lines:
700         if not found:
701             found = find_matching(patterns, line)
702             if found:
703                 continue
704
705         if found and re.search(r'^\s+CONFIG', line):
706             found = False
707
708         if not found:
709             newlines.append(line)
710
711     with open('README', 'w') as f:
712         f.write(''.join(newlines))
713
714
715 ### classes ###
716 class Progress:
717
718     """Progress Indicator"""
719
720     def __init__(self, total):
721         """Create a new progress indicator.
722
723         Arguments:
724           total: A number of defconfig files to process.
725         """
726         self.current = 0
727         self.total = total
728
729     def inc(self):
730         """Increment the number of processed defconfig files."""
731
732         self.current += 1
733
734     def show(self):
735         """Display the progress."""
736         print ' %d defconfigs out of %d\r' % (self.current, self.total),
737         sys.stdout.flush()
738
739 class KconfigParser:
740
741     """A parser of .config and include/autoconf.mk."""
742
743     re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
744     re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
745
746     def __init__(self, configs, options, build_dir):
747         """Create a new parser.
748
749         Arguments:
750           configs: A list of CONFIGs to move.
751           options: option flags.
752           build_dir: Build directory.
753         """
754         self.configs = configs
755         self.options = options
756         self.dotconfig = os.path.join(build_dir, '.config')
757         self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
758         self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
759                                          'autoconf.mk')
760         self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
761         self.defconfig = os.path.join(build_dir, 'defconfig')
762
763     def get_cross_compile(self):
764         """Parse .config file and return CROSS_COMPILE.
765
766         Returns:
767           A string storing the compiler prefix for the architecture.
768           Return a NULL string for architectures that do not require
769           compiler prefix (Sandbox and native build is the case).
770           Return None if the specified compiler is missing in your PATH.
771           Caller should distinguish '' and None.
772         """
773         arch = ''
774         cpu = ''
775         for line in open(self.dotconfig):
776             m = self.re_arch.match(line)
777             if m:
778                 arch = m.group(1)
779                 continue
780             m = self.re_cpu.match(line)
781             if m:
782                 cpu = m.group(1)
783
784         if not arch:
785             return None
786
787         # fix-up for aarch64
788         if arch == 'arm' and cpu == 'armv8':
789             arch = 'aarch64'
790
791         return CROSS_COMPILE.get(arch, None)
792
793     def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
794         """Parse .config, defconfig, include/autoconf.mk for one config.
795
796         This function looks for the config options in the lines from
797         defconfig, .config, and include/autoconf.mk in order to decide
798         which action should be taken for this defconfig.
799
800         Arguments:
801           config: CONFIG name to parse.
802           dotconfig_lines: lines from the .config file.
803           autoconf_lines: lines from the include/autoconf.mk file.
804
805         Returns:
806           A tupple of the action for this defconfig and the line
807           matched for the config.
808         """
809         not_set = '# %s is not set' % config
810
811         for line in autoconf_lines:
812             line = line.rstrip()
813             if line.startswith(config + '='):
814                 new_val = line
815                 break
816         else:
817             new_val = not_set
818
819         for line in dotconfig_lines:
820             line = line.rstrip()
821             if line.startswith(config + '=') or line == not_set:
822                 old_val = line
823                 break
824         else:
825             if new_val == not_set:
826                 return (ACTION_NO_ENTRY, config)
827             else:
828                 return (ACTION_NO_ENTRY_WARN, config)
829
830         # If this CONFIG is neither bool nor trisate
831         if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
832             # tools/scripts/define2mk.sed changes '1' to 'y'.
833             # This is a problem if the CONFIG is int type.
834             # Check the type in Kconfig and handle it correctly.
835             if new_val[-2:] == '=y':
836                 new_val = new_val[:-1] + '1'
837
838         return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
839                 new_val)
840
841     def update_dotconfig(self):
842         """Parse files for the config options and update the .config.
843
844         This function parses the generated .config and include/autoconf.mk
845         searching the target options.
846         Move the config option(s) to the .config as needed.
847
848         Arguments:
849           defconfig: defconfig name.
850
851         Returns:
852           Return a tuple of (updated flag, log string).
853           The "updated flag" is True if the .config was updated, False
854           otherwise.  The "log string" shows what happend to the .config.
855         """
856
857         results = []
858         updated = False
859         suspicious = False
860         rm_files = [self.config_autoconf, self.autoconf]
861
862         if self.options.spl:
863             if os.path.exists(self.spl_autoconf):
864                 autoconf_path = self.spl_autoconf
865                 rm_files.append(self.spl_autoconf)
866             else:
867                 for f in rm_files:
868                     os.remove(f)
869                 return (updated, suspicious,
870                         color_text(self.options.color, COLOR_BROWN,
871                                    "SPL is not enabled.  Skipped.") + '\n')
872         else:
873             autoconf_path = self.autoconf
874
875         with open(self.dotconfig) as f:
876             dotconfig_lines = f.readlines()
877
878         with open(autoconf_path) as f:
879             autoconf_lines = f.readlines()
880
881         for config in self.configs:
882             result = self.parse_one_config(config, dotconfig_lines,
883                                            autoconf_lines)
884             results.append(result)
885
886         log = ''
887
888         for (action, value) in results:
889             if action == ACTION_MOVE:
890                 actlog = "Move '%s'" % value
891                 log_color = COLOR_LIGHT_GREEN
892             elif action == ACTION_NO_ENTRY:
893                 actlog = "%s is not defined in Kconfig.  Do nothing." % value
894                 log_color = COLOR_LIGHT_BLUE
895             elif action == ACTION_NO_ENTRY_WARN:
896                 actlog = "%s is not defined in Kconfig (suspicious).  Do nothing." % value
897                 log_color = COLOR_YELLOW
898                 suspicious = True
899             elif action == ACTION_NO_CHANGE:
900                 actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
901                          % value
902                 log_color = COLOR_LIGHT_PURPLE
903             elif action == ACTION_SPL_NOT_EXIST:
904                 actlog = "SPL is not enabled for this defconfig.  Skip."
905                 log_color = COLOR_PURPLE
906             else:
907                 sys.exit("Internal Error. This should not happen.")
908
909             log += color_text(self.options.color, log_color, actlog) + '\n'
910
911         with open(self.dotconfig, 'a') as f:
912             for (action, value) in results:
913                 if action == ACTION_MOVE:
914                     f.write(value + '\n')
915                     updated = True
916
917         self.results = results
918         for f in rm_files:
919             os.remove(f)
920
921         return (updated, suspicious, log)
922
923     def check_defconfig(self):
924         """Check the defconfig after savedefconfig
925
926         Returns:
927           Return additional log if moved CONFIGs were removed again by
928           'make savedefconfig'.
929         """
930
931         log = ''
932
933         with open(self.defconfig) as f:
934             defconfig_lines = f.readlines()
935
936         for (action, value) in self.results:
937             if action != ACTION_MOVE:
938                 continue
939             if not value + '\n' in defconfig_lines:
940                 log += color_text(self.options.color, COLOR_YELLOW,
941                                   "'%s' was removed by savedefconfig.\n" %
942                                   value)
943
944         return log
945
946
947 class DatabaseThread(threading.Thread):
948     """This thread processes results from Slot threads.
949
950     It collects the data in the master config directary. There is only one
951     result thread, and this helps to serialise the build output.
952     """
953     def __init__(self, config_db, db_queue):
954         """Set up a new result thread
955
956         Args:
957             builder: Builder which will be sent each result
958         """
959         threading.Thread.__init__(self)
960         self.config_db = config_db
961         self.db_queue= db_queue
962
963     def run(self):
964         """Called to start up the result thread.
965
966         We collect the next result job and pass it on to the build.
967         """
968         while True:
969             defconfig, configs = self.db_queue.get()
970             self.config_db[defconfig] = configs
971             self.db_queue.task_done()
972
973
974 class Slot:
975
976     """A slot to store a subprocess.
977
978     Each instance of this class handles one subprocess.
979     This class is useful to control multiple threads
980     for faster processing.
981     """
982
983     def __init__(self, configs, options, progress, devnull, make_cmd,
984                  reference_src_dir, db_queue):
985         """Create a new process slot.
986
987         Arguments:
988           configs: A list of CONFIGs to move.
989           options: option flags.
990           progress: A progress indicator.
991           devnull: A file object of '/dev/null'.
992           make_cmd: command name of GNU Make.
993           reference_src_dir: Determine the true starting config state from this
994                              source tree.
995           db_queue: output queue to write config info for the database
996         """
997         self.options = options
998         self.progress = progress
999         self.build_dir = tempfile.mkdtemp()
1000         self.devnull = devnull
1001         self.make_cmd = (make_cmd, 'O=' + self.build_dir)
1002         self.reference_src_dir = reference_src_dir
1003         self.db_queue = db_queue
1004         self.parser = KconfigParser(configs, options, self.build_dir)
1005         self.state = STATE_IDLE
1006         self.failed_boards = set()
1007         self.suspicious_boards = set()
1008
1009     def __del__(self):
1010         """Delete the working directory
1011
1012         This function makes sure the temporary directory is cleaned away
1013         even if Python suddenly dies due to error.  It should be done in here
1014         because it is guaranteed the destructor is always invoked when the
1015         instance of the class gets unreferenced.
1016
1017         If the subprocess is still running, wait until it finishes.
1018         """
1019         if self.state != STATE_IDLE:
1020             while self.ps.poll() == None:
1021                 pass
1022         shutil.rmtree(self.build_dir)
1023
1024     def add(self, defconfig):
1025         """Assign a new subprocess for defconfig and add it to the slot.
1026
1027         If the slot is vacant, create a new subprocess for processing the
1028         given defconfig and add it to the slot.  Just returns False if
1029         the slot is occupied (i.e. the current subprocess is still running).
1030
1031         Arguments:
1032           defconfig: defconfig name.
1033
1034         Returns:
1035           Return True on success or False on failure
1036         """
1037         if self.state != STATE_IDLE:
1038             return False
1039
1040         self.defconfig = defconfig
1041         self.log = ''
1042         self.current_src_dir = self.reference_src_dir
1043         self.do_defconfig()
1044         return True
1045
1046     def poll(self):
1047         """Check the status of the subprocess and handle it as needed.
1048
1049         Returns True if the slot is vacant (i.e. in idle state).
1050         If the configuration is successfully finished, assign a new
1051         subprocess to build include/autoconf.mk.
1052         If include/autoconf.mk is generated, invoke the parser to
1053         parse the .config and the include/autoconf.mk, moving
1054         config options to the .config as needed.
1055         If the .config was updated, run "make savedefconfig" to sync
1056         it, update the original defconfig, and then set the slot back
1057         to the idle state.
1058
1059         Returns:
1060           Return True if the subprocess is terminated, False otherwise
1061         """
1062         if self.state == STATE_IDLE:
1063             return True
1064
1065         if self.ps.poll() == None:
1066             return False
1067
1068         if self.ps.poll() != 0:
1069             self.handle_error()
1070         elif self.state == STATE_DEFCONFIG:
1071             if self.reference_src_dir and not self.current_src_dir:
1072                 self.do_savedefconfig()
1073             else:
1074                 self.do_autoconf()
1075         elif self.state == STATE_AUTOCONF:
1076             if self.current_src_dir:
1077                 self.current_src_dir = None
1078                 self.do_defconfig()
1079             elif self.options.build_db:
1080                 self.do_build_db()
1081             else:
1082                 self.do_savedefconfig()
1083         elif self.state == STATE_SAVEDEFCONFIG:
1084             self.update_defconfig()
1085         else:
1086             sys.exit("Internal Error. This should not happen.")
1087
1088         return True if self.state == STATE_IDLE else False
1089
1090     def handle_error(self):
1091         """Handle error cases."""
1092
1093         self.log += color_text(self.options.color, COLOR_LIGHT_RED,
1094                                "Failed to process.\n")
1095         if self.options.verbose:
1096             self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
1097                                    self.ps.stderr.read())
1098         self.finish(False)
1099
1100     def do_defconfig(self):
1101         """Run 'make <board>_defconfig' to create the .config file."""
1102
1103         cmd = list(self.make_cmd)
1104         cmd.append(self.defconfig)
1105         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1106                                    stderr=subprocess.PIPE,
1107                                    cwd=self.current_src_dir)
1108         self.state = STATE_DEFCONFIG
1109
1110     def do_autoconf(self):
1111         """Run 'make AUTO_CONF_PATH'."""
1112
1113         self.cross_compile = self.parser.get_cross_compile()
1114         if self.cross_compile is None:
1115             self.log += color_text(self.options.color, COLOR_YELLOW,
1116                                    "Compiler is missing.  Do nothing.\n")
1117             self.finish(False)
1118             return
1119
1120         cmd = list(self.make_cmd)
1121         if self.cross_compile:
1122             cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
1123         cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
1124         cmd.append(AUTO_CONF_PATH)
1125         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1126                                    stderr=subprocess.PIPE,
1127                                    cwd=self.current_src_dir)
1128         self.state = STATE_AUTOCONF
1129
1130     def do_build_db(self):
1131         """Add the board to the database"""
1132         configs = {}
1133         with open(os.path.join(self.build_dir, AUTO_CONF_PATH)) as fd:
1134             for line in fd.readlines():
1135                 if line.startswith('CONFIG'):
1136                     config, value = line.split('=', 1)
1137                     configs[config] = value.rstrip()
1138         self.db_queue.put([self.defconfig, configs])
1139         self.finish(True)
1140
1141     def do_savedefconfig(self):
1142         """Update the .config and run 'make savedefconfig'."""
1143
1144         (updated, suspicious, log) = self.parser.update_dotconfig()
1145         if suspicious:
1146             self.suspicious_boards.add(self.defconfig)
1147         self.log += log
1148
1149         if not self.options.force_sync and not updated:
1150             self.finish(True)
1151             return
1152         if updated:
1153             self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
1154                                    "Syncing by savedefconfig...\n")
1155         else:
1156             self.log += "Syncing by savedefconfig (forced by option)...\n"
1157
1158         cmd = list(self.make_cmd)
1159         cmd.append('savedefconfig')
1160         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1161                                    stderr=subprocess.PIPE)
1162         self.state = STATE_SAVEDEFCONFIG
1163
1164     def update_defconfig(self):
1165         """Update the input defconfig and go back to the idle state."""
1166
1167         log = self.parser.check_defconfig()
1168         if log:
1169             self.suspicious_boards.add(self.defconfig)
1170             self.log += log
1171         orig_defconfig = os.path.join('configs', self.defconfig)
1172         new_defconfig = os.path.join(self.build_dir, 'defconfig')
1173         updated = not filecmp.cmp(orig_defconfig, new_defconfig)
1174
1175         if updated:
1176             self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
1177                                    "defconfig was updated.\n")
1178
1179         if not self.options.dry_run and updated:
1180             shutil.move(new_defconfig, orig_defconfig)
1181         self.finish(True)
1182
1183     def finish(self, success):
1184         """Display log along with progress and go to the idle state.
1185
1186         Arguments:
1187           success: Should be True when the defconfig was processed
1188                    successfully, or False when it fails.
1189         """
1190         # output at least 30 characters to hide the "* defconfigs out of *".
1191         log = self.defconfig.ljust(30) + '\n'
1192
1193         log += '\n'.join([ '    ' + s for s in self.log.split('\n') ])
1194         # Some threads are running in parallel.
1195         # Print log atomically to not mix up logs from different threads.
1196         print >> (sys.stdout if success else sys.stderr), log
1197
1198         if not success:
1199             if self.options.exit_on_error:
1200                 sys.exit("Exit on error.")
1201             # If --exit-on-error flag is not set, skip this board and continue.
1202             # Record the failed board.
1203             self.failed_boards.add(self.defconfig)
1204
1205         self.progress.inc()
1206         self.progress.show()
1207         self.state = STATE_IDLE
1208
1209     def get_failed_boards(self):
1210         """Returns a set of failed boards (defconfigs) in this slot.
1211         """
1212         return self.failed_boards
1213
1214     def get_suspicious_boards(self):
1215         """Returns a set of boards (defconfigs) with possible misconversion.
1216         """
1217         return self.suspicious_boards - self.failed_boards
1218
1219 class Slots:
1220
1221     """Controller of the array of subprocess slots."""
1222
1223     def __init__(self, configs, options, progress, reference_src_dir, db_queue):
1224         """Create a new slots controller.
1225
1226         Arguments:
1227           configs: A list of CONFIGs to move.
1228           options: option flags.
1229           progress: A progress indicator.
1230           reference_src_dir: Determine the true starting config state from this
1231                              source tree.
1232           db_queue: output queue to write config info for the database
1233         """
1234         self.options = options
1235         self.slots = []
1236         devnull = get_devnull()
1237         make_cmd = get_make_cmd()
1238         for i in range(options.jobs):
1239             self.slots.append(Slot(configs, options, progress, devnull,
1240                                    make_cmd, reference_src_dir, db_queue))
1241
1242     def add(self, defconfig):
1243         """Add a new subprocess if a vacant slot is found.
1244
1245         Arguments:
1246           defconfig: defconfig name to be put into.
1247
1248         Returns:
1249           Return True on success or False on failure
1250         """
1251         for slot in self.slots:
1252             if slot.add(defconfig):
1253                 return True
1254         return False
1255
1256     def available(self):
1257         """Check if there is a vacant slot.
1258
1259         Returns:
1260           Return True if at lease one vacant slot is found, False otherwise.
1261         """
1262         for slot in self.slots:
1263             if slot.poll():
1264                 return True
1265         return False
1266
1267     def empty(self):
1268         """Check if all slots are vacant.
1269
1270         Returns:
1271           Return True if all the slots are vacant, False otherwise.
1272         """
1273         ret = True
1274         for slot in self.slots:
1275             if not slot.poll():
1276                 ret = False
1277         return ret
1278
1279     def show_failed_boards(self):
1280         """Display all of the failed boards (defconfigs)."""
1281         boards = set()
1282         output_file = 'moveconfig.failed'
1283
1284         for slot in self.slots:
1285             boards |= slot.get_failed_boards()
1286
1287         if boards:
1288             boards = '\n'.join(boards) + '\n'
1289             msg = "The following boards were not processed due to error:\n"
1290             msg += boards
1291             msg += "(the list has been saved in %s)\n" % output_file
1292             print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
1293                                             msg)
1294
1295             with open(output_file, 'w') as f:
1296                 f.write(boards)
1297
1298     def show_suspicious_boards(self):
1299         """Display all boards (defconfigs) with possible misconversion."""
1300         boards = set()
1301         output_file = 'moveconfig.suspicious'
1302
1303         for slot in self.slots:
1304             boards |= slot.get_suspicious_boards()
1305
1306         if boards:
1307             boards = '\n'.join(boards) + '\n'
1308             msg = "The following boards might have been converted incorrectly.\n"
1309             msg += "It is highly recommended to check them manually:\n"
1310             msg += boards
1311             msg += "(the list has been saved in %s)\n" % output_file
1312             print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
1313                                             msg)
1314
1315             with open(output_file, 'w') as f:
1316                 f.write(boards)
1317
1318 class ReferenceSource:
1319
1320     """Reference source against which original configs should be parsed."""
1321
1322     def __init__(self, commit):
1323         """Create a reference source directory based on a specified commit.
1324
1325         Arguments:
1326           commit: commit to git-clone
1327         """
1328         self.src_dir = tempfile.mkdtemp()
1329         print "Cloning git repo to a separate work directory..."
1330         subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1331                                 cwd=self.src_dir)
1332         print "Checkout '%s' to build the original autoconf.mk." % \
1333             subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
1334         subprocess.check_output(['git', 'checkout', commit],
1335                                 stderr=subprocess.STDOUT, cwd=self.src_dir)
1336
1337     def __del__(self):
1338         """Delete the reference source directory
1339
1340         This function makes sure the temporary directory is cleaned away
1341         even if Python suddenly dies due to error.  It should be done in here
1342         because it is guaranteed the destructor is always invoked when the
1343         instance of the class gets unreferenced.
1344         """
1345         shutil.rmtree(self.src_dir)
1346
1347     def get_dir(self):
1348         """Return the absolute path to the reference source directory."""
1349
1350         return self.src_dir
1351
1352 def move_config(configs, options, db_queue):
1353     """Move config options to defconfig files.
1354
1355     Arguments:
1356       configs: A list of CONFIGs to move.
1357       options: option flags
1358     """
1359     if len(configs) == 0:
1360         if options.force_sync:
1361             print 'No CONFIG is specified. You are probably syncing defconfigs.',
1362         elif options.build_db:
1363             print 'Building %s database' % CONFIG_DATABASE
1364         else:
1365             print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
1366     else:
1367         print 'Move ' + ', '.join(configs),
1368     print '(jobs: %d)\n' % options.jobs
1369
1370     if options.git_ref:
1371         reference_src = ReferenceSource(options.git_ref)
1372         reference_src_dir = reference_src.get_dir()
1373     else:
1374         reference_src_dir = None
1375
1376     if options.defconfigs:
1377         defconfigs = get_matched_defconfigs(options.defconfigs)
1378     else:
1379         defconfigs = get_all_defconfigs()
1380
1381     progress = Progress(len(defconfigs))
1382     slots = Slots(configs, options, progress, reference_src_dir, db_queue)
1383
1384     # Main loop to process defconfig files:
1385     #  Add a new subprocess into a vacant slot.
1386     #  Sleep if there is no available slot.
1387     for defconfig in defconfigs:
1388         while not slots.add(defconfig):
1389             while not slots.available():
1390                 # No available slot: sleep for a while
1391                 time.sleep(SLEEP_TIME)
1392
1393     # wait until all the subprocesses finish
1394     while not slots.empty():
1395         time.sleep(SLEEP_TIME)
1396
1397     print ''
1398     slots.show_failed_boards()
1399     slots.show_suspicious_boards()
1400
1401 def main():
1402     try:
1403         cpu_count = multiprocessing.cpu_count()
1404     except NotImplementedError:
1405         cpu_count = 1
1406
1407     parser = optparse.OptionParser()
1408     # Add options here
1409     parser.add_option('-b', '--build-db', action='store_true', default=False,
1410                       help='build a CONFIG database')
1411     parser.add_option('-c', '--color', action='store_true', default=False,
1412                       help='display the log in color')
1413     parser.add_option('-C', '--commit', action='store_true', default=False,
1414                       help='Create a git commit for the operation')
1415     parser.add_option('-d', '--defconfigs', type='string',
1416                       help='a file containing a list of defconfigs to move, '
1417                       "one per line (for example 'snow_defconfig') "
1418                       "or '-' to read from stdin")
1419     parser.add_option('-n', '--dry-run', action='store_true', default=False,
1420                       help='perform a trial run (show log with no changes)')
1421     parser.add_option('-e', '--exit-on-error', action='store_true',
1422                       default=False,
1423                       help='exit immediately on any error')
1424     parser.add_option('-s', '--force-sync', action='store_true', default=False,
1425                       help='force sync by savedefconfig')
1426     parser.add_option('-S', '--spl', action='store_true', default=False,
1427                       help='parse config options defined for SPL build')
1428     parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1429                       action='store_true', default=False,
1430                       help='only cleanup the headers')
1431     parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1432                       help='the number of jobs to run simultaneously')
1433     parser.add_option('-r', '--git-ref', type='string',
1434                       help='the git ref to clone for building the autoconf.mk')
1435     parser.add_option('-y', '--yes', action='store_true', default=False,
1436                       help="respond 'yes' to any prompts")
1437     parser.add_option('-v', '--verbose', action='store_true', default=False,
1438                       help='show any build errors as boards are built')
1439     parser.usage += ' CONFIG ...'
1440
1441     (options, configs) = parser.parse_args()
1442
1443     if len(configs) == 0 and not any((options.force_sync, options.build_db)):
1444         parser.print_usage()
1445         sys.exit(1)
1446
1447     # prefix the option name with CONFIG_ if missing
1448     configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1449                 for config in configs ]
1450
1451     check_top_directory()
1452
1453     config_db = {}
1454     db_queue = Queue.Queue()
1455     t = DatabaseThread(config_db, db_queue)
1456     t.setDaemon(True)
1457     t.start()
1458
1459     if not options.cleanup_headers_only:
1460         check_clean_directory()
1461         update_cross_compile(options.color)
1462         move_config(configs, options, db_queue)
1463         db_queue.join()
1464
1465     if configs:
1466         cleanup_headers(configs, options)
1467         cleanup_extra_options(configs, options)
1468         cleanup_whitelist(configs, options)
1469         cleanup_readme(configs, options)
1470
1471     if options.commit:
1472         subprocess.call(['git', 'add', '-u'])
1473         if configs:
1474             msg = 'Convert %s %sto Kconfig' % (configs[0],
1475                     'et al ' if len(configs) > 1 else '')
1476             msg += ('\n\nThis converts the following to Kconfig:\n   %s\n' %
1477                     '\n   '.join(configs))
1478         else:
1479             msg = 'configs: Resync with savedefconfig'
1480             msg += '\n\nRsync all defconfig files using moveconfig.py'
1481         subprocess.call(['git', 'commit', '-s', '-m', msg])
1482
1483     if options.build_db:
1484         with open(CONFIG_DATABASE, 'w') as fd:
1485             for defconfig, configs in config_db.iteritems():
1486                 print >>fd, '%s' % defconfig
1487                 for config in sorted(configs.keys()):
1488                     print >>fd, '   %s=%s' % (config, configs[config])
1489                 print >>fd
1490
1491 if __name__ == '__main__':
1492     main()