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