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