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