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