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