xref: /openbmc/u-boot/tools/moveconfig.py (revision 0f67e239)
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 old_val == new_val:
491            return (ACTION_NO_CHANGE, new_val)
492
493        # If this CONFIG is neither bool nor trisate
494        if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
495            # tools/scripts/define2mk.sed changes '1' to 'y'.
496            # This is a problem if the CONFIG is int type.
497            # Check the type in Kconfig and handle it correctly.
498            if new_val[-2:] == '=y':
499                new_val = new_val[:-1] + '1'
500
501        return (ACTION_MOVE, 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
617    def __del__(self):
618        """Delete the working directory
619
620        This function makes sure the temporary directory is cleaned away
621        even if Python suddenly dies due to error.  It should be done in here
622        because it is guaranteed the destructor is always invoked when the
623        instance of the class gets unreferenced.
624
625        If the subprocess is still running, wait until it finishes.
626        """
627        if self.state != STATE_IDLE:
628            while self.ps.poll() == None:
629                pass
630        shutil.rmtree(self.build_dir)
631
632    def add(self, defconfig):
633        """Assign a new subprocess for defconfig and add it to the slot.
634
635        If the slot is vacant, create a new subprocess for processing the
636        given defconfig and add it to the slot.  Just returns False if
637        the slot is occupied (i.e. the current subprocess is still running).
638
639        Arguments:
640          defconfig: defconfig name.
641
642        Returns:
643          Return True on success or False on failure
644        """
645        if self.state != STATE_IDLE:
646            return False
647
648        self.defconfig = defconfig
649        self.log = ''
650        self.use_git_ref = True if self.options.git_ref else False
651        self.do_defconfig()
652        return True
653
654    def poll(self):
655        """Check the status of the subprocess and handle it as needed.
656
657        Returns True if the slot is vacant (i.e. in idle state).
658        If the configuration is successfully finished, assign a new
659        subprocess to build include/autoconf.mk.
660        If include/autoconf.mk is generated, invoke the parser to
661        parse the .config and the include/autoconf.mk, moving
662        config options to the .config as needed.
663        If the .config was updated, run "make savedefconfig" to sync
664        it, update the original defconfig, and then set the slot back
665        to the idle state.
666
667        Returns:
668          Return True if the subprocess is terminated, False otherwise
669        """
670        if self.state == STATE_IDLE:
671            return True
672
673        if self.ps.poll() == None:
674            return False
675
676        if self.ps.poll() != 0:
677            self.handle_error()
678        elif self.state == STATE_DEFCONFIG:
679            if self.options.git_ref and not self.use_git_ref:
680                self.do_savedefconfig()
681            else:
682                self.do_autoconf()
683        elif self.state == STATE_AUTOCONF:
684            if self.use_git_ref:
685                self.use_git_ref = False
686                self.do_defconfig()
687            else:
688                self.do_savedefconfig()
689        elif self.state == STATE_SAVEDEFCONFIG:
690            self.update_defconfig()
691        else:
692            sys.exit("Internal Error. This should not happen.")
693
694        return True if self.state == STATE_IDLE else False
695
696    def handle_error(self):
697        """Handle error cases."""
698
699        self.log += color_text(self.options.color, COLOR_LIGHT_RED,
700                               "Failed to process.\n")
701        if self.options.verbose:
702            self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
703                                   self.ps.stderr.read())
704        self.finish(False)
705
706    def do_defconfig(self):
707        """Run 'make <board>_defconfig' to create the .config file."""
708
709        cmd = list(self.make_cmd)
710        cmd.append(self.defconfig)
711        if self.use_git_ref:
712            cmd.append('-C')
713            cmd.append(self.reference_src_dir)
714        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
715                                   stderr=subprocess.PIPE)
716        self.state = STATE_DEFCONFIG
717
718    def do_autoconf(self):
719        """Run 'make include/config/auto.conf'."""
720
721        self.cross_compile = self.parser.get_cross_compile()
722        if self.cross_compile is None:
723            self.log += color_text(self.options.color, COLOR_YELLOW,
724                                   "Compiler is missing.  Do nothing.\n")
725            self.finish(False)
726            return
727
728        cmd = list(self.make_cmd)
729        if self.cross_compile:
730            cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
731        cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
732        cmd.append('include/config/auto.conf')
733        if self.use_git_ref:
734            cmd.append('-C')
735            cmd.append(self.reference_src_dir)
736        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
737                                   stderr=subprocess.PIPE)
738        self.state = STATE_AUTOCONF
739
740    def do_savedefconfig(self):
741        """Update the .config and run 'make savedefconfig'."""
742
743        (updated, log) = self.parser.update_dotconfig()
744        self.log += log
745
746        if not self.options.force_sync and not updated:
747            self.finish(True)
748            return
749        if updated:
750            self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
751                                   "Syncing by savedefconfig...\n")
752        else:
753            self.log += "Syncing by savedefconfig (forced by option)...\n"
754
755        cmd = list(self.make_cmd)
756        cmd.append('savedefconfig')
757        self.ps = subprocess.Popen(cmd, stdout=self.devnull,
758                                   stderr=subprocess.PIPE)
759        self.state = STATE_SAVEDEFCONFIG
760
761    def update_defconfig(self):
762        """Update the input defconfig and go back to the idle state."""
763
764        self.log += self.parser.check_defconfig()
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
808class Slots:
809
810    """Controller of the array of subprocess slots."""
811
812    def __init__(self, configs, options, progress, reference_src_dir):
813        """Create a new slots controller.
814
815        Arguments:
816          configs: A list of CONFIGs to move.
817          options: option flags.
818          progress: A progress indicator.
819          reference_src_dir: Determine the true starting config state from this
820                             source tree.
821        """
822        self.options = options
823        self.slots = []
824        devnull = get_devnull()
825        make_cmd = get_make_cmd()
826        for i in range(options.jobs):
827            self.slots.append(Slot(configs, options, progress, devnull,
828                                   make_cmd, reference_src_dir))
829
830    def add(self, defconfig):
831        """Add a new subprocess if a vacant slot is found.
832
833        Arguments:
834          defconfig: defconfig name to be put into.
835
836        Returns:
837          Return True on success or False on failure
838        """
839        for slot in self.slots:
840            if slot.add(defconfig):
841                return True
842        return False
843
844    def available(self):
845        """Check if there is a vacant slot.
846
847        Returns:
848          Return True if at lease one vacant slot is found, False otherwise.
849        """
850        for slot in self.slots:
851            if slot.poll():
852                return True
853        return False
854
855    def empty(self):
856        """Check if all slots are vacant.
857
858        Returns:
859          Return True if all the slots are vacant, False otherwise.
860        """
861        ret = True
862        for slot in self.slots:
863            if not slot.poll():
864                ret = False
865        return ret
866
867    def show_failed_boards(self):
868        """Display all of the failed boards (defconfigs)."""
869        failed_boards = []
870
871        for slot in self.slots:
872            failed_boards += slot.get_failed_boards()
873
874        if len(failed_boards) > 0:
875            msg = [ "The following boards were not processed due to error:" ]
876            msg += failed_boards
877            for line in msg:
878                print >> sys.stderr, color_text(self.options.color,
879                                                COLOR_LIGHT_RED, line)
880
881            with open('moveconfig.failed', 'w') as f:
882                for board in failed_boards:
883                    f.write(board + '\n')
884
885class WorkDir:
886    def __init__(self):
887        """Create a new working directory."""
888        self.work_dir = tempfile.mkdtemp()
889
890    def __del__(self):
891        """Delete the working directory
892
893        This function makes sure the temporary directory is cleaned away
894        even if Python suddenly dies due to error.  It should be done in here
895        because it is guaranteed the destructor is always invoked when the
896        instance of the class gets unreferenced.
897        """
898        shutil.rmtree(self.work_dir)
899
900    def get(self):
901        return self.work_dir
902
903def move_config(configs, options):
904    """Move config options to defconfig files.
905
906    Arguments:
907      configs: A list of CONFIGs to move.
908      options: option flags
909    """
910    if len(configs) == 0:
911        if options.force_sync:
912            print 'No CONFIG is specified. You are probably syncing defconfigs.',
913        else:
914            print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
915    else:
916        print 'Move ' + ', '.join(configs),
917    print '(jobs: %d)\n' % options.jobs
918
919    reference_src_dir = ''
920
921    if options.git_ref:
922        work_dir = WorkDir()
923        reference_src_dir = work_dir.get()
924        print "Cloning git repo to a separate work directory..."
925        subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
926                                cwd=reference_src_dir)
927        print "Checkout '%s' to build the original autoconf.mk." % \
928            subprocess.check_output(['git', 'rev-parse', '--short',
929                                    options.git_ref]).strip()
930        subprocess.check_output(['git', 'checkout', options.git_ref],
931                                stderr=subprocess.STDOUT,
932                                cwd=reference_src_dir)
933
934    if options.defconfigs:
935        defconfigs = [line.strip() for line in open(options.defconfigs)]
936        for i, defconfig in enumerate(defconfigs):
937            if not defconfig.endswith('_defconfig'):
938                defconfigs[i] = defconfig + '_defconfig'
939            if not os.path.exists(os.path.join('configs', defconfigs[i])):
940                sys.exit('%s - defconfig does not exist. Stopping.' %
941                         defconfigs[i])
942    else:
943        # All the defconfig files to be processed
944        defconfigs = []
945        for (dirpath, dirnames, filenames) in os.walk('configs'):
946            dirpath = dirpath[len('configs') + 1:]
947            for filename in fnmatch.filter(filenames, '*_defconfig'):
948                defconfigs.append(os.path.join(dirpath, filename))
949
950    progress = Progress(len(defconfigs))
951    slots = Slots(configs, options, progress, reference_src_dir)
952
953    # Main loop to process defconfig files:
954    #  Add a new subprocess into a vacant slot.
955    #  Sleep if there is no available slot.
956    for defconfig in defconfigs:
957        while not slots.add(defconfig):
958            while not slots.available():
959                # No available slot: sleep for a while
960                time.sleep(SLEEP_TIME)
961
962    # wait until all the subprocesses finish
963    while not slots.empty():
964        time.sleep(SLEEP_TIME)
965
966    print ''
967    slots.show_failed_boards()
968
969def main():
970    try:
971        cpu_count = multiprocessing.cpu_count()
972    except NotImplementedError:
973        cpu_count = 1
974
975    parser = optparse.OptionParser()
976    # Add options here
977    parser.add_option('-c', '--color', action='store_true', default=False,
978                      help='display the log in color')
979    parser.add_option('-d', '--defconfigs', type='string',
980                      help='a file containing a list of defconfigs to move')
981    parser.add_option('-n', '--dry-run', action='store_true', default=False,
982                      help='perform a trial run (show log with no changes)')
983    parser.add_option('-e', '--exit-on-error', action='store_true',
984                      default=False,
985                      help='exit immediately on any error')
986    parser.add_option('-s', '--force-sync', action='store_true', default=False,
987                      help='force sync by savedefconfig')
988    parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
989                      action='store_true', default=False,
990                      help='only cleanup the headers')
991    parser.add_option('-j', '--jobs', type='int', default=cpu_count,
992                      help='the number of jobs to run simultaneously')
993    parser.add_option('-r', '--git-ref', type='string',
994                      help='the git ref to clone for building the autoconf.mk')
995    parser.add_option('-v', '--verbose', action='store_true', default=False,
996                      help='show any build errors as boards are built')
997    parser.usage += ' CONFIG ...'
998
999    (options, configs) = parser.parse_args()
1000
1001    if len(configs) == 0 and not options.force_sync:
1002        parser.print_usage()
1003        sys.exit(1)
1004
1005    # prefix the option name with CONFIG_ if missing
1006    configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1007                for config in configs ]
1008
1009    check_top_directory()
1010
1011    check_clean_directory()
1012
1013    update_cross_compile(options.color)
1014
1015    if not options.cleanup_headers_only:
1016        move_config(configs, options)
1017
1018    if configs:
1019        cleanup_headers(configs, options.dry_run)
1020
1021if __name__ == '__main__':
1022    main()
1023