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