xref: /rk3399_rockchip-uboot/tools/moveconfig.py (revision d73fcb12e27277d5b3e80399b4b3ec41abcd80d2)
1#!/usr/bin/env python2
2#
3# Author: Masahiro Yamada <yamada.masahiro@socionext.com>
4#
5# SPDX-License-Identifier:	GPL-2.0+
6#
7
8"""
9Move config options from headers to defconfig files.
10
11Since Kconfig was introduced to U-Boot, we have worked on moving
12config options from headers to Kconfig (defconfig).
13
14This tool intends to help this tremendous work.
15
16
17Usage
18-----
19
20First, you must edit the Kconfig to add the menu entries for the configs
21you are moving.
22
23And then run this tool giving CONFIG names you want to move.
24For example, if you want to move CONFIG_CMD_USB and CONFIG_SYS_TEXT_BASE,
25simply type as follows:
26
27  $ tools/moveconfig.py CONFIG_CMD_USB CONFIG_SYS_TEXT_BASE
28
29The tool walks through all the defconfig files and move the given CONFIGs.
30
31The log is also displayed on the terminal.
32
33The log is printed for each defconfig as follows:
34
35<defconfig_name>
36    <action1>
37    <action2>
38    <action3>
39    ...
40
41<defconfig_name> is the name of the defconfig.
42
43<action*> shows what the tool did for that defconfig.
44It looks like one of the following:
45
46 - Move 'CONFIG_... '
47   This config option was moved to the defconfig
48
49 - CONFIG_... is not defined in Kconfig.  Do nothing.
50   The entry for this CONFIG was not found in Kconfig.  The option is not
51   defined in the config header, either.  So, this case can be just skipped.
52
53 - CONFIG_... is not defined in Kconfig (suspicious).  Do nothing.
54   This option is defined in the config header, but its entry was not found
55   in Kconfig.
56   There are two common cases:
57     - You forgot to create an entry for the CONFIG before running
58       this tool, or made a typo in a CONFIG passed to this tool.
59     - The entry was hidden due to unmet 'depends on'.
60   The tool does not know if the result is reasonable, so please check it
61   manually.
62
63 - 'CONFIG_...' is the same as the define in Kconfig.  Do nothing.
64   The define in the config header matched the one in Kconfig.
65   We do not need to touch it.
66
67 - Compiler is missing.  Do nothing.
68   The compiler specified for this architecture was not found
69   in your PATH environment.
70   (If -e option is passed, the tool exits immediately.)
71
72 - Failed to process.
73   An error occurred during processing this defconfig.  Skipped.
74   (If -e option is passed, the tool exits immediately on error.)
75
76Finally, you will be asked, Clean up headers? [y/n]:
77
78If you say 'y' here, the unnecessary config defines are removed
79from the config headers (include/configs/*.h).
80It just uses the regex method, so you should not rely on it.
81Just in case, please do 'git diff' to see what happened.
82
83
84How does it work?
85-----------------
86
87This tool runs configuration and builds include/autoconf.mk for every
88defconfig.  The config options defined in Kconfig appear in the .config
89file (unless they are hidden because of unmet dependency.)
90On the other hand, the config options defined by board headers are seen
91in include/autoconf.mk.  The tool looks for the specified options in both
92of them to decide the appropriate action for the options.  If the given
93config option is found in the .config, but its value does not match the
94one from the board header, the config option in the .config is replaced
95with the define in the board header.  Then, the .config is synced by
96"make savedefconfig" and the defconfig is updated with it.
97
98For faster processing, this tool handles multi-threading.  It creates
99separate build directories where the out-of-tree build is run.  The
100temporary build directories are automatically created and deleted as
101needed.  The number of threads are chosen based on the number of the CPU
102cores of your system although you can change it via -j (--jobs) option.
103
104
105Toolchains
106----------
107
108Appropriate toolchain are necessary to generate include/autoconf.mk
109for all the architectures supported by U-Boot.  Most of them are available
110at the kernel.org site, some are not provided by kernel.org.
111
112The default per-arch CROSS_COMPILE used by this tool is specified by
113the list below, CROSS_COMPILE.  You may wish to update the list to
114use your own.  Instead of modifying the list directly, you can give
115them via environments.
116
117
118Tips and trips
119--------------
120
121To sync only X86 defconfigs:
122
123   ./tools/moveconfig.py -s -d <(grep -l X86 configs/*)
124
125or:
126
127   grep -l X86 configs/* | ./tools/moveconfig.py -s -d -
128
129To process CONFIG_CMD_FPGAD only for a subset of configs based on path match:
130
131   ls configs/{hrcon*,iocon*,strider*} | \
132       ./tools/moveconfig.py -Cy CONFIG_CMD_FPGAD -d -
133
134
135Available options
136-----------------
137
138 -c, --color
139   Surround each portion of the log with escape sequences to display it
140   in color on the terminal.
141
142 -C, --commit
143   Create a git commit with the changes when the operation is complete. A
144   standard commit message is used which may need to be edited.
145
146 -d, --defconfigs
147  Specify a file containing a list of defconfigs to move.  The defconfig
148  files can be given with shell-style wildcards. Use '-' to read from stdin.
149
150 -n, --dry-run
151   Perform a trial run that does not make any changes.  It is useful to
152   see what is going to happen before one actually runs it.
153
154 -e, --exit-on-error
155   Exit immediately if Make exits with a non-zero status while processing
156   a defconfig file.
157
158 -s, --force-sync
159   Do "make savedefconfig" forcibly for all the defconfig files.
160   If not specified, "make savedefconfig" only occurs for cases
161   where at least one CONFIG was moved.
162
163 -S, --spl
164   Look for moved config options in spl/include/autoconf.mk instead of
165   include/autoconf.mk.  This is useful for moving options for SPL build
166   because SPL related options (mostly prefixed with CONFIG_SPL_) are
167   sometimes blocked by CONFIG_SPL_BUILD ifdef conditionals.
168
169 -H, --headers-only
170   Only cleanup the headers; skip the defconfig processing
171
172 -j, --jobs
173   Specify the number of threads to run simultaneously.  If not specified,
174   the number of threads is the same as the number of CPU cores.
175
176 -r, --git-ref
177   Specify the git ref to clone for building the autoconf.mk. If unspecified
178   use the CWD. This is useful for when changes to the Kconfig affect the
179   default values and you want to capture the state of the defconfig from
180   before that change was in effect. If in doubt, specify a ref pre-Kconfig
181   changes (use HEAD if Kconfig changes are not committed). Worst case it will
182   take a bit longer to run, but will always do the right thing.
183
184 -v, --verbose
185   Show any build errors as boards are built
186
187 -y, --yes
188   Instead of prompting, automatically go ahead with all operations. This
189   includes cleaning up headers, CONFIG_SYS_EXTRA_OPTIONS, the config whitelist
190   and the README.
191
192To see the complete list of supported options, run
193
194  $ tools/moveconfig.py -h
195
196"""
197
198import copy
199import difflib
200import filecmp
201import fnmatch
202import glob
203import multiprocessing
204import optparse
205import os
206import Queue
207import re
208import shutil
209import subprocess
210import sys
211import tempfile
212import threading
213import time
214
215SHOW_GNU_MAKE = 'scripts/show-gnu-make'
216SLEEP_TIME=0.03
217
218# Here is the list of cross-tools I use.
219# Most of them are available at kernel.org
220# (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the following:
221# arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases
222# nds32: http://osdk.andestech.com/packages/nds32le-linux-glibc-v1.tgz
223# nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545
224# sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu
225CROSS_COMPILE = {
226    'arc': 'arc-linux-',
227    'aarch64': 'aarch64-linux-',
228    'arm': 'arm-unknown-linux-gnueabi-',
229    'm68k': 'm68k-linux-',
230    'microblaze': 'microblaze-linux-',
231    'mips': 'mips-linux-',
232    'nds32': 'nds32le-linux-',
233    'nios2': 'nios2-linux-gnu-',
234    'powerpc': 'powerpc-linux-',
235    'sh': 'sh-linux-gnu-',
236    'x86': 'i386-linux-',
237    'xtensa': 'xtensa-linux-'
238}
239
240STATE_IDLE = 0
241STATE_DEFCONFIG = 1
242STATE_AUTOCONF = 2
243STATE_SAVEDEFCONFIG = 3
244
245ACTION_MOVE = 0
246ACTION_NO_ENTRY = 1
247ACTION_NO_ENTRY_WARN = 2
248ACTION_NO_CHANGE = 3
249
250COLOR_BLACK        = '0;30'
251COLOR_RED          = '0;31'
252COLOR_GREEN        = '0;32'
253COLOR_BROWN        = '0;33'
254COLOR_BLUE         = '0;34'
255COLOR_PURPLE       = '0;35'
256COLOR_CYAN         = '0;36'
257COLOR_LIGHT_GRAY   = '0;37'
258COLOR_DARK_GRAY    = '1;30'
259COLOR_LIGHT_RED    = '1;31'
260COLOR_LIGHT_GREEN  = '1;32'
261COLOR_YELLOW       = '1;33'
262COLOR_LIGHT_BLUE   = '1;34'
263COLOR_LIGHT_PURPLE = '1;35'
264COLOR_LIGHT_CYAN   = '1;36'
265COLOR_WHITE        = '1;37'
266
267AUTO_CONF_PATH = 'include/config/auto.conf'
268CONFIG_DATABASE = 'moveconfig.db'
269
270
271### helper functions ###
272def get_devnull():
273    """Get the file object of '/dev/null' device."""
274    try:
275        devnull = subprocess.DEVNULL # py3k
276    except AttributeError:
277        devnull = open(os.devnull, 'wb')
278    return devnull
279
280def check_top_directory():
281    """Exit if we are not at the top of source directory."""
282    for f in ('README', 'Licenses'):
283        if not os.path.exists(f):
284            sys.exit('Please run at the top of source directory.')
285
286def check_clean_directory():
287    """Exit if the source tree is not clean."""
288    for f in ('.config', 'include/config'):
289        if os.path.exists(f):
290            sys.exit("source tree is not clean, please run 'make mrproper'")
291
292def get_make_cmd():
293    """Get the command name of GNU Make.
294
295    U-Boot needs GNU Make for building, but the command name is not
296    necessarily "make". (for example, "gmake" on FreeBSD).
297    Returns the most appropriate command name on your system.
298    """
299    process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
300    ret = process.communicate()
301    if process.returncode:
302        sys.exit('GNU Make not found')
303    return ret[0].rstrip()
304
305def get_matched_defconfig(line):
306    """Get the defconfig files that match a pattern
307
308    Args:
309        line: Path or filename to match, e.g. 'configs/snow_defconfig' or
310            'k2*_defconfig'. If no directory is provided, 'configs/' is
311            prepended
312
313    Returns:
314        a list of matching defconfig files
315    """
316    dirname = os.path.dirname(line)
317    if dirname:
318        pattern = line
319    else:
320        pattern = os.path.join('configs', line)
321    return glob.glob(pattern) + glob.glob(pattern + '_defconfig')
322
323def get_matched_defconfigs(defconfigs_file):
324    """Get all the defconfig files that match the patterns in a file.
325
326    Args:
327        defconfigs_file: File containing a list of defconfigs to process, or
328            '-' to read the list from stdin
329
330    Returns:
331        A list of paths to defconfig files, with no duplicates
332    """
333    defconfigs = []
334    if defconfigs_file == '-':
335        fd = sys.stdin
336        defconfigs_file = 'stdin'
337    else:
338        fd = open(defconfigs_file)
339    for i, line in enumerate(fd):
340        line = line.strip()
341        if not line:
342            continue # skip blank lines silently
343        matched = get_matched_defconfig(line)
344        if not matched:
345            print >> sys.stderr, "warning: %s:%d: no defconfig matched '%s'" % \
346                                                 (defconfigs_file, i + 1, line)
347
348        defconfigs += matched
349
350    # use set() to drop multiple matching
351    return [ defconfig[len('configs') + 1:]  for defconfig in set(defconfigs) ]
352
353def get_all_defconfigs():
354    """Get all the defconfig files under the configs/ directory."""
355    defconfigs = []
356    for (dirpath, dirnames, filenames) in os.walk('configs'):
357        dirpath = dirpath[len('configs') + 1:]
358        for filename in fnmatch.filter(filenames, '*_defconfig'):
359            defconfigs.append(os.path.join(dirpath, filename))
360
361    return defconfigs
362
363def color_text(color_enabled, color, string):
364    """Return colored string."""
365    if color_enabled:
366        # LF should not be surrounded by the escape sequence.
367        # Otherwise, additional whitespace or line-feed might be printed.
368        return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
369                           for s in string.split('\n') ])
370    else:
371        return string
372
373def show_diff(a, b, file_path, color_enabled):
374    """Show unidified diff.
375
376    Arguments:
377      a: A list of lines (before)
378      b: A list of lines (after)
379      file_path: Path to the file
380      color_enabled: Display the diff in color
381    """
382
383    diff = difflib.unified_diff(a, b,
384                                fromfile=os.path.join('a', file_path),
385                                tofile=os.path.join('b', file_path))
386
387    for line in diff:
388        if line[0] == '-' and line[1] != '-':
389            print color_text(color_enabled, COLOR_RED, line),
390        elif line[0] == '+' and line[1] != '+':
391            print color_text(color_enabled, COLOR_GREEN, line),
392        else:
393            print line,
394
395def update_cross_compile(color_enabled):
396    """Update per-arch CROSS_COMPILE via environment variables
397
398    The default CROSS_COMPILE values are available
399    in the CROSS_COMPILE list above.
400
401    You can override them via environment variables
402    CROSS_COMPILE_{ARCH}.
403
404    For example, if you want to override toolchain prefixes
405    for ARM and PowerPC, you can do as follows in your shell:
406
407    export CROSS_COMPILE_ARM=...
408    export CROSS_COMPILE_POWERPC=...
409
410    Then, this function checks if specified compilers really exist in your
411    PATH environment.
412    """
413    archs = []
414
415    for arch in os.listdir('arch'):
416        if os.path.exists(os.path.join('arch', arch, 'Makefile')):
417            archs.append(arch)
418
419    # arm64 is a special case
420    archs.append('aarch64')
421
422    for arch in archs:
423        env = 'CROSS_COMPILE_' + arch.upper()
424        cross_compile = os.environ.get(env)
425        if not cross_compile:
426            cross_compile = CROSS_COMPILE.get(arch, '')
427
428        for path in os.environ["PATH"].split(os.pathsep):
429            gcc_path = os.path.join(path, cross_compile + 'gcc')
430            if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
431                break
432        else:
433            print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
434                 'warning: %sgcc: not found in PATH.  %s architecture boards will be skipped'
435                                            % (cross_compile, arch))
436            cross_compile = None
437
438        CROSS_COMPILE[arch] = cross_compile
439
440def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre,
441                         extend_post):
442    """Extend matched lines if desired patterns are found before/after already
443    matched lines.
444
445    Arguments:
446      lines: A list of lines handled.
447      matched: A list of line numbers that have been already matched.
448               (will be updated by this function)
449      pre_patterns: A list of regular expression that should be matched as
450                    preamble.
451      post_patterns: A list of regular expression that should be matched as
452                     postamble.
453      extend_pre: Add the line number of matched preamble to the matched list.
454      extend_post: Add the line number of matched postamble to the matched list.
455    """
456    extended_matched = []
457
458    j = matched[0]
459
460    for i in matched:
461        if i == 0 or i < j:
462            continue
463        j = i
464        while j in matched:
465            j += 1
466        if j >= len(lines):
467            break
468
469        for p in pre_patterns:
470            if p.search(lines[i - 1]):
471                break
472        else:
473            # not matched
474            continue
475
476        for p in post_patterns:
477            if p.search(lines[j]):
478                break
479        else:
480            # not matched
481            continue
482
483        if extend_pre:
484            extended_matched.append(i - 1)
485        if extend_post:
486            extended_matched.append(j)
487
488    matched += extended_matched
489    matched.sort()
490
491def confirm(options, prompt):
492    if not options.yes:
493        while True:
494            choice = raw_input('{} [y/n]: '.format(prompt))
495            choice = choice.lower()
496            print choice
497            if choice == 'y' or choice == 'n':
498                break
499
500        if choice == 'n':
501            return False
502
503    return True
504
505def cleanup_one_header(header_path, patterns, options):
506    """Clean regex-matched lines away from a file.
507
508    Arguments:
509      header_path: path to the cleaned file.
510      patterns: list of regex patterns.  Any lines matching to these
511                patterns are deleted.
512      options: option flags.
513    """
514    with open(header_path) as f:
515        lines = f.readlines()
516
517    matched = []
518    for i, line in enumerate(lines):
519        if i - 1 in matched and lines[i - 1][-2:] == '\\\n':
520            matched.append(i)
521            continue
522        for pattern in patterns:
523            if pattern.search(line):
524                matched.append(i)
525                break
526
527    if not matched:
528        return
529
530    # remove empty #ifdef ... #endif, successive blank lines
531    pattern_if = re.compile(r'#\s*if(def|ndef)?\W') #  #if, #ifdef, #ifndef
532    pattern_elif = re.compile(r'#\s*el(if|se)\W')   #  #elif, #else
533    pattern_endif = re.compile(r'#\s*endif\W')      #  #endif
534    pattern_blank = re.compile(r'^\s*$')            #  empty line
535
536    while True:
537        old_matched = copy.copy(matched)
538        extend_matched_lines(lines, matched, [pattern_if],
539                             [pattern_endif], True, True)
540        extend_matched_lines(lines, matched, [pattern_elif],
541                             [pattern_elif, pattern_endif], True, False)
542        extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
543                             [pattern_blank], False, True)
544        extend_matched_lines(lines, matched, [pattern_blank],
545                             [pattern_elif, pattern_endif], True, False)
546        extend_matched_lines(lines, matched, [pattern_blank],
547                             [pattern_blank], True, False)
548        if matched == old_matched:
549            break
550
551    tolines = copy.copy(lines)
552
553    for i in reversed(matched):
554        tolines.pop(i)
555
556    show_diff(lines, tolines, header_path, options.color)
557
558    if options.dry_run:
559        return
560
561    with open(header_path, 'w') as f:
562        for line in tolines:
563            f.write(line)
564
565def cleanup_headers(configs, options):
566    """Delete config defines from board headers.
567
568    Arguments:
569      configs: A list of CONFIGs to remove.
570      options: option flags.
571    """
572    if not confirm(options, 'Clean up headers?'):
573        return
574
575    patterns = []
576    for config in configs:
577        patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
578        patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
579
580    for dir in 'include', 'arch', 'board':
581        for (dirpath, dirnames, filenames) in os.walk(dir):
582            if dirpath == os.path.join('include', 'generated'):
583                continue
584            for filename in filenames:
585                if not fnmatch.fnmatch(filename, '*~'):
586                    cleanup_one_header(os.path.join(dirpath, filename),
587                                       patterns, options)
588
589def cleanup_one_extra_option(defconfig_path, configs, options):
590    """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
591
592    Arguments:
593      defconfig_path: path to the cleaned defconfig file.
594      configs: A list of CONFIGs to remove.
595      options: option flags.
596    """
597
598    start = 'CONFIG_SYS_EXTRA_OPTIONS="'
599    end = '"\n'
600
601    with open(defconfig_path) as f:
602        lines = f.readlines()
603
604    for i, line in enumerate(lines):
605        if line.startswith(start) and line.endswith(end):
606            break
607    else:
608        # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
609        return
610
611    old_tokens = line[len(start):-len(end)].split(',')
612    new_tokens = []
613
614    for token in old_tokens:
615        pos = token.find('=')
616        if not (token[:pos] if pos >= 0 else token) in configs:
617            new_tokens.append(token)
618
619    if new_tokens == old_tokens:
620        return
621
622    tolines = copy.copy(lines)
623
624    if new_tokens:
625        tolines[i] = start + ','.join(new_tokens) + end
626    else:
627        tolines.pop(i)
628
629    show_diff(lines, tolines, defconfig_path, options.color)
630
631    if options.dry_run:
632        return
633
634    with open(defconfig_path, 'w') as f:
635        for line in tolines:
636            f.write(line)
637
638def cleanup_extra_options(configs, options):
639    """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
640
641    Arguments:
642      configs: A list of CONFIGs to remove.
643      options: option flags.
644    """
645    if not confirm(options, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'):
646        return
647
648    configs = [ config[len('CONFIG_'):] for config in configs ]
649
650    defconfigs = get_all_defconfigs()
651
652    for defconfig in defconfigs:
653        cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
654                                 options)
655
656def cleanup_whitelist(configs, options):
657    """Delete config whitelist entries
658
659    Arguments:
660      configs: A list of CONFIGs to remove.
661      options: option flags.
662    """
663    if not confirm(options, 'Clean up whitelist entries?'):
664        return
665
666    with open(os.path.join('scripts', 'config_whitelist.txt')) as f:
667        lines = f.readlines()
668
669    lines = [x for x in lines if x.strip() not in configs]
670
671    with open(os.path.join('scripts', 'config_whitelist.txt'), 'w') as f:
672        f.write(''.join(lines))
673
674def find_matching(patterns, line):
675    for pat in patterns:
676        if pat.search(line):
677            return True
678    return False
679
680def cleanup_readme(configs, options):
681    """Delete config description in README
682
683    Arguments:
684      configs: A list of CONFIGs to remove.
685      options: option flags.
686    """
687    if not confirm(options, 'Clean up README?'):
688        return
689
690    patterns = []
691    for config in configs:
692        patterns.append(re.compile(r'^\s+%s' % config))
693
694    with open('README') as f:
695        lines = f.readlines()
696
697    found = False
698    newlines = []
699    for line in lines:
700        if not found:
701            found = find_matching(patterns, line)
702            if found:
703                continue
704
705        if found and re.search(r'^\s+CONFIG', line):
706            found = False
707
708        if not found:
709            newlines.append(line)
710
711    with open('README', 'w') as f:
712        f.write(''.join(newlines))
713
714
715### classes ###
716class Progress:
717
718    """Progress Indicator"""
719
720    def __init__(self, total):
721        """Create a new progress indicator.
722
723        Arguments:
724          total: A number of defconfig files to process.
725        """
726        self.current = 0
727        self.total = total
728
729    def inc(self):
730        """Increment the number of processed defconfig files."""
731
732        self.current += 1
733
734    def show(self):
735        """Display the progress."""
736        print ' %d defconfigs out of %d\r' % (self.current, self.total),
737        sys.stdout.flush()
738
739class KconfigParser:
740
741    """A parser of .config and include/autoconf.mk."""
742
743    re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
744    re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
745
746    def __init__(self, configs, options, build_dir):
747        """Create a new parser.
748
749        Arguments:
750          configs: A list of CONFIGs to move.
751          options: option flags.
752          build_dir: Build directory.
753        """
754        self.configs = configs
755        self.options = options
756        self.dotconfig = os.path.join(build_dir, '.config')
757        self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
758        self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
759                                         'autoconf.mk')
760        self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH)
761        self.defconfig = os.path.join(build_dir, 'defconfig')
762
763    def get_cross_compile(self):
764        """Parse .config file and return CROSS_COMPILE.
765
766        Returns:
767          A string storing the compiler prefix for the architecture.
768          Return a NULL string for architectures that do not require
769          compiler prefix (Sandbox and native build is the case).
770          Return None if the specified compiler is missing in your PATH.
771          Caller should distinguish '' and None.
772        """
773        arch = ''
774        cpu = ''
775        for line in open(self.dotconfig):
776            m = self.re_arch.match(line)
777            if m:
778                arch = m.group(1)
779                continue
780            m = self.re_cpu.match(line)
781            if m:
782                cpu = m.group(1)
783
784        if not arch:
785            return None
786
787        # fix-up for aarch64
788        if arch == 'arm' and cpu == 'armv8':
789            arch = 'aarch64'
790
791        return CROSS_COMPILE.get(arch, None)
792
793    def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
794        """Parse .config, defconfig, include/autoconf.mk for one config.
795
796        This function looks for the config options in the lines from
797        defconfig, .config, and include/autoconf.mk in order to decide
798        which action should be taken for this defconfig.
799
800        Arguments:
801          config: CONFIG name to parse.
802          dotconfig_lines: lines from the .config file.
803          autoconf_lines: lines from the include/autoconf.mk file.
804
805        Returns:
806          A tupple of the action for this defconfig and the line
807          matched for the config.
808        """
809        not_set = '# %s is not set' % config
810
811        for line in autoconf_lines:
812            line = line.rstrip()
813            if line.startswith(config + '='):
814                new_val = line
815                break
816        else:
817            new_val = not_set
818
819        for line in dotconfig_lines:
820            line = line.rstrip()
821            if line.startswith(config + '=') or line == not_set:
822                old_val = line
823                break
824        else:
825            if new_val == not_set:
826                return (ACTION_NO_ENTRY, config)
827            else:
828                return (ACTION_NO_ENTRY_WARN, config)
829
830        # If this CONFIG is neither bool nor trisate
831        if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
832            # tools/scripts/define2mk.sed changes '1' to 'y'.
833            # This is a problem if the CONFIG is int type.
834            # Check the type in Kconfig and handle it correctly.
835            if new_val[-2:] == '=y':
836                new_val = new_val[:-1] + '1'
837
838        return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
839                new_val)
840
841    def update_dotconfig(self):
842        """Parse files for the config options and update the .config.
843
844        This function parses the generated .config and include/autoconf.mk
845        searching the target options.
846        Move the config option(s) to the .config as needed.
847
848        Arguments:
849          defconfig: defconfig name.
850
851        Returns:
852          Return a tuple of (updated flag, log string).
853          The "updated flag" is True if the .config was updated, False
854          otherwise.  The "log string" shows what happend to the .config.
855        """
856
857        results = []
858        updated = False
859        suspicious = False
860        rm_files = [self.config_autoconf, self.autoconf]
861
862        if self.options.spl:
863            if os.path.exists(self.spl_autoconf):
864                autoconf_path = self.spl_autoconf
865                rm_files.append(self.spl_autoconf)
866            else:
867                for f in rm_files:
868                    os.remove(f)
869                return (updated, suspicious,
870                        color_text(self.options.color, COLOR_BROWN,
871                                   "SPL is not enabled.  Skipped.") + '\n')
872        else:
873            autoconf_path = self.autoconf
874
875        with open(self.dotconfig) as f:
876            dotconfig_lines = f.readlines()
877
878        with open(autoconf_path) as f:
879            autoconf_lines = f.readlines()
880
881        for config in self.configs:
882            result = self.parse_one_config(config, dotconfig_lines,
883                                           autoconf_lines)
884            results.append(result)
885
886        log = ''
887
888        for (action, value) in results:
889            if action == ACTION_MOVE:
890                actlog = "Move '%s'" % value
891                log_color = COLOR_LIGHT_GREEN
892            elif action == ACTION_NO_ENTRY:
893                actlog = "%s is not defined in Kconfig.  Do nothing." % value
894                log_color = COLOR_LIGHT_BLUE
895            elif action == ACTION_NO_ENTRY_WARN:
896                actlog = "%s is not defined in Kconfig (suspicious).  Do nothing." % value
897                log_color = COLOR_YELLOW
898                suspicious = True
899            elif action == ACTION_NO_CHANGE:
900                actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
901                         % value
902                log_color = COLOR_LIGHT_PURPLE
903            elif action == ACTION_SPL_NOT_EXIST:
904                actlog = "SPL is not enabled for this defconfig.  Skip."
905                log_color = COLOR_PURPLE
906            else:
907                sys.exit("Internal Error. This should not happen.")
908
909            log += color_text(self.options.color, log_color, actlog) + '\n'
910
911        with open(self.dotconfig, 'a') as f:
912            for (action, value) in results:
913                if action == ACTION_MOVE:
914                    f.write(value + '\n')
915                    updated = True
916
917        self.results = results
918        for f in rm_files:
919            os.remove(f)
920
921        return (updated, suspicious, log)
922
923    def check_defconfig(self):
924        """Check the defconfig after savedefconfig
925
926        Returns:
927          Return additional log if moved CONFIGs were removed again by
928          'make savedefconfig'.
929        """
930
931        log = ''
932
933        with open(self.defconfig) as f:
934            defconfig_lines = f.readlines()
935
936        for (action, value) in self.results:
937            if action != ACTION_MOVE:
938                continue
939            if not value + '\n' in defconfig_lines:
940                log += color_text(self.options.color, COLOR_YELLOW,
941                                  "'%s' was removed by savedefconfig.\n" %
942                                  value)
943
944        return log
945
946
947class DatabaseThread(threading.Thread):
948    """This thread processes results from Slot threads.
949
950    It collects the data in the master config directary. There is only one
951    result thread, and this helps to serialise the build output.
952    """
953    def __init__(self, config_db, db_queue):
954        """Set up a new result thread
955
956        Args:
957            builder: Builder which will be sent each result
958        """
959        threading.Thread.__init__(self)
960        self.config_db = config_db
961        self.db_queue= db_queue
962
963    def run(self):
964        """Called to start up the result thread.
965
966        We collect the next result job and pass it on to the build.
967        """
968        while True:
969            defconfig, configs = self.db_queue.get()
970            self.config_db[defconfig] = configs
971            self.db_queue.task_done()
972
973
974class Slot:
975
976    """A slot to store a subprocess.
977
978    Each instance of this class handles one subprocess.
979    This class is useful to control multiple threads
980    for faster processing.
981    """
982
983    def __init__(self, configs, options, progress, devnull, make_cmd,
984                 reference_src_dir, db_queue):
985        """Create a new process slot.
986
987        Arguments:
988          configs: A list of CONFIGs to move.
989          options: option flags.
990          progress: A progress indicator.
991          devnull: A file object of '/dev/null'.
992          make_cmd: command name of GNU Make.
993          reference_src_dir: Determine the true starting config state from this
994                             source tree.
995          db_queue: output queue to write config info for the database
996        """
997        self.options = options
998        self.progress = progress
999        self.build_dir = tempfile.mkdtemp()
1000        self.devnull = devnull
1001        self.make_cmd = (make_cmd, 'O=' + self.build_dir)
1002        self.reference_src_dir = reference_src_dir
1003        self.db_queue = db_queue
1004        self.parser = KconfigParser(configs, options, self.build_dir)
1005        self.state = STATE_IDLE
1006        self.failed_boards = set()
1007        self.suspicious_boards = set()
1008
1009    def __del__(self):
1010        """Delete the working directory
1011
1012        This function makes sure the temporary directory is cleaned away
1013        even if Python suddenly dies due to error.  It should be done in here
1014        because it is guaranteed the destructor is always invoked when the
1015        instance of the class gets unreferenced.
1016
1017        If the subprocess is still running, wait until it finishes.
1018        """
1019        if self.state != STATE_IDLE:
1020            while self.ps.poll() == None:
1021                pass
1022        shutil.rmtree(self.build_dir)
1023
1024    def add(self, defconfig):
1025        """Assign a new subprocess for defconfig and add it to the slot.
1026
1027        If the slot is vacant, create a new subprocess for processing the
1028        given defconfig and add it to the slot.  Just returns False if
1029        the slot is occupied (i.e. the current subprocess is still running).
1030
1031        Arguments:
1032          defconfig: defconfig name.
1033
1034        Returns:
1035          Return True on success or False on failure
1036        """
1037        if self.state != STATE_IDLE:
1038            return False
1039
1040        self.defconfig = defconfig
1041        self.log = ''
1042        self.current_src_dir = self.reference_src_dir
1043        self.do_defconfig()
1044        return True
1045
1046    def poll(self):
1047        """Check the status of the subprocess and handle it as needed.
1048
1049        Returns True if the slot is vacant (i.e. in idle state).
1050        If the configuration is successfully finished, assign a new
1051        subprocess to build include/autoconf.mk.
1052        If include/autoconf.mk is generated, invoke the parser to
1053        parse the .config and the include/autoconf.mk, moving
1054        config options to the .config as needed.
1055        If the .config was updated, run "make savedefconfig" to sync
1056        it, update the original defconfig, and then set the slot back
1057        to the idle state.
1058
1059        Returns:
1060          Return True if the subprocess is terminated, False otherwise
1061        """
1062        if self.state == STATE_IDLE:
1063            return True
1064
1065        if self.ps.poll() == None:
1066            return False
1067
1068        if self.ps.poll() != 0:
1069            self.handle_error()
1070        elif self.state == STATE_DEFCONFIG:
1071            if self.reference_src_dir and not self.current_src_dir:
1072                self.do_savedefconfig()
1073            else:
1074                self.do_autoconf()
1075        elif self.state == STATE_AUTOCONF:
1076            if self.current_src_dir:
1077                self.current_src_dir = None
1078                self.do_defconfig()
1079            elif self.options.build_db:
1080                self.do_build_db()
1081            else:
1082                self.do_savedefconfig()
1083        elif self.state == STATE_SAVEDEFCONFIG:
1084            self.update_defconfig()
1085        else:
1086            sys.exit("Internal Error. This should not happen.")
1087
1088        return True if self.state == STATE_IDLE else False
1089
1090    def handle_error(self):
1091        """Handle error cases."""
1092
1093        self.log += color_text(self.options.color, COLOR_LIGHT_RED,
1094                               "Failed to process.\n")
1095        if self.options.verbose:
1096            self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
1097                                   self.ps.stderr.read())
1098        self.finish(False)
1099
1100    def do_defconfig(self):
1101        """Run 'make <board>_defconfig' to create the .config file."""
1102
1103        cmd = list(self.make_cmd)
1104        cmd.append(self.defconfig)
1105        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1106                                   stderr=subprocess.PIPE,
1107                                   cwd=self.current_src_dir)
1108        self.state = STATE_DEFCONFIG
1109
1110    def do_autoconf(self):
1111        """Run 'make AUTO_CONF_PATH'."""
1112
1113        self.cross_compile = self.parser.get_cross_compile()
1114        if self.cross_compile is None:
1115            self.log += color_text(self.options.color, COLOR_YELLOW,
1116                                   "Compiler is missing.  Do nothing.\n")
1117            self.finish(False)
1118            return
1119
1120        cmd = list(self.make_cmd)
1121        if self.cross_compile:
1122            cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
1123        cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
1124        cmd.append(AUTO_CONF_PATH)
1125        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1126                                   stderr=subprocess.PIPE,
1127                                   cwd=self.current_src_dir)
1128        self.state = STATE_AUTOCONF
1129
1130    def do_build_db(self):
1131        """Add the board to the database"""
1132        configs = {}
1133        with open(os.path.join(self.build_dir, AUTO_CONF_PATH)) as fd:
1134            for line in fd.readlines():
1135                if line.startswith('CONFIG'):
1136                    config, value = line.split('=', 1)
1137                    configs[config] = value.rstrip()
1138        self.db_queue.put([self.defconfig, configs])
1139        self.finish(True)
1140
1141    def do_savedefconfig(self):
1142        """Update the .config and run 'make savedefconfig'."""
1143
1144        (updated, suspicious, log) = self.parser.update_dotconfig()
1145        if suspicious:
1146            self.suspicious_boards.add(self.defconfig)
1147        self.log += log
1148
1149        if not self.options.force_sync and not updated:
1150            self.finish(True)
1151            return
1152        if updated:
1153            self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
1154                                   "Syncing by savedefconfig...\n")
1155        else:
1156            self.log += "Syncing by savedefconfig (forced by option)...\n"
1157
1158        cmd = list(self.make_cmd)
1159        cmd.append('savedefconfig')
1160        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1161                                   stderr=subprocess.PIPE)
1162        self.state = STATE_SAVEDEFCONFIG
1163
1164    def update_defconfig(self):
1165        """Update the input defconfig and go back to the idle state."""
1166
1167        log = self.parser.check_defconfig()
1168        if log:
1169            self.suspicious_boards.add(self.defconfig)
1170            self.log += log
1171        orig_defconfig = os.path.join('configs', self.defconfig)
1172        new_defconfig = os.path.join(self.build_dir, 'defconfig')
1173        updated = not filecmp.cmp(orig_defconfig, new_defconfig)
1174
1175        if updated:
1176            self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
1177                                   "defconfig was updated.\n")
1178
1179        if not self.options.dry_run and updated:
1180            shutil.move(new_defconfig, orig_defconfig)
1181        self.finish(True)
1182
1183    def finish(self, success):
1184        """Display log along with progress and go to the idle state.
1185
1186        Arguments:
1187          success: Should be True when the defconfig was processed
1188                   successfully, or False when it fails.
1189        """
1190        # output at least 30 characters to hide the "* defconfigs out of *".
1191        log = self.defconfig.ljust(30) + '\n'
1192
1193        log += '\n'.join([ '    ' + s for s in self.log.split('\n') ])
1194        # Some threads are running in parallel.
1195        # Print log atomically to not mix up logs from different threads.
1196        print >> (sys.stdout if success else sys.stderr), log
1197
1198        if not success:
1199            if self.options.exit_on_error:
1200                sys.exit("Exit on error.")
1201            # If --exit-on-error flag is not set, skip this board and continue.
1202            # Record the failed board.
1203            self.failed_boards.add(self.defconfig)
1204
1205        self.progress.inc()
1206        self.progress.show()
1207        self.state = STATE_IDLE
1208
1209    def get_failed_boards(self):
1210        """Returns a set of failed boards (defconfigs) in this slot.
1211        """
1212        return self.failed_boards
1213
1214    def get_suspicious_boards(self):
1215        """Returns a set of boards (defconfigs) with possible misconversion.
1216        """
1217        return self.suspicious_boards - self.failed_boards
1218
1219class Slots:
1220
1221    """Controller of the array of subprocess slots."""
1222
1223    def __init__(self, configs, options, progress, reference_src_dir, db_queue):
1224        """Create a new slots controller.
1225
1226        Arguments:
1227          configs: A list of CONFIGs to move.
1228          options: option flags.
1229          progress: A progress indicator.
1230          reference_src_dir: Determine the true starting config state from this
1231                             source tree.
1232          db_queue: output queue to write config info for the database
1233        """
1234        self.options = options
1235        self.slots = []
1236        devnull = get_devnull()
1237        make_cmd = get_make_cmd()
1238        for i in range(options.jobs):
1239            self.slots.append(Slot(configs, options, progress, devnull,
1240                                   make_cmd, reference_src_dir, db_queue))
1241
1242    def add(self, defconfig):
1243        """Add a new subprocess if a vacant slot is found.
1244
1245        Arguments:
1246          defconfig: defconfig name to be put into.
1247
1248        Returns:
1249          Return True on success or False on failure
1250        """
1251        for slot in self.slots:
1252            if slot.add(defconfig):
1253                return True
1254        return False
1255
1256    def available(self):
1257        """Check if there is a vacant slot.
1258
1259        Returns:
1260          Return True if at lease one vacant slot is found, False otherwise.
1261        """
1262        for slot in self.slots:
1263            if slot.poll():
1264                return True
1265        return False
1266
1267    def empty(self):
1268        """Check if all slots are vacant.
1269
1270        Returns:
1271          Return True if all the slots are vacant, False otherwise.
1272        """
1273        ret = True
1274        for slot in self.slots:
1275            if not slot.poll():
1276                ret = False
1277        return ret
1278
1279    def show_failed_boards(self):
1280        """Display all of the failed boards (defconfigs)."""
1281        boards = set()
1282        output_file = 'moveconfig.failed'
1283
1284        for slot in self.slots:
1285            boards |= slot.get_failed_boards()
1286
1287        if boards:
1288            boards = '\n'.join(boards) + '\n'
1289            msg = "The following boards were not processed due to error:\n"
1290            msg += boards
1291            msg += "(the list has been saved in %s)\n" % output_file
1292            print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
1293                                            msg)
1294
1295            with open(output_file, 'w') as f:
1296                f.write(boards)
1297
1298    def show_suspicious_boards(self):
1299        """Display all boards (defconfigs) with possible misconversion."""
1300        boards = set()
1301        output_file = 'moveconfig.suspicious'
1302
1303        for slot in self.slots:
1304            boards |= slot.get_suspicious_boards()
1305
1306        if boards:
1307            boards = '\n'.join(boards) + '\n'
1308            msg = "The following boards might have been converted incorrectly.\n"
1309            msg += "It is highly recommended to check them manually:\n"
1310            msg += boards
1311            msg += "(the list has been saved in %s)\n" % output_file
1312            print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
1313                                            msg)
1314
1315            with open(output_file, 'w') as f:
1316                f.write(boards)
1317
1318class ReferenceSource:
1319
1320    """Reference source against which original configs should be parsed."""
1321
1322    def __init__(self, commit):
1323        """Create a reference source directory based on a specified commit.
1324
1325        Arguments:
1326          commit: commit to git-clone
1327        """
1328        self.src_dir = tempfile.mkdtemp()
1329        print "Cloning git repo to a separate work directory..."
1330        subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1331                                cwd=self.src_dir)
1332        print "Checkout '%s' to build the original autoconf.mk." % \
1333            subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
1334        subprocess.check_output(['git', 'checkout', commit],
1335                                stderr=subprocess.STDOUT, cwd=self.src_dir)
1336
1337    def __del__(self):
1338        """Delete the reference source directory
1339
1340        This function makes sure the temporary directory is cleaned away
1341        even if Python suddenly dies due to error.  It should be done in here
1342        because it is guaranteed the destructor is always invoked when the
1343        instance of the class gets unreferenced.
1344        """
1345        shutil.rmtree(self.src_dir)
1346
1347    def get_dir(self):
1348        """Return the absolute path to the reference source directory."""
1349
1350        return self.src_dir
1351
1352def move_config(configs, options, db_queue):
1353    """Move config options to defconfig files.
1354
1355    Arguments:
1356      configs: A list of CONFIGs to move.
1357      options: option flags
1358    """
1359    if len(configs) == 0:
1360        if options.force_sync:
1361            print 'No CONFIG is specified. You are probably syncing defconfigs.',
1362        elif options.build_db:
1363            print 'Building %s database' % CONFIG_DATABASE
1364        else:
1365            print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
1366    else:
1367        print 'Move ' + ', '.join(configs),
1368    print '(jobs: %d)\n' % options.jobs
1369
1370    if options.git_ref:
1371        reference_src = ReferenceSource(options.git_ref)
1372        reference_src_dir = reference_src.get_dir()
1373    else:
1374        reference_src_dir = None
1375
1376    if options.defconfigs:
1377        defconfigs = get_matched_defconfigs(options.defconfigs)
1378    else:
1379        defconfigs = get_all_defconfigs()
1380
1381    progress = Progress(len(defconfigs))
1382    slots = Slots(configs, options, progress, reference_src_dir, db_queue)
1383
1384    # Main loop to process defconfig files:
1385    #  Add a new subprocess into a vacant slot.
1386    #  Sleep if there is no available slot.
1387    for defconfig in defconfigs:
1388        while not slots.add(defconfig):
1389            while not slots.available():
1390                # No available slot: sleep for a while
1391                time.sleep(SLEEP_TIME)
1392
1393    # wait until all the subprocesses finish
1394    while not slots.empty():
1395        time.sleep(SLEEP_TIME)
1396
1397    print ''
1398    slots.show_failed_boards()
1399    slots.show_suspicious_boards()
1400
1401def main():
1402    try:
1403        cpu_count = multiprocessing.cpu_count()
1404    except NotImplementedError:
1405        cpu_count = 1
1406
1407    parser = optparse.OptionParser()
1408    # Add options here
1409    parser.add_option('-b', '--build-db', action='store_true', default=False,
1410                      help='build a CONFIG database')
1411    parser.add_option('-c', '--color', action='store_true', default=False,
1412                      help='display the log in color')
1413    parser.add_option('-C', '--commit', action='store_true', default=False,
1414                      help='Create a git commit for the operation')
1415    parser.add_option('-d', '--defconfigs', type='string',
1416                      help='a file containing a list of defconfigs to move, '
1417                      "one per line (for example 'snow_defconfig') "
1418                      "or '-' to read from stdin")
1419    parser.add_option('-n', '--dry-run', action='store_true', default=False,
1420                      help='perform a trial run (show log with no changes)')
1421    parser.add_option('-e', '--exit-on-error', action='store_true',
1422                      default=False,
1423                      help='exit immediately on any error')
1424    parser.add_option('-s', '--force-sync', action='store_true', default=False,
1425                      help='force sync by savedefconfig')
1426    parser.add_option('-S', '--spl', action='store_true', default=False,
1427                      help='parse config options defined for SPL build')
1428    parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1429                      action='store_true', default=False,
1430                      help='only cleanup the headers')
1431    parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1432                      help='the number of jobs to run simultaneously')
1433    parser.add_option('-r', '--git-ref', type='string',
1434                      help='the git ref to clone for building the autoconf.mk')
1435    parser.add_option('-y', '--yes', action='store_true', default=False,
1436                      help="respond 'yes' to any prompts")
1437    parser.add_option('-v', '--verbose', action='store_true', default=False,
1438                      help='show any build errors as boards are built')
1439    parser.usage += ' CONFIG ...'
1440
1441    (options, configs) = parser.parse_args()
1442
1443    if len(configs) == 0 and not any((options.force_sync, options.build_db)):
1444        parser.print_usage()
1445        sys.exit(1)
1446
1447    # prefix the option name with CONFIG_ if missing
1448    configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1449                for config in configs ]
1450
1451    check_top_directory()
1452
1453    config_db = {}
1454    db_queue = Queue.Queue()
1455    t = DatabaseThread(config_db, db_queue)
1456    t.setDaemon(True)
1457    t.start()
1458
1459    if not options.cleanup_headers_only:
1460        check_clean_directory()
1461        update_cross_compile(options.color)
1462        move_config(configs, options, db_queue)
1463        db_queue.join()
1464
1465    if configs:
1466        cleanup_headers(configs, options)
1467        cleanup_extra_options(configs, options)
1468        cleanup_whitelist(configs, options)
1469        cleanup_readme(configs, options)
1470
1471    if options.commit:
1472        subprocess.call(['git', 'add', '-u'])
1473        if configs:
1474            msg = 'Convert %s %sto Kconfig' % (configs[0],
1475                    'et al ' if len(configs) > 1 else '')
1476            msg += ('\n\nThis converts the following to Kconfig:\n   %s\n' %
1477                    '\n   '.join(configs))
1478        else:
1479            msg = 'configs: Resync with savedefconfig'
1480            msg += '\n\nRsync all defconfig files using moveconfig.py'
1481        subprocess.call(['git', 'commit', '-s', '-m', msg])
1482
1483    if options.build_db:
1484        with open(CONFIG_DATABASE, 'w') as fd:
1485            for defconfig, configs in config_db.iteritems():
1486                print >>fd, '%s' % defconfig
1487                for config in sorted(configs.keys()):
1488                    print >>fd, '   %s=%s' % (config, configs[config])
1489                print >>fd
1490
1491if __name__ == '__main__':
1492    main()
1493