patman: Don't convert input data to unicode
[platform/kernel/u-boot.git] / tools / moveconfig.py
1 #!/usr/bin/env python2
2 #
3 # Author: Masahiro Yamada <yamada.masahiro@socionext.com>
4 #
5 # SPDX-License-Identifier:      GPL-2.0+
6 #
7
8 """
9 Move config options from headers to defconfig files.
10
11 Since Kconfig was introduced to U-Boot, we have worked on moving
12 config options from headers to Kconfig (defconfig).
13
14 This tool intends to help this tremendous work.
15
16
17 Usage
18 -----
19
20 First, you must edit the Kconfig to add the menu entries for the configs
21 you are moving.
22
23 And then run this tool giving CONFIG names you want to move.
24 For example, if you want to move CONFIG_CMD_USB and CONFIG_SYS_TEXT_BASE,
25 simply type as follows:
26
27   $ tools/moveconfig.py CONFIG_CMD_USB CONFIG_SYS_TEXT_BASE
28
29 The tool walks through all the defconfig files and move the given CONFIGs.
30
31 The log is also displayed on the terminal.
32
33 The log is printed for each defconfig as follows:
34
35 <defconfig_name>
36     <action1>
37     <action2>
38     <action3>
39     ...
40
41 <defconfig_name> is the name of the defconfig.
42
43 <action*> shows what the tool did for that defconfig.
44 It looks like one of the 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 confirm(options, prompt):
439     if not options.yes:
440         while True:
441             choice = raw_input('{} [y/n]: '.format(prompt))
442             choice = choice.lower()
443             print choice
444             if choice == 'y' or choice == 'n':
445                 break
446
447         if choice == 'n':
448             return False
449
450     return True
451
452 def cleanup_one_header(header_path, patterns, options):
453     """Clean regex-matched lines away from a file.
454
455     Arguments:
456       header_path: path to the cleaned file.
457       patterns: list of regex patterns.  Any lines matching to these
458                 patterns are deleted.
459       options: option flags.
460     """
461     with open(header_path) as f:
462         lines = f.readlines()
463
464     matched = []
465     for i, line in enumerate(lines):
466         if i - 1 in matched and lines[i - 1][-2:] == '\\\n':
467             matched.append(i)
468             continue
469         for pattern in patterns:
470             if pattern.search(line):
471                 matched.append(i)
472                 break
473
474     if not matched:
475         return
476
477     # remove empty #ifdef ... #endif, successive blank lines
478     pattern_if = re.compile(r'#\s*if(def|ndef)?\W') #  #if, #ifdef, #ifndef
479     pattern_elif = re.compile(r'#\s*el(if|se)\W')   #  #elif, #else
480     pattern_endif = re.compile(r'#\s*endif\W')      #  #endif
481     pattern_blank = re.compile(r'^\s*$')            #  empty line
482
483     while True:
484         old_matched = copy.copy(matched)
485         extend_matched_lines(lines, matched, [pattern_if],
486                              [pattern_endif], True, True)
487         extend_matched_lines(lines, matched, [pattern_elif],
488                              [pattern_elif, pattern_endif], True, False)
489         extend_matched_lines(lines, matched, [pattern_if, pattern_elif],
490                              [pattern_blank], False, True)
491         extend_matched_lines(lines, matched, [pattern_blank],
492                              [pattern_elif, pattern_endif], True, False)
493         extend_matched_lines(lines, matched, [pattern_blank],
494                              [pattern_blank], True, False)
495         if matched == old_matched:
496             break
497
498     tolines = copy.copy(lines)
499
500     for i in reversed(matched):
501         tolines.pop(i)
502
503     show_diff(lines, tolines, header_path, options.color)
504
505     if options.dry_run:
506         return
507
508     with open(header_path, 'w') as f:
509         for line in tolines:
510             f.write(line)
511
512 def cleanup_headers(configs, options):
513     """Delete config defines from board headers.
514
515     Arguments:
516       configs: A list of CONFIGs to remove.
517       options: option flags.
518     """
519     if not confirm(options, 'Clean up headers?'):
520         return
521
522     patterns = []
523     for config in configs:
524         patterns.append(re.compile(r'#\s*define\s+%s\W' % config))
525         patterns.append(re.compile(r'#\s*undef\s+%s\W' % config))
526
527     for dir in 'include', 'arch', 'board':
528         for (dirpath, dirnames, filenames) in os.walk(dir):
529             if dirpath == os.path.join('include', 'generated'):
530                 continue
531             for filename in filenames:
532                 if not fnmatch.fnmatch(filename, '*~'):
533                     cleanup_one_header(os.path.join(dirpath, filename),
534                                        patterns, options)
535
536 def cleanup_one_extra_option(defconfig_path, configs, options):
537     """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file.
538
539     Arguments:
540       defconfig_path: path to the cleaned defconfig file.
541       configs: A list of CONFIGs to remove.
542       options: option flags.
543     """
544
545     start = 'CONFIG_SYS_EXTRA_OPTIONS="'
546     end = '"\n'
547
548     with open(defconfig_path) as f:
549         lines = f.readlines()
550
551     for i, line in enumerate(lines):
552         if line.startswith(start) and line.endswith(end):
553             break
554     else:
555         # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig
556         return
557
558     old_tokens = line[len(start):-len(end)].split(',')
559     new_tokens = []
560
561     for token in old_tokens:
562         pos = token.find('=')
563         if not (token[:pos] if pos >= 0 else token) in configs:
564             new_tokens.append(token)
565
566     if new_tokens == old_tokens:
567         return
568
569     tolines = copy.copy(lines)
570
571     if new_tokens:
572         tolines[i] = start + ','.join(new_tokens) + end
573     else:
574         tolines.pop(i)
575
576     show_diff(lines, tolines, defconfig_path, options.color)
577
578     if options.dry_run:
579         return
580
581     with open(defconfig_path, 'w') as f:
582         for line in tolines:
583             f.write(line)
584
585 def cleanup_extra_options(configs, options):
586     """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files.
587
588     Arguments:
589       configs: A list of CONFIGs to remove.
590       options: option flags.
591     """
592     if not confirm(options, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'):
593         return
594
595     configs = [ config[len('CONFIG_'):] for config in configs ]
596
597     defconfigs = get_all_defconfigs()
598
599     for defconfig in defconfigs:
600         cleanup_one_extra_option(os.path.join('configs', defconfig), configs,
601                                  options)
602
603 def cleanup_whitelist(configs, options):
604     """Delete config whitelist entries
605
606     Arguments:
607       configs: A list of CONFIGs to remove.
608       options: option flags.
609     """
610     if not confirm(options, 'Clean up whitelist entries?'):
611         return
612
613     with open(os.path.join('scripts', 'config_whitelist.txt')) as f:
614         lines = f.readlines()
615
616     lines = [x for x in lines if x.strip() not in configs]
617
618     with open(os.path.join('scripts', 'config_whitelist.txt'), 'w') as f:
619         f.write(''.join(lines))
620
621 def find_matching(patterns, line):
622     for pat in patterns:
623         if pat.search(line):
624             return True
625     return False
626
627 def cleanup_readme(configs, options):
628     """Delete config description in README
629
630     Arguments:
631       configs: A list of CONFIGs to remove.
632       options: option flags.
633     """
634     if not confirm(options, 'Clean up README?'):
635         return
636
637     patterns = []
638     for config in configs:
639         patterns.append(re.compile(r'^\s+%s' % config))
640
641     with open('README') as f:
642         lines = f.readlines()
643
644     found = False
645     newlines = []
646     for line in lines:
647         if not found:
648             found = find_matching(patterns, line)
649             if found:
650                 continue
651
652         if found and re.search(r'^\s+CONFIG', line):
653             found = False
654
655         if not found:
656             newlines.append(line)
657
658     with open('README', 'w') as f:
659         f.write(''.join(newlines))
660
661
662 ### classes ###
663 class Progress:
664
665     """Progress Indicator"""
666
667     def __init__(self, total):
668         """Create a new progress indicator.
669
670         Arguments:
671           total: A number of defconfig files to process.
672         """
673         self.current = 0
674         self.total = total
675
676     def inc(self):
677         """Increment the number of processed defconfig files."""
678
679         self.current += 1
680
681     def show(self):
682         """Display the progress."""
683         print ' %d defconfigs out of %d\r' % (self.current, self.total),
684         sys.stdout.flush()
685
686 class KconfigParser:
687
688     """A parser of .config and include/autoconf.mk."""
689
690     re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"')
691     re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"')
692
693     def __init__(self, configs, options, build_dir):
694         """Create a new parser.
695
696         Arguments:
697           configs: A list of CONFIGs to move.
698           options: option flags.
699           build_dir: Build directory.
700         """
701         self.configs = configs
702         self.options = options
703         self.dotconfig = os.path.join(build_dir, '.config')
704         self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk')
705         self.spl_autoconf = os.path.join(build_dir, 'spl', 'include',
706                                          'autoconf.mk')
707         self.config_autoconf = os.path.join(build_dir, 'include', 'config',
708                                             'auto.conf')
709         self.defconfig = os.path.join(build_dir, 'defconfig')
710
711     def get_cross_compile(self):
712         """Parse .config file and return CROSS_COMPILE.
713
714         Returns:
715           A string storing the compiler prefix for the architecture.
716           Return a NULL string for architectures that do not require
717           compiler prefix (Sandbox and native build is the case).
718           Return None if the specified compiler is missing in your PATH.
719           Caller should distinguish '' and None.
720         """
721         arch = ''
722         cpu = ''
723         for line in open(self.dotconfig):
724             m = self.re_arch.match(line)
725             if m:
726                 arch = m.group(1)
727                 continue
728             m = self.re_cpu.match(line)
729             if m:
730                 cpu = m.group(1)
731
732         if not arch:
733             return None
734
735         # fix-up for aarch64
736         if arch == 'arm' and cpu == 'armv8':
737             arch = 'aarch64'
738
739         return CROSS_COMPILE.get(arch, None)
740
741     def parse_one_config(self, config, dotconfig_lines, autoconf_lines):
742         """Parse .config, defconfig, include/autoconf.mk for one config.
743
744         This function looks for the config options in the lines from
745         defconfig, .config, and include/autoconf.mk in order to decide
746         which action should be taken for this defconfig.
747
748         Arguments:
749           config: CONFIG name to parse.
750           dotconfig_lines: lines from the .config file.
751           autoconf_lines: lines from the include/autoconf.mk file.
752
753         Returns:
754           A tupple of the action for this defconfig and the line
755           matched for the config.
756         """
757         not_set = '# %s is not set' % config
758
759         for line in autoconf_lines:
760             line = line.rstrip()
761             if line.startswith(config + '='):
762                 new_val = line
763                 break
764         else:
765             new_val = not_set
766
767         for line in dotconfig_lines:
768             line = line.rstrip()
769             if line.startswith(config + '=') or line == not_set:
770                 old_val = line
771                 break
772         else:
773             if new_val == not_set:
774                 return (ACTION_NO_ENTRY, config)
775             else:
776                 return (ACTION_NO_ENTRY_WARN, config)
777
778         # If this CONFIG is neither bool nor trisate
779         if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set:
780             # tools/scripts/define2mk.sed changes '1' to 'y'.
781             # This is a problem if the CONFIG is int type.
782             # Check the type in Kconfig and handle it correctly.
783             if new_val[-2:] == '=y':
784                 new_val = new_val[:-1] + '1'
785
786         return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE,
787                 new_val)
788
789     def update_dotconfig(self):
790         """Parse files for the config options and update the .config.
791
792         This function parses the generated .config and include/autoconf.mk
793         searching the target options.
794         Move the config option(s) to the .config as needed.
795
796         Arguments:
797           defconfig: defconfig name.
798
799         Returns:
800           Return a tuple of (updated flag, log string).
801           The "updated flag" is True if the .config was updated, False
802           otherwise.  The "log string" shows what happend to the .config.
803         """
804
805         results = []
806         updated = False
807         suspicious = False
808         rm_files = [self.config_autoconf, self.autoconf]
809
810         if self.options.spl:
811             if os.path.exists(self.spl_autoconf):
812                 autoconf_path = self.spl_autoconf
813                 rm_files.append(self.spl_autoconf)
814             else:
815                 for f in rm_files:
816                     os.remove(f)
817                 return (updated, suspicious,
818                         color_text(self.options.color, COLOR_BROWN,
819                                    "SPL is not enabled.  Skipped.") + '\n')
820         else:
821             autoconf_path = self.autoconf
822
823         with open(self.dotconfig) as f:
824             dotconfig_lines = f.readlines()
825
826         with open(autoconf_path) as f:
827             autoconf_lines = f.readlines()
828
829         for config in self.configs:
830             result = self.parse_one_config(config, dotconfig_lines,
831                                            autoconf_lines)
832             results.append(result)
833
834         log = ''
835
836         for (action, value) in results:
837             if action == ACTION_MOVE:
838                 actlog = "Move '%s'" % value
839                 log_color = COLOR_LIGHT_GREEN
840             elif action == ACTION_NO_ENTRY:
841                 actlog = "%s is not defined in Kconfig.  Do nothing." % value
842                 log_color = COLOR_LIGHT_BLUE
843             elif action == ACTION_NO_ENTRY_WARN:
844                 actlog = "%s is not defined in Kconfig (suspicious).  Do nothing." % value
845                 log_color = COLOR_YELLOW
846                 suspicious = True
847             elif action == ACTION_NO_CHANGE:
848                 actlog = "'%s' is the same as the define in Kconfig.  Do nothing." \
849                          % value
850                 log_color = COLOR_LIGHT_PURPLE
851             elif action == ACTION_SPL_NOT_EXIST:
852                 actlog = "SPL is not enabled for this defconfig.  Skip."
853                 log_color = COLOR_PURPLE
854             else:
855                 sys.exit("Internal Error. This should not happen.")
856
857             log += color_text(self.options.color, log_color, actlog) + '\n'
858
859         with open(self.dotconfig, 'a') as f:
860             for (action, value) in results:
861                 if action == ACTION_MOVE:
862                     f.write(value + '\n')
863                     updated = True
864
865         self.results = results
866         for f in rm_files:
867             os.remove(f)
868
869         return (updated, suspicious, log)
870
871     def check_defconfig(self):
872         """Check the defconfig after savedefconfig
873
874         Returns:
875           Return additional log if moved CONFIGs were removed again by
876           'make savedefconfig'.
877         """
878
879         log = ''
880
881         with open(self.defconfig) as f:
882             defconfig_lines = f.readlines()
883
884         for (action, value) in self.results:
885             if action != ACTION_MOVE:
886                 continue
887             if not value + '\n' in defconfig_lines:
888                 log += color_text(self.options.color, COLOR_YELLOW,
889                                   "'%s' was removed by savedefconfig.\n" %
890                                   value)
891
892         return log
893
894 class Slot:
895
896     """A slot to store a subprocess.
897
898     Each instance of this class handles one subprocess.
899     This class is useful to control multiple threads
900     for faster processing.
901     """
902
903     def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir):
904         """Create a new process slot.
905
906         Arguments:
907           configs: A list of CONFIGs to move.
908           options: option flags.
909           progress: A progress indicator.
910           devnull: A file object of '/dev/null'.
911           make_cmd: command name of GNU Make.
912           reference_src_dir: Determine the true starting config state from this
913                              source tree.
914         """
915         self.options = options
916         self.progress = progress
917         self.build_dir = tempfile.mkdtemp()
918         self.devnull = devnull
919         self.make_cmd = (make_cmd, 'O=' + self.build_dir)
920         self.reference_src_dir = reference_src_dir
921         self.parser = KconfigParser(configs, options, self.build_dir)
922         self.state = STATE_IDLE
923         self.failed_boards = set()
924         self.suspicious_boards = set()
925
926     def __del__(self):
927         """Delete the working directory
928
929         This function makes sure the temporary directory is cleaned away
930         even if Python suddenly dies due to error.  It should be done in here
931         because it is guaranteed the destructor is always invoked when the
932         instance of the class gets unreferenced.
933
934         If the subprocess is still running, wait until it finishes.
935         """
936         if self.state != STATE_IDLE:
937             while self.ps.poll() == None:
938                 pass
939         shutil.rmtree(self.build_dir)
940
941     def add(self, defconfig):
942         """Assign a new subprocess for defconfig and add it to the slot.
943
944         If the slot is vacant, create a new subprocess for processing the
945         given defconfig and add it to the slot.  Just returns False if
946         the slot is occupied (i.e. the current subprocess is still running).
947
948         Arguments:
949           defconfig: defconfig name.
950
951         Returns:
952           Return True on success or False on failure
953         """
954         if self.state != STATE_IDLE:
955             return False
956
957         self.defconfig = defconfig
958         self.log = ''
959         self.current_src_dir = self.reference_src_dir
960         self.do_defconfig()
961         return True
962
963     def poll(self):
964         """Check the status of the subprocess and handle it as needed.
965
966         Returns True if the slot is vacant (i.e. in idle state).
967         If the configuration is successfully finished, assign a new
968         subprocess to build include/autoconf.mk.
969         If include/autoconf.mk is generated, invoke the parser to
970         parse the .config and the include/autoconf.mk, moving
971         config options to the .config as needed.
972         If the .config was updated, run "make savedefconfig" to sync
973         it, update the original defconfig, and then set the slot back
974         to the idle state.
975
976         Returns:
977           Return True if the subprocess is terminated, False otherwise
978         """
979         if self.state == STATE_IDLE:
980             return True
981
982         if self.ps.poll() == None:
983             return False
984
985         if self.ps.poll() != 0:
986             self.handle_error()
987         elif self.state == STATE_DEFCONFIG:
988             if self.reference_src_dir and not self.current_src_dir:
989                 self.do_savedefconfig()
990             else:
991                 self.do_autoconf()
992         elif self.state == STATE_AUTOCONF:
993             if self.current_src_dir:
994                 self.current_src_dir = None
995                 self.do_defconfig()
996             else:
997                 self.do_savedefconfig()
998         elif self.state == STATE_SAVEDEFCONFIG:
999             self.update_defconfig()
1000         else:
1001             sys.exit("Internal Error. This should not happen.")
1002
1003         return True if self.state == STATE_IDLE else False
1004
1005     def handle_error(self):
1006         """Handle error cases."""
1007
1008         self.log += color_text(self.options.color, COLOR_LIGHT_RED,
1009                                "Failed to process.\n")
1010         if self.options.verbose:
1011             self.log += color_text(self.options.color, COLOR_LIGHT_CYAN,
1012                                    self.ps.stderr.read())
1013         self.finish(False)
1014
1015     def do_defconfig(self):
1016         """Run 'make <board>_defconfig' to create the .config file."""
1017
1018         cmd = list(self.make_cmd)
1019         cmd.append(self.defconfig)
1020         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1021                                    stderr=subprocess.PIPE,
1022                                    cwd=self.current_src_dir)
1023         self.state = STATE_DEFCONFIG
1024
1025     def do_autoconf(self):
1026         """Run 'make include/config/auto.conf'."""
1027
1028         self.cross_compile = self.parser.get_cross_compile()
1029         if self.cross_compile is None:
1030             self.log += color_text(self.options.color, COLOR_YELLOW,
1031                                    "Compiler is missing.  Do nothing.\n")
1032             self.finish(False)
1033             return
1034
1035         cmd = list(self.make_cmd)
1036         if self.cross_compile:
1037             cmd.append('CROSS_COMPILE=%s' % self.cross_compile)
1038         cmd.append('KCONFIG_IGNORE_DUPLICATES=1')
1039         cmd.append('include/config/auto.conf')
1040         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1041                                    stderr=subprocess.PIPE,
1042                                    cwd=self.current_src_dir)
1043         self.state = STATE_AUTOCONF
1044
1045     def do_savedefconfig(self):
1046         """Update the .config and run 'make savedefconfig'."""
1047
1048         (updated, suspicious, log) = self.parser.update_dotconfig()
1049         if suspicious:
1050             self.suspicious_boards.add(self.defconfig)
1051         self.log += log
1052
1053         if not self.options.force_sync and not updated:
1054             self.finish(True)
1055             return
1056         if updated:
1057             self.log += color_text(self.options.color, COLOR_LIGHT_GREEN,
1058                                    "Syncing by savedefconfig...\n")
1059         else:
1060             self.log += "Syncing by savedefconfig (forced by option)...\n"
1061
1062         cmd = list(self.make_cmd)
1063         cmd.append('savedefconfig')
1064         self.ps = subprocess.Popen(cmd, stdout=self.devnull,
1065                                    stderr=subprocess.PIPE)
1066         self.state = STATE_SAVEDEFCONFIG
1067
1068     def update_defconfig(self):
1069         """Update the input defconfig and go back to the idle state."""
1070
1071         log = self.parser.check_defconfig()
1072         if log:
1073             self.suspicious_boards.add(self.defconfig)
1074             self.log += log
1075         orig_defconfig = os.path.join('configs', self.defconfig)
1076         new_defconfig = os.path.join(self.build_dir, 'defconfig')
1077         updated = not filecmp.cmp(orig_defconfig, new_defconfig)
1078
1079         if updated:
1080             self.log += color_text(self.options.color, COLOR_LIGHT_BLUE,
1081                                    "defconfig was updated.\n")
1082
1083         if not self.options.dry_run and updated:
1084             shutil.move(new_defconfig, orig_defconfig)
1085         self.finish(True)
1086
1087     def finish(self, success):
1088         """Display log along with progress and go to the idle state.
1089
1090         Arguments:
1091           success: Should be True when the defconfig was processed
1092                    successfully, or False when it fails.
1093         """
1094         # output at least 30 characters to hide the "* defconfigs out of *".
1095         log = self.defconfig.ljust(30) + '\n'
1096
1097         log += '\n'.join([ '    ' + s for s in self.log.split('\n') ])
1098         # Some threads are running in parallel.
1099         # Print log atomically to not mix up logs from different threads.
1100         print >> (sys.stdout if success else sys.stderr), log
1101
1102         if not success:
1103             if self.options.exit_on_error:
1104                 sys.exit("Exit on error.")
1105             # If --exit-on-error flag is not set, skip this board and continue.
1106             # Record the failed board.
1107             self.failed_boards.add(self.defconfig)
1108
1109         self.progress.inc()
1110         self.progress.show()
1111         self.state = STATE_IDLE
1112
1113     def get_failed_boards(self):
1114         """Returns a set of failed boards (defconfigs) in this slot.
1115         """
1116         return self.failed_boards
1117
1118     def get_suspicious_boards(self):
1119         """Returns a set of boards (defconfigs) with possible misconversion.
1120         """
1121         return self.suspicious_boards - self.failed_boards
1122
1123 class Slots:
1124
1125     """Controller of the array of subprocess slots."""
1126
1127     def __init__(self, configs, options, progress, reference_src_dir):
1128         """Create a new slots controller.
1129
1130         Arguments:
1131           configs: A list of CONFIGs to move.
1132           options: option flags.
1133           progress: A progress indicator.
1134           reference_src_dir: Determine the true starting config state from this
1135                              source tree.
1136         """
1137         self.options = options
1138         self.slots = []
1139         devnull = get_devnull()
1140         make_cmd = get_make_cmd()
1141         for i in range(options.jobs):
1142             self.slots.append(Slot(configs, options, progress, devnull,
1143                                    make_cmd, reference_src_dir))
1144
1145     def add(self, defconfig):
1146         """Add a new subprocess if a vacant slot is found.
1147
1148         Arguments:
1149           defconfig: defconfig name to be put into.
1150
1151         Returns:
1152           Return True on success or False on failure
1153         """
1154         for slot in self.slots:
1155             if slot.add(defconfig):
1156                 return True
1157         return False
1158
1159     def available(self):
1160         """Check if there is a vacant slot.
1161
1162         Returns:
1163           Return True if at lease one vacant slot is found, False otherwise.
1164         """
1165         for slot in self.slots:
1166             if slot.poll():
1167                 return True
1168         return False
1169
1170     def empty(self):
1171         """Check if all slots are vacant.
1172
1173         Returns:
1174           Return True if all the slots are vacant, False otherwise.
1175         """
1176         ret = True
1177         for slot in self.slots:
1178             if not slot.poll():
1179                 ret = False
1180         return ret
1181
1182     def show_failed_boards(self):
1183         """Display all of the failed boards (defconfigs)."""
1184         boards = set()
1185         output_file = 'moveconfig.failed'
1186
1187         for slot in self.slots:
1188             boards |= slot.get_failed_boards()
1189
1190         if boards:
1191             boards = '\n'.join(boards) + '\n'
1192             msg = "The following boards were not processed due to error:\n"
1193             msg += boards
1194             msg += "(the list has been saved in %s)\n" % output_file
1195             print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED,
1196                                             msg)
1197
1198             with open(output_file, 'w') as f:
1199                 f.write(boards)
1200
1201     def show_suspicious_boards(self):
1202         """Display all boards (defconfigs) with possible misconversion."""
1203         boards = set()
1204         output_file = 'moveconfig.suspicious'
1205
1206         for slot in self.slots:
1207             boards |= slot.get_suspicious_boards()
1208
1209         if boards:
1210             boards = '\n'.join(boards) + '\n'
1211             msg = "The following boards might have been converted incorrectly.\n"
1212             msg += "It is highly recommended to check them manually:\n"
1213             msg += boards
1214             msg += "(the list has been saved in %s)\n" % output_file
1215             print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW,
1216                                             msg)
1217
1218             with open(output_file, 'w') as f:
1219                 f.write(boards)
1220
1221 class ReferenceSource:
1222
1223     """Reference source against which original configs should be parsed."""
1224
1225     def __init__(self, commit):
1226         """Create a reference source directory based on a specified commit.
1227
1228         Arguments:
1229           commit: commit to git-clone
1230         """
1231         self.src_dir = tempfile.mkdtemp()
1232         print "Cloning git repo to a separate work directory..."
1233         subprocess.check_output(['git', 'clone', os.getcwd(), '.'],
1234                                 cwd=self.src_dir)
1235         print "Checkout '%s' to build the original autoconf.mk." % \
1236             subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()
1237         subprocess.check_output(['git', 'checkout', commit],
1238                                 stderr=subprocess.STDOUT, cwd=self.src_dir)
1239
1240     def __del__(self):
1241         """Delete the reference source directory
1242
1243         This function makes sure the temporary directory is cleaned away
1244         even if Python suddenly dies due to error.  It should be done in here
1245         because it is guaranteed the destructor is always invoked when the
1246         instance of the class gets unreferenced.
1247         """
1248         shutil.rmtree(self.src_dir)
1249
1250     def get_dir(self):
1251         """Return the absolute path to the reference source directory."""
1252
1253         return self.src_dir
1254
1255 def move_config(configs, options):
1256     """Move config options to defconfig files.
1257
1258     Arguments:
1259       configs: A list of CONFIGs to move.
1260       options: option flags
1261     """
1262     if len(configs) == 0:
1263         if options.force_sync:
1264             print 'No CONFIG is specified. You are probably syncing defconfigs.',
1265         else:
1266             print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.',
1267     else:
1268         print 'Move ' + ', '.join(configs),
1269     print '(jobs: %d)\n' % options.jobs
1270
1271     if options.git_ref:
1272         reference_src = ReferenceSource(options.git_ref)
1273         reference_src_dir = reference_src.get_dir()
1274     else:
1275         reference_src_dir = None
1276
1277     if options.defconfigs:
1278         defconfigs = get_matched_defconfigs(options.defconfigs)
1279     else:
1280         defconfigs = get_all_defconfigs()
1281
1282     progress = Progress(len(defconfigs))
1283     slots = Slots(configs, options, progress, reference_src_dir)
1284
1285     # Main loop to process defconfig files:
1286     #  Add a new subprocess into a vacant slot.
1287     #  Sleep if there is no available slot.
1288     for defconfig in defconfigs:
1289         while not slots.add(defconfig):
1290             while not slots.available():
1291                 # No available slot: sleep for a while
1292                 time.sleep(SLEEP_TIME)
1293
1294     # wait until all the subprocesses finish
1295     while not slots.empty():
1296         time.sleep(SLEEP_TIME)
1297
1298     print ''
1299     slots.show_failed_boards()
1300     slots.show_suspicious_boards()
1301
1302 def main():
1303     try:
1304         cpu_count = multiprocessing.cpu_count()
1305     except NotImplementedError:
1306         cpu_count = 1
1307
1308     parser = optparse.OptionParser()
1309     # Add options here
1310     parser.add_option('-c', '--color', action='store_true', default=False,
1311                       help='display the log in color')
1312     parser.add_option('-C', '--commit', action='store_true', default=False,
1313                       help='Create a git commit for the operation')
1314     parser.add_option('-d', '--defconfigs', type='string',
1315                       help='a file containing a list of defconfigs to move')
1316     parser.add_option('-n', '--dry-run', action='store_true', default=False,
1317                       help='perform a trial run (show log with no changes)')
1318     parser.add_option('-e', '--exit-on-error', action='store_true',
1319                       default=False,
1320                       help='exit immediately on any error')
1321     parser.add_option('-s', '--force-sync', action='store_true', default=False,
1322                       help='force sync by savedefconfig')
1323     parser.add_option('-S', '--spl', action='store_true', default=False,
1324                       help='parse config options defined for SPL build')
1325     parser.add_option('-H', '--headers-only', dest='cleanup_headers_only',
1326                       action='store_true', default=False,
1327                       help='only cleanup the headers')
1328     parser.add_option('-j', '--jobs', type='int', default=cpu_count,
1329                       help='the number of jobs to run simultaneously')
1330     parser.add_option('-r', '--git-ref', type='string',
1331                       help='the git ref to clone for building the autoconf.mk')
1332     parser.add_option('-y', '--yes', action='store_true', default=False,
1333                       help="respond 'yes' to any prompts")
1334     parser.add_option('-v', '--verbose', action='store_true', default=False,
1335                       help='show any build errors as boards are built')
1336     parser.usage += ' CONFIG ...'
1337
1338     (options, configs) = parser.parse_args()
1339
1340     if len(configs) == 0 and not options.force_sync:
1341         parser.print_usage()
1342         sys.exit(1)
1343
1344     # prefix the option name with CONFIG_ if missing
1345     configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config
1346                 for config in configs ]
1347
1348     check_top_directory()
1349
1350     if not options.cleanup_headers_only:
1351         check_clean_directory()
1352         update_cross_compile(options.color)
1353         move_config(configs, options)
1354
1355     if configs:
1356         cleanup_headers(configs, options)
1357         cleanup_extra_options(configs, options)
1358         cleanup_whitelist(configs, options)
1359         cleanup_readme(configs, options)
1360
1361     if options.commit:
1362         subprocess.call(['git', 'add', '-u'])
1363         if configs:
1364             msg = 'Convert %s %sto Kconfig' % (configs[0],
1365                     'et al ' if len(configs) > 1 else '')
1366             msg += ('\n\nThis converts the following to Kconfig:\n   %s\n' %
1367                     '\n   '.join(configs))
1368         else:
1369             msg = 'configs: Resync with savedefconfig'
1370             msg += '\n\nRsync all defconfig files using moveconfig.py'
1371         subprocess.call(['git', 'commit', '-s', '-m', msg])
1372
1373 if __name__ == '__main__':
1374     main()