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