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