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