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): 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 """ 287 288 diff = difflib.unified_diff(a, b, 289 fromfile=os.path.join('a', file_path), 290 tofile=os.path.join('b', file_path)) 291 292 for line in diff: 293 print line, 294 295def update_cross_compile(color_enabled): 296 """Update per-arch CROSS_COMPILE via environment variables 297 298 The default CROSS_COMPILE values are available 299 in the CROSS_COMPILE list above. 300 301 You can override them via environment variables 302 CROSS_COMPILE_{ARCH}. 303 304 For example, if you want to override toolchain prefixes 305 for ARM and PowerPC, you can do as follows in your shell: 306 307 export CROSS_COMPILE_ARM=... 308 export CROSS_COMPILE_POWERPC=... 309 310 Then, this function checks if specified compilers really exist in your 311 PATH environment. 312 """ 313 archs = [] 314 315 for arch in os.listdir('arch'): 316 if os.path.exists(os.path.join('arch', arch, 'Makefile')): 317 archs.append(arch) 318 319 # arm64 is a special case 320 archs.append('aarch64') 321 322 for arch in archs: 323 env = 'CROSS_COMPILE_' + arch.upper() 324 cross_compile = os.environ.get(env) 325 if not cross_compile: 326 cross_compile = CROSS_COMPILE.get(arch, '') 327 328 for path in os.environ["PATH"].split(os.pathsep): 329 gcc_path = os.path.join(path, cross_compile + 'gcc') 330 if os.path.isfile(gcc_path) and os.access(gcc_path, os.X_OK): 331 break 332 else: 333 print >> sys.stderr, color_text(color_enabled, COLOR_YELLOW, 334 'warning: %sgcc: not found in PATH. %s architecture boards will be skipped' 335 % (cross_compile, arch)) 336 cross_compile = None 337 338 CROSS_COMPILE[arch] = cross_compile 339 340def extend_matched_lines(lines, matched, pre_patterns, post_patterns, extend_pre, 341 extend_post): 342 """Extend matched lines if desired patterns are found before/after already 343 matched lines. 344 345 Arguments: 346 lines: A list of lines handled. 347 matched: A list of line numbers that have been already matched. 348 (will be updated by this function) 349 pre_patterns: A list of regular expression that should be matched as 350 preamble. 351 post_patterns: A list of regular expression that should be matched as 352 postamble. 353 extend_pre: Add the line number of matched preamble to the matched list. 354 extend_post: Add the line number of matched postamble to the matched list. 355 """ 356 extended_matched = [] 357 358 j = matched[0] 359 360 for i in matched: 361 if i == 0 or i < j: 362 continue 363 j = i 364 while j in matched: 365 j += 1 366 if j >= len(lines): 367 break 368 369 for p in pre_patterns: 370 if p.search(lines[i - 1]): 371 break 372 else: 373 # not matched 374 continue 375 376 for p in post_patterns: 377 if p.search(lines[j]): 378 break 379 else: 380 # not matched 381 continue 382 383 if extend_pre: 384 extended_matched.append(i - 1) 385 if extend_post: 386 extended_matched.append(j) 387 388 matched += extended_matched 389 matched.sort() 390 391def cleanup_one_header(header_path, patterns, dry_run): 392 """Clean regex-matched lines away from a file. 393 394 Arguments: 395 header_path: path to the cleaned file. 396 patterns: list of regex patterns. Any lines matching to these 397 patterns are deleted. 398 dry_run: make no changes, but still display log. 399 """ 400 with open(header_path) as f: 401 lines = f.readlines() 402 403 matched = [] 404 for i, line in enumerate(lines): 405 for pattern in patterns: 406 if pattern.search(line): 407 matched.append(i) 408 break 409 410 if not matched: 411 return 412 413 # remove empty #ifdef ... #endif, successive blank lines 414 pattern_if = re.compile(r'#\s*if(def|ndef)?\W') # #if, #ifdef, #ifndef 415 pattern_elif = re.compile(r'#\s*el(if|se)\W') # #elif, #else 416 pattern_endif = re.compile(r'#\s*endif\W') # #endif 417 pattern_blank = re.compile(r'^\s*$') # empty line 418 419 while True: 420 old_matched = copy.copy(matched) 421 extend_matched_lines(lines, matched, [pattern_if], 422 [pattern_endif], True, True) 423 extend_matched_lines(lines, matched, [pattern_elif], 424 [pattern_elif, pattern_endif], True, False) 425 extend_matched_lines(lines, matched, [pattern_if, pattern_elif], 426 [pattern_blank], False, True) 427 extend_matched_lines(lines, matched, [pattern_blank], 428 [pattern_elif, pattern_endif], True, False) 429 extend_matched_lines(lines, matched, [pattern_blank], 430 [pattern_blank], True, False) 431 if matched == old_matched: 432 break 433 434 tolines = copy.copy(lines) 435 436 for i in reversed(matched): 437 tolines.pop(i) 438 439 show_diff(lines, tolines, header_path) 440 441 if dry_run: 442 return 443 444 with open(header_path, 'w') as f: 445 for line in tolines: 446 f.write(line) 447 448def cleanup_headers(configs, dry_run): 449 """Delete config defines from board headers. 450 451 Arguments: 452 configs: A list of CONFIGs to remove. 453 dry_run: make no changes, but still display log. 454 """ 455 while True: 456 choice = raw_input('Clean up headers? [y/n]: ').lower() 457 print choice 458 if choice == 'y' or choice == 'n': 459 break 460 461 if choice == 'n': 462 return 463 464 patterns = [] 465 for config in configs: 466 patterns.append(re.compile(r'#\s*define\s+%s\W' % config)) 467 patterns.append(re.compile(r'#\s*undef\s+%s\W' % config)) 468 469 for dir in 'include', 'arch', 'board': 470 for (dirpath, dirnames, filenames) in os.walk(dir): 471 if dirpath == os.path.join('include', 'generated'): 472 continue 473 for filename in filenames: 474 if not fnmatch.fnmatch(filename, '*~'): 475 cleanup_one_header(os.path.join(dirpath, filename), 476 patterns, dry_run) 477 478### classes ### 479class Progress: 480 481 """Progress Indicator""" 482 483 def __init__(self, total): 484 """Create a new progress indicator. 485 486 Arguments: 487 total: A number of defconfig files to process. 488 """ 489 self.current = 0 490 self.total = total 491 492 def inc(self): 493 """Increment the number of processed defconfig files.""" 494 495 self.current += 1 496 497 def show(self): 498 """Display the progress.""" 499 print ' %d defconfigs out of %d\r' % (self.current, self.total), 500 sys.stdout.flush() 501 502class KconfigParser: 503 504 """A parser of .config and include/autoconf.mk.""" 505 506 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"') 507 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"') 508 509 def __init__(self, configs, options, build_dir): 510 """Create a new parser. 511 512 Arguments: 513 configs: A list of CONFIGs to move. 514 options: option flags. 515 build_dir: Build directory. 516 """ 517 self.configs = configs 518 self.options = options 519 self.dotconfig = os.path.join(build_dir, '.config') 520 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk') 521 self.config_autoconf = os.path.join(build_dir, 'include', 'config', 522 'auto.conf') 523 self.defconfig = os.path.join(build_dir, 'defconfig') 524 525 def get_cross_compile(self): 526 """Parse .config file and return CROSS_COMPILE. 527 528 Returns: 529 A string storing the compiler prefix for the architecture. 530 Return a NULL string for architectures that do not require 531 compiler prefix (Sandbox and native build is the case). 532 Return None if the specified compiler is missing in your PATH. 533 Caller should distinguish '' and None. 534 """ 535 arch = '' 536 cpu = '' 537 for line in open(self.dotconfig): 538 m = self.re_arch.match(line) 539 if m: 540 arch = m.group(1) 541 continue 542 m = self.re_cpu.match(line) 543 if m: 544 cpu = m.group(1) 545 546 if not arch: 547 return None 548 549 # fix-up for aarch64 550 if arch == 'arm' and cpu == 'armv8': 551 arch = 'aarch64' 552 553 return CROSS_COMPILE.get(arch, None) 554 555 def parse_one_config(self, config, dotconfig_lines, autoconf_lines): 556 """Parse .config, defconfig, include/autoconf.mk for one config. 557 558 This function looks for the config options in the lines from 559 defconfig, .config, and include/autoconf.mk in order to decide 560 which action should be taken for this defconfig. 561 562 Arguments: 563 config: CONFIG name to parse. 564 dotconfig_lines: lines from the .config file. 565 autoconf_lines: lines from the include/autoconf.mk file. 566 567 Returns: 568 A tupple of the action for this defconfig and the line 569 matched for the config. 570 """ 571 not_set = '# %s is not set' % config 572 573 for line in dotconfig_lines: 574 line = line.rstrip() 575 if line.startswith(config + '=') or line == not_set: 576 old_val = line 577 break 578 else: 579 return (ACTION_NO_ENTRY, config) 580 581 for line in autoconf_lines: 582 line = line.rstrip() 583 if line.startswith(config + '='): 584 new_val = line 585 break 586 else: 587 new_val = not_set 588 589 # If this CONFIG is neither bool nor trisate 590 if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set: 591 # tools/scripts/define2mk.sed changes '1' to 'y'. 592 # This is a problem if the CONFIG is int type. 593 # Check the type in Kconfig and handle it correctly. 594 if new_val[-2:] == '=y': 595 new_val = new_val[:-1] + '1' 596 597 return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE, 598 new_val) 599 600 def update_dotconfig(self): 601 """Parse files for the config options and update the .config. 602 603 This function parses the generated .config and include/autoconf.mk 604 searching the target options. 605 Move the config option(s) to the .config as needed. 606 607 Arguments: 608 defconfig: defconfig name. 609 610 Returns: 611 Return a tuple of (updated flag, log string). 612 The "updated flag" is True if the .config was updated, False 613 otherwise. The "log string" shows what happend to the .config. 614 """ 615 616 results = [] 617 updated = False 618 619 with open(self.dotconfig) as f: 620 dotconfig_lines = f.readlines() 621 622 with open(self.autoconf) as f: 623 autoconf_lines = f.readlines() 624 625 for config in self.configs: 626 result = self.parse_one_config(config, dotconfig_lines, 627 autoconf_lines) 628 results.append(result) 629 630 log = '' 631 632 for (action, value) in results: 633 if action == ACTION_MOVE: 634 actlog = "Move '%s'" % value 635 log_color = COLOR_LIGHT_GREEN 636 elif action == ACTION_NO_ENTRY: 637 actlog = "%s is not defined in Kconfig. Do nothing." % value 638 log_color = COLOR_LIGHT_BLUE 639 elif action == ACTION_NO_CHANGE: 640 actlog = "'%s' is the same as the define in Kconfig. Do nothing." \ 641 % value 642 log_color = COLOR_LIGHT_PURPLE 643 else: 644 sys.exit("Internal Error. This should not happen.") 645 646 log += color_text(self.options.color, log_color, actlog) + '\n' 647 648 with open(self.dotconfig, 'a') as f: 649 for (action, value) in results: 650 if action == ACTION_MOVE: 651 f.write(value + '\n') 652 updated = True 653 654 self.results = results 655 os.remove(self.config_autoconf) 656 os.remove(self.autoconf) 657 658 return (updated, log) 659 660 def check_defconfig(self): 661 """Check the defconfig after savedefconfig 662 663 Returns: 664 Return additional log if moved CONFIGs were removed again by 665 'make savedefconfig'. 666 """ 667 668 log = '' 669 670 with open(self.defconfig) as f: 671 defconfig_lines = f.readlines() 672 673 for (action, value) in self.results: 674 if action != ACTION_MOVE: 675 continue 676 if not value + '\n' in defconfig_lines: 677 log += color_text(self.options.color, COLOR_YELLOW, 678 "'%s' was removed by savedefconfig.\n" % 679 value) 680 681 return log 682 683class Slot: 684 685 """A slot to store a subprocess. 686 687 Each instance of this class handles one subprocess. 688 This class is useful to control multiple threads 689 for faster processing. 690 """ 691 692 def __init__(self, configs, options, progress, devnull, make_cmd, reference_src_dir): 693 """Create a new process slot. 694 695 Arguments: 696 configs: A list of CONFIGs to move. 697 options: option flags. 698 progress: A progress indicator. 699 devnull: A file object of '/dev/null'. 700 make_cmd: command name of GNU Make. 701 reference_src_dir: Determine the true starting config state from this 702 source tree. 703 """ 704 self.options = options 705 self.progress = progress 706 self.build_dir = tempfile.mkdtemp() 707 self.devnull = devnull 708 self.make_cmd = (make_cmd, 'O=' + self.build_dir) 709 self.reference_src_dir = reference_src_dir 710 self.parser = KconfigParser(configs, options, self.build_dir) 711 self.state = STATE_IDLE 712 self.failed_boards = [] 713 self.suspicious_boards = [] 714 715 def __del__(self): 716 """Delete the working directory 717 718 This function makes sure the temporary directory is cleaned away 719 even if Python suddenly dies due to error. It should be done in here 720 because it is guaranteed the destructor is always invoked when the 721 instance of the class gets unreferenced. 722 723 If the subprocess is still running, wait until it finishes. 724 """ 725 if self.state != STATE_IDLE: 726 while self.ps.poll() == None: 727 pass 728 shutil.rmtree(self.build_dir) 729 730 def add(self, defconfig): 731 """Assign a new subprocess for defconfig and add it to the slot. 732 733 If the slot is vacant, create a new subprocess for processing the 734 given defconfig and add it to the slot. Just returns False if 735 the slot is occupied (i.e. the current subprocess is still running). 736 737 Arguments: 738 defconfig: defconfig name. 739 740 Returns: 741 Return True on success or False on failure 742 """ 743 if self.state != STATE_IDLE: 744 return False 745 746 self.defconfig = defconfig 747 self.log = '' 748 self.current_src_dir = self.reference_src_dir 749 self.do_defconfig() 750 return True 751 752 def poll(self): 753 """Check the status of the subprocess and handle it as needed. 754 755 Returns True if the slot is vacant (i.e. in idle state). 756 If the configuration is successfully finished, assign a new 757 subprocess to build include/autoconf.mk. 758 If include/autoconf.mk is generated, invoke the parser to 759 parse the .config and the include/autoconf.mk, moving 760 config options to the .config as needed. 761 If the .config was updated, run "make savedefconfig" to sync 762 it, update the original defconfig, and then set the slot back 763 to the idle state. 764 765 Returns: 766 Return True if the subprocess is terminated, False otherwise 767 """ 768 if self.state == STATE_IDLE: 769 return True 770 771 if self.ps.poll() == None: 772 return False 773 774 if self.ps.poll() != 0: 775 self.handle_error() 776 elif self.state == STATE_DEFCONFIG: 777 if self.reference_src_dir and not self.current_src_dir: 778 self.do_savedefconfig() 779 else: 780 self.do_autoconf() 781 elif self.state == STATE_AUTOCONF: 782 if self.current_src_dir: 783 self.current_src_dir = None 784 self.do_defconfig() 785 else: 786 self.do_savedefconfig() 787 elif self.state == STATE_SAVEDEFCONFIG: 788 self.update_defconfig() 789 else: 790 sys.exit("Internal Error. This should not happen.") 791 792 return True if self.state == STATE_IDLE else False 793 794 def handle_error(self): 795 """Handle error cases.""" 796 797 self.log += color_text(self.options.color, COLOR_LIGHT_RED, 798 "Failed to process.\n") 799 if self.options.verbose: 800 self.log += color_text(self.options.color, COLOR_LIGHT_CYAN, 801 self.ps.stderr.read()) 802 self.finish(False) 803 804 def do_defconfig(self): 805 """Run 'make <board>_defconfig' to create the .config file.""" 806 807 cmd = list(self.make_cmd) 808 cmd.append(self.defconfig) 809 self.ps = subprocess.Popen(cmd, stdout=self.devnull, 810 stderr=subprocess.PIPE, 811 cwd=self.current_src_dir) 812 self.state = STATE_DEFCONFIG 813 814 def do_autoconf(self): 815 """Run 'make include/config/auto.conf'.""" 816 817 self.cross_compile = self.parser.get_cross_compile() 818 if self.cross_compile is None: 819 self.log += color_text(self.options.color, COLOR_YELLOW, 820 "Compiler is missing. Do nothing.\n") 821 self.finish(False) 822 return 823 824 cmd = list(self.make_cmd) 825 if self.cross_compile: 826 cmd.append('CROSS_COMPILE=%s' % self.cross_compile) 827 cmd.append('KCONFIG_IGNORE_DUPLICATES=1') 828 cmd.append('include/config/auto.conf') 829 self.ps = subprocess.Popen(cmd, stdout=self.devnull, 830 stderr=subprocess.PIPE, 831 cwd=self.current_src_dir) 832 self.state = STATE_AUTOCONF 833 834 def do_savedefconfig(self): 835 """Update the .config and run 'make savedefconfig'.""" 836 837 (updated, log) = self.parser.update_dotconfig() 838 self.log += log 839 840 if not self.options.force_sync and not updated: 841 self.finish(True) 842 return 843 if updated: 844 self.log += color_text(self.options.color, COLOR_LIGHT_GREEN, 845 "Syncing by savedefconfig...\n") 846 else: 847 self.log += "Syncing by savedefconfig (forced by option)...\n" 848 849 cmd = list(self.make_cmd) 850 cmd.append('savedefconfig') 851 self.ps = subprocess.Popen(cmd, stdout=self.devnull, 852 stderr=subprocess.PIPE) 853 self.state = STATE_SAVEDEFCONFIG 854 855 def update_defconfig(self): 856 """Update the input defconfig and go back to the idle state.""" 857 858 log = self.parser.check_defconfig() 859 if log: 860 self.suspicious_boards.append(self.defconfig) 861 self.log += log 862 orig_defconfig = os.path.join('configs', self.defconfig) 863 new_defconfig = os.path.join(self.build_dir, 'defconfig') 864 updated = not filecmp.cmp(orig_defconfig, new_defconfig) 865 866 if updated: 867 self.log += color_text(self.options.color, COLOR_LIGHT_BLUE, 868 "defconfig was updated.\n") 869 870 if not self.options.dry_run and updated: 871 shutil.move(new_defconfig, orig_defconfig) 872 self.finish(True) 873 874 def finish(self, success): 875 """Display log along with progress and go to the idle state. 876 877 Arguments: 878 success: Should be True when the defconfig was processed 879 successfully, or False when it fails. 880 """ 881 # output at least 30 characters to hide the "* defconfigs out of *". 882 log = self.defconfig.ljust(30) + '\n' 883 884 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ]) 885 # Some threads are running in parallel. 886 # Print log atomically to not mix up logs from different threads. 887 print >> (sys.stdout if success else sys.stderr), log 888 889 if not success: 890 if self.options.exit_on_error: 891 sys.exit("Exit on error.") 892 # If --exit-on-error flag is not set, skip this board and continue. 893 # Record the failed board. 894 self.failed_boards.append(self.defconfig) 895 896 self.progress.inc() 897 self.progress.show() 898 self.state = STATE_IDLE 899 900 def get_failed_boards(self): 901 """Returns a list of failed boards (defconfigs) in this slot. 902 """ 903 return self.failed_boards 904 905 def get_suspicious_boards(self): 906 """Returns a list of boards (defconfigs) with possible misconversion. 907 """ 908 return self.suspicious_boards 909 910class Slots: 911 912 """Controller of the array of subprocess slots.""" 913 914 def __init__(self, configs, options, progress, reference_src_dir): 915 """Create a new slots controller. 916 917 Arguments: 918 configs: A list of CONFIGs to move. 919 options: option flags. 920 progress: A progress indicator. 921 reference_src_dir: Determine the true starting config state from this 922 source tree. 923 """ 924 self.options = options 925 self.slots = [] 926 devnull = get_devnull() 927 make_cmd = get_make_cmd() 928 for i in range(options.jobs): 929 self.slots.append(Slot(configs, options, progress, devnull, 930 make_cmd, reference_src_dir)) 931 932 def add(self, defconfig): 933 """Add a new subprocess if a vacant slot is found. 934 935 Arguments: 936 defconfig: defconfig name to be put into. 937 938 Returns: 939 Return True on success or False on failure 940 """ 941 for slot in self.slots: 942 if slot.add(defconfig): 943 return True 944 return False 945 946 def available(self): 947 """Check if there is a vacant slot. 948 949 Returns: 950 Return True if at lease one vacant slot is found, False otherwise. 951 """ 952 for slot in self.slots: 953 if slot.poll(): 954 return True 955 return False 956 957 def empty(self): 958 """Check if all slots are vacant. 959 960 Returns: 961 Return True if all the slots are vacant, False otherwise. 962 """ 963 ret = True 964 for slot in self.slots: 965 if not slot.poll(): 966 ret = False 967 return ret 968 969 def show_failed_boards(self): 970 """Display all of the failed boards (defconfigs).""" 971 boards = [] 972 output_file = 'moveconfig.failed' 973 974 for slot in self.slots: 975 boards += slot.get_failed_boards() 976 977 if boards: 978 boards = '\n'.join(boards) + '\n' 979 msg = "The following boards were not processed due to error:\n" 980 msg += boards 981 msg += "(the list has been saved in %s)\n" % output_file 982 print >> sys.stderr, color_text(self.options.color, COLOR_LIGHT_RED, 983 msg) 984 985 with open(output_file, 'w') as f: 986 f.write(boards) 987 988 def show_suspicious_boards(self): 989 """Display all boards (defconfigs) with possible misconversion.""" 990 boards = [] 991 output_file = 'moveconfig.suspicious' 992 993 for slot in self.slots: 994 boards += slot.get_suspicious_boards() 995 996 if boards: 997 boards = '\n'.join(boards) + '\n' 998 msg = "The following boards might have been converted incorrectly.\n" 999 msg += "It is highly recommended to check them manually:\n" 1000 msg += boards 1001 msg += "(the list has been saved in %s)\n" % output_file 1002 print >> sys.stderr, color_text(self.options.color, COLOR_YELLOW, 1003 msg) 1004 1005 with open(output_file, 'w') as f: 1006 f.write(boards) 1007 1008class ReferenceSource: 1009 1010 """Reference source against which original configs should be parsed.""" 1011 1012 def __init__(self, commit): 1013 """Create a reference source directory based on a specified commit. 1014 1015 Arguments: 1016 commit: commit to git-clone 1017 """ 1018 self.src_dir = tempfile.mkdtemp() 1019 print "Cloning git repo to a separate work directory..." 1020 subprocess.check_output(['git', 'clone', os.getcwd(), '.'], 1021 cwd=self.src_dir) 1022 print "Checkout '%s' to build the original autoconf.mk." % \ 1023 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip() 1024 subprocess.check_output(['git', 'checkout', commit], 1025 stderr=subprocess.STDOUT, cwd=self.src_dir) 1026 1027 def __del__(self): 1028 """Delete the reference source directory 1029 1030 This function makes sure the temporary directory is cleaned away 1031 even if Python suddenly dies due to error. It should be done in here 1032 because it is guaranteed the destructor is always invoked when the 1033 instance of the class gets unreferenced. 1034 """ 1035 shutil.rmtree(self.src_dir) 1036 1037 def get_dir(self): 1038 """Return the absolute path to the reference source directory.""" 1039 1040 return self.src_dir 1041 1042def move_config(configs, options): 1043 """Move config options to defconfig files. 1044 1045 Arguments: 1046 configs: A list of CONFIGs to move. 1047 options: option flags 1048 """ 1049 if len(configs) == 0: 1050 if options.force_sync: 1051 print 'No CONFIG is specified. You are probably syncing defconfigs.', 1052 else: 1053 print 'Neither CONFIG nor --force-sync is specified. Nothing will happen.', 1054 else: 1055 print 'Move ' + ', '.join(configs), 1056 print '(jobs: %d)\n' % options.jobs 1057 1058 if options.git_ref: 1059 reference_src = ReferenceSource(options.git_ref) 1060 reference_src_dir = reference_src.get_dir() 1061 else: 1062 reference_src_dir = None 1063 1064 if options.defconfigs: 1065 defconfigs = [line.strip() for line in open(options.defconfigs)] 1066 for i, defconfig in enumerate(defconfigs): 1067 if not defconfig.endswith('_defconfig'): 1068 defconfigs[i] = defconfig + '_defconfig' 1069 if not os.path.exists(os.path.join('configs', defconfigs[i])): 1070 sys.exit('%s - defconfig does not exist. Stopping.' % 1071 defconfigs[i]) 1072 else: 1073 # All the defconfig files to be processed 1074 defconfigs = [] 1075 for (dirpath, dirnames, filenames) in os.walk('configs'): 1076 dirpath = dirpath[len('configs') + 1:] 1077 for filename in fnmatch.filter(filenames, '*_defconfig'): 1078 defconfigs.append(os.path.join(dirpath, filename)) 1079 1080 progress = Progress(len(defconfigs)) 1081 slots = Slots(configs, options, progress, reference_src_dir) 1082 1083 # Main loop to process defconfig files: 1084 # Add a new subprocess into a vacant slot. 1085 # Sleep if there is no available slot. 1086 for defconfig in defconfigs: 1087 while not slots.add(defconfig): 1088 while not slots.available(): 1089 # No available slot: sleep for a while 1090 time.sleep(SLEEP_TIME) 1091 1092 # wait until all the subprocesses finish 1093 while not slots.empty(): 1094 time.sleep(SLEEP_TIME) 1095 1096 print '' 1097 slots.show_failed_boards() 1098 slots.show_suspicious_boards() 1099 1100def main(): 1101 try: 1102 cpu_count = multiprocessing.cpu_count() 1103 except NotImplementedError: 1104 cpu_count = 1 1105 1106 parser = optparse.OptionParser() 1107 # Add options here 1108 parser.add_option('-c', '--color', action='store_true', default=False, 1109 help='display the log in color') 1110 parser.add_option('-d', '--defconfigs', type='string', 1111 help='a file containing a list of defconfigs to move') 1112 parser.add_option('-n', '--dry-run', action='store_true', default=False, 1113 help='perform a trial run (show log with no changes)') 1114 parser.add_option('-e', '--exit-on-error', action='store_true', 1115 default=False, 1116 help='exit immediately on any error') 1117 parser.add_option('-s', '--force-sync', action='store_true', default=False, 1118 help='force sync by savedefconfig') 1119 parser.add_option('-H', '--headers-only', dest='cleanup_headers_only', 1120 action='store_true', default=False, 1121 help='only cleanup the headers') 1122 parser.add_option('-j', '--jobs', type='int', default=cpu_count, 1123 help='the number of jobs to run simultaneously') 1124 parser.add_option('-r', '--git-ref', type='string', 1125 help='the git ref to clone for building the autoconf.mk') 1126 parser.add_option('-v', '--verbose', action='store_true', default=False, 1127 help='show any build errors as boards are built') 1128 parser.usage += ' CONFIG ...' 1129 1130 (options, configs) = parser.parse_args() 1131 1132 if len(configs) == 0 and not options.force_sync: 1133 parser.print_usage() 1134 sys.exit(1) 1135 1136 # prefix the option name with CONFIG_ if missing 1137 configs = [ config if config.startswith('CONFIG_') else 'CONFIG_' + config 1138 for config in configs ] 1139 1140 check_top_directory() 1141 1142 if not options.cleanup_headers_only: 1143 check_clean_directory() 1144 update_cross_compile(options.color) 1145 move_config(configs, options) 1146 1147 if configs: 1148 cleanup_headers(configs, options.dry_run) 1149 1150if __name__ == '__main__': 1151 main() 1152