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