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