tools: moveconfig: trim garbage lines after header cleanups
[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 copy
164 import filecmp
165 import fnmatch
166 import multiprocessing
167 import optparse
168 import os
169 import re
170 import shutil
171 import subprocess
172 import sys
173 import tempfile
174 import time
175
176 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
177 SLEEP_TIME=0.03
178
179 # Here is the list of cross-tools I use.
180 # Most of them are available at kernel.org
181 # (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the followings:
182 # arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases
183 # blackfin: http://sourceforge.net/projects/adi-toolchain/files/
184 # nds32: http://osdk.andestech.com/packages/nds32le-linux-glibc-v1.tgz
185 # nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545
186 # sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu
187 #
188 # openrisc kernel.org toolchain is out of date, download latest one from
189 # http://opencores.org/or1k/OpenRISC_GNU_tool_chain#Prebuilt_versions
190 CROSS_COMPILE = {
191     'arc': 'arc-linux-',
192     'aarch64': 'aarch64-linux-',
193     'arm': 'arm-unknown-linux-gnueabi-',
194     'avr32': 'avr32-linux-',
195     'blackfin': 'bfin-elf-',
196     'm68k': 'm68k-linux-',
197     'microblaze': 'microblaze-linux-',
198     'mips': 'mips-linux-',
199     'nds32': 'nds32le-linux-',
200     'nios2': 'nios2-linux-gnu-',
201     'openrisc': 'or1k-elf-',
202     'powerpc': 'powerpc-linux-',
203     'sh': 'sh-linux-gnu-',
204     'sparc': 'sparc-linux-',
205     'x86': 'i386-linux-'
206 }
207
208 STATE_IDLE = 0
209 STATE_DEFCONFIG = 1
210 STATE_AUTOCONF = 2
211 STATE_SAVEDEFCONFIG = 3
212
213 ACTION_MOVE = 0
214 ACTION_NO_ENTRY = 1
215 ACTION_NO_CHANGE = 2
216
217 COLOR_BLACK        = '0;30'
218 COLOR_RED          = '0;31'
219 COLOR_GREEN        = '0;32'
220 COLOR_BROWN        = '0;33'
221 COLOR_BLUE         = '0;34'
222 COLOR_PURPLE       = '0;35'
223 COLOR_CYAN         = '0;36'
224 COLOR_LIGHT_GRAY   = '0;37'
225 COLOR_DARK_GRAY    = '1;30'
226 COLOR_LIGHT_RED    = '1;31'
227 COLOR_LIGHT_GREEN  = '1;32'
228 COLOR_YELLOW       = '1;33'
229 COLOR_LIGHT_BLUE   = '1;34'
230 COLOR_LIGHT_PURPLE = '1;35'
231 COLOR_LIGHT_CYAN   = '1;36'
232 COLOR_WHITE        = '1;37'
233
234 ### helper functions ###
235 def get_devnull():
236     """Get the file object of '/dev/null' device."""
237     try:
238         devnull = subprocess.DEVNULL # py3k
239     except AttributeError:
240         devnull = open(os.devnull, 'wb')
241     return devnull
242
243 def check_top_directory():
244     """Exit if we are not at the top of source directory."""
245     for f in ('README', 'Licenses'):
246         if not os.path.exists(f):
247             sys.exit('Please run at the top of source directory.')
248
249 def check_clean_directory():
250     """Exit if the source tree is not clean."""
251     for f in ('.config', 'include/config'):
252         if os.path.exists(f):
253             sys.exit("source tree is not clean, please run 'make mrproper'")
254
255 def get_make_cmd():
256     """Get the command name of GNU Make.
257
258     U-Boot needs GNU Make for building, but the command name is not
259     necessarily "make". (for example, "gmake" on FreeBSD).
260     Returns the most appropriate command name on your system.
261     """
262     process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
263     ret = process.communicate()
264     if process.returncode:
265         sys.exit('GNU Make not found')
266     return ret[0].rstrip()
267
268 def color_text(color_enabled, color, string):
269     """Return colored string."""
270     if color_enabled:
271         # LF should not be surrounded by the escape sequence.
272         # Otherwise, additional whitespace or line-feed might be printed.
273         return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
274                            for s in string.split('\n') ])
275     else:
276         return string
277
278 def update_cross_compile(color_enabled):
279     """Update per-arch CROSS_COMPILE via environment variables
280
281     The default CROSS_COMPILE values are available
282     in the CROSS_COMPILE list above.
283
284     You can override them via environment variables
285     CROSS_COMPILE_{ARCH}.
286
287     For example, if you want to override toolchain prefixes
288     for ARM and PowerPC, you can do as follows in your shell:
289
290     export CROSS_COMPILE_ARM=...
291     export CROSS_COMPILE_POWERPC=...
292
293     Then, this function checks if specified compilers really exist in your
294     PATH environment.
295     """
296     archs = []
297
298     for arch in os.listdir('arch'):
299         if os.path.exists(os.path.join('arch', arch, 'Makefile')):
300             archs.append(arch)
301
302     # arm64 is a special case
303     archs.append('aarch64')
304
305     for arch in archs:
306         env = 'CROSS_COMPILE_' + arch.upper()
307         cross_compile = os.environ.get(env)
308         if not cross_compile:
309             cross_compile = CROSS_COMPILE.get(arch, '')
310
311         for path in os.environ["PATH"].split(os.pathsep):
312             gcc_path = os.path.join(path, cross_compile + 'gcc')
313             if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
314                 break
315         else:
316             print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
317                  'warning: %sgcc: not found in PATH.  %s architecture boards will be skipped'
318                                             % (cross_compile, arch))
319             cross_compile = None
320
321         CROSS_COMPILE[arch] = cross_compile
322
323 def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre,
324                          extend_post):
325     """Extend matched lines if desired patterns are found before/after already
326     matched lines.
327
328     Arguments:
329       lines: A list of lines handled.
330       matched: A list of line numbers that have been already matched.
331                (will be updated by this function)
332       pre_patterns: A list of regular expression that should be matched as
333                     preamble.
334       post_patterns: A list of regular expression that should be matched as
335                      postamble.
336       extend_pre: Add the line number of matched preamble to the matched list.
337       extend_post: Add the line number of matched postamble to the matched list.
338     """
339     extended_matched = []
340
341     j = matched[0]
342
343     for i in matched:
344         if i == 0 or i < j:
345             continue
346         j = i
347         while j in matched:
348             j += 1
349         if j >= len(lines):
350             break
351
352         for p in pre_patterns:
353             if p.search(lines[i - 1]):
354                 break
355         else:
356             # not matched
357             continue
358
359         for p in post_patterns:
360             if p.search(lines[j]):
361                 break
362         else:
363             # not matched
364             continue
365
366         if extend_pre:
367             extended_matched.append(i - 1)
368         if extend_post:
369             extended_matched.append(j)
370
371     matched += extended_matched
372     matched.sort()
373
374 def cleanup_one_header(header_path, patterns, dry_run):
375     """Clean regex-matched lines away from a file.
376
377     Arguments:
378       header_path: path to the cleaned file.
379       patterns: list of regex patterns.  Any lines matching to these
380                 patterns are deleted.
381       dry_run: make no changes, but still display log.
382     """
383     with open(header_path) as f:
384         lines = f.readlines()
385
386     matched = []
387     for i, line in enumerate(lines):
388         for pattern in patterns:
389             if pattern.search(line):
390                 matched.append(i)
391                 break
392
393     if not matched:
394         return
395
396     # remove empty #ifdef ... #endif, successive blank lines
397     pattern_if = re.compile(r'#\s*if(def|ndef)?\W') #  #if, #ifdef, #ifndef
398     pattern_elif = re.compile(r'#\s*el(if|se)\W')   #  #elif, #else
399     pattern_endif = re.compile(r'#\s*endif\W')      #  #endif
400     pattern_blank = re.compile(r'^\s*$')            #  empty line
401
402     while True:
403         old_matched = copy.copy(matched)
404         extend_matched_lines(lines, matched, [pattern_if],
405                              [pattern_endif], True, True)
406         extend_matched_lines(lines, matched, [pattern_elif],
407                              [pattern_elif, pattern_endif], True, False)
408         extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
409                              [pattern_blank], False, True)
410         extend_matched_lines(lines, matched, [pattern_blank],
411                              [pattern_elif, pattern_endif], True, False)
412         extend_matched_lines(lines, matched, [pattern_blank],
413                              [pattern_blank], True, False)
414         if matched == old_matched:
415             break
416
417     for i in matched:
418         print '%s: %s: %s' % (header_path, i + 1, lines[i]),
419
420     if dry_run:
421         return
422
423     with open(header_path, 'w') as f:
424         for i, line in enumerate(lines):
425             if not i in matched:
426                 f.write(line)
427
428 def cleanup_headers(configs, dry_run):
429     """Delete config defines from board headers.
430
431     Arguments:
432       configs: A list of CONFIGs to remove.
433       dry_run: make no changes, but still display log.
434     """
435     while True:
436         choice = raw_input('Clean up headers? [y/n]: ').lower()
437         print choice
438         if choice == 'y' or choice == 'n':
439             break
440
441     if choice == 'n':
442         return
443
444     patterns = []
445     for config in configs:
446         patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
447         patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
448
449     for dir in 'include', 'arch', 'board':
450         for (dirpath, dirnames, filenames) in os.walk(dir):
451             if dirpath == os.path.join('include', 'generated'):
452                 continue
453             for filename in filenames:
454                 if not fnmatch.fnmatch(filename, '*~'):
455                     cleanup_one_header(os.path.join(dirpath, filename),
456                                        patterns, dry_run)
457
458 ### classes ###
459 class Progress:
460
461     """Progress Indicator"""
462
463     def __init__(self, total):
464         """Create a new progress indicator.
465
466         Arguments:
467           total: A number of defconfig files to process.
468         """
469         self.current = 0
470         self.total = total
471
472     def inc(self):
473         """Increment the number of processed defconfig files."""
474
475         self.current += 1
476
477     def show(self):
478         """Display the progress."""
479         print ' %d defconfigs out of %d\r' % (self.current, self.total),
480         sys.stdout.flush()
481
482 class KconfigParser:
483
484     """A parser of .config and include/autoconf.mk."""
485
486     re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
487     re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
488
489     def __init__(self, configs, options, build_dir):
490         """Create a new parser.
491
492         Arguments:
493           configs: A list of CONFIGs to move.
494           options: option flags.
495           build_dir: Build directory.
496         """
497         self.configs = configs
498         self.options = options
499         self.dotconfig = os.path.join(build_dir, '.config')
500         self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
501         self.config_autoconf = os.path.join(build_dir, 'include', 'config',
502                                             'auto.conf')
503         self.defconfig = os.path.join(build_dir, 'defconfig')
504
505     def get_cross_compile(self):
506         """Parse .config file and return CROSS_COMPILE.
507
508         Returns:
509           A string storing the compiler prefix for the architecture.
510           Return a NULL string for architectures that do not require
511           compiler prefix (Sandbox and native build is the case).
512           Return None if the specified compiler is missing in your PATH.
513           Caller should distinguish '' and None.
514         """
515         arch = ''
516         cpu = ''
517         for line in open(self.dotconfig):
518             m = self.re_arch.match(line)
519             if m:
520                 arch = m.group(1)
521                 continue
522             m = self.re_cpu.match(line)
523             if m:
524                 cpu = m.group(1)
525
526         if not arch:
527             return None
528
529         # fix-up for aarch64
530         if arch == 'arm' and cpu == 'armv8':
531             arch = 'aarch64'
532
533         return CROSS_COMPILE.get(arch, None)
534
535     def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
536         """Parse .config, defconfig, include/autoconf.mk for one config.
537
538         This function looks for the config options in the lines from
539         defconfig, .config, and include/autoconf.mk in order to decide
540         which action should be taken for this defconfig.
541
542         Arguments:
543           config: CONFIG name to parse.
544           dotconfig_lines: lines from the .config file.
545           autoconf_lines: lines from the include/autoconf.mk file.
546
547         Returns:
548           A tupple of the action for this defconfig and the line
549           matched for the config.
550         """
551         not_set = '# %s is not set' % config
552
553         for line in dotconfig_lines:
554             line = line.rstrip()
555             if line.startswith(config + '=') or line == not_set:
556                 old_val = line
557                 break
558         else:
559             return (ACTION_NO_ENTRY, config)
560
561         for line in autoconf_lines:
562             line = line.rstrip()
563             if line.startswith(config + '='):
564                 new_val = line
565                 break
566         else:
567             new_val = not_set
568
569         # If this CONFIG is neither bool nor trisate
570         if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
571             # tools/scripts/define2mk.sed changes '1' to 'y'.
572             # This is a problem if the CONFIG is int type.
573             # Check the type in Kconfig and handle it correctly.
574             if new_val[-2:] == '=y':
575                 new_val = new_val[:-1] + '1'
576
577         return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
578                 new_val)
579
580     def update_dotconfig(self):
581         """Parse files for the config options and update the .config.
582
583         This function parses the generated .config and include/autoconf.mk
584         searching the target options.
585         Move the config option(s) to the .config as needed.
586
587         Arguments:
588           defconfig: defconfig name.
589
590         Returns:
591           Return a tuple of (updated flag, log string).
592           The "updated flag" is True if the .config was updated, False
593           otherwise.  The "log string" shows what happend to the .config.
594         """
595
596         results = []
597         updated = False
598
599         with open(self.dotconfig) as f:
600             dotconfig_lines = f.readlines()
601
602         with open(self.autoconf) as f:
603             autoconf_lines = f.readlines()
604
605         for config in self.configs:
606             result = self.parse_one_config(config, dotconfig_lines,
607                                            autoconf_lines)
608             results.append(result)
609
610         log = ''
611
612         for (action, value) in results:
613             if action == ACTION_MOVE:
614                 actlog = "Move '%s'" % value
615                 log_color = COLOR_LIGHT_GREEN
616             elif action == ACTION_NO_ENTRY:
617                 actlog = "%s is not defined in Kconfig.  Do nothing." % value
618                 log_color = COLOR_LIGHT_BLUE
619             elif action == ACTION_NO_CHANGE:
620                 actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
621                          % value
622                 log_color = COLOR_LIGHT_PURPLE
623             else:
624                 sys.exit("Internal Error. This should not happen.")
625
626             log += color_text(self.options.color, log_color, actlog) + '\n'
627
628         with open(self.dotconfig, 'a') as f:
629             for (action, value) in results:
630                 if action == ACTION_MOVE:
631                     f.write(value + '\n')
632                     updated = True
633
634         self.results = results
635         os.remove(self.config_autoconf)
636         os.remove(self.autoconf)
637
638         return (updated, log)
639
640     def check_defconfig(self):
641         """Check the defconfig after savedefconfig
642
643         Returns:
644           Return additional log if moved CONFIGs were removed again by
645           'make savedefconfig'.
646         """
647
648         log = ''
649
650         with open(self.defconfig) as f:
651             defconfig_lines = f.readlines()
652
653         for (action, value) in self.results:
654             if action != ACTION_MOVE:
655                 continue
656             if not value + '\n' in defconfig_lines:
657                 log += color_text(self.options.color, COLOR_YELLOW,
658                                   "'%s' was removed by savedefconfig.\n" %
659                                   value)
660
661         return log
662
663 class Slot:
664
665     """A slot to store a subprocess.
666
667     Each instance of this class handles one subprocess.
668     This class is useful to control multiple threads
669     for faster processing.
670     """
671
672     def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir):
673         """Create a new process slot.
674
675         Arguments:
676           configs: A list of CONFIGs to move.
677           options: option flags.
678           progress: A progress indicator.
679           devnull: A file object of '/dev/null'.
680           make_cmd: command name of GNU Make.
681           reference_src_dir: Determine the true starting config state from this
682                              source tree.
683         """
684         self.options = options
685         self.progress = progress
686         self.build_dir = tempfile.mkdtemp()
687         self.devnull = devnull
688         self.make_cmd = (make_cmd, 'O=' + self.build_dir)
689         self.reference_src_dir = reference_src_dir
690         self.parser = KconfigParser(configs, options, self.build_dir)
691         self.state = STATE_IDLE
692         self.failed_boards = []
693         self.suspicious_boards = []
694
695     def __del__(self):
696         """Delete the working directory
697
698         This function makes sure the temporary directory is cleaned away
699         even if Python suddenly dies due to error.  It should be done in here
700         because it is guaranteed the destructor is always invoked when the
701         instance of the class gets unreferenced.
702
703         If the subprocess is still running, wait until it finishes.
704         """
705         if self.state != STATE_IDLE:
706             while self.ps.poll() == None:
707                 pass
708         shutil.rmtree(self.build_dir)
709
710     def add(self, defconfig):
711         """Assign a new subprocess for defconfig and add it to the slot.
712
713         If the slot is vacant, create a new subprocess for processing the
714         given defconfig and add it to the slot.  Just returns False if
715         the slot is occupied (i.e. the current subprocess is still running).
716
717         Arguments:
718           defconfig: defconfig name.
719
720         Returns:
721           Return True on success or False on failure
722         """
723         if self.state != STATE_IDLE:
724             return False
725
726         self.defconfig = defconfig
727         self.log = ''
728         self.current_src_dir = self.reference_src_dir
729         self.do_defconfig()
730         return True
731
732     def poll(self):
733         """Check the status of the subprocess and handle it as needed.
734
735         Returns True if the slot is vacant (i.e. in idle state).
736         If the configuration is successfully finished, assign a new
737         subprocess to build include/autoconf.mk.
738         If include/autoconf.mk is generated, invoke the parser to
739         parse the .config and the include/autoconf.mk, moving
740         config options to the .config as needed.
741         If the .config was updated, run "make savedefconfig" to sync
742         it, update the original defconfig, and then set the slot back
743         to the idle state.
744
745         Returns:
746           Return True if the subprocess is terminated, False otherwise
747         """
748         if self.state == STATE_IDLE:
749             return True
750
751         if self.ps.poll() == None:
752             return False
753
754         if self.ps.poll() != 0:
755             self.handle_error()
756         elif self.state == STATE_DEFCONFIG:
757             if self.reference_src_dir and not self.current_src_dir:
758                 self.do_savedefconfig()
759             else:
760                 self.do_autoconf()
761         elif self.state == STATE_AUTOCONF:
762             if self.current_src_dir:
763                 self.current_src_dir = None
764                 self.do_defconfig()
765             else:
766                 self.do_savedefconfig()
767         elif self.state == STATE_SAVEDEFCONFIG:
768             self.update_defconfig()
769         else:
770             sys.exit("Internal Error. This should not happen.")
771
772         return True if self.state == STATE_IDLE else False
773
774     def handle_error(self):
775         """Handle error cases."""
776
777         self.log += color_text(self.options.color, COLOR_LIGHT_RED,
778                                "Failed to process.\n")
779         if self.options.verbose:
780             self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
781                                    self.ps.stderr.read())
782         self.finish(False)
783
784     def do_defconfig(self):
785         """Run 'make <board>_defconfig' to create the .config file."""
786
787         cmd = list(self.make_cmd)
788         cmd.append(self.defconfig)
789         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
790                                    stderr=subprocess.PIPE,
791                                    cwd=self.current_src_dir)
792         self.state = STATE_DEFCONFIG
793
794     def do_autoconf(self):
795         """Run 'make include/config/auto.conf'."""
796
797         self.cross_compile = self.parser.get_cross_compile()
798         if self.cross_compile is None:
799             self.log += color_text(self.options.color, COLOR_YELLOW,
800                                    "Compiler is missing.  Do nothing.\n")
801             self.finish(False)
802             return
803
804         cmd = list(self.make_cmd)
805         if self.cross_compile:
806             cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
807         cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
808         cmd.append('include/config/auto.conf')
809         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
810                                    stderr=subprocess.PIPE,
811                                    cwd=self.current_src_dir)
812         self.state = STATE_AUTOCONF
813
814     def do_savedefconfig(self):
815         """Update the .config and run 'make savedefconfig'."""
816
817         (updated, log) = self.parser.update_dotconfig()
818         self.log += log
819
820         if not self.options.force_sync and not updated:
821             self.finish(True)
822             return
823         if updated:
824             self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
825                                    "Syncing by savedefconfig...\n")
826         else:
827             self.log += "Syncing by savedefconfig (forced by option)...\n"
828
829         cmd = list(self.make_cmd)
830         cmd.append('savedefconfig')
831         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
832                                    stderr=subprocess.PIPE)
833         self.state = STATE_SAVEDEFCONFIG
834
835     def update_defconfig(self):
836         """Update the input defconfig and go back to the idle state."""
837
838         log = self.parser.check_defconfig()
839         if log:
840             self.suspicious_boards.append(self.defconfig)
841             self.log += log
842         orig_defconfig = os.path.join('configs', self.defconfig)
843         new_defconfig = os.path.join(self.build_dir, 'defconfig')
844         updated = not filecmp.cmp(orig_defconfig, new_defconfig)
845
846         if updated:
847             self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
848                                    "defconfig was updated.\n")
849
850         if not self.options.dry_run and updated:
851             shutil.move(new_defconfig, orig_defconfig)
852         self.finish(True)
853
854     def finish(self, success):
855         """Display log along with progress and go to the idle state.
856
857         Arguments:
858           success: Should be True when the defconfig was processed
859                    successfully, or False when it fails.
860         """
861         # output at least 30 characters to hide the "* defconfigs out of *".
862         log = self.defconfig.ljust(30) + '\n'
863
864         log += '\n'.join([ '    ' + s for s in self.log.split('\n') ])
865         # Some threads are running in parallel.
866         # Print log atomically to not mix up logs from different threads.
867         print >> (sys.stdout if success else sys.stderr), log
868
869         if not success:
870             if self.options.exit_on_error:
871                 sys.exit("Exit on error.")
872             # If --exit-on-error flag is not set, skip this board and continue.
873             # Record the failed board.
874             self.failed_boards.append(self.defconfig)
875
876         self.progress.inc()
877         self.progress.show()
878         self.state = STATE_IDLE
879
880     def get_failed_boards(self):
881         """Returns a list of failed boards (defconfigs) in this slot.
882         """
883         return self.failed_boards
884
885     def get_suspicious_boards(self):
886         """Returns a list of boards (defconfigs) with possible misconversion.
887         """
888         return self.suspicious_boards
889
890 class Slots:
891
892     """Controller of the array of subprocess slots."""
893
894     def __init__(self, configs, options, progress, reference_src_dir):
895         """Create a new slots controller.
896
897         Arguments:
898           configs: A list of CONFIGs to move.
899           options: option flags.
900           progress: A progress indicator.
901           reference_src_dir: Determine the true starting config state from this
902                              source tree.
903         """
904         self.options = options
905         self.slots = []
906         devnull = get_devnull()
907         make_cmd = get_make_cmd()
908         for i in range(options.jobs):
909             self.slots.append(Slot(configs, options, progress, devnull,
910                                    make_cmd, reference_src_dir))
911
912     def add(self, defconfig):
913         """Add a new subprocess if a vacant slot is found.
914
915         Arguments:
916           defconfig: defconfig name to be put into.
917
918         Returns:
919           Return True on success or False on failure
920         """
921         for slot in self.slots:
922             if slot.add(defconfig):
923                 return True
924         return False
925
926     def available(self):
927         """Check if there is a vacant slot.
928
929         Returns:
930           Return True if at lease one vacant slot is found, False otherwise.
931         """
932         for slot in self.slots:
933             if slot.poll():
934                 return True
935         return False
936
937     def empty(self):
938         """Check if all slots are vacant.
939
940         Returns:
941           Return True if all the slots are vacant, False otherwise.
942         """
943         ret = True
944         for slot in self.slots:
945             if not slot.poll():
946                 ret = False
947         return ret
948
949     def show_failed_boards(self):
950         """Display all of the failed boards (defconfigs)."""
951         boards = []
952         output_file = 'moveconfig.failed'
953
954         for slot in self.slots:
955             boards += slot.get_failed_boards()
956
957         if boards:
958             boards = '\n'.join(boards) + '\n'
959             msg = "The following boards were not processed due to error:\n"
960             msg += boards
961             msg += "(the list has been saved in %s)\n" % output_file
962             print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
963                                             msg)
964
965             with open(output_file, 'w') as f:
966                 f.write(boards)
967
968     def show_suspicious_boards(self):
969         """Display all boards (defconfigs) with possible misconversion."""
970         boards = []
971         output_file = 'moveconfig.suspicious'
972
973         for slot in self.slots:
974             boards += slot.get_suspicious_boards()
975
976         if boards:
977             boards = '\n'.join(boards) + '\n'
978             msg = "The following boards might have been converted incorrectly.\n"
979             msg += "It is highly recommended to check them manually:\n"
980             msg += boards
981             msg += "(the list has been saved in %s)\n" % output_file
982             print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
983                                             msg)
984
985             with open(output_file, 'w') as f:
986                 f.write(boards)
987
988 class ReferenceSource:
989
990     """Reference source against which original configs should be parsed."""
991
992     def __init__(self, commit):
993         """Create a reference source directory based on a specified commit.
994
995         Arguments:
996           commit: commit to git-clone
997         """
998         self.src_dir = tempfile.mkdtemp()
999         print "Cloning git repo to a separate work directory..."
1000         subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1001                                 cwd=self.src_dir)
1002         print "Checkout '%s' to build the original autoconf.mk." % \
1003             subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
1004         subprocess.check_output(['git', 'checkout', commit],
1005                                 stderr=subprocess.STDOUT, cwd=self.src_dir)
1006
1007     def __del__(self):
1008         """Delete the reference source directory
1009
1010         This function makes sure the temporary directory is cleaned away
1011         even if Python suddenly dies due to error.  It should be done in here
1012         because it is guaranteed the destructor is always invoked when the
1013         instance of the class gets unreferenced.
1014         """
1015         shutil.rmtree(self.src_dir)
1016
1017     def get_dir(self):
1018         """Return the absolute path to the reference source directory."""
1019
1020         return self.src_dir
1021
1022 def move_config(configs, options):
1023     """Move config options to defconfig files.
1024
1025     Arguments:
1026       configs: A list of CONFIGs to move.
1027       options: option flags
1028     """
1029     if len(configs) == 0:
1030         if options.force_sync:
1031             print 'No CONFIG is specified. You are probably syncing defconfigs.',
1032         else:
1033             print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
1034     else:
1035         print 'Move ' + ', '.join(configs),
1036     print '(jobs: %d)\n' % options.jobs
1037
1038     if options.git_ref:
1039         reference_src = ReferenceSource(options.git_ref)
1040         reference_src_dir = reference_src.get_dir()
1041     else:
1042         reference_src_dir = None
1043
1044     if options.defconfigs:
1045         defconfigs = [line.strip() for line in open(options.defconfigs)]
1046         for i, defconfig in enumerate(defconfigs):
1047             if not defconfig.endswith('_defconfig'):
1048                 defconfigs[i] = defconfig + '_defconfig'
1049             if not os.path.exists(os.path.join('configs', defconfigs[i])):
1050                 sys.exit('%s - defconfig does not exist. Stopping.' %
1051                          defconfigs[i])
1052     else:
1053         # All the defconfig files to be processed
1054         defconfigs = []
1055         for (dirpath, dirnames, filenames) in os.walk('configs'):
1056             dirpath = dirpath[len('configs') + 1:]
1057             for filename in fnmatch.filter(filenames, '*_defconfig'):
1058                 defconfigs.append(os.path.join(dirpath, filename))
1059
1060     progress = Progress(len(defconfigs))
1061     slots = Slots(configs, options, progress, reference_src_dir)
1062
1063     # Main loop to process defconfig files:
1064     #  Add a new subprocess into a vacant slot.
1065     #  Sleep if there is no available slot.
1066     for defconfig in defconfigs:
1067         while not slots.add(defconfig):
1068             while not slots.available():
1069                 # No available slot: sleep for a while
1070                 time.sleep(SLEEP_TIME)
1071
1072     # wait until all the subprocesses finish
1073     while not slots.empty():
1074         time.sleep(SLEEP_TIME)
1075
1076     print ''
1077     slots.show_failed_boards()
1078     slots.show_suspicious_boards()
1079
1080 def main():
1081     try:
1082         cpu_count = multiprocessing.cpu_count()
1083     except NotImplementedError:
1084         cpu_count = 1
1085
1086     parser = optparse.OptionParser()
1087     # Add options here
1088     parser.add_option('-c', '--color', action='store_true', default=False,
1089                       help='display the log in color')
1090     parser.add_option('-d', '--defconfigs', type='string',
1091                       help='a file containing a list of defconfigs to move')
1092     parser.add_option('-n', '--dry-run', action='store_true', default=False,
1093                       help='perform a trial run (show log with no changes)')
1094     parser.add_option('-e', '--exit-on-error', action='store_true',
1095                       default=False,
1096                       help='exit immediately on any error')
1097     parser.add_option('-s', '--force-sync', action='store_true', default=False,
1098                       help='force sync by savedefconfig')
1099     parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1100                       action='store_true', default=False,
1101                       help='only cleanup the headers')
1102     parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1103                       help='the number of jobs to run simultaneously')
1104     parser.add_option('-r', '--git-ref', type='string',
1105                       help='the git ref to clone for building the autoconf.mk')
1106     parser.add_option('-v', '--verbose', action='store_true', default=False,
1107                       help='show any build errors as boards are built')
1108     parser.usage += ' CONFIG ...'
1109
1110     (options, configs) = parser.parse_args()
1111
1112     if len(configs) == 0 and not options.force_sync:
1113         parser.print_usage()
1114         sys.exit(1)
1115
1116     # prefix the option name with CONFIG_ if missing
1117     configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1118                 for config in configs ]
1119
1120     check_top_directory()
1121
1122     if not options.cleanup_headers_only:
1123         check_clean_directory()
1124         update_cross_compile(options.color)
1125         move_config(configs, options)
1126
1127     if configs:
1128         cleanup_headers(configs, options.dry_run)
1129
1130 if __name__ == '__main__':
1131     main()