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