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