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