xref: /rk3399_rockchip-uboot/tools/moveconfig.py (revision 90ed6cba51446f0e623ce076d6e565fad8909b46)
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 get_make_cmd():
256    """Get the command name of GNU Make.
257
258    U-Boot needs GNU Make for building, but the command name is not
259    necessarily "make". (for example, "gmake" on FreeBSD).
260    Returns the most appropriate command name on your system.
261    """
262    process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
263    ret = process.communicate()
264    if process.returncode:
265        sys.exit('GNU Make not found')
266    return ret[0].rstrip()
267
268def color_text(color_enabled, color, string):
269    """Return colored string."""
270    if color_enabled:
271        return '\033[' + color + 'm' + string + '\033[0m'
272    else:
273        return string
274
275def log_msg(color_enabled, color, defconfig, msg):
276    """Return the formated line for the log."""
277    return defconfig[:-len('_defconfig')].ljust(37) + ': ' + \
278        color_text(color_enabled, color, msg) + '\n'
279
280def update_cross_compile(color_enabled):
281    """Update per-arch CROSS_COMPILE via environment variables
282
283    The default CROSS_COMPILE values are available
284    in the CROSS_COMPILE list above.
285
286    You can override them via environment variables
287    CROSS_COMPILE_{ARCH}.
288
289    For example, if you want to override toolchain prefixes
290    for ARM and PowerPC, you can do as follows in your shell:
291
292    export CROSS_COMPILE_ARM=...
293    export CROSS_COMPILE_POWERPC=...
294
295    Then, this function checks if specified compilers really exist in your
296    PATH environment.
297    """
298    archs = []
299
300    for arch in os.listdir('arch'):
301        if os.path.exists(os.path.join('arch', arch, 'Makefile')):
302            archs.append(arch)
303
304    # arm64 is a special case
305    archs.append('aarch64')
306
307    for arch in archs:
308        env = 'CROSS_COMPILE_' + arch.upper()
309        cross_compile = os.environ.get(env)
310        if not cross_compile:
311            cross_compile = CROSS_COMPILE.get(arch, '')
312
313        for path in os.environ["PATH"].split(os.pathsep):
314            gcc_path = os.path.join(path, cross_compile + 'gcc')
315            if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
316                break
317        else:
318            print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
319                 'warning: %sgcc: not found in PATH.  %s architecture boards will be skipped'
320                                            % (cross_compile, arch))
321            cross_compile = None
322
323        CROSS_COMPILE[arch] = cross_compile
324
325def cleanup_one_header(header_path, patterns, dry_run):
326    """Clean regex-matched lines away from a file.
327
328    Arguments:
329      header_path: path to the cleaned file.
330      patterns: list of regex patterns.  Any lines matching to these
331                patterns are deleted.
332      dry_run: make no changes, but still display log.
333    """
334    with open(header_path) as f:
335        lines = f.readlines()
336
337    matched = []
338    for i, line in enumerate(lines):
339        for pattern in patterns:
340            m = pattern.search(line)
341            if m:
342                print '%s: %s: %s' % (header_path, i + 1, line),
343                matched.append(i)
344                break
345
346    if dry_run or not matched:
347        return
348
349    with open(header_path, 'w') as f:
350        for i, line in enumerate(lines):
351            if not i in matched:
352                f.write(line)
353
354def cleanup_headers(config_attrs, dry_run):
355    """Delete config defines from board headers.
356
357    Arguments:
358      config_attrs: A list of dictionaris, each of them includes the name,
359                    the type, and the default value of the target config.
360      dry_run: make no changes, but still display log.
361    """
362    while True:
363        choice = raw_input('Clean up headers? [y/n]: ').lower()
364        print choice
365        if choice == 'y' or choice == 'n':
366            break
367
368    if choice == 'n':
369        return
370
371    patterns = []
372    for config_attr in config_attrs:
373        config = config_attr['config']
374        patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
375        patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
376
377    for dir in 'include', 'arch', 'board':
378        for (dirpath, dirnames, filenames) in os.walk(dir):
379            for filename in filenames:
380                if not fnmatch.fnmatch(filename, '*~'):
381                    cleanup_one_header(os.path.join(dirpath, filename),
382                                       patterns, dry_run)
383
384### classes ###
385class KconfigParser:
386
387    """A parser of .config and include/autoconf.mk."""
388
389    re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
390    re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
391
392    def __init__(self, config_attrs, options, build_dir):
393        """Create a new parser.
394
395        Arguments:
396          config_attrs: A list of dictionaris, each of them includes the name,
397                        the type, and the default value of the target config.
398          options: option flags.
399          build_dir: Build directory.
400        """
401        self.config_attrs = config_attrs
402        self.options = options
403        self.build_dir = build_dir
404
405    def get_cross_compile(self):
406        """Parse .config file and return CROSS_COMPILE.
407
408        Returns:
409          A string storing the compiler prefix for the architecture.
410          Return a NULL string for architectures that do not require
411          compiler prefix (Sandbox and native build is the case).
412          Return None if the specified compiler is missing in your PATH.
413          Caller should distinguish '' and None.
414        """
415        arch = ''
416        cpu = ''
417        dotconfig = os.path.join(self.build_dir, '.config')
418        for line in open(dotconfig):
419            m = self.re_arch.match(line)
420            if m:
421                arch = m.group(1)
422                continue
423            m = self.re_cpu.match(line)
424            if m:
425                cpu = m.group(1)
426
427        if not arch:
428            return None
429
430        # fix-up for aarch64
431        if arch == 'arm' and cpu == 'armv8':
432            arch = 'aarch64'
433
434        return CROSS_COMPILE.get(arch, None)
435
436    def parse_one_config(self, config_attr, defconfig_lines, autoconf_lines):
437        """Parse .config, defconfig, include/autoconf.mk for one config.
438
439        This function looks for the config options in the lines from
440        defconfig, .config, and include/autoconf.mk in order to decide
441        which action should be taken for this defconfig.
442
443        Arguments:
444          config_attr: A dictionary including the name, the type,
445                       and the default value of the target config.
446          defconfig_lines: lines from the original defconfig file.
447          autoconf_lines: lines from the include/autoconf.mk file.
448
449        Returns:
450          A tupple of the action for this defconfig and the line
451          matched for the config.
452        """
453        config = config_attr['config']
454        not_set = '# %s is not set' % config
455
456        if config_attr['type'] in ('bool', 'tristate') and \
457           config_attr['default'] == 'n':
458            default = not_set
459        else:
460            default = config + '=' + config_attr['default']
461
462        for line in defconfig_lines:
463            line = line.rstrip()
464            if line.startswith(config + '=') or line == not_set:
465                return (ACTION_ALREADY_EXIST, line)
466
467        if config_attr['type'] in ('bool', 'tristate'):
468            value = not_set
469        else:
470            value = '(undefined)'
471
472        for line in autoconf_lines:
473            line = line.rstrip()
474            if line.startswith(config + '='):
475                value = line
476                break
477
478        if value == default:
479            action = ACTION_DEFAULT_VALUE
480        elif value == '(undefined)':
481            action = ACTION_UNDEFINED
482        else:
483            action = ACTION_MOVE
484
485        return (action, value)
486
487    def update_dotconfig(self, defconfig):
488        """Parse files for the config options and update the .config.
489
490        This function parses the given defconfig, the generated .config
491        and include/autoconf.mk searching the target options.
492        Move the config option(s) to the .config as needed.
493        Also, display the log to show what happened to the .config.
494
495        Arguments:
496          defconfig: defconfig name.
497        """
498
499        defconfig_path = os.path.join('configs', defconfig)
500        dotconfig_path = os.path.join(self.build_dir, '.config')
501        autoconf_path = os.path.join(self.build_dir, 'include', 'autoconf.mk')
502        results = []
503
504        with open(defconfig_path) as f:
505            defconfig_lines = f.readlines()
506
507        with open(autoconf_path) as f:
508            autoconf_lines = f.readlines()
509
510        for config_attr in self.config_attrs:
511            result = self.parse_one_config(config_attr, defconfig_lines,
512                                           autoconf_lines)
513            results.append(result)
514
515        log = ''
516
517        for (action, value) in results:
518            if action == ACTION_MOVE:
519                actlog = "Move '%s'" % value
520                log_color = COLOR_LIGHT_GREEN
521            elif action == ACTION_DEFAULT_VALUE:
522                actlog = "Default value '%s'.  Do nothing." % value
523                log_color = COLOR_LIGHT_BLUE
524            elif action == ACTION_ALREADY_EXIST:
525                actlog = "'%s' already defined in Kconfig.  Do nothing." % value
526                log_color = COLOR_LIGHT_PURPLE
527            elif action == ACTION_UNDEFINED:
528                actlog = "Undefined.  Do nothing."
529                log_color = COLOR_DARK_GRAY
530            else:
531                sys.exit("Internal Error. This should not happen.")
532
533            log += log_msg(self.options.color, log_color, defconfig, actlog)
534
535        # Some threads are running in parallel.
536        # Print log in one shot to not mix up logs from different threads.
537        print log,
538
539        with open(dotconfig_path, 'a') as f:
540            for (action, value) in results:
541                if action == ACTION_MOVE:
542                    f.write(value + '\n')
543
544        os.remove(os.path.join(self.build_dir, 'include', 'config', 'auto.conf'))
545        os.remove(autoconf_path)
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, config_attrs, options, devnull, make_cmd):
557        """Create a new process slot.
558
559        Arguments:
560          config_attrs: A list of dictionaris, each of them includes the name,
561                        the type, and the default value of the target config.
562          options: option flags.
563          devnull: A file object of '/dev/null'.
564          make_cmd: command name of GNU Make.
565        """
566        self.options = options
567        self.build_dir = tempfile.mkdtemp()
568        self.devnull = devnull
569        self.make_cmd = (make_cmd, 'O=' + self.build_dir)
570        self.parser = KconfigParser(config_attrs, options, self.build_dir)
571        self.state = STATE_IDLE
572        self.failed_boards = []
573
574    def __del__(self):
575        """Delete the working directory
576
577        This function makes sure the temporary directory is cleaned away
578        even if Python suddenly dies due to error.  It should be done in here
579        because it is guranteed the destructor is always invoked when the
580        instance of the class gets unreferenced.
581
582        If the subprocess is still running, wait until it finishes.
583        """
584        if self.state != STATE_IDLE:
585            while self.ps.poll() == None:
586                pass
587        shutil.rmtree(self.build_dir)
588
589    def add(self, defconfig, num, total):
590        """Assign a new subprocess for defconfig and add it to the slot.
591
592        If the slot is vacant, create a new subprocess for processing the
593        given defconfig and add it to the slot.  Just returns False if
594        the slot is occupied (i.e. the current subprocess is still running).
595
596        Arguments:
597          defconfig: defconfig name.
598
599        Returns:
600          Return True on success or False on failure
601        """
602        if self.state != STATE_IDLE:
603            return False
604        cmd = list(self.make_cmd)
605        cmd.append(defconfig)
606        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
607                                   stderr=subprocess.PIPE)
608        self.defconfig = defconfig
609        self.state = STATE_DEFCONFIG
610        self.num = num
611        self.total = total
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, and then set the
622        slot back to the idle state.
623
624        Returns:
625          Return True if the subprocess is terminated, False otherwise
626        """
627        if self.state == STATE_IDLE:
628            return True
629
630        if self.ps.poll() == None:
631            return False
632
633        if self.ps.poll() != 0:
634            print >> sys.stderr, log_msg(self.options.color, COLOR_LIGHT_RED,
635                                         self.defconfig, "Failed to process."),
636            if self.options.verbose:
637                print >> sys.stderr, color_text(self.options.color,
638                                                COLOR_LIGHT_CYAN,
639                                                self.ps.stderr.read())
640            if self.options.exit_on_error:
641                sys.exit("Exit on error.")
642            # If --exit-on-error flag is not set, skip this board and continue.
643            # Record the failed board.
644            self.failed_boards.append(self.defconfig)
645            self.state = STATE_IDLE
646            return True
647
648        if self.state == STATE_AUTOCONF:
649            self.parser.update_dotconfig(self.defconfig)
650
651            print ' %d defconfigs out of %d\r' % (self.num + 1, self.total),
652            sys.stdout.flush()
653
654            """Save off the defconfig in a consistent way"""
655            cmd = list(self.make_cmd)
656            cmd.append('savedefconfig')
657            self.ps = subprocess.Popen(cmd, stdout=self.devnull,
658                                       stderr=subprocess.PIPE)
659            self.state = STATE_SAVEDEFCONFIG
660            return False
661
662        if self.state == STATE_SAVEDEFCONFIG:
663            if not self.options.dry_run:
664                shutil.move(os.path.join(self.build_dir, 'defconfig'),
665                            os.path.join('configs', self.defconfig))
666            self.state = STATE_IDLE
667            return True
668
669        self.cross_compile = self.parser.get_cross_compile()
670        if self.cross_compile is None:
671            print >> sys.stderr, log_msg(self.options.color, COLOR_YELLOW,
672                                         self.defconfig,
673                                         "Compiler is missing.  Do nothing."),
674            if self.options.exit_on_error:
675                sys.exit("Exit on error.")
676            # If --exit-on-error flag is not set, skip this board and continue.
677            # Record the failed board.
678            self.failed_boards.append(self.defconfig)
679            self.state = STATE_IDLE
680            return True
681
682        cmd = list(self.make_cmd)
683        if self.cross_compile:
684            cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
685        cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
686        cmd.append('include/config/auto.conf')
687        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
688                                   stderr=subprocess.PIPE)
689        self.state = STATE_AUTOCONF
690        return False
691
692    def get_failed_boards(self):
693        """Returns a list of failed boards (defconfigs) in this slot.
694        """
695        return self.failed_boards
696
697class Slots:
698
699    """Controller of the array of subprocess slots."""
700
701    def __init__(self, config_attrs, options):
702        """Create a new slots controller.
703
704        Arguments:
705          config_attrs: A list of dictionaris containing the name, the type,
706                        and the default value of the target CONFIG.
707          options: option flags.
708        """
709        self.options = options
710        self.slots = []
711        devnull = get_devnull()
712        make_cmd = get_make_cmd()
713        for i in range(options.jobs):
714            self.slots.append(Slot(config_attrs, options, devnull, make_cmd))
715
716    def add(self, defconfig, num, total):
717        """Add a new subprocess if a vacant slot is found.
718
719        Arguments:
720          defconfig: defconfig name to be put into.
721
722        Returns:
723          Return True on success or False on failure
724        """
725        for slot in self.slots:
726            if slot.add(defconfig, num, total):
727                return True
728        return False
729
730    def available(self):
731        """Check if there is a vacant slot.
732
733        Returns:
734          Return True if at lease one vacant slot is found, False otherwise.
735        """
736        for slot in self.slots:
737            if slot.poll():
738                return True
739        return False
740
741    def empty(self):
742        """Check if all slots are vacant.
743
744        Returns:
745          Return True if all the slots are vacant, False otherwise.
746        """
747        ret = True
748        for slot in self.slots:
749            if not slot.poll():
750                ret = False
751        return ret
752
753    def show_failed_boards(self):
754        """Display all of the failed boards (defconfigs)."""
755        failed_boards = []
756
757        for slot in self.slots:
758            failed_boards += slot.get_failed_boards()
759
760        if len(failed_boards) > 0:
761            msg = [ "The following boards were not processed due to error:" ]
762            msg += failed_boards
763            for line in msg:
764                print >> sys.stderr, color_text(self.options.color,
765                                                COLOR_LIGHT_RED, line)
766
767            with open('moveconfig.failed', 'w') as f:
768                for board in failed_boards:
769                    f.write(board + '\n')
770
771def move_config(config_attrs, options):
772    """Move config options to defconfig files.
773
774    Arguments:
775      config_attrs: A list of dictionaris, each of them includes the name,
776                    the type, and the default value of the target config.
777      options: option flags
778    """
779    if len(config_attrs) == 0:
780        print 'Nothing to do. exit.'
781        sys.exit(0)
782
783    print 'Move the following CONFIG options (jobs: %d)' % options.jobs
784    for config_attr in config_attrs:
785        print '  %s (type: %s, default: %s)' % (config_attr['config'],
786                                                config_attr['type'],
787                                                config_attr['default'])
788
789    if options.defconfigs:
790        defconfigs = [line.strip() for line in open(options.defconfigs)]
791        for i, defconfig in enumerate(defconfigs):
792            if not defconfig.endswith('_defconfig'):
793                defconfigs[i] = defconfig + '_defconfig'
794            if not os.path.exists(os.path.join('configs', defconfigs[i])):
795                sys.exit('%s - defconfig does not exist. Stopping.' %
796                         defconfigs[i])
797    else:
798        # All the defconfig files to be processed
799        defconfigs = []
800        for (dirpath, dirnames, filenames) in os.walk('configs'):
801            dirpath = dirpath[len('configs') + 1:]
802            for filename in fnmatch.filter(filenames, '*_defconfig'):
803                defconfigs.append(os.path.join(dirpath, filename))
804
805    slots = Slots(config_attrs, options)
806
807    # Main loop to process defconfig files:
808    #  Add a new subprocess into a vacant slot.
809    #  Sleep if there is no available slot.
810    for i, defconfig in enumerate(defconfigs):
811        while not slots.add(defconfig, i, len(defconfigs)):
812            while not slots.available():
813                # No available slot: sleep for a while
814                time.sleep(SLEEP_TIME)
815
816    # wait until all the subprocesses finish
817    while not slots.empty():
818        time.sleep(SLEEP_TIME)
819
820    print ''
821    slots.show_failed_boards()
822
823def bad_recipe(filename, linenum, msg):
824    """Print error message with the file name and the line number and exit."""
825    sys.exit("%s: line %d: error : " % (filename, linenum) + msg)
826
827def parse_recipe(filename):
828    """Parse the recipe file and retrieve the config attributes.
829
830    This function parses the given recipe file and gets the name,
831    the type, and the default value of the target config options.
832
833    Arguments:
834      filename: path to file to be parsed.
835    Returns:
836      A list of dictionaris, each of them includes the name,
837      the type, and the default value of the target config.
838    """
839    config_attrs = []
840    linenum = 1
841
842    for line in open(filename):
843        tokens = line.split()
844        if len(tokens) != 3:
845            bad_recipe(filename, linenum,
846                       "%d fields in this line.  Each line must contain 3 fields"
847                       % len(tokens))
848
849        (config, type, default) = tokens
850
851        # prefix the option name with CONFIG_ if missing
852        if not config.startswith('CONFIG_'):
853            config = 'CONFIG_' + config
854
855        # sanity check of default values
856        if type == 'bool':
857            if not default in ('y', 'n'):
858                bad_recipe(filename, linenum,
859                           "default for bool type must be either y or n")
860        elif type == 'tristate':
861            if not default in ('y', 'm', 'n'):
862                bad_recipe(filename, linenum,
863                           "default for tristate type must be y, m, or n")
864        elif type == 'string':
865            if default[0] != '"' or default[-1] != '"':
866                bad_recipe(filename, linenum,
867                           "default for string type must be surrounded by double-quotations")
868        elif type == 'int':
869            try:
870                int(default)
871            except:
872                bad_recipe(filename, linenum,
873                           "type is int, but default value is not decimal")
874        elif type == 'hex':
875            if len(default) < 2 or default[:2] != '0x':
876                bad_recipe(filename, linenum,
877                           "default for hex type must be prefixed with 0x")
878            try:
879                int(default, 16)
880            except:
881                bad_recipe(filename, linenum,
882                           "type is hex, but default value is not hexadecimal")
883        else:
884            bad_recipe(filename, linenum,
885                       "unsupported type '%s'. type must be one of bool, tristate, string, int, hex"
886                       % type)
887
888        config_attrs.append({'config': config, 'type': type, 'default': default})
889        linenum += 1
890
891    return config_attrs
892
893def main():
894    try:
895        cpu_count = multiprocessing.cpu_count()
896    except NotImplementedError:
897        cpu_count = 1
898
899    parser = optparse.OptionParser()
900    # Add options here
901    parser.add_option('-c', '--color', action='store_true', default=False,
902                      help='display the log in color')
903    parser.add_option('-d', '--defconfigs', type='string',
904                      help='a file containing a list of defconfigs to move')
905    parser.add_option('-n', '--dry-run', action='store_true', default=False,
906                      help='perform a trial run (show log with no changes)')
907    parser.add_option('-e', '--exit-on-error', action='store_true',
908                      default=False,
909                      help='exit immediately on any error')
910    parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
911                      action='store_true', default=False,
912                      help='only cleanup the headers')
913    parser.add_option('-j', '--jobs', type='int', default=cpu_count,
914                      help='the number of jobs to run simultaneously')
915    parser.add_option('-v', '--verbose', action='store_true', default=False,
916                      help='show any build errors as boards are built')
917    parser.usage += ' recipe_file\n\n' + \
918                    'The recipe_file should describe config options you want to move.\n' + \
919                    'Each line should contain config_name, type, default_value\n\n' + \
920                    'Example:\n' + \
921                    'CONFIG_FOO bool n\n' + \
922                    'CONFIG_BAR int 100\n' + \
923                    'CONFIG_BAZ string "hello"\n'
924
925    (options, args) = parser.parse_args()
926
927    if len(args) != 1:
928        parser.print_usage()
929        sys.exit(1)
930
931    config_attrs = parse_recipe(args[0])
932
933    check_top_directory()
934
935    update_cross_compile(options.color)
936
937    if not options.cleanup_headers_only:
938        move_config(config_attrs, options)
939
940    cleanup_headers(config_attrs, options.dry_run)
941
942if __name__ == '__main__':
943    main()
944