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