]> git.sur5r.net Git - u-boot/blob - tools/moveconfig.py
Merge branch 'master' of git://git.denx.de/u-boot-uniphier
[u-boot] / tools / moveconfig.py
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 """
9 Move config options from headers to defconfig files.
10
11 Since Kconfig was introduced to U-Boot, we have worked on moving
12 config options from headers to Kconfig (defconfig).
13
14 This tool intends to help this tremendous work.
15
16
17 Usage
18 -----
19
20 First, you must edit the Kconfig to add the menu entries for the configs
21 you are moving.
22
23 And then run this tool giving CONFIG names you want to move.
24 For example, if you want to move CONFIG_CMD_USB and CONFIG_SYS_TEXT_BASE,
25 simply type as follows:
26
27   $ tools/moveconfig.py CONFIG_CMD_USB CONFIG_SYS_TEXT_BASE
28
29 The tool walks through all the defconfig files and move the given CONFIGs.
30
31 The log is also displayed on the terminal.
32
33 The 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.
44 It 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
74 Finally, you will be asked, Clean up headers? [y/n]:
75
76 If you say 'y' here, the unnecessary config defines are removed
77 from the config headers (include/configs/*.h).
78 It just uses the regex method, so you should not rely on it.
79 Just in case, please do 'git diff' to see what happened.
80
81
82 How does it work?
83 -----------------
84
85 This tool runs configuration and builds include/autoconf.mk for every
86 defconfig.  The config options defined in Kconfig appear in the .config
87 file (unless they are hidden because of unmet dependency.)
88 On the other hand, the config options defined by board headers are seen
89 in include/autoconf.mk.  The tool looks for the specified options in both
90 of them to decide the appropriate action for the options.  If the given
91 config option is found in the .config, but its value does not match the
92 one from the board header, the config option in the .config is replaced
93 with the define in the board header.  Then, the .config is synced by
94 "make savedefconfig" and the defconfig is updated with it.
95
96 For faster processing, this tool handles multi-threading.  It creates
97 separate build directories where the out-of-tree build is run.  The
98 temporary build directories are automatically created and deleted as
99 needed.  The number of threads are chosen based on the number of the CPU
100 cores of your system although you can change it via -j (--jobs) option.
101
102
103 Toolchains
104 ----------
105
106 Appropriate toolchain are necessary to generate include/autoconf.mk
107 for all the architectures supported by U-Boot.  Most of them are available
108 at the kernel.org site, some are not provided by kernel.org.
109
110 The default per-arch CROSS_COMPILE used by this tool is specified by
111 the list below, CROSS_COMPILE.  You may wish to update the list to
112 use your own.  Instead of modifying the list directly, you can give
113 them via environments.
114
115
116 Available 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
157 To see the complete list of supported options, run
158
159   $ tools/moveconfig.py -h
160
161 """
162
163 import filecmp
164 import fnmatch
165 import multiprocessing
166 import optparse
167 import os
168 import re
169 import shutil
170 import subprocess
171 import sys
172 import tempfile
173 import time
174
175 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
176 SLEEP_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
189 CROSS_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
207 STATE_IDLE = 0
208 STATE_DEFCONFIG = 1
209 STATE_AUTOCONF = 2
210 STATE_SAVEDEFCONFIG = 3
211
212 ACTION_MOVE = 0
213 ACTION_NO_ENTRY = 1
214 ACTION_NO_CHANGE = 2
215
216 COLOR_BLACK        = '0;30'
217 COLOR_RED          = '0;31'
218 COLOR_GREEN        = '0;32'
219 COLOR_BROWN        = '0;33'
220 COLOR_BLUE         = '0;34'
221 COLOR_PURPLE       = '0;35'
222 COLOR_CYAN         = '0;36'
223 COLOR_LIGHT_GRAY   = '0;37'
224 COLOR_DARK_GRAY    = '1;30'
225 COLOR_LIGHT_RED    = '1;31'
226 COLOR_LIGHT_GREEN  = '1;32'
227 COLOR_YELLOW       = '1;33'
228 COLOR_LIGHT_BLUE   = '1;34'
229 COLOR_LIGHT_PURPLE = '1;35'
230 COLOR_LIGHT_CYAN   = '1;36'
231 COLOR_WHITE        = '1;37'
232
233 ### helper functions ###
234 def 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
242 def 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
248 def 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
254 def 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
267 def 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
277 def 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
322 def 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
351 def 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 ###
380 class 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
403 class 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
584 class 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
811 class 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
909 class 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
943 def 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
1001 def 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
1053 if __name__ == '__main__':
1054     main()