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