xref: /rk3399_rockchip-uboot/tools/moveconfig.py (revision e9ea122159ae9b17355f7891f09530ed79940441)
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 followings:
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.
51   There are two common cases:
52     - You forgot to create an entry for the CONFIG before running
53       this tool, or made a typo in a CONFIG passed to this tool.
54     - The entry was hidden due to unmet 'depends on'.
55       This is correct behavior.
56
57 - 'CONFIG_...' is the same as the define in Kconfig.  Do nothing.
58   The define in the config header matched the one in Kconfig.
59   We do not need to touch it.
60
61 - Undefined.  Do nothing.
62   This config option was not found in the config header.
63   Nothing to do.
64
65 - Compiler is missing.  Do nothing.
66   The compiler specified for this architecture was not found
67   in your PATH environment.
68   (If -e option is passed, the tool exits immediately.)
69
70 - Failed to process.
71   An error occurred during processing this defconfig.  Skipped.
72   (If -e option is passed, the tool exits immediately on error.)
73
74Finally, you will be asked, Clean up headers? [y/n]:
75
76If you say 'y' here, the unnecessary config defines are removed
77from the config headers (include/configs/*.h).
78It just uses the regex method, so you should not rely on it.
79Just in case, please do 'git diff' to see what happened.
80
81
82How does it work?
83-----------------
84
85This tool runs configuration and builds include/autoconf.mk for every
86defconfig.  The config options defined in Kconfig appear in the .config
87file (unless they are hidden because of unmet dependency.)
88On the other hand, the config options defined by board headers are seen
89in include/autoconf.mk.  The tool looks for the specified options in both
90of them to decide the appropriate action for the options.  If the given
91config option is found in the .config, but its value does not match the
92one from the board header, the config option in the .config is replaced
93with the define in the board header.  Then, the .config is synced by
94"make savedefconfig" and the defconfig is updated with it.
95
96For faster processing, this tool handles multi-threading.  It creates
97separate build directories where the out-of-tree build is run.  The
98temporary build directories are automatically created and deleted as
99needed.  The number of threads are chosen based on the number of the CPU
100cores of your system although you can change it via -j (--jobs) option.
101
102
103Toolchains
104----------
105
106Appropriate toolchain are necessary to generate include/autoconf.mk
107for all the architectures supported by U-Boot.  Most of them are available
108at the kernel.org site, some are not provided by kernel.org.
109
110The default per-arch CROSS_COMPILE used by this tool is specified by
111the list below, CROSS_COMPILE.  You may wish to update the list to
112use your own.  Instead of modifying the list directly, you can give
113them via environments.
114
115
116Available options
117-----------------
118
119 -c, --color
120   Surround each portion of the log with escape sequences to display it
121   in color on the terminal.
122
123 -d, --defconfigs
124  Specify a file containing a list of defconfigs to move
125
126 -n, --dry-run
127   Perform a trial run that does not make any changes.  It is useful to
128   see what is going to happen before one actually runs it.
129
130 -e, --exit-on-error
131   Exit immediately if Make exits with a non-zero status while processing
132   a defconfig file.
133
134 -s, --force-sync
135   Do "make savedefconfig" forcibly for all the defconfig files.
136   If not specified, "make savedefconfig" only occurs for cases
137   where at least one CONFIG was moved.
138
139 -H, --headers-only
140   Only cleanup the headers; skip the defconfig processing
141
142 -j, --jobs
143   Specify the number of threads to run simultaneously.  If not specified,
144   the number of threads is the same as the number of CPU cores.
145
146 -r, --git-ref
147   Specify the git ref to clone for building the autoconf.mk. If unspecified
148   use the CWD. This is useful for when changes to the Kconfig affect the
149   default values and you want to capture the state of the defconfig from
150   before that change was in effect. If in doubt, specify a ref pre-Kconfig
151   changes (use HEAD if Kconfig changes are not committed). Worst case it will
152   take a bit longer to run, but will always do the right thing.
153
154 -v, --verbose
155   Show any build errors as boards are built
156
157To see the complete list of supported options, run
158
159  $ tools/moveconfig.py -h
160
161"""
162
163import copy
164import difflib
165import filecmp
166import fnmatch
167import multiprocessing
168import optparse
169import os
170import re
171import shutil
172import subprocess
173import sys
174import tempfile
175import time
176
177SHOW_GNU_MAKE = 'scripts/show-gnu-make'
178SLEEP_TIME=0.03
179
180# Here is the list of cross-tools I use.
181# Most of them are available at kernel.org
182# (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the followings:
183# arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases
184# blackfin: http://sourceforge.net/projects/adi-toolchain/files/
185# nds32: http://osdk.andestech.com/packages/nds32le-linux-glibc-v1.tgz
186# nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545
187# sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu
188#
189# openrisc kernel.org toolchain is out of date, download latest one from
190# http://opencores.org/or1k/OpenRISC_GNU_tool_chain#Prebuilt_versions
191CROSS_COMPILE = {
192    'arc': 'arc-linux-',
193    'aarch64': 'aarch64-linux-',
194    'arm': 'arm-unknown-linux-gnueabi-',
195    'avr32': 'avr32-linux-',
196    'blackfin': 'bfin-elf-',
197    'm68k': 'm68k-linux-',
198    'microblaze': 'microblaze-linux-',
199    'mips': 'mips-linux-',
200    'nds32': 'nds32le-linux-',
201    'nios2': 'nios2-linux-gnu-',
202    'openrisc': 'or1k-elf-',
203    'powerpc': 'powerpc-linux-',
204    'sh': 'sh-linux-gnu-',
205    'sparc': 'sparc-linux-',
206    'x86': 'i386-linux-'
207}
208
209STATE_IDLE = 0
210STATE_DEFCONFIG = 1
211STATE_AUTOCONF = 2
212STATE_SAVEDEFCONFIG = 3
213
214ACTION_MOVE = 0
215ACTION_NO_ENTRY = 1
216ACTION_NO_CHANGE = 2
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 check_clean_directory():
251    """Exit if the source tree is not clean."""
252    for f in ('.config', 'include/config'):
253        if os.path.exists(f):
254            sys.exit("source tree is not clean, please run 'make mrproper'")
255
256def get_make_cmd():
257    """Get the command name of GNU Make.
258
259    U-Boot needs GNU Make for building, but the command name is not
260    necessarily "make". (for example, "gmake" on FreeBSD).
261    Returns the most appropriate command name on your system.
262    """
263    process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
264    ret = process.communicate()
265    if process.returncode:
266        sys.exit('GNU Make not found')
267    return ret[0].rstrip()
268
269def color_text(color_enabled, color, string):
270    """Return colored string."""
271    if color_enabled:
272        # LF should not be surrounded by the escape sequence.
273        # Otherwise, additional whitespace or line-feed might be printed.
274        return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
275                           for s in string.split('\n') ])
276    else:
277        return string
278
279def show_diff(a, b, file_path, color_enabled):
280    """Show unidified diff.
281
282    Arguments:
283      a: A list of lines (before)
284      b: A list of lines (after)
285      file_path: Path to the file
286      color_enabled: Display the diff in color
287    """
288
289    diff = difflib.unified_diff(a, b,
290                                fromfile=os.path.join('a', file_path),
291                                tofile=os.path.join('b', file_path))
292
293    for line in diff:
294        if line[0] == '-' and line[1] != '-':
295            print color_text(color_enabled, COLOR_RED, line),
296        elif line[0] == '+' and line[1] != '+':
297            print color_text(color_enabled, COLOR_GREEN, line),
298        else:
299            print line,
300
301def update_cross_compile(color_enabled):
302    """Update per-arch CROSS_COMPILE via environment variables
303
304    The default CROSS_COMPILE values are available
305    in the CROSS_COMPILE list above.
306
307    You can override them via environment variables
308    CROSS_COMPILE_{ARCH}.
309
310    For example, if you want to override toolchain prefixes
311    for ARM and PowerPC, you can do as follows in your shell:
312
313    export CROSS_COMPILE_ARM=...
314    export CROSS_COMPILE_POWERPC=...
315
316    Then, this function checks if specified compilers really exist in your
317    PATH environment.
318    """
319    archs = []
320
321    for arch in os.listdir('arch'):
322        if os.path.exists(os.path.join('arch', arch, 'Makefile')):
323            archs.append(arch)
324
325    # arm64 is a special case
326    archs.append('aarch64')
327
328    for arch in archs:
329        env = 'CROSS_COMPILE_' + arch.upper()
330        cross_compile = os.environ.get(env)
331        if not cross_compile:
332            cross_compile = CROSS_COMPILE.get(arch, '')
333
334        for path in os.environ["PATH"].split(os.pathsep):
335            gcc_path = os.path.join(path, cross_compile + 'gcc')
336            if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
337                break
338        else:
339            print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
340                 'warning: %sgcc: not found in PATH.  %s architecture boards will be skipped'
341                                            % (cross_compile, arch))
342            cross_compile = None
343
344        CROSS_COMPILE[arch] = cross_compile
345
346def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre,
347                         extend_post):
348    """Extend matched lines if desired patterns are found before/after already
349    matched lines.
350
351    Arguments:
352      lines: A list of lines handled.
353      matched: A list of line numbers that have been already matched.
354               (will be updated by this function)
355      pre_patterns: A list of regular expression that should be matched as
356                    preamble.
357      post_patterns: A list of regular expression that should be matched as
358                     postamble.
359      extend_pre: Add the line number of matched preamble to the matched list.
360      extend_post: Add the line number of matched postamble to the matched list.
361    """
362    extended_matched = []
363
364    j = matched[0]
365
366    for i in matched:
367        if i == 0 or i < j:
368            continue
369        j = i
370        while j in matched:
371            j += 1
372        if j >= len(lines):
373            break
374
375        for p in pre_patterns:
376            if p.search(lines[i - 1]):
377                break
378        else:
379            # not matched
380            continue
381
382        for p in post_patterns:
383            if p.search(lines[j]):
384                break
385        else:
386            # not matched
387            continue
388
389        if extend_pre:
390            extended_matched.append(i - 1)
391        if extend_post:
392            extended_matched.append(j)
393
394    matched += extended_matched
395    matched.sort()
396
397def cleanup_one_header(header_path, patterns, options):
398    """Clean regex-matched lines away from a file.
399
400    Arguments:
401      header_path: path to the cleaned file.
402      patterns: list of regex patterns.  Any lines matching to these
403                patterns are deleted.
404      options: option flags.
405    """
406    with open(header_path) as f:
407        lines = f.readlines()
408
409    matched = []
410    for i, line in enumerate(lines):
411        for pattern in patterns:
412            if pattern.search(line):
413                matched.append(i)
414                break
415
416    if not matched:
417        return
418
419    # remove empty #ifdef ... #endif, successive blank lines
420    pattern_if = re.compile(r'#\s*if(def|ndef)?\W') #  #if, #ifdef, #ifndef
421    pattern_elif = re.compile(r'#\s*el(if|se)\W')   #  #elif, #else
422    pattern_endif = re.compile(r'#\s*endif\W')      #  #endif
423    pattern_blank = re.compile(r'^\s*$')            #  empty line
424
425    while True:
426        old_matched = copy.copy(matched)
427        extend_matched_lines(lines, matched, [pattern_if],
428                             [pattern_endif], True, True)
429        extend_matched_lines(lines, matched, [pattern_elif],
430                             [pattern_elif, pattern_endif], True, False)
431        extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
432                             [pattern_blank], False, True)
433        extend_matched_lines(lines, matched, [pattern_blank],
434                             [pattern_elif, pattern_endif], True, False)
435        extend_matched_lines(lines, matched, [pattern_blank],
436                             [pattern_blank], True, False)
437        if matched == old_matched:
438            break
439
440    tolines = copy.copy(lines)
441
442    for i in reversed(matched):
443        tolines.pop(i)
444
445    show_diff(lines, tolines, header_path, options.color)
446
447    if options.dry_run:
448        return
449
450    with open(header_path, 'w') as f:
451        for line in tolines:
452            f.write(line)
453
454def cleanup_headers(configs, options):
455    """Delete config defines from board headers.
456
457    Arguments:
458      configs: A list of CONFIGs to remove.
459      options: option flags.
460    """
461    while True:
462        choice = raw_input('Clean up headers? [y/n]: ').lower()
463        print choice
464        if choice == 'y' or choice == 'n':
465            break
466
467    if choice == 'n':
468        return
469
470    patterns = []
471    for config in configs:
472        patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
473        patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
474
475    for dir in 'include', 'arch', 'board':
476        for (dirpath, dirnames, filenames) in os.walk(dir):
477            if dirpath == os.path.join('include', 'generated'):
478                continue
479            for filename in filenames:
480                if not fnmatch.fnmatch(filename, '*~'):
481                    cleanup_one_header(os.path.join(dirpath, filename),
482                                       patterns, options)
483
484### classes ###
485class Progress:
486
487    """Progress Indicator"""
488
489    def __init__(self, total):
490        """Create a new progress indicator.
491
492        Arguments:
493          total: A number of defconfig files to process.
494        """
495        self.current = 0
496        self.total = total
497
498    def inc(self):
499        """Increment the number of processed defconfig files."""
500
501        self.current += 1
502
503    def show(self):
504        """Display the progress."""
505        print ' %d defconfigs out of %d\r' % (self.current, self.total),
506        sys.stdout.flush()
507
508class KconfigParser:
509
510    """A parser of .config and include/autoconf.mk."""
511
512    re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
513    re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
514
515    def __init__(self, configs, options, build_dir):
516        """Create a new parser.
517
518        Arguments:
519          configs: A list of CONFIGs to move.
520          options: option flags.
521          build_dir: Build directory.
522        """
523        self.configs = configs
524        self.options = options
525        self.dotconfig = os.path.join(build_dir, '.config')
526        self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
527        self.config_autoconf = os.path.join(build_dir, 'include', 'config',
528                                            'auto.conf')
529        self.defconfig = os.path.join(build_dir, 'defconfig')
530
531    def get_cross_compile(self):
532        """Parse .config file and return CROSS_COMPILE.
533
534        Returns:
535          A string storing the compiler prefix for the architecture.
536          Return a NULL string for architectures that do not require
537          compiler prefix (Sandbox and native build is the case).
538          Return None if the specified compiler is missing in your PATH.
539          Caller should distinguish '' and None.
540        """
541        arch = ''
542        cpu = ''
543        for line in open(self.dotconfig):
544            m = self.re_arch.match(line)
545            if m:
546                arch = m.group(1)
547                continue
548            m = self.re_cpu.match(line)
549            if m:
550                cpu = m.group(1)
551
552        if not arch:
553            return None
554
555        # fix-up for aarch64
556        if arch == 'arm' and cpu == 'armv8':
557            arch = 'aarch64'
558
559        return CROSS_COMPILE.get(arch, None)
560
561    def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
562        """Parse .config, defconfig, include/autoconf.mk for one config.
563
564        This function looks for the config options in the lines from
565        defconfig, .config, and include/autoconf.mk in order to decide
566        which action should be taken for this defconfig.
567
568        Arguments:
569          config: CONFIG name to parse.
570          dotconfig_lines: lines from the .config file.
571          autoconf_lines: lines from the include/autoconf.mk file.
572
573        Returns:
574          A tupple of the action for this defconfig and the line
575          matched for the config.
576        """
577        not_set = '# %s is not set' % config
578
579        for line in dotconfig_lines:
580            line = line.rstrip()
581            if line.startswith(config + '=') or line == not_set:
582                old_val = line
583                break
584        else:
585            return (ACTION_NO_ENTRY, config)
586
587        for line in autoconf_lines:
588            line = line.rstrip()
589            if line.startswith(config + '='):
590                new_val = line
591                break
592        else:
593            new_val = not_set
594
595        # If this CONFIG is neither bool nor trisate
596        if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
597            # tools/scripts/define2mk.sed changes '1' to 'y'.
598            # This is a problem if the CONFIG is int type.
599            # Check the type in Kconfig and handle it correctly.
600            if new_val[-2:] == '=y':
601                new_val = new_val[:-1] + '1'
602
603        return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
604                new_val)
605
606    def update_dotconfig(self):
607        """Parse files for the config options and update the .config.
608
609        This function parses the generated .config and include/autoconf.mk
610        searching the target options.
611        Move the config option(s) to the .config as needed.
612
613        Arguments:
614          defconfig: defconfig name.
615
616        Returns:
617          Return a tuple of (updated flag, log string).
618          The "updated flag" is True if the .config was updated, False
619          otherwise.  The "log string" shows what happend to the .config.
620        """
621
622        results = []
623        updated = False
624
625        with open(self.dotconfig) as f:
626            dotconfig_lines = f.readlines()
627
628        with open(self.autoconf) as f:
629            autoconf_lines = f.readlines()
630
631        for config in self.configs:
632            result = self.parse_one_config(config, dotconfig_lines,
633                                           autoconf_lines)
634            results.append(result)
635
636        log = ''
637
638        for (action, value) in results:
639            if action == ACTION_MOVE:
640                actlog = "Move '%s'" % value
641                log_color = COLOR_LIGHT_GREEN
642            elif action == ACTION_NO_ENTRY:
643                actlog = "%s is not defined in Kconfig.  Do nothing." % value
644                log_color = COLOR_LIGHT_BLUE
645            elif action == ACTION_NO_CHANGE:
646                actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
647                         % value
648                log_color = COLOR_LIGHT_PURPLE
649            else:
650                sys.exit("Internal Error. This should not happen.")
651
652            log += color_text(self.options.color, log_color, actlog) + '\n'
653
654        with open(self.dotconfig, 'a') as f:
655            for (action, value) in results:
656                if action == ACTION_MOVE:
657                    f.write(value + '\n')
658                    updated = True
659
660        self.results = results
661        os.remove(self.config_autoconf)
662        os.remove(self.autoconf)
663
664        return (updated, log)
665
666    def check_defconfig(self):
667        """Check the defconfig after savedefconfig
668
669        Returns:
670          Return additional log if moved CONFIGs were removed again by
671          'make savedefconfig'.
672        """
673
674        log = ''
675
676        with open(self.defconfig) as f:
677            defconfig_lines = f.readlines()
678
679        for (action, value) in self.results:
680            if action != ACTION_MOVE:
681                continue
682            if not value + '\n' in defconfig_lines:
683                log += color_text(self.options.color, COLOR_YELLOW,
684                                  "'%s' was removed by savedefconfig.\n" %
685                                  value)
686
687        return log
688
689class Slot:
690
691    """A slot to store a subprocess.
692
693    Each instance of this class handles one subprocess.
694    This class is useful to control multiple threads
695    for faster processing.
696    """
697
698    def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir):
699        """Create a new process slot.
700
701        Arguments:
702          configs: A list of CONFIGs to move.
703          options: option flags.
704          progress: A progress indicator.
705          devnull: A file object of '/dev/null'.
706          make_cmd: command name of GNU Make.
707          reference_src_dir: Determine the true starting config state from this
708                             source tree.
709        """
710        self.options = options
711        self.progress = progress
712        self.build_dir = tempfile.mkdtemp()
713        self.devnull = devnull
714        self.make_cmd = (make_cmd, 'O=' + self.build_dir)
715        self.reference_src_dir = reference_src_dir
716        self.parser = KconfigParser(configs, options, self.build_dir)
717        self.state = STATE_IDLE
718        self.failed_boards = []
719        self.suspicious_boards = []
720
721    def __del__(self):
722        """Delete the working directory
723
724        This function makes sure the temporary directory is cleaned away
725        even if Python suddenly dies due to error.  It should be done in here
726        because it is guaranteed the destructor is always invoked when the
727        instance of the class gets unreferenced.
728
729        If the subprocess is still running, wait until it finishes.
730        """
731        if self.state != STATE_IDLE:
732            while self.ps.poll() == None:
733                pass
734        shutil.rmtree(self.build_dir)
735
736    def add(self, defconfig):
737        """Assign a new subprocess for defconfig and add it to the slot.
738
739        If the slot is vacant, create a new subprocess for processing the
740        given defconfig and add it to the slot.  Just returns False if
741        the slot is occupied (i.e. the current subprocess is still running).
742
743        Arguments:
744          defconfig: defconfig name.
745
746        Returns:
747          Return True on success or False on failure
748        """
749        if self.state != STATE_IDLE:
750            return False
751
752        self.defconfig = defconfig
753        self.log = ''
754        self.current_src_dir = self.reference_src_dir
755        self.do_defconfig()
756        return True
757
758    def poll(self):
759        """Check the status of the subprocess and handle it as needed.
760
761        Returns True if the slot is vacant (i.e. in idle state).
762        If the configuration is successfully finished, assign a new
763        subprocess to build include/autoconf.mk.
764        If include/autoconf.mk is generated, invoke the parser to
765        parse the .config and the include/autoconf.mk, moving
766        config options to the .config as needed.
767        If the .config was updated, run "make savedefconfig" to sync
768        it, update the original defconfig, and then set the slot back
769        to the idle state.
770
771        Returns:
772          Return True if the subprocess is terminated, False otherwise
773        """
774        if self.state == STATE_IDLE:
775            return True
776
777        if self.ps.poll() == None:
778            return False
779
780        if self.ps.poll() != 0:
781            self.handle_error()
782        elif self.state == STATE_DEFCONFIG:
783            if self.reference_src_dir and not self.current_src_dir:
784                self.do_savedefconfig()
785            else:
786                self.do_autoconf()
787        elif self.state == STATE_AUTOCONF:
788            if self.current_src_dir:
789                self.current_src_dir = None
790                self.do_defconfig()
791            else:
792                self.do_savedefconfig()
793        elif self.state == STATE_SAVEDEFCONFIG:
794            self.update_defconfig()
795        else:
796            sys.exit("Internal Error. This should not happen.")
797
798        return True if self.state == STATE_IDLE else False
799
800    def handle_error(self):
801        """Handle error cases."""
802
803        self.log += color_text(self.options.color, COLOR_LIGHT_RED,
804                               "Failed to process.\n")
805        if self.options.verbose:
806            self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
807                                   self.ps.stderr.read())
808        self.finish(False)
809
810    def do_defconfig(self):
811        """Run 'make <board>_defconfig' to create the .config file."""
812
813        cmd = list(self.make_cmd)
814        cmd.append(self.defconfig)
815        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
816                                   stderr=subprocess.PIPE,
817                                   cwd=self.current_src_dir)
818        self.state = STATE_DEFCONFIG
819
820    def do_autoconf(self):
821        """Run 'make include/config/auto.conf'."""
822
823        self.cross_compile = self.parser.get_cross_compile()
824        if self.cross_compile is None:
825            self.log += color_text(self.options.color, COLOR_YELLOW,
826                                   "Compiler is missing.  Do nothing.\n")
827            self.finish(False)
828            return
829
830        cmd = list(self.make_cmd)
831        if self.cross_compile:
832            cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
833        cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
834        cmd.append('include/config/auto.conf')
835        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
836                                   stderr=subprocess.PIPE,
837                                   cwd=self.current_src_dir)
838        self.state = STATE_AUTOCONF
839
840    def do_savedefconfig(self):
841        """Update the .config and run 'make savedefconfig'."""
842
843        (updated, log) = self.parser.update_dotconfig()
844        self.log += log
845
846        if not self.options.force_sync and not updated:
847            self.finish(True)
848            return
849        if updated:
850            self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
851                                   "Syncing by savedefconfig...\n")
852        else:
853            self.log += "Syncing by savedefconfig (forced by option)...\n"
854
855        cmd = list(self.make_cmd)
856        cmd.append('savedefconfig')
857        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
858                                   stderr=subprocess.PIPE)
859        self.state = STATE_SAVEDEFCONFIG
860
861    def update_defconfig(self):
862        """Update the input defconfig and go back to the idle state."""
863
864        log = self.parser.check_defconfig()
865        if log:
866            self.suspicious_boards.append(self.defconfig)
867            self.log += log
868        orig_defconfig = os.path.join('configs', self.defconfig)
869        new_defconfig = os.path.join(self.build_dir, 'defconfig')
870        updated = not filecmp.cmp(orig_defconfig, new_defconfig)
871
872        if updated:
873            self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
874                                   "defconfig was updated.\n")
875
876        if not self.options.dry_run and updated:
877            shutil.move(new_defconfig, orig_defconfig)
878        self.finish(True)
879
880    def finish(self, success):
881        """Display log along with progress and go to the idle state.
882
883        Arguments:
884          success: Should be True when the defconfig was processed
885                   successfully, or False when it fails.
886        """
887        # output at least 30 characters to hide the "* defconfigs out of *".
888        log = self.defconfig.ljust(30) + '\n'
889
890        log += '\n'.join([ '    ' + s for s in self.log.split('\n') ])
891        # Some threads are running in parallel.
892        # Print log atomically to not mix up logs from different threads.
893        print >> (sys.stdout if success else sys.stderr), log
894
895        if not success:
896            if self.options.exit_on_error:
897                sys.exit("Exit on error.")
898            # If --exit-on-error flag is not set, skip this board and continue.
899            # Record the failed board.
900            self.failed_boards.append(self.defconfig)
901
902        self.progress.inc()
903        self.progress.show()
904        self.state = STATE_IDLE
905
906    def get_failed_boards(self):
907        """Returns a list of failed boards (defconfigs) in this slot.
908        """
909        return self.failed_boards
910
911    def get_suspicious_boards(self):
912        """Returns a list of boards (defconfigs) with possible misconversion.
913        """
914        return self.suspicious_boards
915
916class Slots:
917
918    """Controller of the array of subprocess slots."""
919
920    def __init__(self, configs, options, progress, reference_src_dir):
921        """Create a new slots controller.
922
923        Arguments:
924          configs: A list of CONFIGs to move.
925          options: option flags.
926          progress: A progress indicator.
927          reference_src_dir: Determine the true starting config state from this
928                             source tree.
929        """
930        self.options = options
931        self.slots = []
932        devnull = get_devnull()
933        make_cmd = get_make_cmd()
934        for i in range(options.jobs):
935            self.slots.append(Slot(configs, options, progress, devnull,
936                                   make_cmd, reference_src_dir))
937
938    def add(self, defconfig):
939        """Add a new subprocess if a vacant slot is found.
940
941        Arguments:
942          defconfig: defconfig name to be put into.
943
944        Returns:
945          Return True on success or False on failure
946        """
947        for slot in self.slots:
948            if slot.add(defconfig):
949                return True
950        return False
951
952    def available(self):
953        """Check if there is a vacant slot.
954
955        Returns:
956          Return True if at lease one vacant slot is found, False otherwise.
957        """
958        for slot in self.slots:
959            if slot.poll():
960                return True
961        return False
962
963    def empty(self):
964        """Check if all slots are vacant.
965
966        Returns:
967          Return True if all the slots are vacant, False otherwise.
968        """
969        ret = True
970        for slot in self.slots:
971            if not slot.poll():
972                ret = False
973        return ret
974
975    def show_failed_boards(self):
976        """Display all of the failed boards (defconfigs)."""
977        boards = []
978        output_file = 'moveconfig.failed'
979
980        for slot in self.slots:
981            boards += slot.get_failed_boards()
982
983        if boards:
984            boards = '\n'.join(boards) + '\n'
985            msg = "The following boards were not processed due to error:\n"
986            msg += boards
987            msg += "(the list has been saved in %s)\n" % output_file
988            print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
989                                            msg)
990
991            with open(output_file, 'w') as f:
992                f.write(boards)
993
994    def show_suspicious_boards(self):
995        """Display all boards (defconfigs) with possible misconversion."""
996        boards = []
997        output_file = 'moveconfig.suspicious'
998
999        for slot in self.slots:
1000            boards += slot.get_suspicious_boards()
1001
1002        if boards:
1003            boards = '\n'.join(boards) + '\n'
1004            msg = "The following boards might have been converted incorrectly.\n"
1005            msg += "It is highly recommended to check them manually:\n"
1006            msg += boards
1007            msg += "(the list has been saved in %s)\n" % output_file
1008            print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
1009                                            msg)
1010
1011            with open(output_file, 'w') as f:
1012                f.write(boards)
1013
1014class ReferenceSource:
1015
1016    """Reference source against which original configs should be parsed."""
1017
1018    def __init__(self, commit):
1019        """Create a reference source directory based on a specified commit.
1020
1021        Arguments:
1022          commit: commit to git-clone
1023        """
1024        self.src_dir = tempfile.mkdtemp()
1025        print "Cloning git repo to a separate work directory..."
1026        subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1027                                cwd=self.src_dir)
1028        print "Checkout '%s' to build the original autoconf.mk." % \
1029            subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
1030        subprocess.check_output(['git', 'checkout', commit],
1031                                stderr=subprocess.STDOUT, cwd=self.src_dir)
1032
1033    def __del__(self):
1034        """Delete the reference source directory
1035
1036        This function makes sure the temporary directory is cleaned away
1037        even if Python suddenly dies due to error.  It should be done in here
1038        because it is guaranteed the destructor is always invoked when the
1039        instance of the class gets unreferenced.
1040        """
1041        shutil.rmtree(self.src_dir)
1042
1043    def get_dir(self):
1044        """Return the absolute path to the reference source directory."""
1045
1046        return self.src_dir
1047
1048def move_config(configs, options):
1049    """Move config options to defconfig files.
1050
1051    Arguments:
1052      configs: A list of CONFIGs to move.
1053      options: option flags
1054    """
1055    if len(configs) == 0:
1056        if options.force_sync:
1057            print 'No CONFIG is specified. You are probably syncing defconfigs.',
1058        else:
1059            print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
1060    else:
1061        print 'Move ' + ', '.join(configs),
1062    print '(jobs: %d)\n' % options.jobs
1063
1064    if options.git_ref:
1065        reference_src = ReferenceSource(options.git_ref)
1066        reference_src_dir = reference_src.get_dir()
1067    else:
1068        reference_src_dir = None
1069
1070    if options.defconfigs:
1071        defconfigs = [line.strip() for line in open(options.defconfigs)]
1072        for i, defconfig in enumerate(defconfigs):
1073            if not defconfig.endswith('_defconfig'):
1074                defconfigs[i] = defconfig + '_defconfig'
1075            if not os.path.exists(os.path.join('configs', defconfigs[i])):
1076                sys.exit('%s - defconfig does not exist. Stopping.' %
1077                         defconfigs[i])
1078    else:
1079        # All the defconfig files to be processed
1080        defconfigs = []
1081        for (dirpath, dirnames, filenames) in os.walk('configs'):
1082            dirpath = dirpath[len('configs') + 1:]
1083            for filename in fnmatch.filter(filenames, '*_defconfig'):
1084                defconfigs.append(os.path.join(dirpath, filename))
1085
1086    progress = Progress(len(defconfigs))
1087    slots = Slots(configs, options, progress, reference_src_dir)
1088
1089    # Main loop to process defconfig files:
1090    #  Add a new subprocess into a vacant slot.
1091    #  Sleep if there is no available slot.
1092    for defconfig in defconfigs:
1093        while not slots.add(defconfig):
1094            while not slots.available():
1095                # No available slot: sleep for a while
1096                time.sleep(SLEEP_TIME)
1097
1098    # wait until all the subprocesses finish
1099    while not slots.empty():
1100        time.sleep(SLEEP_TIME)
1101
1102    print ''
1103    slots.show_failed_boards()
1104    slots.show_suspicious_boards()
1105
1106def main():
1107    try:
1108        cpu_count = multiprocessing.cpu_count()
1109    except NotImplementedError:
1110        cpu_count = 1
1111
1112    parser = optparse.OptionParser()
1113    # Add options here
1114    parser.add_option('-c', '--color', action='store_true', default=False,
1115                      help='display the log in color')
1116    parser.add_option('-d', '--defconfigs', type='string',
1117                      help='a file containing a list of defconfigs to move')
1118    parser.add_option('-n', '--dry-run', action='store_true', default=False,
1119                      help='perform a trial run (show log with no changes)')
1120    parser.add_option('-e', '--exit-on-error', action='store_true',
1121                      default=False,
1122                      help='exit immediately on any error')
1123    parser.add_option('-s', '--force-sync', action='store_true', default=False,
1124                      help='force sync by savedefconfig')
1125    parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1126                      action='store_true', default=False,
1127                      help='only cleanup the headers')
1128    parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1129                      help='the number of jobs to run simultaneously')
1130    parser.add_option('-r', '--git-ref', type='string',
1131                      help='the git ref to clone for building the autoconf.mk')
1132    parser.add_option('-v', '--verbose', action='store_true', default=False,
1133                      help='show any build errors as boards are built')
1134    parser.usage += ' CONFIG ...'
1135
1136    (options, configs) = parser.parse_args()
1137
1138    if len(configs) == 0 and not options.force_sync:
1139        parser.print_usage()
1140        sys.exit(1)
1141
1142    # prefix the option name with CONFIG_ if missing
1143    configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1144                for config in configs ]
1145
1146    check_top_directory()
1147
1148    if not options.cleanup_headers_only:
1149        check_clean_directory()
1150        update_cross_compile(options.color)
1151        move_config(configs, options)
1152
1153    if configs:
1154        cleanup_headers(configs, options)
1155
1156if __name__ == '__main__':
1157    main()
1158