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