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