tools: moveconfig: do not cleanup headers in include/generated
[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 followings:
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.
51    There are two common cases:
52      - You forgot to create an entry for the CONFIG before running
53        this tool, or made a typo in a CONFIG passed to this tool.
54      - The entry was hidden due to unmet 'depends on'.
55        This is correct behavior.
56
57  - 'CONFIG_...' is the same as the define in Kconfig.  Do nothing.
58    The define in the config header matched the one in Kconfig.
59    We do not need to touch it.
60
61  - Undefined.  Do nothing.
62    This config option was not found in the config header.
63    Nothing to do.
64
65  - Compiler is missing.  Do nothing.
66    The compiler specified for this architecture was not found
67    in your PATH environment.
68    (If -e option is passed, the tool exits immediately.)
69
70  - Failed to process.
71    An error occurred during processing this defconfig.  Skipped.
72    (If -e option is passed, the tool exits immediately on error.)
73
74 Finally, you will be asked, Clean up headers? [y/n]:
75
76 If you say 'y' here, the unnecessary config defines are removed
77 from the config headers (include/configs/*.h).
78 It just uses the regex method, so you should not rely on it.
79 Just in case, please do 'git diff' to see what happened.
80
81
82 How does it work?
83 -----------------
84
85 This tool runs configuration and builds include/autoconf.mk for every
86 defconfig.  The config options defined in Kconfig appear in the .config
87 file (unless they are hidden because of unmet dependency.)
88 On the other hand, the config options defined by board headers are seen
89 in include/autoconf.mk.  The tool looks for the specified options in both
90 of them to decide the appropriate action for the options.  If the given
91 config option is found in the .config, but its value does not match the
92 one from the board header, the config option in the .config is replaced
93 with the define in the board header.  Then, the .config is synced by
94 "make savedefconfig" and the defconfig is updated with it.
95
96 For faster processing, this tool handles multi-threading.  It creates
97 separate build directories where the out-of-tree build is run.  The
98 temporary build directories are automatically created and deleted as
99 needed.  The number of threads are chosen based on the number of the CPU
100 cores of your system although you can change it via -j (--jobs) option.
101
102
103 Toolchains
104 ----------
105
106 Appropriate toolchain are necessary to generate include/autoconf.mk
107 for all the architectures supported by U-Boot.  Most of them are available
108 at the kernel.org site, some are not provided by kernel.org.
109
110 The default per-arch CROSS_COMPILE used by this tool is specified by
111 the list below, CROSS_COMPILE.  You may wish to update the list to
112 use your own.  Instead of modifying the list directly, you can give
113 them via environments.
114
115
116 Available options
117 -----------------
118
119  -c, --color
120    Surround each portion of the log with escape sequences to display it
121    in color on the terminal.
122
123  -d, --defconfigs
124   Specify a file containing a list of defconfigs to move
125
126  -n, --dry-run
127    Perform a trial run that does not make any changes.  It is useful to
128    see what is going to happen before one actually runs it.
129
130  -e, --exit-on-error
131    Exit immediately if Make exits with a non-zero status while processing
132    a defconfig file.
133
134  -s, --force-sync
135    Do "make savedefconfig" forcibly for all the defconfig files.
136    If not specified, "make savedefconfig" only occurs for cases
137    where at least one CONFIG was moved.
138
139  -H, --headers-only
140    Only cleanup the headers; skip the defconfig processing
141
142  -j, --jobs
143    Specify the number of threads to run simultaneously.  If not specified,
144    the number of threads is the same as the number of CPU cores.
145
146  -r, --git-ref
147    Specify the git ref to clone for building the autoconf.mk. If unspecified
148    use the CWD. This is useful for when changes to the Kconfig affect the
149    default values and you want to capture the state of the defconfig from
150    before that change was in effect. If in doubt, specify a ref pre-Kconfig
151    changes (use HEAD if Kconfig changes are not committed). Worst case it will
152    take a bit longer to run, but will always do the right thing.
153
154  -v, --verbose
155    Show any build errors as boards are built
156
157 To see the complete list of supported options, run
158
159   $ tools/moveconfig.py -h
160
161 """
162
163 import filecmp
164 import fnmatch
165 import multiprocessing
166 import optparse
167 import os
168 import re
169 import shutil
170 import subprocess
171 import sys
172 import tempfile
173 import time
174
175 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
176 SLEEP_TIME=0.03
177
178 # Here is the list of cross-tools I use.
179 # Most of them are available at kernel.org
180 # (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the followings:
181 # arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases
182 # blackfin: http://sourceforge.net/projects/adi-toolchain/files/
183 # nds32: http://osdk.andestech.com/packages/nds32le-linux-glibc-v1.tgz
184 # nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545
185 # sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu
186 #
187 # openrisc kernel.org toolchain is out of date, download latest one from
188 # http://opencores.org/or1k/OpenRISC_GNU_tool_chain#Prebuilt_versions
189 CROSS_COMPILE = {
190     'arc': 'arc-linux-',
191     'aarch64': 'aarch64-linux-',
192     'arm': 'arm-unknown-linux-gnueabi-',
193     'avr32': 'avr32-linux-',
194     'blackfin': 'bfin-elf-',
195     'm68k': 'm68k-linux-',
196     'microblaze': 'microblaze-linux-',
197     'mips': 'mips-linux-',
198     'nds32': 'nds32le-linux-',
199     'nios2': 'nios2-linux-gnu-',
200     'openrisc': 'or1k-elf-',
201     'powerpc': 'powerpc-linux-',
202     'sh': 'sh-linux-gnu-',
203     'sparc': 'sparc-linux-',
204     'x86': 'i386-linux-'
205 }
206
207 STATE_IDLE = 0
208 STATE_DEFCONFIG = 1
209 STATE_AUTOCONF = 2
210 STATE_SAVEDEFCONFIG = 3
211
212 ACTION_MOVE = 0
213 ACTION_NO_ENTRY = 1
214 ACTION_NO_CHANGE = 2
215
216 COLOR_BLACK        = '0;30'
217 COLOR_RED          = '0;31'
218 COLOR_GREEN        = '0;32'
219 COLOR_BROWN        = '0;33'
220 COLOR_BLUE         = '0;34'
221 COLOR_PURPLE       = '0;35'
222 COLOR_CYAN         = '0;36'
223 COLOR_LIGHT_GRAY   = '0;37'
224 COLOR_DARK_GRAY    = '1;30'
225 COLOR_LIGHT_RED    = '1;31'
226 COLOR_LIGHT_GREEN  = '1;32'
227 COLOR_YELLOW       = '1;33'
228 COLOR_LIGHT_BLUE   = '1;34'
229 COLOR_LIGHT_PURPLE = '1;35'
230 COLOR_LIGHT_CYAN   = '1;36'
231 COLOR_WHITE        = '1;37'
232
233 ### helper functions ###
234 def get_devnull():
235     """Get the file object of '/dev/null' device."""
236     try:
237         devnull = subprocess.DEVNULL # py3k
238     except AttributeError:
239         devnull = open(os.devnull, 'wb')
240     return devnull
241
242 def check_top_directory():
243     """Exit if we are not at the top of source directory."""
244     for f in ('README', 'Licenses'):
245         if not os.path.exists(f):
246             sys.exit('Please run at the top of source directory.')
247
248 def check_clean_directory():
249     """Exit if the source tree is not clean."""
250     for f in ('.config', 'include/config'):
251         if os.path.exists(f):
252             sys.exit("source tree is not clean, please run 'make mrproper'")
253
254 def get_make_cmd():
255     """Get the command name of GNU Make.
256
257     U-Boot needs GNU Make for building, but the command name is not
258     necessarily "make". (for example, "gmake" on FreeBSD).
259     Returns the most appropriate command name on your system.
260     """
261     process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
262     ret = process.communicate()
263     if process.returncode:
264         sys.exit('GNU Make not found')
265     return ret[0].rstrip()
266
267 def color_text(color_enabled, color, string):
268     """Return colored string."""
269     if color_enabled:
270         # LF should not be surrounded by the escape sequence.
271         # Otherwise, additional whitespace or line-feed might be printed.
272         return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
273                            for s in string.split('\n') ])
274     else:
275         return string
276
277 def update_cross_compile(color_enabled):
278     """Update per-arch CROSS_COMPILE via environment variables
279
280     The default CROSS_COMPILE values are available
281     in the CROSS_COMPILE list above.
282
283     You can override them via environment variables
284     CROSS_COMPILE_{ARCH}.
285
286     For example, if you want to override toolchain prefixes
287     for ARM and PowerPC, you can do as follows in your shell:
288
289     export CROSS_COMPILE_ARM=...
290     export CROSS_COMPILE_POWERPC=...
291
292     Then, this function checks if specified compilers really exist in your
293     PATH environment.
294     """
295     archs = []
296
297     for arch in os.listdir('arch'):
298         if os.path.exists(os.path.join('arch', arch, 'Makefile')):
299             archs.append(arch)
300
301     # arm64 is a special case
302     archs.append('aarch64')
303
304     for arch in archs:
305         env = 'CROSS_COMPILE_' + arch.upper()
306         cross_compile = os.environ.get(env)
307         if not cross_compile:
308             cross_compile = CROSS_COMPILE.get(arch, '')
309
310         for path in os.environ["PATH"].split(os.pathsep):
311             gcc_path = os.path.join(path, cross_compile + 'gcc')
312             if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
313                 break
314         else:
315             print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
316                  'warning: %sgcc: not found in PATH.  %s architecture boards will be skipped'
317                                             % (cross_compile, arch))
318             cross_compile = None
319
320         CROSS_COMPILE[arch] = cross_compile
321
322 def cleanup_one_header(header_path, patterns, dry_run):
323     """Clean regex-matched lines away from a file.
324
325     Arguments:
326       header_path: path to the cleaned file.
327       patterns: list of regex patterns.  Any lines matching to these
328                 patterns are deleted.
329       dry_run: make no changes, but still display log.
330     """
331     with open(header_path) as f:
332         lines = f.readlines()
333
334     matched = []
335     for i, line in enumerate(lines):
336         for pattern in patterns:
337             m = pattern.search(line)
338             if m:
339                 print '%s: %s: %s' % (header_path, i + 1, line),
340                 matched.append(i)
341                 break
342
343     if dry_run or not matched:
344         return
345
346     with open(header_path, 'w') as f:
347         for i, line in enumerate(lines):
348             if not i in matched:
349                 f.write(line)
350
351 def cleanup_headers(configs, dry_run):
352     """Delete config defines from board headers.
353
354     Arguments:
355       configs: A list of CONFIGs to remove.
356       dry_run: make no changes, but still display log.
357     """
358     while True:
359         choice = raw_input('Clean up headers? [y/n]: ').lower()
360         print choice
361         if choice == 'y' or choice == 'n':
362             break
363
364     if choice == 'n':
365         return
366
367     patterns = []
368     for config in configs:
369         patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
370         patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
371
372     for dir in 'include', 'arch', 'board':
373         for (dirpath, dirnames, filenames) in os.walk(dir):
374             if dirpath == os.path.join('include', 'generated'):
375                 continue
376             for filename in filenames:
377                 if not fnmatch.fnmatch(filename, '*~'):
378                     cleanup_one_header(os.path.join(dirpath, filename),
379                                        patterns, dry_run)
380
381 ### classes ###
382 class Progress:
383
384     """Progress Indicator"""
385
386     def __init__(self, total):
387         """Create a new progress indicator.
388
389         Arguments:
390           total: A number of defconfig files to process.
391         """
392         self.current = 0
393         self.total = total
394
395     def inc(self):
396         """Increment the number of processed defconfig files."""
397
398         self.current += 1
399
400     def show(self):
401         """Display the progress."""
402         print ' %d defconfigs out of %d\r' % (self.current, self.total),
403         sys.stdout.flush()
404
405 class KconfigParser:
406
407     """A parser of .config and include/autoconf.mk."""
408
409     re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
410     re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
411
412     def __init__(self, configs, options, build_dir):
413         """Create a new parser.
414
415         Arguments:
416           configs: A list of CONFIGs to move.
417           options: option flags.
418           build_dir: Build directory.
419         """
420         self.configs = configs
421         self.options = options
422         self.dotconfig = os.path.join(build_dir, '.config')
423         self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
424         self.config_autoconf = os.path.join(build_dir, 'include', 'config',
425                                             'auto.conf')
426         self.defconfig = os.path.join(build_dir, 'defconfig')
427
428     def get_cross_compile(self):
429         """Parse .config file and return CROSS_COMPILE.
430
431         Returns:
432           A string storing the compiler prefix for the architecture.
433           Return a NULL string for architectures that do not require
434           compiler prefix (Sandbox and native build is the case).
435           Return None if the specified compiler is missing in your PATH.
436           Caller should distinguish '' and None.
437         """
438         arch = ''
439         cpu = ''
440         for line in open(self.dotconfig):
441             m = self.re_arch.match(line)
442             if m:
443                 arch = m.group(1)
444                 continue
445             m = self.re_cpu.match(line)
446             if m:
447                 cpu = m.group(1)
448
449         if not arch:
450             return None
451
452         # fix-up for aarch64
453         if arch == 'arm' and cpu == 'armv8':
454             arch = 'aarch64'
455
456         return CROSS_COMPILE.get(arch, None)
457
458     def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
459         """Parse .config, defconfig, include/autoconf.mk for one config.
460
461         This function looks for the config options in the lines from
462         defconfig, .config, and include/autoconf.mk in order to decide
463         which action should be taken for this defconfig.
464
465         Arguments:
466           config: CONFIG name to parse.
467           dotconfig_lines: lines from the .config file.
468           autoconf_lines: lines from the include/autoconf.mk file.
469
470         Returns:
471           A tupple of the action for this defconfig and the line
472           matched for the config.
473         """
474         not_set = '# %s is not set' % config
475
476         for line in dotconfig_lines:
477             line = line.rstrip()
478             if line.startswith(config + '=') or line == not_set:
479                 old_val = line
480                 break
481         else:
482             return (ACTION_NO_ENTRY, config)
483
484         for line in autoconf_lines:
485             line = line.rstrip()
486             if line.startswith(config + '='):
487                 new_val = line
488                 break
489         else:
490             new_val = not_set
491
492         # If this CONFIG is neither bool nor trisate
493         if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
494             # tools/scripts/define2mk.sed changes '1' to 'y'.
495             # This is a problem if the CONFIG is int type.
496             # Check the type in Kconfig and handle it correctly.
497             if new_val[-2:] == '=y':
498                 new_val = new_val[:-1] + '1'
499
500         return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
501                 new_val)
502
503     def update_dotconfig(self):
504         """Parse files for the config options and update the .config.
505
506         This function parses the generated .config and include/autoconf.mk
507         searching the target options.
508         Move the config option(s) to the .config as needed.
509
510         Arguments:
511           defconfig: defconfig name.
512
513         Returns:
514           Return a tuple of (updated flag, log string).
515           The "updated flag" is True if the .config was updated, False
516           otherwise.  The "log string" shows what happend to the .config.
517         """
518
519         results = []
520         updated = False
521
522         with open(self.dotconfig) as f:
523             dotconfig_lines = f.readlines()
524
525         with open(self.autoconf) as f:
526             autoconf_lines = f.readlines()
527
528         for config in self.configs:
529             result = self.parse_one_config(config, dotconfig_lines,
530                                            autoconf_lines)
531             results.append(result)
532
533         log = ''
534
535         for (action, value) in results:
536             if action == ACTION_MOVE:
537                 actlog = "Move '%s'" % value
538                 log_color = COLOR_LIGHT_GREEN
539             elif action == ACTION_NO_ENTRY:
540                 actlog = "%s is not defined in Kconfig.  Do nothing." % value
541                 log_color = COLOR_LIGHT_BLUE
542             elif action == ACTION_NO_CHANGE:
543                 actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
544                          % value
545                 log_color = COLOR_LIGHT_PURPLE
546             else:
547                 sys.exit("Internal Error. This should not happen.")
548
549             log += color_text(self.options.color, log_color, actlog) + '\n'
550
551         with open(self.dotconfig, 'a') as f:
552             for (action, value) in results:
553                 if action == ACTION_MOVE:
554                     f.write(value + '\n')
555                     updated = True
556
557         self.results = results
558         os.remove(self.config_autoconf)
559         os.remove(self.autoconf)
560
561         return (updated, log)
562
563     def check_defconfig(self):
564         """Check the defconfig after savedefconfig
565
566         Returns:
567           Return additional log if moved CONFIGs were removed again by
568           'make savedefconfig'.
569         """
570
571         log = ''
572
573         with open(self.defconfig) as f:
574             defconfig_lines = f.readlines()
575
576         for (action, value) in self.results:
577             if action != ACTION_MOVE:
578                 continue
579             if not value + '\n' in defconfig_lines:
580                 log += color_text(self.options.color, COLOR_YELLOW,
581                                   "'%s' was removed by savedefconfig.\n" %
582                                   value)
583
584         return log
585
586 class Slot:
587
588     """A slot to store a subprocess.
589
590     Each instance of this class handles one subprocess.
591     This class is useful to control multiple threads
592     for faster processing.
593     """
594
595     def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir):
596         """Create a new process slot.
597
598         Arguments:
599           configs: A list of CONFIGs to move.
600           options: option flags.
601           progress: A progress indicator.
602           devnull: A file object of '/dev/null'.
603           make_cmd: command name of GNU Make.
604           reference_src_dir: Determine the true starting config state from this
605                              source tree.
606         """
607         self.options = options
608         self.progress = progress
609         self.build_dir = tempfile.mkdtemp()
610         self.devnull = devnull
611         self.make_cmd = (make_cmd, 'O=' + self.build_dir)
612         self.reference_src_dir = reference_src_dir
613         self.parser = KconfigParser(configs, options, self.build_dir)
614         self.state = STATE_IDLE
615         self.failed_boards = []
616         self.suspicious_boards = []
617
618     def __del__(self):
619         """Delete the working directory
620
621         This function makes sure the temporary directory is cleaned away
622         even if Python suddenly dies due to error.  It should be done in here
623         because it is guaranteed the destructor is always invoked when the
624         instance of the class gets unreferenced.
625
626         If the subprocess is still running, wait until it finishes.
627         """
628         if self.state != STATE_IDLE:
629             while self.ps.poll() == None:
630                 pass
631         shutil.rmtree(self.build_dir)
632
633     def add(self, defconfig):
634         """Assign a new subprocess for defconfig and add it to the slot.
635
636         If the slot is vacant, create a new subprocess for processing the
637         given defconfig and add it to the slot.  Just returns False if
638         the slot is occupied (i.e. the current subprocess is still running).
639
640         Arguments:
641           defconfig: defconfig name.
642
643         Returns:
644           Return True on success or False on failure
645         """
646         if self.state != STATE_IDLE:
647             return False
648
649         self.defconfig = defconfig
650         self.log = ''
651         self.current_src_dir = self.reference_src_dir
652         self.do_defconfig()
653         return True
654
655     def poll(self):
656         """Check the status of the subprocess and handle it as needed.
657
658         Returns True if the slot is vacant (i.e. in idle state).
659         If the configuration is successfully finished, assign a new
660         subprocess to build include/autoconf.mk.
661         If include/autoconf.mk is generated, invoke the parser to
662         parse the .config and the include/autoconf.mk, moving
663         config options to the .config as needed.
664         If the .config was updated, run "make savedefconfig" to sync
665         it, update the original defconfig, and then set the slot back
666         to the idle state.
667
668         Returns:
669           Return True if the subprocess is terminated, False otherwise
670         """
671         if self.state == STATE_IDLE:
672             return True
673
674         if self.ps.poll() == None:
675             return False
676
677         if self.ps.poll() != 0:
678             self.handle_error()
679         elif self.state == STATE_DEFCONFIG:
680             if self.reference_src_dir and not self.current_src_dir:
681                 self.do_savedefconfig()
682             else:
683                 self.do_autoconf()
684         elif self.state == STATE_AUTOCONF:
685             if self.current_src_dir:
686                 self.current_src_dir = None
687                 self.do_defconfig()
688             else:
689                 self.do_savedefconfig()
690         elif self.state == STATE_SAVEDEFCONFIG:
691             self.update_defconfig()
692         else:
693             sys.exit("Internal Error. This should not happen.")
694
695         return True if self.state == STATE_IDLE else False
696
697     def handle_error(self):
698         """Handle error cases."""
699
700         self.log += color_text(self.options.color, COLOR_LIGHT_RED,
701                                "Failed to process.\n")
702         if self.options.verbose:
703             self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
704                                    self.ps.stderr.read())
705         self.finish(False)
706
707     def do_defconfig(self):
708         """Run 'make <board>_defconfig' to create the .config file."""
709
710         cmd = list(self.make_cmd)
711         cmd.append(self.defconfig)
712         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
713                                    stderr=subprocess.PIPE,
714                                    cwd=self.current_src_dir)
715         self.state = STATE_DEFCONFIG
716
717     def do_autoconf(self):
718         """Run 'make include/config/auto.conf'."""
719
720         self.cross_compile = self.parser.get_cross_compile()
721         if self.cross_compile is None:
722             self.log += color_text(self.options.color, COLOR_YELLOW,
723                                    "Compiler is missing.  Do nothing.\n")
724             self.finish(False)
725             return
726
727         cmd = list(self.make_cmd)
728         if self.cross_compile:
729             cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
730         cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
731         cmd.append('include/config/auto.conf')
732         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
733                                    stderr=subprocess.PIPE,
734                                    cwd=self.current_src_dir)
735         self.state = STATE_AUTOCONF
736
737     def do_savedefconfig(self):
738         """Update the .config and run 'make savedefconfig'."""
739
740         (updated, log) = self.parser.update_dotconfig()
741         self.log += log
742
743         if not self.options.force_sync and not updated:
744             self.finish(True)
745             return
746         if updated:
747             self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
748                                    "Syncing by savedefconfig...\n")
749         else:
750             self.log += "Syncing by savedefconfig (forced by option)...\n"
751
752         cmd = list(self.make_cmd)
753         cmd.append('savedefconfig')
754         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
755                                    stderr=subprocess.PIPE)
756         self.state = STATE_SAVEDEFCONFIG
757
758     def update_defconfig(self):
759         """Update the input defconfig and go back to the idle state."""
760
761         log = self.parser.check_defconfig()
762         if log:
763             self.suspicious_boards.append(self.defconfig)
764             self.log += log
765         orig_defconfig = os.path.join('configs', self.defconfig)
766         new_defconfig = os.path.join(self.build_dir, 'defconfig')
767         updated = not filecmp.cmp(orig_defconfig, new_defconfig)
768
769         if updated:
770             self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
771                                    "defconfig was updated.\n")
772
773         if not self.options.dry_run and updated:
774             shutil.move(new_defconfig, orig_defconfig)
775         self.finish(True)
776
777     def finish(self, success):
778         """Display log along with progress and go to the idle state.
779
780         Arguments:
781           success: Should be True when the defconfig was processed
782                    successfully, or False when it fails.
783         """
784         # output at least 30 characters to hide the "* defconfigs out of *".
785         log = self.defconfig.ljust(30) + '\n'
786
787         log += '\n'.join([ '    ' + s for s in self.log.split('\n') ])
788         # Some threads are running in parallel.
789         # Print log atomically to not mix up logs from different threads.
790         print >> (sys.stdout if success else sys.stderr), log
791
792         if not success:
793             if self.options.exit_on_error:
794                 sys.exit("Exit on error.")
795             # If --exit-on-error flag is not set, skip this board and continue.
796             # Record the failed board.
797             self.failed_boards.append(self.defconfig)
798
799         self.progress.inc()
800         self.progress.show()
801         self.state = STATE_IDLE
802
803     def get_failed_boards(self):
804         """Returns a list of failed boards (defconfigs) in this slot.
805         """
806         return self.failed_boards
807
808     def get_suspicious_boards(self):
809         """Returns a list of boards (defconfigs) with possible misconversion.
810         """
811         return self.suspicious_boards
812
813 class Slots:
814
815     """Controller of the array of subprocess slots."""
816
817     def __init__(self, configs, options, progress, reference_src_dir):
818         """Create a new slots controller.
819
820         Arguments:
821           configs: A list of CONFIGs to move.
822           options: option flags.
823           progress: A progress indicator.
824           reference_src_dir: Determine the true starting config state from this
825                              source tree.
826         """
827         self.options = options
828         self.slots = []
829         devnull = get_devnull()
830         make_cmd = get_make_cmd()
831         for i in range(options.jobs):
832             self.slots.append(Slot(configs, options, progress, devnull,
833                                    make_cmd, reference_src_dir))
834
835     def add(self, defconfig):
836         """Add a new subprocess if a vacant slot is found.
837
838         Arguments:
839           defconfig: defconfig name to be put into.
840
841         Returns:
842           Return True on success or False on failure
843         """
844         for slot in self.slots:
845             if slot.add(defconfig):
846                 return True
847         return False
848
849     def available(self):
850         """Check if there is a vacant slot.
851
852         Returns:
853           Return True if at lease one vacant slot is found, False otherwise.
854         """
855         for slot in self.slots:
856             if slot.poll():
857                 return True
858         return False
859
860     def empty(self):
861         """Check if all slots are vacant.
862
863         Returns:
864           Return True if all the slots are vacant, False otherwise.
865         """
866         ret = True
867         for slot in self.slots:
868             if not slot.poll():
869                 ret = False
870         return ret
871
872     def show_failed_boards(self):
873         """Display all of the failed boards (defconfigs)."""
874         boards = []
875         output_file = 'moveconfig.failed'
876
877         for slot in self.slots:
878             boards += slot.get_failed_boards()
879
880         if boards:
881             boards = '\n'.join(boards) + '\n'
882             msg = "The following boards were not processed due to error:\n"
883             msg += boards
884             msg += "(the list has been saved in %s)\n" % output_file
885             print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
886                                             msg)
887
888             with open(output_file, 'w') as f:
889                 f.write(boards)
890
891     def show_suspicious_boards(self):
892         """Display all boards (defconfigs) with possible misconversion."""
893         boards = []
894         output_file = 'moveconfig.suspicious'
895
896         for slot in self.slots:
897             boards += slot.get_suspicious_boards()
898
899         if boards:
900             boards = '\n'.join(boards) + '\n'
901             msg = "The following boards might have been converted incorrectly.\n"
902             msg += "It is highly recommended to check them manually:\n"
903             msg += boards
904             msg += "(the list has been saved in %s)\n" % output_file
905             print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
906                                             msg)
907
908             with open(output_file, 'w') as f:
909                 f.write(boards)
910
911 class ReferenceSource:
912
913     """Reference source against which original configs should be parsed."""
914
915     def __init__(self, commit):
916         """Create a reference source directory based on a specified commit.
917
918         Arguments:
919           commit: commit to git-clone
920         """
921         self.src_dir = tempfile.mkdtemp()
922         print "Cloning git repo to a separate work directory..."
923         subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
924                                 cwd=self.src_dir)
925         print "Checkout '%s' to build the original autoconf.mk." % \
926             subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
927         subprocess.check_output(['git', 'checkout', commit],
928                                 stderr=subprocess.STDOUT, cwd=self.src_dir)
929
930     def __del__(self):
931         """Delete the reference source directory
932
933         This function makes sure the temporary directory is cleaned away
934         even if Python suddenly dies due to error.  It should be done in here
935         because it is guaranteed the destructor is always invoked when the
936         instance of the class gets unreferenced.
937         """
938         shutil.rmtree(self.src_dir)
939
940     def get_dir(self):
941         """Return the absolute path to the reference source directory."""
942
943         return self.src_dir
944
945 def move_config(configs, options):
946     """Move config options to defconfig files.
947
948     Arguments:
949       configs: A list of CONFIGs to move.
950       options: option flags
951     """
952     if len(configs) == 0:
953         if options.force_sync:
954             print 'No CONFIG is specified. You are probably syncing defconfigs.',
955         else:
956             print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
957     else:
958         print 'Move ' + ', '.join(configs),
959     print '(jobs: %d)\n' % options.jobs
960
961     if options.git_ref:
962         reference_src = ReferenceSource(options.git_ref)
963         reference_src_dir = reference_src.get_dir()
964     else:
965         reference_src_dir = None
966
967     if options.defconfigs:
968         defconfigs = [line.strip() for line in open(options.defconfigs)]
969         for i, defconfig in enumerate(defconfigs):
970             if not defconfig.endswith('_defconfig'):
971                 defconfigs[i] = defconfig + '_defconfig'
972             if not os.path.exists(os.path.join('configs', defconfigs[i])):
973                 sys.exit('%s - defconfig does not exist. Stopping.' %
974                          defconfigs[i])
975     else:
976         # All the defconfig files to be processed
977         defconfigs = []
978         for (dirpath, dirnames, filenames) in os.walk('configs'):
979             dirpath = dirpath[len('configs') + 1:]
980             for filename in fnmatch.filter(filenames, '*_defconfig'):
981                 defconfigs.append(os.path.join(dirpath, filename))
982
983     progress = Progress(len(defconfigs))
984     slots = Slots(configs, options, progress, reference_src_dir)
985
986     # Main loop to process defconfig files:
987     #  Add a new subprocess into a vacant slot.
988     #  Sleep if there is no available slot.
989     for defconfig in defconfigs:
990         while not slots.add(defconfig):
991             while not slots.available():
992                 # No available slot: sleep for a while
993                 time.sleep(SLEEP_TIME)
994
995     # wait until all the subprocesses finish
996     while not slots.empty():
997         time.sleep(SLEEP_TIME)
998
999     print ''
1000     slots.show_failed_boards()
1001     slots.show_suspicious_boards()
1002
1003 def main():
1004     try:
1005         cpu_count = multiprocessing.cpu_count()
1006     except NotImplementedError:
1007         cpu_count = 1
1008
1009     parser = optparse.OptionParser()
1010     # Add options here
1011     parser.add_option('-c', '--color', action='store_true', default=False,
1012                       help='display the log in color')
1013     parser.add_option('-d', '--defconfigs', type='string',
1014                       help='a file containing a list of defconfigs to move')
1015     parser.add_option('-n', '--dry-run', action='store_true', default=False,
1016                       help='perform a trial run (show log with no changes)')
1017     parser.add_option('-e', '--exit-on-error', action='store_true',
1018                       default=False,
1019                       help='exit immediately on any error')
1020     parser.add_option('-s', '--force-sync', action='store_true', default=False,
1021                       help='force sync by savedefconfig')
1022     parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1023                       action='store_true', default=False,
1024                       help='only cleanup the headers')
1025     parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1026                       help='the number of jobs to run simultaneously')
1027     parser.add_option('-r', '--git-ref', type='string',
1028                       help='the git ref to clone for building the autoconf.mk')
1029     parser.add_option('-v', '--verbose', action='store_true', default=False,
1030                       help='show any build errors as boards are built')
1031     parser.usage += ' CONFIG ...'
1032
1033     (options, configs) = parser.parse_args()
1034
1035     if len(configs) == 0 and not options.force_sync:
1036         parser.print_usage()
1037         sys.exit(1)
1038
1039     # prefix the option name with CONFIG_ if missing
1040     configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1041                 for config in configs ]
1042
1043     check_top_directory()
1044
1045     check_clean_directory()
1046
1047     update_cross_compile(options.color)
1048
1049     if not options.cleanup_headers_only:
1050         move_config(configs, options)
1051
1052     if configs:
1053         cleanup_headers(configs, options.dry_run)
1054
1055 if __name__ == '__main__':
1056     main()