xref: /rk3399_rockchip-uboot/tools/moveconfig.py (revision 5da4f857beac1cf859aefac3cf2ae980115ec1ae)
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        self.defconfig = os.path.join(build_dir, 'defconfig')
411
412    def get_cross_compile(self):
413        """Parse .config file and return CROSS_COMPILE.
414
415        Returns:
416          A string storing the compiler prefix for the architecture.
417          Return a NULL string for architectures that do not require
418          compiler prefix (Sandbox and native build is the case).
419          Return None if the specified compiler is missing in your PATH.
420          Caller should distinguish '' and None.
421        """
422        arch = ''
423        cpu = ''
424        for line in open(self.dotconfig):
425            m = self.re_arch.match(line)
426            if m:
427                arch = m.group(1)
428                continue
429            m = self.re_cpu.match(line)
430            if m:
431                cpu = m.group(1)
432
433        if not arch:
434            return None
435
436        # fix-up for aarch64
437        if arch == 'arm' and cpu == 'armv8':
438            arch = 'aarch64'
439
440        return CROSS_COMPILE.get(arch, None)
441
442    def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
443        """Parse .config, defconfig, include/autoconf.mk for one config.
444
445        This function looks for the config options in the lines from
446        defconfig, .config, and include/autoconf.mk in order to decide
447        which action should be taken for this defconfig.
448
449        Arguments:
450          config: CONFIG name to parse.
451          dotconfig_lines: lines from the .config file.
452          autoconf_lines: lines from the include/autoconf.mk file.
453
454        Returns:
455          A tupple of the action for this defconfig and the line
456          matched for the config.
457        """
458        not_set = '# %s is not set' % config
459
460        for line in dotconfig_lines:
461            line = line.rstrip()
462            if line.startswith(config + '=') or line == not_set:
463                old_val = line
464                break
465        else:
466            return (ACTION_NO_ENTRY, config)
467
468        for line in autoconf_lines:
469            line = line.rstrip()
470            if line.startswith(config + '='):
471                new_val = line
472                break
473        else:
474            new_val = not_set
475
476        if old_val == new_val:
477            return (ACTION_NO_CHANGE, new_val)
478
479        # If this CONFIG is neither bool nor trisate
480        if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
481            # tools/scripts/define2mk.sed changes '1' to 'y'.
482            # This is a problem if the CONFIG is int type.
483            # Check the type in Kconfig and handle it correctly.
484            if new_val[-2:] == '=y':
485                new_val = new_val[:-1] + '1'
486
487        return (ACTION_MOVE, new_val)
488
489    def update_dotconfig(self):
490        """Parse files for the config options and update the .config.
491
492        This function parses the generated .config and include/autoconf.mk
493        searching the target options.
494        Move the config option(s) to the .config as needed.
495
496        Arguments:
497          defconfig: defconfig name.
498
499        Returns:
500          Return a tuple of (updated flag, log string).
501          The "updated flag" is True if the .config was updated, False
502          otherwise.  The "log string" shows what happend to the .config.
503        """
504
505        results = []
506        updated = False
507
508        with open(self.dotconfig) as f:
509            dotconfig_lines = f.readlines()
510
511        with open(self.autoconf) as f:
512            autoconf_lines = f.readlines()
513
514        for config in self.configs:
515            result = self.parse_one_config(config, dotconfig_lines,
516                                           autoconf_lines)
517            results.append(result)
518
519        log = ''
520
521        for (action, value) in results:
522            if action == ACTION_MOVE:
523                actlog = "Move '%s'" % value
524                log_color = COLOR_LIGHT_GREEN
525            elif action == ACTION_NO_ENTRY:
526                actlog = "%s is not defined in Kconfig.  Do nothing." % value
527                log_color = COLOR_LIGHT_BLUE
528            elif action == ACTION_NO_CHANGE:
529                actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
530                         % value
531                log_color = COLOR_LIGHT_PURPLE
532            else:
533                sys.exit("Internal Error. This should not happen.")
534
535            log += color_text(self.options.color, log_color, actlog) + '\n'
536
537        with open(self.dotconfig, 'a') as f:
538            for (action, value) in results:
539                if action == ACTION_MOVE:
540                    f.write(value + '\n')
541                    updated = True
542
543        self.results = results
544        os.remove(self.config_autoconf)
545        os.remove(self.autoconf)
546
547        return (updated, log)
548
549    def check_defconfig(self):
550        """Check the defconfig after savedefconfig
551
552        Returns:
553          Return additional log if moved CONFIGs were removed again by
554          'make savedefconfig'.
555        """
556
557        log = ''
558
559        with open(self.defconfig) as f:
560            defconfig_lines = f.readlines()
561
562        for (action, value) in self.results:
563            if action != ACTION_MOVE:
564                continue
565            if not value + '\n' in defconfig_lines:
566                log += color_text(self.options.color, COLOR_YELLOW,
567                                  "'%s' was removed by savedefconfig.\n" %
568                                  value)
569
570        return log
571
572class Slot:
573
574    """A slot to store a subprocess.
575
576    Each instance of this class handles one subprocess.
577    This class is useful to control multiple threads
578    for faster processing.
579    """
580
581    def __init__(self, configs, options, progress, devnull, make_cmd):
582        """Create a new process slot.
583
584        Arguments:
585          configs: A list of CONFIGs to move.
586          options: option flags.
587          progress: A progress indicator.
588          devnull: A file object of '/dev/null'.
589          make_cmd: command name of GNU Make.
590        """
591        self.options = options
592        self.progress = progress
593        self.build_dir = tempfile.mkdtemp()
594        self.devnull = devnull
595        self.make_cmd = (make_cmd, 'O=' + self.build_dir)
596        self.parser = KconfigParser(configs, options, self.build_dir)
597        self.state = STATE_IDLE
598        self.failed_boards = []
599
600    def __del__(self):
601        """Delete the working directory
602
603        This function makes sure the temporary directory is cleaned away
604        even if Python suddenly dies due to error.  It should be done in here
605        because it is guranteed the destructor is always invoked when the
606        instance of the class gets unreferenced.
607
608        If the subprocess is still running, wait until it finishes.
609        """
610        if self.state != STATE_IDLE:
611            while self.ps.poll() == None:
612                pass
613        shutil.rmtree(self.build_dir)
614
615    def add(self, defconfig):
616        """Assign a new subprocess for defconfig and add it to the slot.
617
618        If the slot is vacant, create a new subprocess for processing the
619        given defconfig and add it to the slot.  Just returns False if
620        the slot is occupied (i.e. the current subprocess is still running).
621
622        Arguments:
623          defconfig: defconfig name.
624
625        Returns:
626          Return True on success or False on failure
627        """
628        if self.state != STATE_IDLE:
629            return False
630        cmd = list(self.make_cmd)
631        cmd.append(defconfig)
632        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
633                                   stderr=subprocess.PIPE)
634        self.defconfig = defconfig
635        self.state = STATE_DEFCONFIG
636        self.log = ''
637        return True
638
639    def poll(self):
640        """Check the status of the subprocess and handle it as needed.
641
642        Returns True if the slot is vacant (i.e. in idle state).
643        If the configuration is successfully finished, assign a new
644        subprocess to build include/autoconf.mk.
645        If include/autoconf.mk is generated, invoke the parser to
646        parse the .config and the include/autoconf.mk, moving
647        config options to the .config as needed.
648        If the .config was updated, run "make savedefconfig" to sync
649        it, update the original defconfig, and then set the slot back
650        to the idle state.
651
652        Returns:
653          Return True if the subprocess is terminated, False otherwise
654        """
655        if self.state == STATE_IDLE:
656            return True
657
658        if self.ps.poll() == None:
659            return False
660
661        if self.ps.poll() != 0:
662            self.log += color_text(self.options.color, COLOR_LIGHT_RED,
663                                   "Failed to process.\n")
664            if self.options.verbose:
665                self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
666                                       self.ps.stderr.read())
667            self.finish(False)
668            return True
669
670        if self.state == STATE_AUTOCONF:
671            (updated, log) = self.parser.update_dotconfig()
672            self.log += log
673
674            if not updated:
675                self.finish(True)
676                return True
677            self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
678                                   "Syncing by savedefconfig...\n")
679            cmd = list(self.make_cmd)
680            cmd.append('savedefconfig')
681            self.ps = subprocess.Popen(cmd, stdout=self.devnull,
682                                       stderr=subprocess.PIPE)
683            self.state = STATE_SAVEDEFCONFIG
684            return False
685
686        if self.state == STATE_SAVEDEFCONFIG:
687            self.log += self.parser.check_defconfig()
688            if not self.options.dry_run:
689                shutil.move(os.path.join(self.build_dir, 'defconfig'),
690                            os.path.join('configs', self.defconfig))
691            self.finish(True)
692            return True
693
694        self.cross_compile = self.parser.get_cross_compile()
695        if self.cross_compile is None:
696            self.log += color_text(self.options.color, COLOR_YELLOW,
697                                   "Compiler is missing.  Do nothing.\n")
698            self.finish(False)
699            return True
700
701        cmd = list(self.make_cmd)
702        if self.cross_compile:
703            cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
704        cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
705        cmd.append('include/config/auto.conf')
706        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
707                                   stderr=subprocess.PIPE)
708        self.state = STATE_AUTOCONF
709        return False
710
711    def finish(self, success):
712        """Display log along with progress and go to the idle state.
713
714        Arguments:
715          success: Should be True when the defconfig was processed
716                   successfully, or False when it fails.
717        """
718        # output at least 30 characters to hide the "* defconfigs out of *".
719        log = self.defconfig.ljust(30) + '\n'
720
721        log += '\n'.join([ '    ' + s for s in self.log.split('\n') ])
722        # Some threads are running in parallel.
723        # Print log atomically to not mix up logs from different threads.
724        print >> (sys.stdout if success else sys.stderr), log
725
726        if not success:
727            if self.options.exit_on_error:
728                sys.exit("Exit on error.")
729            # If --exit-on-error flag is not set, skip this board and continue.
730            # Record the failed board.
731            self.failed_boards.append(self.defconfig)
732
733        self.progress.inc()
734        self.progress.show()
735        self.state = STATE_IDLE
736
737    def get_failed_boards(self):
738        """Returns a list of failed boards (defconfigs) in this slot.
739        """
740        return self.failed_boards
741
742class Slots:
743
744    """Controller of the array of subprocess slots."""
745
746    def __init__(self, configs, options, progress):
747        """Create a new slots controller.
748
749        Arguments:
750          configs: A list of CONFIGs to move.
751          options: option flags.
752          progress: A progress indicator.
753        """
754        self.options = options
755        self.slots = []
756        devnull = get_devnull()
757        make_cmd = get_make_cmd()
758        for i in range(options.jobs):
759            self.slots.append(Slot(configs, options, progress, devnull,
760                                   make_cmd))
761
762    def add(self, defconfig):
763        """Add a new subprocess if a vacant slot is found.
764
765        Arguments:
766          defconfig: defconfig name to be put into.
767
768        Returns:
769          Return True on success or False on failure
770        """
771        for slot in self.slots:
772            if slot.add(defconfig):
773                return True
774        return False
775
776    def available(self):
777        """Check if there is a vacant slot.
778
779        Returns:
780          Return True if at lease one vacant slot is found, False otherwise.
781        """
782        for slot in self.slots:
783            if slot.poll():
784                return True
785        return False
786
787    def empty(self):
788        """Check if all slots are vacant.
789
790        Returns:
791          Return True if all the slots are vacant, False otherwise.
792        """
793        ret = True
794        for slot in self.slots:
795            if not slot.poll():
796                ret = False
797        return ret
798
799    def show_failed_boards(self):
800        """Display all of the failed boards (defconfigs)."""
801        failed_boards = []
802
803        for slot in self.slots:
804            failed_boards += slot.get_failed_boards()
805
806        if len(failed_boards) > 0:
807            msg = [ "The following boards were not processed due to error:" ]
808            msg += failed_boards
809            for line in msg:
810                print >> sys.stderr, color_text(self.options.color,
811                                                COLOR_LIGHT_RED, line)
812
813            with open('moveconfig.failed', 'w') as f:
814                for board in failed_boards:
815                    f.write(board + '\n')
816
817def move_config(configs, options):
818    """Move config options to defconfig files.
819
820    Arguments:
821      configs: A list of CONFIGs to move.
822      options: option flags
823    """
824    if len(configs) == 0:
825        print 'Nothing to do. exit.'
826        sys.exit(0)
827
828    print 'Move %s (jobs: %d)' % (', '.join(configs), options.jobs)
829
830    if options.defconfigs:
831        defconfigs = [line.strip() for line in open(options.defconfigs)]
832        for i, defconfig in enumerate(defconfigs):
833            if not defconfig.endswith('_defconfig'):
834                defconfigs[i] = defconfig + '_defconfig'
835            if not os.path.exists(os.path.join('configs', defconfigs[i])):
836                sys.exit('%s - defconfig does not exist. Stopping.' %
837                         defconfigs[i])
838    else:
839        # All the defconfig files to be processed
840        defconfigs = []
841        for (dirpath, dirnames, filenames) in os.walk('configs'):
842            dirpath = dirpath[len('configs') + 1:]
843            for filename in fnmatch.filter(filenames, '*_defconfig'):
844                defconfigs.append(os.path.join(dirpath, filename))
845
846    progress = Progress(len(defconfigs))
847    slots = Slots(configs, options, progress)
848
849    # Main loop to process defconfig files:
850    #  Add a new subprocess into a vacant slot.
851    #  Sleep if there is no available slot.
852    for defconfig in defconfigs:
853        while not slots.add(defconfig):
854            while not slots.available():
855                # No available slot: sleep for a while
856                time.sleep(SLEEP_TIME)
857
858    # wait until all the subprocesses finish
859    while not slots.empty():
860        time.sleep(SLEEP_TIME)
861
862    print ''
863    slots.show_failed_boards()
864
865def main():
866    try:
867        cpu_count = multiprocessing.cpu_count()
868    except NotImplementedError:
869        cpu_count = 1
870
871    parser = optparse.OptionParser()
872    # Add options here
873    parser.add_option('-c', '--color', action='store_true', default=False,
874                      help='display the log in color')
875    parser.add_option('-d', '--defconfigs', type='string',
876                      help='a file containing a list of defconfigs to move')
877    parser.add_option('-n', '--dry-run', action='store_true', default=False,
878                      help='perform a trial run (show log with no changes)')
879    parser.add_option('-e', '--exit-on-error', action='store_true',
880                      default=False,
881                      help='exit immediately on any error')
882    parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
883                      action='store_true', default=False,
884                      help='only cleanup the headers')
885    parser.add_option('-j', '--jobs', type='int', default=cpu_count,
886                      help='the number of jobs to run simultaneously')
887    parser.add_option('-v', '--verbose', action='store_true', default=False,
888                      help='show any build errors as boards are built')
889    parser.usage += ' CONFIG ...'
890
891    (options, configs) = parser.parse_args()
892
893    if len(configs) == 0:
894        parser.print_usage()
895        sys.exit(1)
896
897    # prefix the option name with CONFIG_ if missing
898    configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
899                for config in configs ]
900
901    check_top_directory()
902
903    check_clean_directory()
904
905    update_cross_compile(options.color)
906
907    if not options.cleanup_headers_only:
908        move_config(configs, options)
909
910    cleanup_headers(configs, options.dry_run)
911
912if __name__ == '__main__':
913    main()
914