xref: /rk3399_rockchip-uboot/tools/moveconfig.py (revision a3a779f7f7b5e48672b4e049beadc0112ff1c342)
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        if i - 1 in matched and lines[i - 1][-2:] == '\\\n':
412            matched.append(i)
413            continue
414        for pattern in patterns:
415            if pattern.search(line):
416                matched.append(i)
417                break
418
419    if not matched:
420        return
421
422    # remove empty #ifdef ... #endif, successive blank lines
423    pattern_if = re.compile(r'#\s*if(def|ndef)?\W') #  #if, #ifdef, #ifndef
424    pattern_elif = re.compile(r'#\s*el(if|se)\W')   #  #elif, #else
425    pattern_endif = re.compile(r'#\s*endif\W')      #  #endif
426    pattern_blank = re.compile(r'^\s*$')            #  empty line
427
428    while True:
429        old_matched = copy.copy(matched)
430        extend_matched_lines(lines, matched, [pattern_if],
431                             [pattern_endif], True, True)
432        extend_matched_lines(lines, matched, [pattern_elif],
433                             [pattern_elif, pattern_endif], True, False)
434        extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
435                             [pattern_blank], False, True)
436        extend_matched_lines(lines, matched, [pattern_blank],
437                             [pattern_elif, pattern_endif], True, False)
438        extend_matched_lines(lines, matched, [pattern_blank],
439                             [pattern_blank], True, False)
440        if matched == old_matched:
441            break
442
443    tolines = copy.copy(lines)
444
445    for i in reversed(matched):
446        tolines.pop(i)
447
448    show_diff(lines, tolines, header_path, options.color)
449
450    if options.dry_run:
451        return
452
453    with open(header_path, 'w') as f:
454        for line in tolines:
455            f.write(line)
456
457def cleanup_headers(configs, options):
458    """Delete config defines from board headers.
459
460    Arguments:
461      configs: A list of CONFIGs to remove.
462      options: option flags.
463    """
464    while True:
465        choice = raw_input('Clean up headers? [y/n]: ').lower()
466        print choice
467        if choice == 'y' or choice == 'n':
468            break
469
470    if choice == 'n':
471        return
472
473    patterns = []
474    for config in configs:
475        patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
476        patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
477
478    for dir in 'include', 'arch', 'board':
479        for (dirpath, dirnames, filenames) in os.walk(dir):
480            if dirpath == os.path.join('include', 'generated'):
481                continue
482            for filename in filenames:
483                if not fnmatch.fnmatch(filename, '*~'):
484                    cleanup_one_header(os.path.join(dirpath, filename),
485                                       patterns, options)
486
487### classes ###
488class Progress:
489
490    """Progress Indicator"""
491
492    def __init__(self, total):
493        """Create a new progress indicator.
494
495        Arguments:
496          total: A number of defconfig files to process.
497        """
498        self.current = 0
499        self.total = total
500
501    def inc(self):
502        """Increment the number of processed defconfig files."""
503
504        self.current += 1
505
506    def show(self):
507        """Display the progress."""
508        print ' %d defconfigs out of %d\r' % (self.current, self.total),
509        sys.stdout.flush()
510
511class KconfigParser:
512
513    """A parser of .config and include/autoconf.mk."""
514
515    re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
516    re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
517
518    def __init__(self, configs, options, build_dir):
519        """Create a new parser.
520
521        Arguments:
522          configs: A list of CONFIGs to move.
523          options: option flags.
524          build_dir: Build directory.
525        """
526        self.configs = configs
527        self.options = options
528        self.dotconfig = os.path.join(build_dir, '.config')
529        self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
530        self.config_autoconf = os.path.join(build_dir, 'include', 'config',
531                                            'auto.conf')
532        self.defconfig = os.path.join(build_dir, 'defconfig')
533
534    def get_cross_compile(self):
535        """Parse .config file and return CROSS_COMPILE.
536
537        Returns:
538          A string storing the compiler prefix for the architecture.
539          Return a NULL string for architectures that do not require
540          compiler prefix (Sandbox and native build is the case).
541          Return None if the specified compiler is missing in your PATH.
542          Caller should distinguish '' and None.
543        """
544        arch = ''
545        cpu = ''
546        for line in open(self.dotconfig):
547            m = self.re_arch.match(line)
548            if m:
549                arch = m.group(1)
550                continue
551            m = self.re_cpu.match(line)
552            if m:
553                cpu = m.group(1)
554
555        if not arch:
556            return None
557
558        # fix-up for aarch64
559        if arch == 'arm' and cpu == 'armv8':
560            arch = 'aarch64'
561
562        return CROSS_COMPILE.get(arch, None)
563
564    def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
565        """Parse .config, defconfig, include/autoconf.mk for one config.
566
567        This function looks for the config options in the lines from
568        defconfig, .config, and include/autoconf.mk in order to decide
569        which action should be taken for this defconfig.
570
571        Arguments:
572          config: CONFIG name to parse.
573          dotconfig_lines: lines from the .config file.
574          autoconf_lines: lines from the include/autoconf.mk file.
575
576        Returns:
577          A tupple of the action for this defconfig and the line
578          matched for the config.
579        """
580        not_set = '# %s is not set' % config
581
582        for line in dotconfig_lines:
583            line = line.rstrip()
584            if line.startswith(config + '=') or line == not_set:
585                old_val = line
586                break
587        else:
588            return (ACTION_NO_ENTRY, config)
589
590        for line in autoconf_lines:
591            line = line.rstrip()
592            if line.startswith(config + '='):
593                new_val = line
594                break
595        else:
596            new_val = not_set
597
598        # If this CONFIG is neither bool nor trisate
599        if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
600            # tools/scripts/define2mk.sed changes '1' to 'y'.
601            # This is a problem if the CONFIG is int type.
602            # Check the type in Kconfig and handle it correctly.
603            if new_val[-2:] == '=y':
604                new_val = new_val[:-1] + '1'
605
606        return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
607                new_val)
608
609    def update_dotconfig(self):
610        """Parse files for the config options and update the .config.
611
612        This function parses the generated .config and include/autoconf.mk
613        searching the target options.
614        Move the config option(s) to the .config as needed.
615
616        Arguments:
617          defconfig: defconfig name.
618
619        Returns:
620          Return a tuple of (updated flag, log string).
621          The "updated flag" is True if the .config was updated, False
622          otherwise.  The "log string" shows what happend to the .config.
623        """
624
625        results = []
626        updated = False
627
628        with open(self.dotconfig) as f:
629            dotconfig_lines = f.readlines()
630
631        with open(self.autoconf) as f:
632            autoconf_lines = f.readlines()
633
634        for config in self.configs:
635            result = self.parse_one_config(config, dotconfig_lines,
636                                           autoconf_lines)
637            results.append(result)
638
639        log = ''
640
641        for (action, value) in results:
642            if action == ACTION_MOVE:
643                actlog = "Move '%s'" % value
644                log_color = COLOR_LIGHT_GREEN
645            elif action == ACTION_NO_ENTRY:
646                actlog = "%s is not defined in Kconfig.  Do nothing." % value
647                log_color = COLOR_LIGHT_BLUE
648            elif action == ACTION_NO_CHANGE:
649                actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
650                         % value
651                log_color = COLOR_LIGHT_PURPLE
652            else:
653                sys.exit("Internal Error. This should not happen.")
654
655            log += color_text(self.options.color, log_color, actlog) + '\n'
656
657        with open(self.dotconfig, 'a') as f:
658            for (action, value) in results:
659                if action == ACTION_MOVE:
660                    f.write(value + '\n')
661                    updated = True
662
663        self.results = results
664        os.remove(self.config_autoconf)
665        os.remove(self.autoconf)
666
667        return (updated, log)
668
669    def check_defconfig(self):
670        """Check the defconfig after savedefconfig
671
672        Returns:
673          Return additional log if moved CONFIGs were removed again by
674          'make savedefconfig'.
675        """
676
677        log = ''
678
679        with open(self.defconfig) as f:
680            defconfig_lines = f.readlines()
681
682        for (action, value) in self.results:
683            if action != ACTION_MOVE:
684                continue
685            if not value + '\n' in defconfig_lines:
686                log += color_text(self.options.color, COLOR_YELLOW,
687                                  "'%s' was removed by savedefconfig.\n" %
688                                  value)
689
690        return log
691
692class Slot:
693
694    """A slot to store a subprocess.
695
696    Each instance of this class handles one subprocess.
697    This class is useful to control multiple threads
698    for faster processing.
699    """
700
701    def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir):
702        """Create a new process slot.
703
704        Arguments:
705          configs: A list of CONFIGs to move.
706          options: option flags.
707          progress: A progress indicator.
708          devnull: A file object of '/dev/null'.
709          make_cmd: command name of GNU Make.
710          reference_src_dir: Determine the true starting config state from this
711                             source tree.
712        """
713        self.options = options
714        self.progress = progress
715        self.build_dir = tempfile.mkdtemp()
716        self.devnull = devnull
717        self.make_cmd = (make_cmd, 'O=' + self.build_dir)
718        self.reference_src_dir = reference_src_dir
719        self.parser = KconfigParser(configs, options, self.build_dir)
720        self.state = STATE_IDLE
721        self.failed_boards = []
722        self.suspicious_boards = []
723
724    def __del__(self):
725        """Delete the working directory
726
727        This function makes sure the temporary directory is cleaned away
728        even if Python suddenly dies due to error.  It should be done in here
729        because it is guaranteed the destructor is always invoked when the
730        instance of the class gets unreferenced.
731
732        If the subprocess is still running, wait until it finishes.
733        """
734        if self.state != STATE_IDLE:
735            while self.ps.poll() == None:
736                pass
737        shutil.rmtree(self.build_dir)
738
739    def add(self, defconfig):
740        """Assign a new subprocess for defconfig and add it to the slot.
741
742        If the slot is vacant, create a new subprocess for processing the
743        given defconfig and add it to the slot.  Just returns False if
744        the slot is occupied (i.e. the current subprocess is still running).
745
746        Arguments:
747          defconfig: defconfig name.
748
749        Returns:
750          Return True on success or False on failure
751        """
752        if self.state != STATE_IDLE:
753            return False
754
755        self.defconfig = defconfig
756        self.log = ''
757        self.current_src_dir = self.reference_src_dir
758        self.do_defconfig()
759        return True
760
761    def poll(self):
762        """Check the status of the subprocess and handle it as needed.
763
764        Returns True if the slot is vacant (i.e. in idle state).
765        If the configuration is successfully finished, assign a new
766        subprocess to build include/autoconf.mk.
767        If include/autoconf.mk is generated, invoke the parser to
768        parse the .config and the include/autoconf.mk, moving
769        config options to the .config as needed.
770        If the .config was updated, run "make savedefconfig" to sync
771        it, update the original defconfig, and then set the slot back
772        to the idle state.
773
774        Returns:
775          Return True if the subprocess is terminated, False otherwise
776        """
777        if self.state == STATE_IDLE:
778            return True
779
780        if self.ps.poll() == None:
781            return False
782
783        if self.ps.poll() != 0:
784            self.handle_error()
785        elif self.state == STATE_DEFCONFIG:
786            if self.reference_src_dir and not self.current_src_dir:
787                self.do_savedefconfig()
788            else:
789                self.do_autoconf()
790        elif self.state == STATE_AUTOCONF:
791            if self.current_src_dir:
792                self.current_src_dir = None
793                self.do_defconfig()
794            else:
795                self.do_savedefconfig()
796        elif self.state == STATE_SAVEDEFCONFIG:
797            self.update_defconfig()
798        else:
799            sys.exit("Internal Error. This should not happen.")
800
801        return True if self.state == STATE_IDLE else False
802
803    def handle_error(self):
804        """Handle error cases."""
805
806        self.log += color_text(self.options.color, COLOR_LIGHT_RED,
807                               "Failed to process.\n")
808        if self.options.verbose:
809            self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
810                                   self.ps.stderr.read())
811        self.finish(False)
812
813    def do_defconfig(self):
814        """Run 'make <board>_defconfig' to create the .config file."""
815
816        cmd = list(self.make_cmd)
817        cmd.append(self.defconfig)
818        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
819                                   stderr=subprocess.PIPE,
820                                   cwd=self.current_src_dir)
821        self.state = STATE_DEFCONFIG
822
823    def do_autoconf(self):
824        """Run 'make include/config/auto.conf'."""
825
826        self.cross_compile = self.parser.get_cross_compile()
827        if self.cross_compile is None:
828            self.log += color_text(self.options.color, COLOR_YELLOW,
829                                   "Compiler is missing.  Do nothing.\n")
830            self.finish(False)
831            return
832
833        cmd = list(self.make_cmd)
834        if self.cross_compile:
835            cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
836        cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
837        cmd.append('include/config/auto.conf')
838        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
839                                   stderr=subprocess.PIPE,
840                                   cwd=self.current_src_dir)
841        self.state = STATE_AUTOCONF
842
843    def do_savedefconfig(self):
844        """Update the .config and run 'make savedefconfig'."""
845
846        (updated, log) = self.parser.update_dotconfig()
847        self.log += log
848
849        if not self.options.force_sync and not updated:
850            self.finish(True)
851            return
852        if updated:
853            self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
854                                   "Syncing by savedefconfig...\n")
855        else:
856            self.log += "Syncing by savedefconfig (forced by option)...\n"
857
858        cmd = list(self.make_cmd)
859        cmd.append('savedefconfig')
860        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
861                                   stderr=subprocess.PIPE)
862        self.state = STATE_SAVEDEFCONFIG
863
864    def update_defconfig(self):
865        """Update the input defconfig and go back to the idle state."""
866
867        log = self.parser.check_defconfig()
868        if log:
869            self.suspicious_boards.append(self.defconfig)
870            self.log += log
871        orig_defconfig = os.path.join('configs', self.defconfig)
872        new_defconfig = os.path.join(self.build_dir, 'defconfig')
873        updated = not filecmp.cmp(orig_defconfig, new_defconfig)
874
875        if updated:
876            self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
877                                   "defconfig was updated.\n")
878
879        if not self.options.dry_run and updated:
880            shutil.move(new_defconfig, orig_defconfig)
881        self.finish(True)
882
883    def finish(self, success):
884        """Display log along with progress and go to the idle state.
885
886        Arguments:
887          success: Should be True when the defconfig was processed
888                   successfully, or False when it fails.
889        """
890        # output at least 30 characters to hide the "* defconfigs out of *".
891        log = self.defconfig.ljust(30) + '\n'
892
893        log += '\n'.join([ '    ' + s for s in self.log.split('\n') ])
894        # Some threads are running in parallel.
895        # Print log atomically to not mix up logs from different threads.
896        print >> (sys.stdout if success else sys.stderr), log
897
898        if not success:
899            if self.options.exit_on_error:
900                sys.exit("Exit on error.")
901            # If --exit-on-error flag is not set, skip this board and continue.
902            # Record the failed board.
903            self.failed_boards.append(self.defconfig)
904
905        self.progress.inc()
906        self.progress.show()
907        self.state = STATE_IDLE
908
909    def get_failed_boards(self):
910        """Returns a list of failed boards (defconfigs) in this slot.
911        """
912        return self.failed_boards
913
914    def get_suspicious_boards(self):
915        """Returns a list of boards (defconfigs) with possible misconversion.
916        """
917        return self.suspicious_boards
918
919class Slots:
920
921    """Controller of the array of subprocess slots."""
922
923    def __init__(self, configs, options, progress, reference_src_dir):
924        """Create a new slots controller.
925
926        Arguments:
927          configs: A list of CONFIGs to move.
928          options: option flags.
929          progress: A progress indicator.
930          reference_src_dir: Determine the true starting config state from this
931                             source tree.
932        """
933        self.options = options
934        self.slots = []
935        devnull = get_devnull()
936        make_cmd = get_make_cmd()
937        for i in range(options.jobs):
938            self.slots.append(Slot(configs, options, progress, devnull,
939                                   make_cmd, reference_src_dir))
940
941    def add(self, defconfig):
942        """Add a new subprocess if a vacant slot is found.
943
944        Arguments:
945          defconfig: defconfig name to be put into.
946
947        Returns:
948          Return True on success or False on failure
949        """
950        for slot in self.slots:
951            if slot.add(defconfig):
952                return True
953        return False
954
955    def available(self):
956        """Check if there is a vacant slot.
957
958        Returns:
959          Return True if at lease one vacant slot is found, False otherwise.
960        """
961        for slot in self.slots:
962            if slot.poll():
963                return True
964        return False
965
966    def empty(self):
967        """Check if all slots are vacant.
968
969        Returns:
970          Return True if all the slots are vacant, False otherwise.
971        """
972        ret = True
973        for slot in self.slots:
974            if not slot.poll():
975                ret = False
976        return ret
977
978    def show_failed_boards(self):
979        """Display all of the failed boards (defconfigs)."""
980        boards = []
981        output_file = 'moveconfig.failed'
982
983        for slot in self.slots:
984            boards += slot.get_failed_boards()
985
986        if boards:
987            boards = '\n'.join(boards) + '\n'
988            msg = "The following boards were not processed due to error:\n"
989            msg += boards
990            msg += "(the list has been saved in %s)\n" % output_file
991            print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
992                                            msg)
993
994            with open(output_file, 'w') as f:
995                f.write(boards)
996
997    def show_suspicious_boards(self):
998        """Display all boards (defconfigs) with possible misconversion."""
999        boards = []
1000        output_file = 'moveconfig.suspicious'
1001
1002        for slot in self.slots:
1003            boards += slot.get_suspicious_boards()
1004
1005        if boards:
1006            boards = '\n'.join(boards) + '\n'
1007            msg = "The following boards might have been converted incorrectly.\n"
1008            msg += "It is highly recommended to check them manually:\n"
1009            msg += boards
1010            msg += "(the list has been saved in %s)\n" % output_file
1011            print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
1012                                            msg)
1013
1014            with open(output_file, 'w') as f:
1015                f.write(boards)
1016
1017class ReferenceSource:
1018
1019    """Reference source against which original configs should be parsed."""
1020
1021    def __init__(self, commit):
1022        """Create a reference source directory based on a specified commit.
1023
1024        Arguments:
1025          commit: commit to git-clone
1026        """
1027        self.src_dir = tempfile.mkdtemp()
1028        print "Cloning git repo to a separate work directory..."
1029        subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1030                                cwd=self.src_dir)
1031        print "Checkout '%s' to build the original autoconf.mk." % \
1032            subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
1033        subprocess.check_output(['git', 'checkout', commit],
1034                                stderr=subprocess.STDOUT, cwd=self.src_dir)
1035
1036    def __del__(self):
1037        """Delete the reference source directory
1038
1039        This function makes sure the temporary directory is cleaned away
1040        even if Python suddenly dies due to error.  It should be done in here
1041        because it is guaranteed the destructor is always invoked when the
1042        instance of the class gets unreferenced.
1043        """
1044        shutil.rmtree(self.src_dir)
1045
1046    def get_dir(self):
1047        """Return the absolute path to the reference source directory."""
1048
1049        return self.src_dir
1050
1051def move_config(configs, options):
1052    """Move config options to defconfig files.
1053
1054    Arguments:
1055      configs: A list of CONFIGs to move.
1056      options: option flags
1057    """
1058    if len(configs) == 0:
1059        if options.force_sync:
1060            print 'No CONFIG is specified. You are probably syncing defconfigs.',
1061        else:
1062            print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
1063    else:
1064        print 'Move ' + ', '.join(configs),
1065    print '(jobs: %d)\n' % options.jobs
1066
1067    if options.git_ref:
1068        reference_src = ReferenceSource(options.git_ref)
1069        reference_src_dir = reference_src.get_dir()
1070    else:
1071        reference_src_dir = None
1072
1073    if options.defconfigs:
1074        defconfigs = [line.strip() for line in open(options.defconfigs)]
1075        for i, defconfig in enumerate(defconfigs):
1076            if not defconfig.endswith('_defconfig'):
1077                defconfigs[i] = defconfig + '_defconfig'
1078            if not os.path.exists(os.path.join('configs', defconfigs[i])):
1079                sys.exit('%s - defconfig does not exist. Stopping.' %
1080                         defconfigs[i])
1081    else:
1082        # All the defconfig files to be processed
1083        defconfigs = []
1084        for (dirpath, dirnames, filenames) in os.walk('configs'):
1085            dirpath = dirpath[len('configs') + 1:]
1086            for filename in fnmatch.filter(filenames, '*_defconfig'):
1087                defconfigs.append(os.path.join(dirpath, filename))
1088
1089    progress = Progress(len(defconfigs))
1090    slots = Slots(configs, options, progress, reference_src_dir)
1091
1092    # Main loop to process defconfig files:
1093    #  Add a new subprocess into a vacant slot.
1094    #  Sleep if there is no available slot.
1095    for defconfig in defconfigs:
1096        while not slots.add(defconfig):
1097            while not slots.available():
1098                # No available slot: sleep for a while
1099                time.sleep(SLEEP_TIME)
1100
1101    # wait until all the subprocesses finish
1102    while not slots.empty():
1103        time.sleep(SLEEP_TIME)
1104
1105    print ''
1106    slots.show_failed_boards()
1107    slots.show_suspicious_boards()
1108
1109def main():
1110    try:
1111        cpu_count = multiprocessing.cpu_count()
1112    except NotImplementedError:
1113        cpu_count = 1
1114
1115    parser = optparse.OptionParser()
1116    # Add options here
1117    parser.add_option('-c', '--color', action='store_true', default=False,
1118                      help='display the log in color')
1119    parser.add_option('-d', '--defconfigs', type='string',
1120                      help='a file containing a list of defconfigs to move')
1121    parser.add_option('-n', '--dry-run', action='store_true', default=False,
1122                      help='perform a trial run (show log with no changes)')
1123    parser.add_option('-e', '--exit-on-error', action='store_true',
1124                      default=False,
1125                      help='exit immediately on any error')
1126    parser.add_option('-s', '--force-sync', action='store_true', default=False,
1127                      help='force sync by savedefconfig')
1128    parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1129                      action='store_true', default=False,
1130                      help='only cleanup the headers')
1131    parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1132                      help='the number of jobs to run simultaneously')
1133    parser.add_option('-r', '--git-ref', type='string',
1134                      help='the git ref to clone for building the autoconf.mk')
1135    parser.add_option('-v', '--verbose', action='store_true', default=False,
1136                      help='show any build errors as boards are built')
1137    parser.usage += ' CONFIG ...'
1138
1139    (options, configs) = parser.parse_args()
1140
1141    if len(configs) == 0 and not options.force_sync:
1142        parser.print_usage()
1143        sys.exit(1)
1144
1145    # prefix the option name with CONFIG_ if missing
1146    configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1147                for config in configs ]
1148
1149    check_top_directory()
1150
1151    if not options.cleanup_headers_only:
1152        check_clean_directory()
1153        update_cross_compile(options.color)
1154        move_config(configs, options)
1155
1156    if configs:
1157        cleanup_headers(configs, options)
1158
1159if __name__ == '__main__':
1160    main()
1161