xref: /rk3399_rockchip-uboot/tools/moveconfig.py (revision dc6de50bd6873deccea3c27f407bad8f987383e7)
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 filecmp
164import fnmatch
165import multiprocessing
166import optparse
167import os
168import re
169import shutil
170import subprocess
171import sys
172import tempfile
173import time
174
175SHOW_GNU_MAKE = 'scripts/show-gnu-make'
176SLEEP_TIME=0.03
177
178# Here is the list of cross-tools I use.
179# Most of them are available at kernel.org
180# (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the followings:
181# arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases
182# blackfin: http://sourceforge.net/projects/adi-toolchain/files/
183# nds32: http://osdk.andestech.com/packages/nds32le-linux-glibc-v1.tgz
184# nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545
185# sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu
186#
187# openrisc kernel.org toolchain is out of date, download latest one from
188# http://opencores.org/or1k/OpenRISC_GNU_tool_chain#Prebuilt_versions
189CROSS_COMPILE = {
190    'arc': 'arc-linux-',
191    'aarch64': 'aarch64-linux-',
192    'arm': 'arm-unknown-linux-gnueabi-',
193    'avr32': 'avr32-linux-',
194    'blackfin': 'bfin-elf-',
195    'm68k': 'm68k-linux-',
196    'microblaze': 'microblaze-linux-',
197    'mips': 'mips-linux-',
198    'nds32': 'nds32le-linux-',
199    'nios2': 'nios2-linux-gnu-',
200    'openrisc': 'or1k-elf-',
201    'powerpc': 'powerpc-linux-',
202    'sh': 'sh-linux-gnu-',
203    'sparc': 'sparc-linux-',
204    'x86': 'i386-linux-'
205}
206
207STATE_IDLE = 0
208STATE_DEFCONFIG = 1
209STATE_AUTOCONF = 2
210STATE_SAVEDEFCONFIG = 3
211
212ACTION_MOVE = 0
213ACTION_NO_ENTRY = 1
214ACTION_NO_CHANGE = 2
215
216COLOR_BLACK        = '0;30'
217COLOR_RED          = '0;31'
218COLOR_GREEN        = '0;32'
219COLOR_BROWN        = '0;33'
220COLOR_BLUE         = '0;34'
221COLOR_PURPLE       = '0;35'
222COLOR_CYAN         = '0;36'
223COLOR_LIGHT_GRAY   = '0;37'
224COLOR_DARK_GRAY    = '1;30'
225COLOR_LIGHT_RED    = '1;31'
226COLOR_LIGHT_GREEN  = '1;32'
227COLOR_YELLOW       = '1;33'
228COLOR_LIGHT_BLUE   = '1;34'
229COLOR_LIGHT_PURPLE = '1;35'
230COLOR_LIGHT_CYAN   = '1;36'
231COLOR_WHITE        = '1;37'
232
233### helper functions ###
234def get_devnull():
235    """Get the file object of '/dev/null' device."""
236    try:
237        devnull = subprocess.DEVNULL # py3k
238    except AttributeError:
239        devnull = open(os.devnull, 'wb')
240    return devnull
241
242def check_top_directory():
243    """Exit if we are not at the top of source directory."""
244    for f in ('README', 'Licenses'):
245        if not os.path.exists(f):
246            sys.exit('Please run at the top of source directory.')
247
248def check_clean_directory():
249    """Exit if the source tree is not clean."""
250    for f in ('.config', 'include/config'):
251        if os.path.exists(f):
252            sys.exit("source tree is not clean, please run 'make mrproper'")
253
254def get_make_cmd():
255    """Get the command name of GNU Make.
256
257    U-Boot needs GNU Make for building, but the command name is not
258    necessarily "make". (for example, "gmake" on FreeBSD).
259    Returns the most appropriate command name on your system.
260    """
261    process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
262    ret = process.communicate()
263    if process.returncode:
264        sys.exit('GNU Make not found')
265    return ret[0].rstrip()
266
267def color_text(color_enabled, color, string):
268    """Return colored string."""
269    if color_enabled:
270        # LF should not be surrounded by the escape sequence.
271        # Otherwise, additional whitespace or line-feed might be printed.
272        return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
273                           for s in string.split('\n') ])
274    else:
275        return string
276
277def update_cross_compile(color_enabled):
278    """Update per-arch CROSS_COMPILE via environment variables
279
280    The default CROSS_COMPILE values are available
281    in the CROSS_COMPILE list above.
282
283    You can override them via environment variables
284    CROSS_COMPILE_{ARCH}.
285
286    For example, if you want to override toolchain prefixes
287    for ARM and PowerPC, you can do as follows in your shell:
288
289    export CROSS_COMPILE_ARM=...
290    export CROSS_COMPILE_POWERPC=...
291
292    Then, this function checks if specified compilers really exist in your
293    PATH environment.
294    """
295    archs = []
296
297    for arch in os.listdir('arch'):
298        if os.path.exists(os.path.join('arch', arch, 'Makefile')):
299            archs.append(arch)
300
301    # arm64 is a special case
302    archs.append('aarch64')
303
304    for arch in archs:
305        env = 'CROSS_COMPILE_' + arch.upper()
306        cross_compile = os.environ.get(env)
307        if not cross_compile:
308            cross_compile = CROSS_COMPILE.get(arch, '')
309
310        for path in os.environ["PATH"].split(os.pathsep):
311            gcc_path = os.path.join(path, cross_compile + 'gcc')
312            if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
313                break
314        else:
315            print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
316                 'warning: %sgcc: not found in PATH.  %s architecture boards will be skipped'
317                                            % (cross_compile, arch))
318            cross_compile = None
319
320        CROSS_COMPILE[arch] = cross_compile
321
322def cleanup_one_header(header_path, patterns, dry_run):
323    """Clean regex-matched lines away from a file.
324
325    Arguments:
326      header_path: path to the cleaned file.
327      patterns: list of regex patterns.  Any lines matching to these
328                patterns are deleted.
329      dry_run: make no changes, but still display log.
330    """
331    with open(header_path) as f:
332        lines = f.readlines()
333
334    matched = []
335    for i, line in enumerate(lines):
336        for pattern in patterns:
337            m = pattern.search(line)
338            if m:
339                print '%s: %s: %s' % (header_path, i + 1, line),
340                matched.append(i)
341                break
342
343    if dry_run or not matched:
344        return
345
346    with open(header_path, 'w') as f:
347        for i, line in enumerate(lines):
348            if not i in matched:
349                f.write(line)
350
351def cleanup_headers(configs, dry_run):
352    """Delete config defines from board headers.
353
354    Arguments:
355      configs: A list of CONFIGs to remove.
356      dry_run: make no changes, but still display log.
357    """
358    while True:
359        choice = raw_input('Clean up headers? [y/n]: ').lower()
360        print choice
361        if choice == 'y' or choice == 'n':
362            break
363
364    if choice == 'n':
365        return
366
367    patterns = []
368    for config in configs:
369        patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
370        patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
371
372    for dir in 'include', 'arch', 'board':
373        for (dirpath, dirnames, filenames) in os.walk(dir):
374            if dirpath == os.path.join('include', 'generated'):
375                continue
376            for filename in filenames:
377                if not fnmatch.fnmatch(filename, '*~'):
378                    cleanup_one_header(os.path.join(dirpath, filename),
379                                       patterns, dry_run)
380
381### classes ###
382class Progress:
383
384    """Progress Indicator"""
385
386    def __init__(self, total):
387        """Create a new progress indicator.
388
389        Arguments:
390          total: A number of defconfig files to process.
391        """
392        self.current = 0
393        self.total = total
394
395    def inc(self):
396        """Increment the number of processed defconfig files."""
397
398        self.current += 1
399
400    def show(self):
401        """Display the progress."""
402        print ' %d defconfigs out of %d\r' % (self.current, self.total),
403        sys.stdout.flush()
404
405class KconfigParser:
406
407    """A parser of .config and include/autoconf.mk."""
408
409    re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
410    re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
411
412    def __init__(self, configs, options, build_dir):
413        """Create a new parser.
414
415        Arguments:
416          configs: A list of CONFIGs to move.
417          options: option flags.
418          build_dir: Build directory.
419        """
420        self.configs = configs
421        self.options = options
422        self.dotconfig = os.path.join(build_dir, '.config')
423        self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
424        self.config_autoconf = os.path.join(build_dir, 'include', 'config',
425                                            'auto.conf')
426        self.defconfig = os.path.join(build_dir, 'defconfig')
427
428    def get_cross_compile(self):
429        """Parse .config file and return CROSS_COMPILE.
430
431        Returns:
432          A string storing the compiler prefix for the architecture.
433          Return a NULL string for architectures that do not require
434          compiler prefix (Sandbox and native build is the case).
435          Return None if the specified compiler is missing in your PATH.
436          Caller should distinguish '' and None.
437        """
438        arch = ''
439        cpu = ''
440        for line in open(self.dotconfig):
441            m = self.re_arch.match(line)
442            if m:
443                arch = m.group(1)
444                continue
445            m = self.re_cpu.match(line)
446            if m:
447                cpu = m.group(1)
448
449        if not arch:
450            return None
451
452        # fix-up for aarch64
453        if arch == 'arm' and cpu == 'armv8':
454            arch = 'aarch64'
455
456        return CROSS_COMPILE.get(arch, None)
457
458    def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
459        """Parse .config, defconfig, include/autoconf.mk for one config.
460
461        This function looks for the config options in the lines from
462        defconfig, .config, and include/autoconf.mk in order to decide
463        which action should be taken for this defconfig.
464
465        Arguments:
466          config: CONFIG name to parse.
467          dotconfig_lines: lines from the .config file.
468          autoconf_lines: lines from the include/autoconf.mk file.
469
470        Returns:
471          A tupple of the action for this defconfig and the line
472          matched for the config.
473        """
474        not_set = '# %s is not set' % config
475
476        for line in dotconfig_lines:
477            line = line.rstrip()
478            if line.startswith(config + '=') or line == not_set:
479                old_val = line
480                break
481        else:
482            return (ACTION_NO_ENTRY, config)
483
484        for line in autoconf_lines:
485            line = line.rstrip()
486            if line.startswith(config + '='):
487                new_val = line
488                break
489        else:
490            new_val = not_set
491
492        # If this CONFIG is neither bool nor trisate
493        if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
494            # tools/scripts/define2mk.sed changes '1' to 'y'.
495            # This is a problem if the CONFIG is int type.
496            # Check the type in Kconfig and handle it correctly.
497            if new_val[-2:] == '=y':
498                new_val = new_val[:-1] + '1'
499
500        return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
501                new_val)
502
503    def update_dotconfig(self):
504        """Parse files for the config options and update the .config.
505
506        This function parses the generated .config and include/autoconf.mk
507        searching the target options.
508        Move the config option(s) to the .config as needed.
509
510        Arguments:
511          defconfig: defconfig name.
512
513        Returns:
514          Return a tuple of (updated flag, log string).
515          The "updated flag" is True if the .config was updated, False
516          otherwise.  The "log string" shows what happend to the .config.
517        """
518
519        results = []
520        updated = False
521
522        with open(self.dotconfig) as f:
523            dotconfig_lines = f.readlines()
524
525        with open(self.autoconf) as f:
526            autoconf_lines = f.readlines()
527
528        for config in self.configs:
529            result = self.parse_one_config(config, dotconfig_lines,
530                                           autoconf_lines)
531            results.append(result)
532
533        log = ''
534
535        for (action, value) in results:
536            if action == ACTION_MOVE:
537                actlog = "Move '%s'" % value
538                log_color = COLOR_LIGHT_GREEN
539            elif action == ACTION_NO_ENTRY:
540                actlog = "%s is not defined in Kconfig.  Do nothing." % value
541                log_color = COLOR_LIGHT_BLUE
542            elif action == ACTION_NO_CHANGE:
543                actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
544                         % value
545                log_color = COLOR_LIGHT_PURPLE
546            else:
547                sys.exit("Internal Error. This should not happen.")
548
549            log += color_text(self.options.color, log_color, actlog) + '\n'
550
551        with open(self.dotconfig, 'a') as f:
552            for (action, value) in results:
553                if action == ACTION_MOVE:
554                    f.write(value + '\n')
555                    updated = True
556
557        self.results = results
558        os.remove(self.config_autoconf)
559        os.remove(self.autoconf)
560
561        return (updated, log)
562
563    def check_defconfig(self):
564        """Check the defconfig after savedefconfig
565
566        Returns:
567          Return additional log if moved CONFIGs were removed again by
568          'make savedefconfig'.
569        """
570
571        log = ''
572
573        with open(self.defconfig) as f:
574            defconfig_lines = f.readlines()
575
576        for (action, value) in self.results:
577            if action != ACTION_MOVE:
578                continue
579            if not value + '\n' in defconfig_lines:
580                log += color_text(self.options.color, COLOR_YELLOW,
581                                  "'%s' was removed by savedefconfig.\n" %
582                                  value)
583
584        return log
585
586class Slot:
587
588    """A slot to store a subprocess.
589
590    Each instance of this class handles one subprocess.
591    This class is useful to control multiple threads
592    for faster processing.
593    """
594
595    def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir):
596        """Create a new process slot.
597
598        Arguments:
599          configs: A list of CONFIGs to move.
600          options: option flags.
601          progress: A progress indicator.
602          devnull: A file object of '/dev/null'.
603          make_cmd: command name of GNU Make.
604          reference_src_dir: Determine the true starting config state from this
605                             source tree.
606        """
607        self.options = options
608        self.progress = progress
609        self.build_dir = tempfile.mkdtemp()
610        self.devnull = devnull
611        self.make_cmd = (make_cmd, 'O=' + self.build_dir)
612        self.reference_src_dir = reference_src_dir
613        self.parser = KconfigParser(configs, options, self.build_dir)
614        self.state = STATE_IDLE
615        self.failed_boards = []
616        self.suspicious_boards = []
617
618    def __del__(self):
619        """Delete the working directory
620
621        This function makes sure the temporary directory is cleaned away
622        even if Python suddenly dies due to error.  It should be done in here
623        because it is guaranteed the destructor is always invoked when the
624        instance of the class gets unreferenced.
625
626        If the subprocess is still running, wait until it finishes.
627        """
628        if self.state != STATE_IDLE:
629            while self.ps.poll() == None:
630                pass
631        shutil.rmtree(self.build_dir)
632
633    def add(self, defconfig):
634        """Assign a new subprocess for defconfig and add it to the slot.
635
636        If the slot is vacant, create a new subprocess for processing the
637        given defconfig and add it to the slot.  Just returns False if
638        the slot is occupied (i.e. the current subprocess is still running).
639
640        Arguments:
641          defconfig: defconfig name.
642
643        Returns:
644          Return True on success or False on failure
645        """
646        if self.state != STATE_IDLE:
647            return False
648
649        self.defconfig = defconfig
650        self.log = ''
651        self.current_src_dir = self.reference_src_dir
652        self.do_defconfig()
653        return True
654
655    def poll(self):
656        """Check the status of the subprocess and handle it as needed.
657
658        Returns True if the slot is vacant (i.e. in idle state).
659        If the configuration is successfully finished, assign a new
660        subprocess to build include/autoconf.mk.
661        If include/autoconf.mk is generated, invoke the parser to
662        parse the .config and the include/autoconf.mk, moving
663        config options to the .config as needed.
664        If the .config was updated, run "make savedefconfig" to sync
665        it, update the original defconfig, and then set the slot back
666        to the idle state.
667
668        Returns:
669          Return True if the subprocess is terminated, False otherwise
670        """
671        if self.state == STATE_IDLE:
672            return True
673
674        if self.ps.poll() == None:
675            return False
676
677        if self.ps.poll() != 0:
678            self.handle_error()
679        elif self.state == STATE_DEFCONFIG:
680            if self.reference_src_dir and not self.current_src_dir:
681                self.do_savedefconfig()
682            else:
683                self.do_autoconf()
684        elif self.state == STATE_AUTOCONF:
685            if self.current_src_dir:
686                self.current_src_dir = None
687                self.do_defconfig()
688            else:
689                self.do_savedefconfig()
690        elif self.state == STATE_SAVEDEFCONFIG:
691            self.update_defconfig()
692        else:
693            sys.exit("Internal Error. This should not happen.")
694
695        return True if self.state == STATE_IDLE else False
696
697    def handle_error(self):
698        """Handle error cases."""
699
700        self.log += color_text(self.options.color, COLOR_LIGHT_RED,
701                               "Failed to process.\n")
702        if self.options.verbose:
703            self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
704                                   self.ps.stderr.read())
705        self.finish(False)
706
707    def do_defconfig(self):
708        """Run 'make <board>_defconfig' to create the .config file."""
709
710        cmd = list(self.make_cmd)
711        cmd.append(self.defconfig)
712        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
713                                   stderr=subprocess.PIPE,
714                                   cwd=self.current_src_dir)
715        self.state = STATE_DEFCONFIG
716
717    def do_autoconf(self):
718        """Run 'make include/config/auto.conf'."""
719
720        self.cross_compile = self.parser.get_cross_compile()
721        if self.cross_compile is None:
722            self.log += color_text(self.options.color, COLOR_YELLOW,
723                                   "Compiler is missing.  Do nothing.\n")
724            self.finish(False)
725            return
726
727        cmd = list(self.make_cmd)
728        if self.cross_compile:
729            cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
730        cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
731        cmd.append('include/config/auto.conf')
732        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
733                                   stderr=subprocess.PIPE,
734                                   cwd=self.current_src_dir)
735        self.state = STATE_AUTOCONF
736
737    def do_savedefconfig(self):
738        """Update the .config and run 'make savedefconfig'."""
739
740        (updated, log) = self.parser.update_dotconfig()
741        self.log += log
742
743        if not self.options.force_sync and not updated:
744            self.finish(True)
745            return
746        if updated:
747            self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
748                                   "Syncing by savedefconfig...\n")
749        else:
750            self.log += "Syncing by savedefconfig (forced by option)...\n"
751
752        cmd = list(self.make_cmd)
753        cmd.append('savedefconfig')
754        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
755                                   stderr=subprocess.PIPE)
756        self.state = STATE_SAVEDEFCONFIG
757
758    def update_defconfig(self):
759        """Update the input defconfig and go back to the idle state."""
760
761        log = self.parser.check_defconfig()
762        if log:
763            self.suspicious_boards.append(self.defconfig)
764            self.log += log
765        orig_defconfig = os.path.join('configs', self.defconfig)
766        new_defconfig = os.path.join(self.build_dir, 'defconfig')
767        updated = not filecmp.cmp(orig_defconfig, new_defconfig)
768
769        if updated:
770            self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
771                                   "defconfig was updated.\n")
772
773        if not self.options.dry_run and updated:
774            shutil.move(new_defconfig, orig_defconfig)
775        self.finish(True)
776
777    def finish(self, success):
778        """Display log along with progress and go to the idle state.
779
780        Arguments:
781          success: Should be True when the defconfig was processed
782                   successfully, or False when it fails.
783        """
784        # output at least 30 characters to hide the "* defconfigs out of *".
785        log = self.defconfig.ljust(30) + '\n'
786
787        log += '\n'.join([ '    ' + s for s in self.log.split('\n') ])
788        # Some threads are running in parallel.
789        # Print log atomically to not mix up logs from different threads.
790        print >> (sys.stdout if success else sys.stderr), log
791
792        if not success:
793            if self.options.exit_on_error:
794                sys.exit("Exit on error.")
795            # If --exit-on-error flag is not set, skip this board and continue.
796            # Record the failed board.
797            self.failed_boards.append(self.defconfig)
798
799        self.progress.inc()
800        self.progress.show()
801        self.state = STATE_IDLE
802
803    def get_failed_boards(self):
804        """Returns a list of failed boards (defconfigs) in this slot.
805        """
806        return self.failed_boards
807
808    def get_suspicious_boards(self):
809        """Returns a list of boards (defconfigs) with possible misconversion.
810        """
811        return self.suspicious_boards
812
813class Slots:
814
815    """Controller of the array of subprocess slots."""
816
817    def __init__(self, configs, options, progress, reference_src_dir):
818        """Create a new slots controller.
819
820        Arguments:
821          configs: A list of CONFIGs to move.
822          options: option flags.
823          progress: A progress indicator.
824          reference_src_dir: Determine the true starting config state from this
825                             source tree.
826        """
827        self.options = options
828        self.slots = []
829        devnull = get_devnull()
830        make_cmd = get_make_cmd()
831        for i in range(options.jobs):
832            self.slots.append(Slot(configs, options, progress, devnull,
833                                   make_cmd, reference_src_dir))
834
835    def add(self, defconfig):
836        """Add a new subprocess if a vacant slot is found.
837
838        Arguments:
839          defconfig: defconfig name to be put into.
840
841        Returns:
842          Return True on success or False on failure
843        """
844        for slot in self.slots:
845            if slot.add(defconfig):
846                return True
847        return False
848
849    def available(self):
850        """Check if there is a vacant slot.
851
852        Returns:
853          Return True if at lease one vacant slot is found, False otherwise.
854        """
855        for slot in self.slots:
856            if slot.poll():
857                return True
858        return False
859
860    def empty(self):
861        """Check if all slots are vacant.
862
863        Returns:
864          Return True if all the slots are vacant, False otherwise.
865        """
866        ret = True
867        for slot in self.slots:
868            if not slot.poll():
869                ret = False
870        return ret
871
872    def show_failed_boards(self):
873        """Display all of the failed boards (defconfigs)."""
874        boards = []
875        output_file = 'moveconfig.failed'
876
877        for slot in self.slots:
878            boards += slot.get_failed_boards()
879
880        if boards:
881            boards = '\n'.join(boards) + '\n'
882            msg = "The following boards were not processed due to error:\n"
883            msg += boards
884            msg += "(the list has been saved in %s)\n" % output_file
885            print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
886                                            msg)
887
888            with open(output_file, 'w') as f:
889                f.write(boards)
890
891    def show_suspicious_boards(self):
892        """Display all boards (defconfigs) with possible misconversion."""
893        boards = []
894        output_file = 'moveconfig.suspicious'
895
896        for slot in self.slots:
897            boards += slot.get_suspicious_boards()
898
899        if boards:
900            boards = '\n'.join(boards) + '\n'
901            msg = "The following boards might have been converted incorrectly.\n"
902            msg += "It is highly recommended to check them manually:\n"
903            msg += boards
904            msg += "(the list has been saved in %s)\n" % output_file
905            print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
906                                            msg)
907
908            with open(output_file, 'w') as f:
909                f.write(boards)
910
911class ReferenceSource:
912
913    """Reference source against which original configs should be parsed."""
914
915    def __init__(self, commit):
916        """Create a reference source directory based on a specified commit.
917
918        Arguments:
919          commit: commit to git-clone
920        """
921        self.src_dir = tempfile.mkdtemp()
922        print "Cloning git repo to a separate work directory..."
923        subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
924                                cwd=self.src_dir)
925        print "Checkout '%s' to build the original autoconf.mk." % \
926            subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
927        subprocess.check_output(['git', 'checkout', commit],
928                                stderr=subprocess.STDOUT, cwd=self.src_dir)
929
930    def __del__(self):
931        """Delete the reference source directory
932
933        This function makes sure the temporary directory is cleaned away
934        even if Python suddenly dies due to error.  It should be done in here
935        because it is guaranteed the destructor is always invoked when the
936        instance of the class gets unreferenced.
937        """
938        shutil.rmtree(self.src_dir)
939
940    def get_dir(self):
941        """Return the absolute path to the reference source directory."""
942
943        return self.src_dir
944
945def move_config(configs, options):
946    """Move config options to defconfig files.
947
948    Arguments:
949      configs: A list of CONFIGs to move.
950      options: option flags
951    """
952    if len(configs) == 0:
953        if options.force_sync:
954            print 'No CONFIG is specified. You are probably syncing defconfigs.',
955        else:
956            print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
957    else:
958        print 'Move ' + ', '.join(configs),
959    print '(jobs: %d)\n' % options.jobs
960
961    if options.git_ref:
962        reference_src = ReferenceSource(options.git_ref)
963        reference_src_dir = reference_src.get_dir()
964    else:
965        reference_src_dir = None
966
967    if options.defconfigs:
968        defconfigs = [line.strip() for line in open(options.defconfigs)]
969        for i, defconfig in enumerate(defconfigs):
970            if not defconfig.endswith('_defconfig'):
971                defconfigs[i] = defconfig + '_defconfig'
972            if not os.path.exists(os.path.join('configs', defconfigs[i])):
973                sys.exit('%s - defconfig does not exist. Stopping.' %
974                         defconfigs[i])
975    else:
976        # All the defconfig files to be processed
977        defconfigs = []
978        for (dirpath, dirnames, filenames) in os.walk('configs'):
979            dirpath = dirpath[len('configs') + 1:]
980            for filename in fnmatch.filter(filenames, '*_defconfig'):
981                defconfigs.append(os.path.join(dirpath, filename))
982
983    progress = Progress(len(defconfigs))
984    slots = Slots(configs, options, progress, reference_src_dir)
985
986    # Main loop to process defconfig files:
987    #  Add a new subprocess into a vacant slot.
988    #  Sleep if there is no available slot.
989    for defconfig in defconfigs:
990        while not slots.add(defconfig):
991            while not slots.available():
992                # No available slot: sleep for a while
993                time.sleep(SLEEP_TIME)
994
995    # wait until all the subprocesses finish
996    while not slots.empty():
997        time.sleep(SLEEP_TIME)
998
999    print ''
1000    slots.show_failed_boards()
1001    slots.show_suspicious_boards()
1002
1003def main():
1004    try:
1005        cpu_count = multiprocessing.cpu_count()
1006    except NotImplementedError:
1007        cpu_count = 1
1008
1009    parser = optparse.OptionParser()
1010    # Add options here
1011    parser.add_option('-c', '--color', action='store_true', default=False,
1012                      help='display the log in color')
1013    parser.add_option('-d', '--defconfigs', type='string',
1014                      help='a file containing a list of defconfigs to move')
1015    parser.add_option('-n', '--dry-run', action='store_true', default=False,
1016                      help='perform a trial run (show log with no changes)')
1017    parser.add_option('-e', '--exit-on-error', action='store_true',
1018                      default=False,
1019                      help='exit immediately on any error')
1020    parser.add_option('-s', '--force-sync', action='store_true', default=False,
1021                      help='force sync by savedefconfig')
1022    parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1023                      action='store_true', default=False,
1024                      help='only cleanup the headers')
1025    parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1026                      help='the number of jobs to run simultaneously')
1027    parser.add_option('-r', '--git-ref', type='string',
1028                      help='the git ref to clone for building the autoconf.mk')
1029    parser.add_option('-v', '--verbose', action='store_true', default=False,
1030                      help='show any build errors as boards are built')
1031    parser.usage += ' CONFIG ...'
1032
1033    (options, configs) = parser.parse_args()
1034
1035    if len(configs) == 0 and not options.force_sync:
1036        parser.print_usage()
1037        sys.exit(1)
1038
1039    # prefix the option name with CONFIG_ if missing
1040    configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1041                for config in configs ]
1042
1043    check_top_directory()
1044
1045    check_clean_directory()
1046
1047    update_cross_compile(options.color)
1048
1049    if not options.cleanup_headers_only:
1050        move_config(configs, options)
1051
1052    if configs:
1053        cleanup_headers(configs, options.dry_run)
1054
1055if __name__ == '__main__':
1056    main()
1057