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