]> git.sur5r.net Git - u-boot/blob - tools/moveconfig.py
tools: moveconfig: fix needless move for config with default 1
[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
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.use_git_ref = True if self.options.git_ref else False
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.options.git_ref and not self.use_git_ref:
678                 self.do_savedefconfig()
679             else:
680                 self.do_autoconf()
681         elif self.state == STATE_AUTOCONF:
682             if self.use_git_ref:
683                 self.use_git_ref = False
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         if self.use_git_ref:
710             cmd.append('-C')
711             cmd.append(self.reference_src_dir)
712         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
713                                    stderr=subprocess.PIPE)
714         self.state = STATE_DEFCONFIG
715
716     def do_autoconf(self):
717         """Run 'make include/config/auto.conf'."""
718
719         self.cross_compile = self.parser.get_cross_compile()
720         if self.cross_compile is None:
721             self.log += color_text(self.options.color, COLOR_YELLOW,
722                                    "Compiler is missing.  Do nothing.\n")
723             self.finish(False)
724             return
725
726         cmd = list(self.make_cmd)
727         if self.cross_compile:
728             cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
729         cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
730         cmd.append('include/config/auto.conf')
731         if self.use_git_ref:
732             cmd.append('-C')
733             cmd.append(self.reference_src_dir)
734         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
735                                    stderr=subprocess.PIPE)
736         self.state = STATE_AUTOCONF
737
738     def do_savedefconfig(self):
739         """Update the .config and run 'make savedefconfig'."""
740
741         (updated, log) = self.parser.update_dotconfig()
742         self.log += log
743
744         if not self.options.force_sync and not updated:
745             self.finish(True)
746             return
747         if updated:
748             self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
749                                    "Syncing by savedefconfig...\n")
750         else:
751             self.log += "Syncing by savedefconfig (forced by option)...\n"
752
753         cmd = list(self.make_cmd)
754         cmd.append('savedefconfig')
755         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
756                                    stderr=subprocess.PIPE)
757         self.state = STATE_SAVEDEFCONFIG
758
759     def update_defconfig(self):
760         """Update the input defconfig and go back to the idle state."""
761
762         self.log += self.parser.check_defconfig()
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 class Slots:
807
808     """Controller of the array of subprocess slots."""
809
810     def __init__(self, configs, options, progress, reference_src_dir):
811         """Create a new slots controller.
812
813         Arguments:
814           configs: A list of CONFIGs to move.
815           options: option flags.
816           progress: A progress indicator.
817           reference_src_dir: Determine the true starting config state from this
818                              source tree.
819         """
820         self.options = options
821         self.slots = []
822         devnull = get_devnull()
823         make_cmd = get_make_cmd()
824         for i in range(options.jobs):
825             self.slots.append(Slot(configs, options, progress, devnull,
826                                    make_cmd, reference_src_dir))
827
828     def add(self, defconfig):
829         """Add a new subprocess if a vacant slot is found.
830
831         Arguments:
832           defconfig: defconfig name to be put into.
833
834         Returns:
835           Return True on success or False on failure
836         """
837         for slot in self.slots:
838             if slot.add(defconfig):
839                 return True
840         return False
841
842     def available(self):
843         """Check if there is a vacant slot.
844
845         Returns:
846           Return True if at lease one vacant slot is found, False otherwise.
847         """
848         for slot in self.slots:
849             if slot.poll():
850                 return True
851         return False
852
853     def empty(self):
854         """Check if all slots are vacant.
855
856         Returns:
857           Return True if all the slots are vacant, False otherwise.
858         """
859         ret = True
860         for slot in self.slots:
861             if not slot.poll():
862                 ret = False
863         return ret
864
865     def show_failed_boards(self):
866         """Display all of the failed boards (defconfigs)."""
867         failed_boards = []
868
869         for slot in self.slots:
870             failed_boards += slot.get_failed_boards()
871
872         if len(failed_boards) > 0:
873             msg = [ "The following boards were not processed due to error:" ]
874             msg += failed_boards
875             for line in msg:
876                 print >> sys.stderr, color_text(self.options.color,
877                                                 COLOR_LIGHT_RED, line)
878
879             with open('moveconfig.failed', 'w') as f:
880                 for board in failed_boards:
881                     f.write(board + '\n')
882
883 class WorkDir:
884     def __init__(self):
885         """Create a new working directory."""
886         self.work_dir = tempfile.mkdtemp()
887
888     def __del__(self):
889         """Delete the working directory
890
891         This function makes sure the temporary directory is cleaned away
892         even if Python suddenly dies due to error.  It should be done in here
893         because it is guaranteed the destructor is always invoked when the
894         instance of the class gets unreferenced.
895         """
896         shutil.rmtree(self.work_dir)
897
898     def get(self):
899         return self.work_dir
900
901 def move_config(configs, options):
902     """Move config options to defconfig files.
903
904     Arguments:
905       configs: A list of CONFIGs to move.
906       options: option flags
907     """
908     if len(configs) == 0:
909         if options.force_sync:
910             print 'No CONFIG is specified. You are probably syncing defconfigs.',
911         else:
912             print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
913     else:
914         print 'Move ' + ', '.join(configs),
915     print '(jobs: %d)\n' % options.jobs
916
917     reference_src_dir = ''
918
919     if options.git_ref:
920         work_dir = WorkDir()
921         reference_src_dir = work_dir.get()
922         print "Cloning git repo to a separate work directory..."
923         subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
924                                 cwd=reference_src_dir)
925         print "Checkout '%s' to build the original autoconf.mk." % \
926             subprocess.check_output(['git', 'rev-parse', '--short',
927                                     options.git_ref]).strip()
928         subprocess.check_output(['git', 'checkout', options.git_ref],
929                                 stderr=subprocess.STDOUT,
930                                 cwd=reference_src_dir)
931
932     if options.defconfigs:
933         defconfigs = [line.strip() for line in open(options.defconfigs)]
934         for i, defconfig in enumerate(defconfigs):
935             if not defconfig.endswith('_defconfig'):
936                 defconfigs[i] = defconfig + '_defconfig'
937             if not os.path.exists(os.path.join('configs', defconfigs[i])):
938                 sys.exit('%s - defconfig does not exist. Stopping.' %
939                          defconfigs[i])
940     else:
941         # All the defconfig files to be processed
942         defconfigs = []
943         for (dirpath, dirnames, filenames) in os.walk('configs'):
944             dirpath = dirpath[len('configs') + 1:]
945             for filename in fnmatch.filter(filenames, '*_defconfig'):
946                 defconfigs.append(os.path.join(dirpath, filename))
947
948     progress = Progress(len(defconfigs))
949     slots = Slots(configs, options, progress, reference_src_dir)
950
951     # Main loop to process defconfig files:
952     #  Add a new subprocess into a vacant slot.
953     #  Sleep if there is no available slot.
954     for defconfig in defconfigs:
955         while not slots.add(defconfig):
956             while not slots.available():
957                 # No available slot: sleep for a while
958                 time.sleep(SLEEP_TIME)
959
960     # wait until all the subprocesses finish
961     while not slots.empty():
962         time.sleep(SLEEP_TIME)
963
964     print ''
965     slots.show_failed_boards()
966
967 def main():
968     try:
969         cpu_count = multiprocessing.cpu_count()
970     except NotImplementedError:
971         cpu_count = 1
972
973     parser = optparse.OptionParser()
974     # Add options here
975     parser.add_option('-c', '--color', action='store_true', default=False,
976                       help='display the log in color')
977     parser.add_option('-d', '--defconfigs', type='string',
978                       help='a file containing a list of defconfigs to move')
979     parser.add_option('-n', '--dry-run', action='store_true', default=False,
980                       help='perform a trial run (show log with no changes)')
981     parser.add_option('-e', '--exit-on-error', action='store_true',
982                       default=False,
983                       help='exit immediately on any error')
984     parser.add_option('-s', '--force-sync', action='store_true', default=False,
985                       help='force sync by savedefconfig')
986     parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
987                       action='store_true', default=False,
988                       help='only cleanup the headers')
989     parser.add_option('-j', '--jobs', type='int', default=cpu_count,
990                       help='the number of jobs to run simultaneously')
991     parser.add_option('-r', '--git-ref', type='string',
992                       help='the git ref to clone for building the autoconf.mk')
993     parser.add_option('-v', '--verbose', action='store_true', default=False,
994                       help='show any build errors as boards are built')
995     parser.usage += ' CONFIG ...'
996
997     (options, configs) = parser.parse_args()
998
999     if len(configs) == 0 and not options.force_sync:
1000         parser.print_usage()
1001         sys.exit(1)
1002
1003     # prefix the option name with CONFIG_ if missing
1004     configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1005                 for config in configs ]
1006
1007     check_top_directory()
1008
1009     check_clean_directory()
1010
1011     update_cross_compile(options.color)
1012
1013     if not options.cleanup_headers_only:
1014         move_config(configs, options)
1015
1016     if configs:
1017         cleanup_headers(configs, options.dry_run)
1018
1019 if __name__ == '__main__':
1020     main()