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