]> git.sur5r.net Git - u-boot/blob - tools/moveconfig.py
Merge git://git.denx.de/u-boot-sunxi
[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 following:
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.  The option is not
51    defined in the config header, either.  So, this case can be just skipped.
52
53  - CONFIG_... is not defined in Kconfig (suspicious).  Do nothing.
54    This option is defined in the config header, but its entry was not found
55    in Kconfig.
56    There are two common cases:
57      - You forgot to create an entry for the CONFIG before running
58        this tool, or made a typo in a CONFIG passed to this tool.
59      - The entry was hidden due to unmet 'depends on'.
60    The tool does not know if the result is reasonable, so please check it
61    manually.
62
63  - 'CONFIG_...' is the same as the define in Kconfig.  Do nothing.
64    The define in the config header matched the one in Kconfig.
65    We do not need to touch it.
66
67  - Compiler is missing.  Do nothing.
68    The compiler specified for this architecture was not found
69    in your PATH environment.
70    (If -e option is passed, the tool exits immediately.)
71
72  - Failed to process.
73    An error occurred during processing this defconfig.  Skipped.
74    (If -e option is passed, the tool exits immediately on error.)
75
76 Finally, you will be asked, Clean up headers? [y/n]:
77
78 If you say 'y' here, the unnecessary config defines are removed
79 from the config headers (include/configs/*.h).
80 It just uses the regex method, so you should not rely on it.
81 Just in case, please do 'git diff' to see what happened.
82
83
84 How does it work?
85 -----------------
86
87 This tool runs configuration and builds include/autoconf.mk for every
88 defconfig.  The config options defined in Kconfig appear in the .config
89 file (unless they are hidden because of unmet dependency.)
90 On the other hand, the config options defined by board headers are seen
91 in include/autoconf.mk.  The tool looks for the specified options in both
92 of them to decide the appropriate action for the options.  If the given
93 config option is found in the .config, but its value does not match the
94 one from the board header, the config option in the .config is replaced
95 with the define in the board header.  Then, the .config is synced by
96 "make savedefconfig" and the defconfig is updated with it.
97
98 For faster processing, this tool handles multi-threading.  It creates
99 separate build directories where the out-of-tree build is run.  The
100 temporary build directories are automatically created and deleted as
101 needed.  The number of threads are chosen based on the number of the CPU
102 cores of your system although you can change it via -j (--jobs) option.
103
104
105 Toolchains
106 ----------
107
108 Appropriate toolchain are necessary to generate include/autoconf.mk
109 for all the architectures supported by U-Boot.  Most of them are available
110 at the kernel.org site, some are not provided by kernel.org.
111
112 The default per-arch CROSS_COMPILE used by this tool is specified by
113 the list below, CROSS_COMPILE.  You may wish to update the list to
114 use your own.  Instead of modifying the list directly, you can give
115 them via environments.
116
117
118 Available options
119 -----------------
120
121  -c, --color
122    Surround each portion of the log with escape sequences to display it
123    in color on the terminal.
124
125  -C, --commit
126    Create a git commit with the changes when the operation is complete. A
127    standard commit message is used which may need to be edited.
128
129  -d, --defconfigs
130   Specify a file containing a list of defconfigs to move.  The defconfig
131   files can be given with shell-style wildcards.
132
133  -n, --dry-run
134    Perform a trial run that does not make any changes.  It is useful to
135    see what is going to happen before one actually runs it.
136
137  -e, --exit-on-error
138    Exit immediately if Make exits with a non-zero status while processing
139    a defconfig file.
140
141  -s, --force-sync
142    Do "make savedefconfig" forcibly for all the defconfig files.
143    If not specified, "make savedefconfig" only occurs for cases
144    where at least one CONFIG was moved.
145
146  -S, --spl
147    Look for moved config options in spl/include/autoconf.mk instead of
148    include/autoconf.mk.  This is useful for moving options for SPL build
149    because SPL related options (mostly prefixed with CONFIG_SPL_) are
150    sometimes blocked by CONFIG_SPL_BUILD ifdef conditionals.
151
152  -H, --headers-only
153    Only cleanup the headers; skip the defconfig processing
154
155  -j, --jobs
156    Specify the number of threads to run simultaneously.  If not specified,
157    the number of threads is the same as the number of CPU cores.
158
159  -r, --git-ref
160    Specify the git ref to clone for building the autoconf.mk. If unspecified
161    use the CWD. This is useful for when changes to the Kconfig affect the
162    default values and you want to capture the state of the defconfig from
163    before that change was in effect. If in doubt, specify a ref pre-Kconfig
164    changes (use HEAD if Kconfig changes are not committed). Worst case it will
165    take a bit longer to run, but will always do the right thing.
166
167  -v, --verbose
168    Show any build errors as boards are built
169
170  -y, --yes
171    Instead of prompting, automatically go ahead with all operations. This
172    includes cleaning up headers and CONFIG_SYS_EXTRA_OPTIONS.
173
174 To see the complete list of supported options, run
175
176   $ tools/moveconfig.py -h
177
178 """
179
180 import copy
181 import difflib
182 import filecmp
183 import fnmatch
184 import glob
185 import multiprocessing
186 import optparse
187 import os
188 import re
189 import shutil
190 import subprocess
191 import sys
192 import tempfile
193 import time
194
195 SHOW_GNU_MAKE = 'scripts/show-gnu-make'
196 SLEEP_TIME=0.03
197
198 # Here is the list of cross-tools I use.
199 # Most of them are available at kernel.org
200 # (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the following:
201 # arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases
202 # nds32: http://osdk.andestech.com/packages/nds32le-linux-glibc-v1.tgz
203 # nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545
204 # sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu
205 CROSS_COMPILE = {
206     'arc': 'arc-linux-',
207     'aarch64': 'aarch64-linux-',
208     'arm': 'arm-unknown-linux-gnueabi-',
209     'avr32': 'avr32-linux-',
210     'm68k': 'm68k-linux-',
211     'microblaze': 'microblaze-linux-',
212     'mips': 'mips-linux-',
213     'nds32': 'nds32le-linux-',
214     'nios2': 'nios2-linux-gnu-',
215     'powerpc': 'powerpc-linux-',
216     'sh': 'sh-linux-gnu-',
217     'x86': 'i386-linux-',
218     'xtensa': 'xtensa-linux-'
219 }
220
221 STATE_IDLE = 0
222 STATE_DEFCONFIG = 1
223 STATE_AUTOCONF = 2
224 STATE_SAVEDEFCONFIG = 3
225
226 ACTION_MOVE = 0
227 ACTION_NO_ENTRY = 1
228 ACTION_NO_ENTRY_WARN = 2
229 ACTION_NO_CHANGE = 3
230
231 COLOR_BLACK        = '0;30'
232 COLOR_RED          = '0;31'
233 COLOR_GREEN        = '0;32'
234 COLOR_BROWN        = '0;33'
235 COLOR_BLUE         = '0;34'
236 COLOR_PURPLE       = '0;35'
237 COLOR_CYAN         = '0;36'
238 COLOR_LIGHT_GRAY   = '0;37'
239 COLOR_DARK_GRAY    = '1;30'
240 COLOR_LIGHT_RED    = '1;31'
241 COLOR_LIGHT_GREEN  = '1;32'
242 COLOR_YELLOW       = '1;33'
243 COLOR_LIGHT_BLUE   = '1;34'
244 COLOR_LIGHT_PURPLE = '1;35'
245 COLOR_LIGHT_CYAN   = '1;36'
246 COLOR_WHITE        = '1;37'
247
248 ### helper functions ###
249 def get_devnull():
250     """Get the file object of '/dev/null' device."""
251     try:
252         devnull = subprocess.DEVNULL # py3k
253     except AttributeError:
254         devnull = open(os.devnull, 'wb')
255     return devnull
256
257 def check_top_directory():
258     """Exit if we are not at the top of source directory."""
259     for f in ('README', 'Licenses'):
260         if not os.path.exists(f):
261             sys.exit('Please run at the top of source directory.')
262
263 def check_clean_directory():
264     """Exit if the source tree is not clean."""
265     for f in ('.config', 'include/config'):
266         if os.path.exists(f):
267             sys.exit("source tree is not clean, please run 'make mrproper'")
268
269 def get_make_cmd():
270     """Get the command name of GNU Make.
271
272     U-Boot needs GNU Make for building, but the command name is not
273     necessarily "make". (for example, "gmake" on FreeBSD).
274     Returns the most appropriate command name on your system.
275     """
276     process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE)
277     ret = process.communicate()
278     if process.returncode:
279         sys.exit('GNU Make not found')
280     return ret[0].rstrip()
281
282 def get_matched_defconfigs(defconfigs_file):
283     """Get all the defconfig files that match the patterns in a file."""
284     defconfigs = []
285     for i, line in enumerate(open(defconfigs_file)):
286         line = line.strip()
287         if not line:
288             continue # skip blank lines silently
289         pattern = os.path.join('configs', line)
290         matched = glob.glob(pattern) + glob.glob(pattern + '_defconfig')
291         if not matched:
292             print >> sys.stderr, "warning: %s:%d: no defconfig matched '%s'" % \
293                                                  (defconfigs_file, i + 1, line)
294
295         defconfigs += matched
296
297     # use set() to drop multiple matching
298     return [ defconfig[len('configs') + 1:]  for defconfig in set(defconfigs) ]
299
300 def get_all_defconfigs():
301     """Get all the defconfig files under the configs/ directory."""
302     defconfigs = []
303     for (dirpath, dirnames, filenames) in os.walk('configs'):
304         dirpath = dirpath[len('configs') + 1:]
305         for filename in fnmatch.filter(filenames, '*_defconfig'):
306             defconfigs.append(os.path.join(dirpath, filename))
307
308     return defconfigs
309
310 def color_text(color_enabled, color, string):
311     """Return colored string."""
312     if color_enabled:
313         # LF should not be surrounded by the escape sequence.
314         # Otherwise, additional whitespace or line-feed might be printed.
315         return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else ''
316                            for s in string.split('\n') ])
317     else:
318         return string
319
320 def show_diff(a, b, file_path, color_enabled):
321     """Show unidified diff.
322
323     Arguments:
324       a: A list of lines (before)
325       b: A list of lines (after)
326       file_path: Path to the file
327       color_enabled: Display the diff in color
328     """
329
330     diff = difflib.unified_diff(a, b,
331                                 fromfile=os.path.join('a', file_path),
332                                 tofile=os.path.join('b', file_path))
333
334     for line in diff:
335         if line[0] == '-' and line[1] != '-':
336             print color_text(color_enabled, COLOR_RED, line),
337         elif line[0] == '+' and line[1] != '+':
338             print color_text(color_enabled, COLOR_GREEN, line),
339         else:
340             print line,
341
342 def update_cross_compile(color_enabled):
343     """Update per-arch CROSS_COMPILE via environment variables
344
345     The default CROSS_COMPILE values are available
346     in the CROSS_COMPILE list above.
347
348     You can override them via environment variables
349     CROSS_COMPILE_{ARCH}.
350
351     For example, if you want to override toolchain prefixes
352     for ARM and PowerPC, you can do as follows in your shell:
353
354     export CROSS_COMPILE_ARM=...
355     export CROSS_COMPILE_POWERPC=...
356
357     Then, this function checks if specified compilers really exist in your
358     PATH environment.
359     """
360     archs = []
361
362     for arch in os.listdir('arch'):
363         if os.path.exists(os.path.join('arch', arch, 'Makefile')):
364             archs.append(arch)
365
366     # arm64 is a special case
367     archs.append('aarch64')
368
369     for arch in archs:
370         env = 'CROSS_COMPILE_' + arch.upper()
371         cross_compile = os.environ.get(env)
372         if not cross_compile:
373             cross_compile = CROSS_COMPILE.get(arch, '')
374
375         for path in os.environ["PATH"].split(os.pathsep):
376             gcc_path = os.path.join(path, cross_compile + 'gcc')
377             if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK):
378                 break
379         else:
380             print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW,
381                  'warning: %sgcc: not found in PATH.  %s architecture boards will be skipped'
382                                             % (cross_compile, arch))
383             cross_compile = None
384
385         CROSS_COMPILE[arch] = cross_compile
386
387 def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre,
388                          extend_post):
389     """Extend matched lines if desired patterns are found before/after already
390     matched lines.
391
392     Arguments:
393       lines: A list of lines handled.
394       matched: A list of line numbers that have been already matched.
395                (will be updated by this function)
396       pre_patterns: A list of regular expression that should be matched as
397                     preamble.
398       post_patterns: A list of regular expression that should be matched as
399                      postamble.
400       extend_pre: Add the line number of matched preamble to the matched list.
401       extend_post: Add the line number of matched postamble to the matched list.
402     """
403     extended_matched = []
404
405     j = matched[0]
406
407     for i in matched:
408         if i == 0 or i < j:
409             continue
410         j = i
411         while j in matched:
412             j += 1
413         if j >= len(lines):
414             break
415
416         for p in pre_patterns:
417             if p.search(lines[i - 1]):
418                 break
419         else:
420             # not matched
421             continue
422
423         for p in post_patterns:
424             if p.search(lines[j]):
425                 break
426         else:
427             # not matched
428             continue
429
430         if extend_pre:
431             extended_matched.append(i - 1)
432         if extend_post:
433             extended_matched.append(j)
434
435     matched += extended_matched
436     matched.sort()
437
438 def cleanup_one_header(header_path, patterns, options):
439     """Clean regex-matched lines away from a file.
440
441     Arguments:
442       header_path: path to the cleaned file.
443       patterns: list of regex patterns.  Any lines matching to these
444                 patterns are deleted.
445       options: option flags.
446     """
447     with open(header_path) as f:
448         lines = f.readlines()
449
450     matched = []
451     for i, line in enumerate(lines):
452         if i - 1 in matched and lines[i - 1][-2:] == '\\\n':
453             matched.append(i)
454             continue
455         for pattern in patterns:
456             if pattern.search(line):
457                 matched.append(i)
458                 break
459
460     if not matched:
461         return
462
463     # remove empty #ifdef ... #endif, successive blank lines
464     pattern_if = re.compile(r'#\s*if(def|ndef)?\W') #  #if, #ifdef, #ifndef
465     pattern_elif = re.compile(r'#\s*el(if|se)\W')   #  #elif, #else
466     pattern_endif = re.compile(r'#\s*endif\W')      #  #endif
467     pattern_blank = re.compile(r'^\s*$')            #  empty line
468
469     while True:
470         old_matched = copy.copy(matched)
471         extend_matched_lines(lines, matched, [pattern_if],
472                              [pattern_endif], True, True)
473         extend_matched_lines(lines, matched, [pattern_elif],
474                              [pattern_elif, pattern_endif], True, False)
475         extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
476                              [pattern_blank], False, True)
477         extend_matched_lines(lines, matched, [pattern_blank],
478                              [pattern_elif, pattern_endif], True, False)
479         extend_matched_lines(lines, matched, [pattern_blank],
480                              [pattern_blank], True, False)
481         if matched == old_matched:
482             break
483
484     tolines = copy.copy(lines)
485
486     for i in reversed(matched):
487         tolines.pop(i)
488
489     show_diff(lines, tolines, header_path, options.color)
490
491     if options.dry_run:
492         return
493
494     with open(header_path, 'w') as f:
495         for line in tolines:
496             f.write(line)
497
498 def cleanup_headers(configs, options):
499     """Delete config defines from board headers.
500
501     Arguments:
502       configs: A list of CONFIGs to remove.
503       options: option flags.
504     """
505     if not options.yes:
506         while True:
507             choice = raw_input('Clean up headers? [y/n]: ').lower()
508             print choice
509             if choice == 'y' or choice == 'n':
510                 break
511
512         if choice == 'n':
513             return
514
515     patterns = []
516     for config in configs:
517         patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
518         patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
519
520     for dir in 'include', 'arch', 'board':
521         for (dirpath, dirnames, filenames) in os.walk(dir):
522             if dirpath == os.path.join('include', 'generated'):
523                 continue
524             for filename in filenames:
525                 if not fnmatch.fnmatch(filename, '*~'):
526                     cleanup_one_header(os.path.join(dirpath, filename),
527                                        patterns, options)
528
529 def cleanup_one_extra_option(defconfig_path, configs, options):
530     """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
531
532     Arguments:
533       defconfig_path: path to the cleaned defconfig file.
534       configs: A list of CONFIGs to remove.
535       options: option flags.
536     """
537
538     start = 'CONFIG_SYS_EXTRA_OPTIONS="'
539     end = '"\n'
540
541     with open(defconfig_path) as f:
542         lines = f.readlines()
543
544     for i, line in enumerate(lines):
545         if line.startswith(start) and line.endswith(end):
546             break
547     else:
548         # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
549         return
550
551     old_tokens = line[len(start):-len(end)].split(',')
552     new_tokens = []
553
554     for token in old_tokens:
555         pos = token.find('=')
556         if not (token[:pos] if pos >= 0 else token) in configs:
557             new_tokens.append(token)
558
559     if new_tokens == old_tokens:
560         return
561
562     tolines = copy.copy(lines)
563
564     if new_tokens:
565         tolines[i] = start + ','.join(new_tokens) + end
566     else:
567         tolines.pop(i)
568
569     show_diff(lines, tolines, defconfig_path, options.color)
570
571     if options.dry_run:
572         return
573
574     with open(defconfig_path, 'w') as f:
575         for line in tolines:
576             f.write(line)
577
578 def cleanup_extra_options(configs, options):
579     """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
580
581     Arguments:
582       configs: A list of CONFIGs to remove.
583       options: option flags.
584     """
585     if not options.yes:
586         while True:
587             choice = (raw_input('Clean up CONFIG_SYS_EXTRA_OPTIONS? [y/n]: ').
588                       lower())
589             print choice
590             if choice == 'y' or choice == 'n':
591                 break
592
593         if choice == 'n':
594             return
595
596     configs = [ config[len('CONFIG_'):] for config in configs ]
597
598     defconfigs = get_all_defconfigs()
599
600     for defconfig in defconfigs:
601         cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
602                                  options)
603
604 ### classes ###
605 class Progress:
606
607     """Progress Indicator"""
608
609     def __init__(self, total):
610         """Create a new progress indicator.
611
612         Arguments:
613           total: A number of defconfig files to process.
614         """
615         self.current = 0
616         self.total = total
617
618     def inc(self):
619         """Increment the number of processed defconfig files."""
620
621         self.current += 1
622
623     def show(self):
624         """Display the progress."""
625         print ' %d defconfigs out of %d\r' % (self.current, self.total),
626         sys.stdout.flush()
627
628 class KconfigParser:
629
630     """A parser of .config and include/autoconf.mk."""
631
632     re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
633     re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
634
635     def __init__(self, configs, options, build_dir):
636         """Create a new parser.
637
638         Arguments:
639           configs: A list of CONFIGs to move.
640           options: option flags.
641           build_dir: Build directory.
642         """
643         self.configs = configs
644         self.options = options
645         self.dotconfig = os.path.join(build_dir, '.config')
646         self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
647         self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
648                                          'autoconf.mk')
649         self.config_autoconf = os.path.join(build_dir, 'include', 'config',
650                                             'auto.conf')
651         self.defconfig = os.path.join(build_dir, 'defconfig')
652
653     def get_cross_compile(self):
654         """Parse .config file and return CROSS_COMPILE.
655
656         Returns:
657           A string storing the compiler prefix for the architecture.
658           Return a NULL string for architectures that do not require
659           compiler prefix (Sandbox and native build is the case).
660           Return None if the specified compiler is missing in your PATH.
661           Caller should distinguish '' and None.
662         """
663         arch = ''
664         cpu = ''
665         for line in open(self.dotconfig):
666             m = self.re_arch.match(line)
667             if m:
668                 arch = m.group(1)
669                 continue
670             m = self.re_cpu.match(line)
671             if m:
672                 cpu = m.group(1)
673
674         if not arch:
675             return None
676
677         # fix-up for aarch64
678         if arch == 'arm' and cpu == 'armv8':
679             arch = 'aarch64'
680
681         return CROSS_COMPILE.get(arch, None)
682
683     def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
684         """Parse .config, defconfig, include/autoconf.mk for one config.
685
686         This function looks for the config options in the lines from
687         defconfig, .config, and include/autoconf.mk in order to decide
688         which action should be taken for this defconfig.
689
690         Arguments:
691           config: CONFIG name to parse.
692           dotconfig_lines: lines from the .config file.
693           autoconf_lines: lines from the include/autoconf.mk file.
694
695         Returns:
696           A tupple of the action for this defconfig and the line
697           matched for the config.
698         """
699         not_set = '# %s is not set' % config
700
701         for line in autoconf_lines:
702             line = line.rstrip()
703             if line.startswith(config + '='):
704                 new_val = line
705                 break
706         else:
707             new_val = not_set
708
709         for line in dotconfig_lines:
710             line = line.rstrip()
711             if line.startswith(config + '=') or line == not_set:
712                 old_val = line
713                 break
714         else:
715             if new_val == not_set:
716                 return (ACTION_NO_ENTRY, config)
717             else:
718                 return (ACTION_NO_ENTRY_WARN, config)
719
720         # If this CONFIG is neither bool nor trisate
721         if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
722             # tools/scripts/define2mk.sed changes '1' to 'y'.
723             # This is a problem if the CONFIG is int type.
724             # Check the type in Kconfig and handle it correctly.
725             if new_val[-2:] == '=y':
726                 new_val = new_val[:-1] + '1'
727
728         return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
729                 new_val)
730
731     def update_dotconfig(self):
732         """Parse files for the config options and update the .config.
733
734         This function parses the generated .config and include/autoconf.mk
735         searching the target options.
736         Move the config option(s) to the .config as needed.
737
738         Arguments:
739           defconfig: defconfig name.
740
741         Returns:
742           Return a tuple of (updated flag, log string).
743           The "updated flag" is True if the .config was updated, False
744           otherwise.  The "log string" shows what happend to the .config.
745         """
746
747         results = []
748         updated = False
749         suspicious = False
750         rm_files = [self.config_autoconf, self.autoconf]
751
752         if self.options.spl:
753             if os.path.exists(self.spl_autoconf):
754                 autoconf_path = self.spl_autoconf
755                 rm_files.append(self.spl_autoconf)
756             else:
757                 for f in rm_files:
758                     os.remove(f)
759                 return (updated, suspicious,
760                         color_text(self.options.color, COLOR_BROWN,
761                                    "SPL is not enabled.  Skipped.") + '\n')
762         else:
763             autoconf_path = self.autoconf
764
765         with open(self.dotconfig) as f:
766             dotconfig_lines = f.readlines()
767
768         with open(autoconf_path) as f:
769             autoconf_lines = f.readlines()
770
771         for config in self.configs:
772             result = self.parse_one_config(config, dotconfig_lines,
773                                            autoconf_lines)
774             results.append(result)
775
776         log = ''
777
778         for (action, value) in results:
779             if action == ACTION_MOVE:
780                 actlog = "Move '%s'" % value
781                 log_color = COLOR_LIGHT_GREEN
782             elif action == ACTION_NO_ENTRY:
783                 actlog = "%s is not defined in Kconfig.  Do nothing." % value
784                 log_color = COLOR_LIGHT_BLUE
785             elif action == ACTION_NO_ENTRY_WARN:
786                 actlog = "%s is not defined in Kconfig (suspicious).  Do nothing." % value
787                 log_color = COLOR_YELLOW
788                 suspicious = True
789             elif action == ACTION_NO_CHANGE:
790                 actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
791                          % value
792                 log_color = COLOR_LIGHT_PURPLE
793             elif action == ACTION_SPL_NOT_EXIST:
794                 actlog = "SPL is not enabled for this defconfig.  Skip."
795                 log_color = COLOR_PURPLE
796             else:
797                 sys.exit("Internal Error. This should not happen.")
798
799             log += color_text(self.options.color, log_color, actlog) + '\n'
800
801         with open(self.dotconfig, 'a') as f:
802             for (action, value) in results:
803                 if action == ACTION_MOVE:
804                     f.write(value + '\n')
805                     updated = True
806
807         self.results = results
808         for f in rm_files:
809             os.remove(f)
810
811         return (updated, suspicious, log)
812
813     def check_defconfig(self):
814         """Check the defconfig after savedefconfig
815
816         Returns:
817           Return additional log if moved CONFIGs were removed again by
818           'make savedefconfig'.
819         """
820
821         log = ''
822
823         with open(self.defconfig) as f:
824             defconfig_lines = f.readlines()
825
826         for (action, value) in self.results:
827             if action != ACTION_MOVE:
828                 continue
829             if not value + '\n' in defconfig_lines:
830                 log += color_text(self.options.color, COLOR_YELLOW,
831                                   "'%s' was removed by savedefconfig.\n" %
832                                   value)
833
834         return log
835
836 class Slot:
837
838     """A slot to store a subprocess.
839
840     Each instance of this class handles one subprocess.
841     This class is useful to control multiple threads
842     for faster processing.
843     """
844
845     def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir):
846         """Create a new process slot.
847
848         Arguments:
849           configs: A list of CONFIGs to move.
850           options: option flags.
851           progress: A progress indicator.
852           devnull: A file object of '/dev/null'.
853           make_cmd: command name of GNU Make.
854           reference_src_dir: Determine the true starting config state from this
855                              source tree.
856         """
857         self.options = options
858         self.progress = progress
859         self.build_dir = tempfile.mkdtemp()
860         self.devnull = devnull
861         self.make_cmd = (make_cmd, 'O=' + self.build_dir)
862         self.reference_src_dir = reference_src_dir
863         self.parser = KconfigParser(configs, options, self.build_dir)
864         self.state = STATE_IDLE
865         self.failed_boards = set()
866         self.suspicious_boards = set()
867
868     def __del__(self):
869         """Delete the working directory
870
871         This function makes sure the temporary directory is cleaned away
872         even if Python suddenly dies due to error.  It should be done in here
873         because it is guaranteed the destructor is always invoked when the
874         instance of the class gets unreferenced.
875
876         If the subprocess is still running, wait until it finishes.
877         """
878         if self.state != STATE_IDLE:
879             while self.ps.poll() == None:
880                 pass
881         shutil.rmtree(self.build_dir)
882
883     def add(self, defconfig):
884         """Assign a new subprocess for defconfig and add it to the slot.
885
886         If the slot is vacant, create a new subprocess for processing the
887         given defconfig and add it to the slot.  Just returns False if
888         the slot is occupied (i.e. the current subprocess is still running).
889
890         Arguments:
891           defconfig: defconfig name.
892
893         Returns:
894           Return True on success or False on failure
895         """
896         if self.state != STATE_IDLE:
897             return False
898
899         self.defconfig = defconfig
900         self.log = ''
901         self.current_src_dir = self.reference_src_dir
902         self.do_defconfig()
903         return True
904
905     def poll(self):
906         """Check the status of the subprocess and handle it as needed.
907
908         Returns True if the slot is vacant (i.e. in idle state).
909         If the configuration is successfully finished, assign a new
910         subprocess to build include/autoconf.mk.
911         If include/autoconf.mk is generated, invoke the parser to
912         parse the .config and the include/autoconf.mk, moving
913         config options to the .config as needed.
914         If the .config was updated, run "make savedefconfig" to sync
915         it, update the original defconfig, and then set the slot back
916         to the idle state.
917
918         Returns:
919           Return True if the subprocess is terminated, False otherwise
920         """
921         if self.state == STATE_IDLE:
922             return True
923
924         if self.ps.poll() == None:
925             return False
926
927         if self.ps.poll() != 0:
928             self.handle_error()
929         elif self.state == STATE_DEFCONFIG:
930             if self.reference_src_dir and not self.current_src_dir:
931                 self.do_savedefconfig()
932             else:
933                 self.do_autoconf()
934         elif self.state == STATE_AUTOCONF:
935             if self.current_src_dir:
936                 self.current_src_dir = None
937                 self.do_defconfig()
938             else:
939                 self.do_savedefconfig()
940         elif self.state == STATE_SAVEDEFCONFIG:
941             self.update_defconfig()
942         else:
943             sys.exit("Internal Error. This should not happen.")
944
945         return True if self.state == STATE_IDLE else False
946
947     def handle_error(self):
948         """Handle error cases."""
949
950         self.log += color_text(self.options.color, COLOR_LIGHT_RED,
951                                "Failed to process.\n")
952         if self.options.verbose:
953             self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
954                                    self.ps.stderr.read())
955         self.finish(False)
956
957     def do_defconfig(self):
958         """Run 'make <board>_defconfig' to create the .config file."""
959
960         cmd = list(self.make_cmd)
961         cmd.append(self.defconfig)
962         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
963                                    stderr=subprocess.PIPE,
964                                    cwd=self.current_src_dir)
965         self.state = STATE_DEFCONFIG
966
967     def do_autoconf(self):
968         """Run 'make include/config/auto.conf'."""
969
970         self.cross_compile = self.parser.get_cross_compile()
971         if self.cross_compile is None:
972             self.log += color_text(self.options.color, COLOR_YELLOW,
973                                    "Compiler is missing.  Do nothing.\n")
974             self.finish(False)
975             return
976
977         cmd = list(self.make_cmd)
978         if self.cross_compile:
979             cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
980         cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
981         cmd.append('include/config/auto.conf')
982         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
983                                    stderr=subprocess.PIPE,
984                                    cwd=self.current_src_dir)
985         self.state = STATE_AUTOCONF
986
987     def do_savedefconfig(self):
988         """Update the .config and run 'make savedefconfig'."""
989
990         (updated, suspicious, log) = self.parser.update_dotconfig()
991         if suspicious:
992             self.suspicious_boards.add(self.defconfig)
993         self.log += log
994
995         if not self.options.force_sync and not updated:
996             self.finish(True)
997             return
998         if updated:
999             self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
1000                                    "Syncing by savedefconfig...\n")
1001         else:
1002             self.log += "Syncing by savedefconfig (forced by option)...\n"
1003
1004         cmd = list(self.make_cmd)
1005         cmd.append('savedefconfig')
1006         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1007                                    stderr=subprocess.PIPE)
1008         self.state = STATE_SAVEDEFCONFIG
1009
1010     def update_defconfig(self):
1011         """Update the input defconfig and go back to the idle state."""
1012
1013         log = self.parser.check_defconfig()
1014         if log:
1015             self.suspicious_boards.add(self.defconfig)
1016             self.log += log
1017         orig_defconfig = os.path.join('configs', self.defconfig)
1018         new_defconfig = os.path.join(self.build_dir, 'defconfig')
1019         updated = not filecmp.cmp(orig_defconfig, new_defconfig)
1020
1021         if updated:
1022             self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
1023                                    "defconfig was updated.\n")
1024
1025         if not self.options.dry_run and updated:
1026             shutil.move(new_defconfig, orig_defconfig)
1027         self.finish(True)
1028
1029     def finish(self, success):
1030         """Display log along with progress and go to the idle state.
1031
1032         Arguments:
1033           success: Should be True when the defconfig was processed
1034                    successfully, or False when it fails.
1035         """
1036         # output at least 30 characters to hide the "* defconfigs out of *".
1037         log = self.defconfig.ljust(30) + '\n'
1038
1039         log += '\n'.join([ '    ' + s for s in self.log.split('\n') ])
1040         # Some threads are running in parallel.
1041         # Print log atomically to not mix up logs from different threads.
1042         print >> (sys.stdout if success else sys.stderr), log
1043
1044         if not success:
1045             if self.options.exit_on_error:
1046                 sys.exit("Exit on error.")
1047             # If --exit-on-error flag is not set, skip this board and continue.
1048             # Record the failed board.
1049             self.failed_boards.add(self.defconfig)
1050
1051         self.progress.inc()
1052         self.progress.show()
1053         self.state = STATE_IDLE
1054
1055     def get_failed_boards(self):
1056         """Returns a set of failed boards (defconfigs) in this slot.
1057         """
1058         return self.failed_boards
1059
1060     def get_suspicious_boards(self):
1061         """Returns a set of boards (defconfigs) with possible misconversion.
1062         """
1063         return self.suspicious_boards - self.failed_boards
1064
1065 class Slots:
1066
1067     """Controller of the array of subprocess slots."""
1068
1069     def __init__(self, configs, options, progress, reference_src_dir):
1070         """Create a new slots controller.
1071
1072         Arguments:
1073           configs: A list of CONFIGs to move.
1074           options: option flags.
1075           progress: A progress indicator.
1076           reference_src_dir: Determine the true starting config state from this
1077                              source tree.
1078         """
1079         self.options = options
1080         self.slots = []
1081         devnull = get_devnull()
1082         make_cmd = get_make_cmd()
1083         for i in range(options.jobs):
1084             self.slots.append(Slot(configs, options, progress, devnull,
1085                                    make_cmd, reference_src_dir))
1086
1087     def add(self, defconfig):
1088         """Add a new subprocess if a vacant slot is found.
1089
1090         Arguments:
1091           defconfig: defconfig name to be put into.
1092
1093         Returns:
1094           Return True on success or False on failure
1095         """
1096         for slot in self.slots:
1097             if slot.add(defconfig):
1098                 return True
1099         return False
1100
1101     def available(self):
1102         """Check if there is a vacant slot.
1103
1104         Returns:
1105           Return True if at lease one vacant slot is found, False otherwise.
1106         """
1107         for slot in self.slots:
1108             if slot.poll():
1109                 return True
1110         return False
1111
1112     def empty(self):
1113         """Check if all slots are vacant.
1114
1115         Returns:
1116           Return True if all the slots are vacant, False otherwise.
1117         """
1118         ret = True
1119         for slot in self.slots:
1120             if not slot.poll():
1121                 ret = False
1122         return ret
1123
1124     def show_failed_boards(self):
1125         """Display all of the failed boards (defconfigs)."""
1126         boards = set()
1127         output_file = 'moveconfig.failed'
1128
1129         for slot in self.slots:
1130             boards |= slot.get_failed_boards()
1131
1132         if boards:
1133             boards = '\n'.join(boards) + '\n'
1134             msg = "The following boards were not processed due to error:\n"
1135             msg += boards
1136             msg += "(the list has been saved in %s)\n" % output_file
1137             print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
1138                                             msg)
1139
1140             with open(output_file, 'w') as f:
1141                 f.write(boards)
1142
1143     def show_suspicious_boards(self):
1144         """Display all boards (defconfigs) with possible misconversion."""
1145         boards = set()
1146         output_file = 'moveconfig.suspicious'
1147
1148         for slot in self.slots:
1149             boards |= slot.get_suspicious_boards()
1150
1151         if boards:
1152             boards = '\n'.join(boards) + '\n'
1153             msg = "The following boards might have been converted incorrectly.\n"
1154             msg += "It is highly recommended to check them manually:\n"
1155             msg += boards
1156             msg += "(the list has been saved in %s)\n" % output_file
1157             print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
1158                                             msg)
1159
1160             with open(output_file, 'w') as f:
1161                 f.write(boards)
1162
1163 class ReferenceSource:
1164
1165     """Reference source against which original configs should be parsed."""
1166
1167     def __init__(self, commit):
1168         """Create a reference source directory based on a specified commit.
1169
1170         Arguments:
1171           commit: commit to git-clone
1172         """
1173         self.src_dir = tempfile.mkdtemp()
1174         print "Cloning git repo to a separate work directory..."
1175         subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1176                                 cwd=self.src_dir)
1177         print "Checkout '%s' to build the original autoconf.mk." % \
1178             subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
1179         subprocess.check_output(['git', 'checkout', commit],
1180                                 stderr=subprocess.STDOUT, cwd=self.src_dir)
1181
1182     def __del__(self):
1183         """Delete the reference source directory
1184
1185         This function makes sure the temporary directory is cleaned away
1186         even if Python suddenly dies due to error.  It should be done in here
1187         because it is guaranteed the destructor is always invoked when the
1188         instance of the class gets unreferenced.
1189         """
1190         shutil.rmtree(self.src_dir)
1191
1192     def get_dir(self):
1193         """Return the absolute path to the reference source directory."""
1194
1195         return self.src_dir
1196
1197 def move_config(configs, options):
1198     """Move config options to defconfig files.
1199
1200     Arguments:
1201       configs: A list of CONFIGs to move.
1202       options: option flags
1203     """
1204     if len(configs) == 0:
1205         if options.force_sync:
1206             print 'No CONFIG is specified. You are probably syncing defconfigs.',
1207         else:
1208             print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
1209     else:
1210         print 'Move ' + ', '.join(configs),
1211     print '(jobs: %d)\n' % options.jobs
1212
1213     if options.git_ref:
1214         reference_src = ReferenceSource(options.git_ref)
1215         reference_src_dir = reference_src.get_dir()
1216     else:
1217         reference_src_dir = None
1218
1219     if options.defconfigs:
1220         defconfigs = get_matched_defconfigs(options.defconfigs)
1221     else:
1222         defconfigs = get_all_defconfigs()
1223
1224     progress = Progress(len(defconfigs))
1225     slots = Slots(configs, options, progress, reference_src_dir)
1226
1227     # Main loop to process defconfig files:
1228     #  Add a new subprocess into a vacant slot.
1229     #  Sleep if there is no available slot.
1230     for defconfig in defconfigs:
1231         while not slots.add(defconfig):
1232             while not slots.available():
1233                 # No available slot: sleep for a while
1234                 time.sleep(SLEEP_TIME)
1235
1236     # wait until all the subprocesses finish
1237     while not slots.empty():
1238         time.sleep(SLEEP_TIME)
1239
1240     print ''
1241     slots.show_failed_boards()
1242     slots.show_suspicious_boards()
1243
1244 def main():
1245     try:
1246         cpu_count = multiprocessing.cpu_count()
1247     except NotImplementedError:
1248         cpu_count = 1
1249
1250     parser = optparse.OptionParser()
1251     # Add options here
1252     parser.add_option('-c', '--color', action='store_true', default=False,
1253                       help='display the log in color')
1254     parser.add_option('-C', '--commit', action='store_true', default=False,
1255                       help='Create a git commit for the operation')
1256     parser.add_option('-d', '--defconfigs', type='string',
1257                       help='a file containing a list of defconfigs to move')
1258     parser.add_option('-n', '--dry-run', action='store_true', default=False,
1259                       help='perform a trial run (show log with no changes)')
1260     parser.add_option('-e', '--exit-on-error', action='store_true',
1261                       default=False,
1262                       help='exit immediately on any error')
1263     parser.add_option('-s', '--force-sync', action='store_true', default=False,
1264                       help='force sync by savedefconfig')
1265     parser.add_option('-S', '--spl', action='store_true', default=False,
1266                       help='parse config options defined for SPL build')
1267     parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1268                       action='store_true', default=False,
1269                       help='only cleanup the headers')
1270     parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1271                       help='the number of jobs to run simultaneously')
1272     parser.add_option('-r', '--git-ref', type='string',
1273                       help='the git ref to clone for building the autoconf.mk')
1274     parser.add_option('-y', '--yes', action='store_true', default=False,
1275                       help="respond 'yes' to any prompts")
1276     parser.add_option('-v', '--verbose', action='store_true', default=False,
1277                       help='show any build errors as boards are built')
1278     parser.usage += ' CONFIG ...'
1279
1280     (options, configs) = parser.parse_args()
1281
1282     if len(configs) == 0 and not options.force_sync:
1283         parser.print_usage()
1284         sys.exit(1)
1285
1286     # prefix the option name with CONFIG_ if missing
1287     configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1288                 for config in configs ]
1289
1290     check_top_directory()
1291
1292     if not options.cleanup_headers_only:
1293         check_clean_directory()
1294         update_cross_compile(options.color)
1295         move_config(configs, options)
1296
1297     if configs:
1298         cleanup_headers(configs, options)
1299         cleanup_extra_options(configs, options)
1300
1301     if options.commit:
1302         subprocess.call(['git', 'add', '-u'])
1303         if configs:
1304             msg = 'Convert %s %sto Kconfig' % (configs[0],
1305                     'et al ' if len(configs) > 1 else '')
1306             msg += ('\n\nThis converts the following to Kconfig:\n   %s\n' %
1307                     '\n   '.join(configs))
1308         else:
1309             msg = 'configs: Resync with savedefconfig'
1310             msg += '\n\nRsync all defconfig files using moveconfig.py'
1311         subprocess.call(['git', 'commit', '-s', '-m', msg])
1312
1313 if __name__ == '__main__':
1314     main()