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