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 confirm(options, prompt): 439 if not options.yes: 440 while True: 441 choice = raw_input('{} [y/n]: '.format(prompt)) 442 choice = choice.lower() 443 print choice 444 if choice == 'y' or choice == 'n': 445 break 446 447 if choice == 'n': 448 return False 449 450 return True 451 452def cleanup_one_header(header_path, patterns, options): 453 """Clean regex-matched lines away from a file. 454 455 Arguments: 456 header_path: path to the cleaned file. 457 patterns: list of regex patterns. Any lines matching to these 458 patterns are deleted. 459 options: option flags. 460 """ 461 with open(header_path) as f: 462 lines = f.readlines() 463 464 matched = [] 465 for i, line in enumerate(lines): 466 if i - 1 in matched and lines[i - 1][-2:] == '\\\n': 467 matched.append(i) 468 continue 469 for pattern in patterns: 470 if pattern.search(line): 471 matched.append(i) 472 break 473 474 if not matched: 475 return 476 477 # remove empty #ifdef ... #endif, successive blank lines 478 pattern_if = re.compile(r'#\s*if(def|ndef)?\W') # #if, #ifdef, #ifndef 479 pattern_elif = re.compile(r'#\s*el(if|se)\W') # #elif, #else 480 pattern_endif = re.compile(r'#\s*endif\W') # #endif 481 pattern_blank = re.compile(r'^\s*$') # empty line 482 483 while True: 484 old_matched = copy.copy(matched) 485 extend_matched_lines(lines, matched, [pattern_if], 486 [pattern_endif], True, True) 487 extend_matched_lines(lines, matched, [pattern_elif], 488 [pattern_elif, pattern_endif], True, False) 489 extend_matched_lines(lines, matched, [pattern_if, pattern_elif], 490 [pattern_blank], False, True) 491 extend_matched_lines(lines, matched, [pattern_blank], 492 [pattern_elif, pattern_endif], True, False) 493 extend_matched_lines(lines, matched, [pattern_blank], 494 [pattern_blank], True, False) 495 if matched == old_matched: 496 break 497 498 tolines = copy.copy(lines) 499 500 for i in reversed(matched): 501 tolines.pop(i) 502 503 show_diff(lines, tolines, header_path, options.color) 504 505 if options.dry_run: 506 return 507 508 with open(header_path, 'w') as f: 509 for line in tolines: 510 f.write(line) 511 512def cleanup_headers(configs, options): 513 """Delete config defines from board headers. 514 515 Arguments: 516 configs: A list of CONFIGs to remove. 517 options: option flags. 518 """ 519 if not confirm(options, 'Clean up headers?'): 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 confirm(options, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'): 593 return 594 595 configs = [ config[len('CONFIG_'):] for config in configs ] 596 597 defconfigs = get_all_defconfigs() 598 599 for defconfig in defconfigs: 600 cleanup_one_extra_option(os.path.join('configs', defconfig), configs, 601 options) 602 603### classes ### 604class Progress: 605 606 """Progress Indicator""" 607 608 def __init__(self, total): 609 """Create a new progress indicator. 610 611 Arguments: 612 total: A number of defconfig files to process. 613 """ 614 self.current = 0 615 self.total = total 616 617 def inc(self): 618 """Increment the number of processed defconfig files.""" 619 620 self.current += 1 621 622 def show(self): 623 """Display the progress.""" 624 print ' %d defconfigs out of %d\r' % (self.current, self.total), 625 sys.stdout.flush() 626 627class KconfigParser: 628 629 """A parser of .config and include/autoconf.mk.""" 630 631 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"') 632 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"') 633 634 def __init__(self, configs, options, build_dir): 635 """Create a new parser. 636 637 Arguments: 638 configs: A list of CONFIGs to move. 639 options: option flags. 640 build_dir: Build directory. 641 """ 642 self.configs = configs 643 self.options = options 644 self.dotconfig = os.path.join(build_dir, '.config') 645 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk') 646 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include', 647 'autoconf.mk') 648 self.config_autoconf = os.path.join(build_dir, 'include', 'config', 649 'auto.conf') 650 self.defconfig = os.path.join(build_dir, 'defconfig') 651 652 def get_cross_compile(self): 653 """Parse .config file and return CROSS_COMPILE. 654 655 Returns: 656 A string storing the compiler prefix for the architecture. 657 Return a NULL string for architectures that do not require 658 compiler prefix (Sandbox and native build is the case). 659 Return None if the specified compiler is missing in your PATH. 660 Caller should distinguish '' and None. 661 """ 662 arch = '' 663 cpu = '' 664 for line in open(self.dotconfig): 665 m = self.re_arch.match(line) 666 if m: 667 arch = m.group(1) 668 continue 669 m = self.re_cpu.match(line) 670 if m: 671 cpu = m.group(1) 672 673 if not arch: 674 return None 675 676 # fix-up for aarch64 677 if arch == 'arm' and cpu == 'armv8': 678 arch = 'aarch64' 679 680 return CROSS_COMPILE.get(arch, None) 681 682 def parse_one_config(self, config, dotconfig_lines, autoconf_lines): 683 """Parse .config, defconfig, include/autoconf.mk for one config. 684 685 This function looks for the config options in the lines from 686 defconfig, .config, and include/autoconf.mk in order to decide 687 which action should be taken for this defconfig. 688 689 Arguments: 690 config: CONFIG name to parse. 691 dotconfig_lines: lines from the .config file. 692 autoconf_lines: lines from the include/autoconf.mk file. 693 694 Returns: 695 A tupple of the action for this defconfig and the line 696 matched for the config. 697 """ 698 not_set = '# %s is not set' % config 699 700 for line in autoconf_lines: 701 line = line.rstrip() 702 if line.startswith(config + '='): 703 new_val = line 704 break 705 else: 706 new_val = not_set 707 708 for line in dotconfig_lines: 709 line = line.rstrip() 710 if line.startswith(config + '=') or line == not_set: 711 old_val = line 712 break 713 else: 714 if new_val == not_set: 715 return (ACTION_NO_ENTRY, config) 716 else: 717 return (ACTION_NO_ENTRY_WARN, config) 718 719 # If this CONFIG is neither bool nor trisate 720 if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set: 721 # tools/scripts/define2mk.sed changes '1' to 'y'. 722 # This is a problem if the CONFIG is int type. 723 # Check the type in Kconfig and handle it correctly. 724 if new_val[-2:] == '=y': 725 new_val = new_val[:-1] + '1' 726 727 return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE, 728 new_val) 729 730 def update_dotconfig(self): 731 """Parse files for the config options and update the .config. 732 733 This function parses the generated .config and include/autoconf.mk 734 searching the target options. 735 Move the config option(s) to the .config as needed. 736 737 Arguments: 738 defconfig: defconfig name. 739 740 Returns: 741 Return a tuple of (updated flag, log string). 742 The "updated flag" is True if the .config was updated, False 743 otherwise. The "log string" shows what happend to the .config. 744 """ 745 746 results = [] 747 updated = False 748 suspicious = False 749 rm_files = [self.config_autoconf, self.autoconf] 750 751 if self.options.spl: 752 if os.path.exists(self.spl_autoconf): 753 autoconf_path = self.spl_autoconf 754 rm_files.append(self.spl_autoconf) 755 else: 756 for f in rm_files: 757 os.remove(f) 758 return (updated, suspicious, 759 color_text(self.options.color, COLOR_BROWN, 760 "SPL is not enabled. Skipped.") + '\n') 761 else: 762 autoconf_path = self.autoconf 763 764 with open(self.dotconfig) as f: 765 dotconfig_lines = f.readlines() 766 767 with open(autoconf_path) as f: 768 autoconf_lines = f.readlines() 769 770 for config in self.configs: 771 result = self.parse_one_config(config, dotconfig_lines, 772 autoconf_lines) 773 results.append(result) 774 775 log = '' 776 777 for (action, value) in results: 778 if action == ACTION_MOVE: 779 actlog = "Move '%s'" % value 780 log_color = COLOR_LIGHT_GREEN 781 elif action == ACTION_NO_ENTRY: 782 actlog = "%s is not defined in Kconfig. Do nothing." % value 783 log_color = COLOR_LIGHT_BLUE 784 elif action == ACTION_NO_ENTRY_WARN: 785 actlog = "%s is not defined in Kconfig (suspicious). Do nothing." % value 786 log_color = COLOR_YELLOW 787 suspicious = True 788 elif action == ACTION_NO_CHANGE: 789 actlog = "'%s' is the same as the define in Kconfig. Do nothing." \ 790 % value 791 log_color = COLOR_LIGHT_PURPLE 792 elif action == ACTION_SPL_NOT_EXIST: 793 actlog = "SPL is not enabled for this defconfig. Skip." 794 log_color = COLOR_PURPLE 795 else: 796 sys.exit("Internal Error. This should not happen.") 797 798 log += color_text(self.options.color, log_color, actlog) + '\n' 799 800 with open(self.dotconfig, 'a') as f: 801 for (action, value) in results: 802 if action == ACTION_MOVE: 803 f.write(value + '\n') 804 updated = True 805 806 self.results = results 807 for f in rm_files: 808 os.remove(f) 809 810 return (updated, suspicious, log) 811 812 def check_defconfig(self): 813 """Check the defconfig after savedefconfig 814 815 Returns: 816 Return additional log if moved CONFIGs were removed again by 817 'make savedefconfig'. 818 """ 819 820 log = '' 821 822 with open(self.defconfig) as f: 823 defconfig_lines = f.readlines() 824 825 for (action, value) in self.results: 826 if action != ACTION_MOVE: 827 continue 828 if not value + '\n' in defconfig_lines: 829 log += color_text(self.options.color, COLOR_YELLOW, 830 "'%s' was removed by savedefconfig.\n" % 831 value) 832 833 return log 834 835class Slot: 836 837 """A slot to store a subprocess. 838 839 Each instance of this class handles one subprocess. 840 This class is useful to control multiple threads 841 for faster processing. 842 """ 843 844 def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir): 845 """Create a new process slot. 846 847 Arguments: 848 configs: A list of CONFIGs to move. 849 options: option flags. 850 progress: A progress indicator. 851 devnull: A file object of '/dev/null'. 852 make_cmd: command name of GNU Make. 853 reference_src_dir: Determine the true starting config state from this 854 source tree. 855 """ 856 self.options = options 857 self.progress = progress 858 self.build_dir = tempfile.mkdtemp() 859 self.devnull = devnull 860 self.make_cmd = (make_cmd, 'O=' + self.build_dir) 861 self.reference_src_dir = reference_src_dir 862 self.parser = KconfigParser(configs, options, self.build_dir) 863 self.state = STATE_IDLE 864 self.failed_boards = set() 865 self.suspicious_boards = set() 866 867 def __del__(self): 868 """Delete the working directory 869 870 This function makes sure the temporary directory is cleaned away 871 even if Python suddenly dies due to error. It should be done in here 872 because it is guaranteed the destructor is always invoked when the 873 instance of the class gets unreferenced. 874 875 If the subprocess is still running, wait until it finishes. 876 """ 877 if self.state != STATE_IDLE: 878 while self.ps.poll() == None: 879 pass 880 shutil.rmtree(self.build_dir) 881 882 def add(self, defconfig): 883 """Assign a new subprocess for defconfig and add it to the slot. 884 885 If the slot is vacant, create a new subprocess for processing the 886 given defconfig and add it to the slot. Just returns False if 887 the slot is occupied (i.e. the current subprocess is still running). 888 889 Arguments: 890 defconfig: defconfig name. 891 892 Returns: 893 Return True on success or False on failure 894 """ 895 if self.state != STATE_IDLE: 896 return False 897 898 self.defconfig = defconfig 899 self.log = '' 900 self.current_src_dir = self.reference_src_dir 901 self.do_defconfig() 902 return True 903 904 def poll(self): 905 """Check the status of the subprocess and handle it as needed. 906 907 Returns True if the slot is vacant (i.e. in idle state). 908 If the configuration is successfully finished, assign a new 909 subprocess to build include/autoconf.mk. 910 If include/autoconf.mk is generated, invoke the parser to 911 parse the .config and the include/autoconf.mk, moving 912 config options to the .config as needed. 913 If the .config was updated, run "make savedefconfig" to sync 914 it, update the original defconfig, and then set the slot back 915 to the idle state. 916 917 Returns: 918 Return True if the subprocess is terminated, False otherwise 919 """ 920 if self.state == STATE_IDLE: 921 return True 922 923 if self.ps.poll() == None: 924 return False 925 926 if self.ps.poll() != 0: 927 self.handle_error() 928 elif self.state == STATE_DEFCONFIG: 929 if self.reference_src_dir and not self.current_src_dir: 930 self.do_savedefconfig() 931 else: 932 self.do_autoconf() 933 elif self.state == STATE_AUTOCONF: 934 if self.current_src_dir: 935 self.current_src_dir = None 936 self.do_defconfig() 937 else: 938 self.do_savedefconfig() 939 elif self.state == STATE_SAVEDEFCONFIG: 940 self.update_defconfig() 941 else: 942 sys.exit("Internal Error. This should not happen.") 943 944 return True if self.state == STATE_IDLE else False 945 946 def handle_error(self): 947 """Handle error cases.""" 948 949 self.log += color_text(self.options.color, COLOR_LIGHT_RED, 950 "Failed to process.\n") 951 if self.options.verbose: 952 self.log += color_text(self.options.color, COLOR_LIGHT_CYAN, 953 self.ps.stderr.read()) 954 self.finish(False) 955 956 def do_defconfig(self): 957 """Run 'make <board>_defconfig' to create the .config file.""" 958 959 cmd = list(self.make_cmd) 960 cmd.append(self.defconfig) 961 self.ps = subprocess.Popen(cmd, stdout=self.devnull, 962 stderr=subprocess.PIPE, 963 cwd=self.current_src_dir) 964 self.state = STATE_DEFCONFIG 965 966 def do_autoconf(self): 967 """Run 'make include/config/auto.conf'.""" 968 969 self.cross_compile = self.parser.get_cross_compile() 970 if self.cross_compile is None: 971 self.log += color_text(self.options.color, COLOR_YELLOW, 972 "Compiler is missing. Do nothing.\n") 973 self.finish(False) 974 return 975 976 cmd = list(self.make_cmd) 977 if self.cross_compile: 978 cmd.append('CROSS_COMPILE=%s' % self.cross_compile) 979 cmd.append('KCONFIG_IGNORE_DUPLICATES=1') 980 cmd.append('include/config/auto.conf') 981 self.ps = subprocess.Popen(cmd, stdout=self.devnull, 982 stderr=subprocess.PIPE, 983 cwd=self.current_src_dir) 984 self.state = STATE_AUTOCONF 985 986 def do_savedefconfig(self): 987 """Update the .config and run 'make savedefconfig'.""" 988 989 (updated, suspicious, log) = self.parser.update_dotconfig() 990 if suspicious: 991 self.suspicious_boards.add(self.defconfig) 992 self.log += log 993 994 if not self.options.force_sync and not updated: 995 self.finish(True) 996 return 997 if updated: 998 self.log += color_text(self.options.color, COLOR_LIGHT_GREEN, 999 "Syncing by savedefconfig...\n") 1000 else: 1001 self.log += "Syncing by savedefconfig (forced by option)...\n" 1002 1003 cmd = list(self.make_cmd) 1004 cmd.append('savedefconfig') 1005 self.ps = subprocess.Popen(cmd, stdout=self.devnull, 1006 stderr=subprocess.PIPE) 1007 self.state = STATE_SAVEDEFCONFIG 1008 1009 def update_defconfig(self): 1010 """Update the input defconfig and go back to the idle state.""" 1011 1012 log = self.parser.check_defconfig() 1013 if log: 1014 self.suspicious_boards.add(self.defconfig) 1015 self.log += log 1016 orig_defconfig = os.path.join('configs', self.defconfig) 1017 new_defconfig = os.path.join(self.build_dir, 'defconfig') 1018 updated = not filecmp.cmp(orig_defconfig, new_defconfig) 1019 1020 if updated: 1021 self.log += color_text(self.options.color, COLOR_LIGHT_BLUE, 1022 "defconfig was updated.\n") 1023 1024 if not self.options.dry_run and updated: 1025 shutil.move(new_defconfig, orig_defconfig) 1026 self.finish(True) 1027 1028 def finish(self, success): 1029 """Display log along with progress and go to the idle state. 1030 1031 Arguments: 1032 success: Should be True when the defconfig was processed 1033 successfully, or False when it fails. 1034 """ 1035 # output at least 30 characters to hide the "* defconfigs out of *". 1036 log = self.defconfig.ljust(30) + '\n' 1037 1038 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ]) 1039 # Some threads are running in parallel. 1040 # Print log atomically to not mix up logs from different threads. 1041 print >> (sys.stdout if success else sys.stderr), log 1042 1043 if not success: 1044 if self.options.exit_on_error: 1045 sys.exit("Exit on error.") 1046 # If --exit-on-error flag is not set, skip this board and continue. 1047 # Record the failed board. 1048 self.failed_boards.add(self.defconfig) 1049 1050 self.progress.inc() 1051 self.progress.show() 1052 self.state = STATE_IDLE 1053 1054 def get_failed_boards(self): 1055 """Returns a set of failed boards (defconfigs) in this slot. 1056 """ 1057 return self.failed_boards 1058 1059 def get_suspicious_boards(self): 1060 """Returns a set of boards (defconfigs) with possible misconversion. 1061 """ 1062 return self.suspicious_boards - self.failed_boards 1063 1064class Slots: 1065 1066 """Controller of the array of subprocess slots.""" 1067 1068 def __init__(self, configs, options, progress, reference_src_dir): 1069 """Create a new slots controller. 1070 1071 Arguments: 1072 configs: A list of CONFIGs to move. 1073 options: option flags. 1074 progress: A progress indicator. 1075 reference_src_dir: Determine the true starting config state from this 1076 source tree. 1077 """ 1078 self.options = options 1079 self.slots = [] 1080 devnull = get_devnull() 1081 make_cmd = get_make_cmd() 1082 for i in range(options.jobs): 1083 self.slots.append(Slot(configs, options, progress, devnull, 1084 make_cmd, reference_src_dir)) 1085 1086 def add(self, defconfig): 1087 """Add a new subprocess if a vacant slot is found. 1088 1089 Arguments: 1090 defconfig: defconfig name to be put into. 1091 1092 Returns: 1093 Return True on success or False on failure 1094 """ 1095 for slot in self.slots: 1096 if slot.add(defconfig): 1097 return True 1098 return False 1099 1100 def available(self): 1101 """Check if there is a vacant slot. 1102 1103 Returns: 1104 Return True if at lease one vacant slot is found, False otherwise. 1105 """ 1106 for slot in self.slots: 1107 if slot.poll(): 1108 return True 1109 return False 1110 1111 def empty(self): 1112 """Check if all slots are vacant. 1113 1114 Returns: 1115 Return True if all the slots are vacant, False otherwise. 1116 """ 1117 ret = True 1118 for slot in self.slots: 1119 if not slot.poll(): 1120 ret = False 1121 return ret 1122 1123 def show_failed_boards(self): 1124 """Display all of the failed boards (defconfigs).""" 1125 boards = set() 1126 output_file = 'moveconfig.failed' 1127 1128 for slot in self.slots: 1129 boards |= slot.get_failed_boards() 1130 1131 if boards: 1132 boards = '\n'.join(boards) + '\n' 1133 msg = "The following boards were not processed due to error:\n" 1134 msg += boards 1135 msg += "(the list has been saved in %s)\n" % output_file 1136 print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED, 1137 msg) 1138 1139 with open(output_file, 'w') as f: 1140 f.write(boards) 1141 1142 def show_suspicious_boards(self): 1143 """Display all boards (defconfigs) with possible misconversion.""" 1144 boards = set() 1145 output_file = 'moveconfig.suspicious' 1146 1147 for slot in self.slots: 1148 boards |= slot.get_suspicious_boards() 1149 1150 if boards: 1151 boards = '\n'.join(boards) + '\n' 1152 msg = "The following boards might have been converted incorrectly.\n" 1153 msg += "It is highly recommended to check them manually:\n" 1154 msg += boards 1155 msg += "(the list has been saved in %s)\n" % output_file 1156 print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW, 1157 msg) 1158 1159 with open(output_file, 'w') as f: 1160 f.write(boards) 1161 1162class ReferenceSource: 1163 1164 """Reference source against which original configs should be parsed.""" 1165 1166 def __init__(self, commit): 1167 """Create a reference source directory based on a specified commit. 1168 1169 Arguments: 1170 commit: commit to git-clone 1171 """ 1172 self.src_dir = tempfile.mkdtemp() 1173 print "Cloning git repo to a separate work directory..." 1174 subprocess.check_output(['git', 'clone', os.getcwd(), '.'], 1175 cwd=self.src_dir) 1176 print "Checkout '%s' to build the original autoconf.mk." % \ 1177 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip() 1178 subprocess.check_output(['git', 'checkout', commit], 1179 stderr=subprocess.STDOUT, cwd=self.src_dir) 1180 1181 def __del__(self): 1182 """Delete the reference source directory 1183 1184 This function makes sure the temporary directory is cleaned away 1185 even if Python suddenly dies due to error. It should be done in here 1186 because it is guaranteed the destructor is always invoked when the 1187 instance of the class gets unreferenced. 1188 """ 1189 shutil.rmtree(self.src_dir) 1190 1191 def get_dir(self): 1192 """Return the absolute path to the reference source directory.""" 1193 1194 return self.src_dir 1195 1196def move_config(configs, options): 1197 """Move config options to defconfig files. 1198 1199 Arguments: 1200 configs: A list of CONFIGs to move. 1201 options: option flags 1202 """ 1203 if len(configs) == 0: 1204 if options.force_sync: 1205 print 'No CONFIG is specified. You are probably syncing defconfigs.', 1206 else: 1207 print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.', 1208 else: 1209 print 'Move ' + ', '.join(configs), 1210 print '(jobs: %d)\n' % options.jobs 1211 1212 if options.git_ref: 1213 reference_src = ReferenceSource(options.git_ref) 1214 reference_src_dir = reference_src.get_dir() 1215 else: 1216 reference_src_dir = None 1217 1218 if options.defconfigs: 1219 defconfigs = get_matched_defconfigs(options.defconfigs) 1220 else: 1221 defconfigs = get_all_defconfigs() 1222 1223 progress = Progress(len(defconfigs)) 1224 slots = Slots(configs, options, progress, reference_src_dir) 1225 1226 # Main loop to process defconfig files: 1227 # Add a new subprocess into a vacant slot. 1228 # Sleep if there is no available slot. 1229 for defconfig in defconfigs: 1230 while not slots.add(defconfig): 1231 while not slots.available(): 1232 # No available slot: sleep for a while 1233 time.sleep(SLEEP_TIME) 1234 1235 # wait until all the subprocesses finish 1236 while not slots.empty(): 1237 time.sleep(SLEEP_TIME) 1238 1239 print '' 1240 slots.show_failed_boards() 1241 slots.show_suspicious_boards() 1242 1243def main(): 1244 try: 1245 cpu_count = multiprocessing.cpu_count() 1246 except NotImplementedError: 1247 cpu_count = 1 1248 1249 parser = optparse.OptionParser() 1250 # Add options here 1251 parser.add_option('-c', '--color', action='store_true', default=False, 1252 help='display the log in color') 1253 parser.add_option('-C', '--commit', action='store_true', default=False, 1254 help='Create a git commit for the operation') 1255 parser.add_option('-d', '--defconfigs', type='string', 1256 help='a file containing a list of defconfigs to move') 1257 parser.add_option('-n', '--dry-run', action='store_true', default=False, 1258 help='perform a trial run (show log with no changes)') 1259 parser.add_option('-e', '--exit-on-error', action='store_true', 1260 default=False, 1261 help='exit immediately on any error') 1262 parser.add_option('-s', '--force-sync', action='store_true', default=False, 1263 help='force sync by savedefconfig') 1264 parser.add_option('-S', '--spl', action='store_true', default=False, 1265 help='parse config options defined for SPL build') 1266 parser.add_option('-H', '--headers-only', dest='cleanup_headers_only', 1267 action='store_true', default=False, 1268 help='only cleanup the headers') 1269 parser.add_option('-j', '--jobs', type='int', default=cpu_count, 1270 help='the number of jobs to run simultaneously') 1271 parser.add_option('-r', '--git-ref', type='string', 1272 help='the git ref to clone for building the autoconf.mk') 1273 parser.add_option('-y', '--yes', action='store_true', default=False, 1274 help="respond 'yes' to any prompts") 1275 parser.add_option('-v', '--verbose', action='store_true', default=False, 1276 help='show any build errors as boards are built') 1277 parser.usage += ' CONFIG ...' 1278 1279 (options, configs) = parser.parse_args() 1280 1281 if len(configs) == 0 and not options.force_sync: 1282 parser.print_usage() 1283 sys.exit(1) 1284 1285 # prefix the option name with CONFIG_ if missing 1286 configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config 1287 for config in configs ] 1288 1289 check_top_directory() 1290 1291 if not options.cleanup_headers_only: 1292 check_clean_directory() 1293 update_cross_compile(options.color) 1294 move_config(configs, options) 1295 1296 if configs: 1297 cleanup_headers(configs, options) 1298 cleanup_extra_options(configs, options) 1299 1300 if options.commit: 1301 subprocess.call(['git', 'add', '-u']) 1302 if configs: 1303 msg = 'Convert %s %sto Kconfig' % (configs[0], 1304 'et al ' if len(configs) > 1 else '') 1305 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' % 1306 '\n '.join(configs)) 1307 else: 1308 msg = 'configs: Resync with savedefconfig' 1309 msg += '\n\nRsync all defconfig files using moveconfig.py' 1310 subprocess.call(['git', 'commit', '-s', '-m', msg]) 1311 1312if __name__ == '__main__': 1313 main() 1314