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 20This tool takes one input file. (let's say 'recipe' file here.) 21The recipe describes the list of config options you want to move. 22Each line takes the form: 23<config_name> <type> <default> 24(the fields must be separated with whitespaces.) 25 26<config_name> is the name of config option. 27 28<type> is the type of the option. It must be one of bool, tristate, 29string, int, and hex. 30 31<default> is the default value of the option. It must be appropriate 32value corresponding to the option type. It must be either y or n for 33the bool type. Tristate options can also take m (although U-Boot has 34not supported the module feature). 35 36You can add two or more lines in the recipe file, so you can move 37multiple options at once. 38 39Let's say, for example, you want to move CONFIG_CMD_USB and 40CONFIG_SYS_TEXT_BASE. 41 42The type should be bool, hex, respectively. So, the recipe file 43should look like this: 44 45 $ cat recipe 46 CONFIG_CMD_USB bool n 47 CONFIG_SYS_TEXT_BASE hex 0x00000000 48 49And then run this tool giving the file name of the recipe 50 51 $ tools/moveconfig.py recipe 52 53The tool walks through all the defconfig files to move the config 54options specified by the recipe file. 55 56The log is also displayed on the terminal. 57 58Each line is printed in the format 59<defconfig_name> : <action> 60 61<defconfig_name> is the name of the defconfig 62(without the suffix _defconfig). 63 64<action> shows what the tool did for that defconfig. 65It looks like one of the followings: 66 67 - Move 'CONFIG_... ' 68 This config option was moved to the defconfig 69 70 - Default value 'CONFIG_...'. Do nothing. 71 The value of this option is the same as default. 72 We do not have to add it to the defconfig. 73 74 - 'CONFIG_...' already exists in Kconfig. Do nothing. 75 This config option is already defined in Kconfig. 76 We do not need/want to touch it. 77 78 - Undefined. Do nothing. 79 This config option was not found in the config header. 80 Nothing to do. 81 82 - Failed to process. Skip. 83 An error occurred during processing this defconfig. Skipped. 84 (If -e option is passed, the tool exits immediately on error.) 85 86Finally, you will be asked, Clean up headers? [y/n]: 87 88If you say 'y' here, the unnecessary config defines are removed 89from the config headers (include/configs/*.h). 90It just uses the regex method, so you should not rely on it. 91Just in case, please do 'git diff' to see what happened. 92 93 94How does it works? 95------------------ 96 97This tool runs configuration and builds include/autoconf.mk for every 98defconfig. The config options defined in Kconfig appear in the .config 99file (unless they are hidden because of unmet dependency.) 100On the other hand, the config options defined by board headers are seen 101in include/autoconf.mk. The tool looks for the specified options in both 102of them to decide the appropriate action for the options. If the option 103is found in the .config or the value is the same as the specified default, 104the option does not need to be touched. If the option is found in 105include/autoconf.mk, but not in the .config, and the value is different 106from the default, the tools adds the option to the defconfig. 107 108For faster processing, this tool handles multi-threading. It creates 109separate build directories where the out-of-tree build is run. The 110temporary build directories are automatically created and deleted as 111needed. The number of threads are chosen based on the number of the CPU 112cores of your system although you can change it via -j (--jobs) option. 113 114 115Toolchains 116---------- 117 118Appropriate toolchain are necessary to generate include/autoconf.mk 119for all the architectures supported by U-Boot. Most of them are available 120at the kernel.org site, some are not provided by kernel.org. 121 122The default per-arch CROSS_COMPILE used by this tool is specified by 123the list below, CROSS_COMPILE. You may wish to update the list to 124use your own. Instead of modifying the list directly, you can give 125them via environments. 126 127 128Available options 129----------------- 130 131 -c, --color 132 Surround each portion of the log with escape sequences to display it 133 in color on the terminal. 134 135 -n, --dry-run 136 Peform a trial run that does not make any changes. It is useful to 137 see what is going to happen before one actually runs it. 138 139 -e, --exit-on-error 140 Exit immediately if Make exits with a non-zero status while processing 141 a defconfig file. 142 143 -j, --jobs 144 Specify the number of threads to run simultaneously. If not specified, 145 the number of threads is the same as the number of CPU cores. 146 147To see the complete list of supported options, run 148 149 $ tools/moveconfig.py -h 150 151""" 152 153import fnmatch 154import multiprocessing 155import optparse 156import os 157import re 158import shutil 159import subprocess 160import sys 161import tempfile 162import time 163 164SHOW_GNU_MAKE = 'scripts/show-gnu-make' 165SLEEP_TIME=0.03 166 167# Here is the list of cross-tools I use. 168# Most of them are available at kernel.org 169# (https://www.kernel.org/pub/tools/crosstool/files/bin/), except the followings: 170# arc: https://github.com/foss-for-synopsys-dwc-arc-processors/toolchain/releases 171# blackfin: http://sourceforge.net/projects/adi-toolchain/files/ 172# nds32: http://osdk.andestech.com/packages/ 173# nios2: https://sourcery.mentor.com/GNUToolchain/subscription42545 174# sh: http://sourcery.mentor.com/public/gnu_toolchain/sh-linux-gnu 175CROSS_COMPILE = { 176 'arc': 'arc-linux-', 177 'aarch64': 'aarch64-linux-', 178 'arm': 'arm-unknown-linux-gnueabi-', 179 'avr32': 'avr32-linux-', 180 'blackfin': 'bfin-elf-', 181 'm68k': 'm68k-linux-', 182 'microblaze': 'microblaze-linux-', 183 'mips': 'mips-linux-', 184 'nds32': 'nds32le-linux-', 185 'nios2': 'nios2-linux-gnu-', 186 'openrisc': 'or32-linux-', 187 'powerpc': 'powerpc-linux-', 188 'sh': 'sh-linux-gnu-', 189 'sparc': 'sparc-linux-', 190 'x86': 'i386-linux-' 191} 192 193STATE_IDLE = 0 194STATE_DEFCONFIG = 1 195STATE_AUTOCONF = 2 196 197ACTION_MOVE = 0 198ACTION_DEFAULT_VALUE = 1 199ACTION_ALREADY_EXIST = 2 200ACTION_UNDEFINED = 3 201 202COLOR_BLACK = '0;30' 203COLOR_RED = '0;31' 204COLOR_GREEN = '0;32' 205COLOR_BROWN = '0;33' 206COLOR_BLUE = '0;34' 207COLOR_PURPLE = '0;35' 208COLOR_CYAN = '0;36' 209COLOR_LIGHT_GRAY = '0;37' 210COLOR_DARK_GRAY = '1;30' 211COLOR_LIGHT_RED = '1;31' 212COLOR_LIGHT_GREEN = '1;32' 213COLOR_YELLOW = '1;33' 214COLOR_LIGHT_BLUE = '1;34' 215COLOR_LIGHT_PURPLE = '1;35' 216COLOR_LIGHT_CYAN = '1;36' 217COLOR_WHITE = '1;37' 218 219### helper functions ### 220def get_devnull(): 221 """Get the file object of '/dev/null' device.""" 222 try: 223 devnull = subprocess.DEVNULL # py3k 224 except AttributeError: 225 devnull = open(os.devnull, 'wb') 226 return devnull 227 228def check_top_directory(): 229 """Exit if we are not at the top of source directory.""" 230 for f in ('README', 'Licenses'): 231 if not os.path.exists(f): 232 sys.exit('Please run at the top of source directory.') 233 234def get_make_cmd(): 235 """Get the command name of GNU Make. 236 237 U-Boot needs GNU Make for building, but the command name is not 238 necessarily "make". (for example, "gmake" on FreeBSD). 239 Returns the most appropriate command name on your system. 240 """ 241 process = subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) 242 ret = process.communicate() 243 if process.returncode: 244 sys.exit('GNU Make not found') 245 return ret[0].rstrip() 246 247def color_text(color_enabled, color, string): 248 """Return colored string.""" 249 if color_enabled: 250 return '\033[' + color + 'm' + string + '\033[0m' 251 else: 252 return string 253 254def log_msg(color_enabled, color, defconfig, msg): 255 """Return the formated line for the log.""" 256 return defconfig[:-len('_defconfig')].ljust(37) + ': ' + \ 257 color_text(color_enabled, color, msg) + '\n' 258 259def update_cross_compile(): 260 """Update per-arch CROSS_COMPILE via enviroment variables 261 262 The default CROSS_COMPILE values are available 263 in the CROSS_COMPILE list above. 264 265 You can override them via enviroment variables 266 CROSS_COMPILE_{ARCH}. 267 268 For example, if you want to override toolchain prefixes 269 for ARM and PowerPC, you can do as follows in your shell: 270 271 export CROSS_COMPILE_ARM=... 272 export CROSS_COMPILE_POWERPC=... 273 """ 274 archs = [] 275 276 for arch in os.listdir('arch'): 277 if os.path.exists(os.path.join('arch', arch, 'Makefile')): 278 archs.append(arch) 279 280 # arm64 is a special case 281 archs.append('aarch64') 282 283 for arch in archs: 284 env = 'CROSS_COMPILE_' + arch.upper() 285 cross_compile = os.environ.get(env) 286 if cross_compile: 287 CROSS_COMPILE[arch] = cross_compile 288 289def cleanup_one_header(header_path, patterns, dry_run): 290 """Clean regex-matched lines away from a file. 291 292 Arguments: 293 header_path: path to the cleaned file. 294 patterns: list of regex patterns. Any lines matching to these 295 patterns are deleted. 296 dry_run: make no changes, but still display log. 297 """ 298 with open(header_path) as f: 299 lines = f.readlines() 300 301 matched = [] 302 for i, line in enumerate(lines): 303 for pattern in patterns: 304 m = pattern.search(line) 305 if m: 306 print '%s: %s: %s' % (header_path, i + 1, line), 307 matched.append(i) 308 break 309 310 if dry_run or not matched: 311 return 312 313 with open(header_path, 'w') as f: 314 for i, line in enumerate(lines): 315 if not i in matched: 316 f.write(line) 317 318def cleanup_headers(config_attrs, dry_run): 319 """Delete config defines from board headers. 320 321 Arguments: 322 config_attrs: A list of dictionaris, each of them includes the name, 323 the type, and the default value of the target config. 324 dry_run: make no changes, but still display log. 325 """ 326 while True: 327 choice = raw_input('Clean up headers? [y/n]: ').lower() 328 print choice 329 if choice == 'y' or choice == 'n': 330 break 331 332 if choice == 'n': 333 return 334 335 patterns = [] 336 for config_attr in config_attrs: 337 config = config_attr['config'] 338 patterns.append(re.compile(r'#\s*define\s+%s\W' % config)) 339 patterns.append(re.compile(r'#\s*undef\s+%s\W' % config)) 340 341 for (dirpath, dirnames, filenames) in os.walk('include'): 342 for filename in filenames: 343 if not fnmatch.fnmatch(filename, '*~'): 344 cleanup_one_header(os.path.join(dirpath, filename), patterns, 345 dry_run) 346 347### classes ### 348class KconfigParser: 349 350 """A parser of .config and include/autoconf.mk.""" 351 352 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"') 353 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"') 354 355 def __init__(self, config_attrs, options, build_dir): 356 """Create a new parser. 357 358 Arguments: 359 config_attrs: A list of dictionaris, each of them includes the name, 360 the type, and the default value of the target config. 361 options: option flags. 362 build_dir: Build directory. 363 """ 364 self.config_attrs = config_attrs 365 self.options = options 366 self.build_dir = build_dir 367 368 def get_cross_compile(self): 369 """Parse .config file and return CROSS_COMPILE. 370 371 Returns: 372 A string storing the compiler prefix for the architecture. 373 """ 374 arch = '' 375 cpu = '' 376 dotconfig = os.path.join(self.build_dir, '.config') 377 for line in open(dotconfig): 378 m = self.re_arch.match(line) 379 if m: 380 arch = m.group(1) 381 continue 382 m = self.re_cpu.match(line) 383 if m: 384 cpu = m.group(1) 385 386 assert arch, 'Error: arch is not defined in %s' % defconfig 387 388 # fix-up for aarch64 389 if arch == 'arm' and cpu == 'armv8': 390 arch = 'aarch64' 391 392 return CROSS_COMPILE.get(arch, '') 393 394 def parse_one_config(self, config_attr, defconfig_lines, 395 dotconfig_lines, autoconf_lines): 396 """Parse .config, defconfig, include/autoconf.mk for one config. 397 398 This function looks for the config options in the lines from 399 defconfig, .config, and include/autoconf.mk in order to decide 400 which action should be taken for this defconfig. 401 402 Arguments: 403 config_attr: A dictionary including the name, the type, 404 and the default value of the target config. 405 defconfig_lines: lines from the original defconfig file. 406 dotconfig_lines: lines from the .config file. 407 autoconf_lines: lines from the include/autoconf.mk file. 408 409 Returns: 410 A tupple of the action for this defconfig and the line 411 matched for the config. 412 """ 413 config = config_attr['config'] 414 not_set = '# %s is not set' % config 415 416 if config_attr['type'] in ('bool', 'tristate') and \ 417 config_attr['default'] == 'n': 418 default = not_set 419 else: 420 default = config + '=' + config_attr['default'] 421 422 for line in defconfig_lines + dotconfig_lines: 423 line = line.rstrip() 424 if line.startswith(config + '=') or line == not_set: 425 return (ACTION_ALREADY_EXIST, line) 426 427 if config_attr['type'] in ('bool', 'tristate'): 428 value = not_set 429 else: 430 value = '(undefined)' 431 432 for line in autoconf_lines: 433 line = line.rstrip() 434 if line.startswith(config + '='): 435 value = line 436 break 437 438 if value == default: 439 action = ACTION_DEFAULT_VALUE 440 elif value == '(undefined)': 441 action = ACTION_UNDEFINED 442 else: 443 action = ACTION_MOVE 444 445 return (action, value) 446 447 def update_defconfig(self, defconfig): 448 """Parse files for the config options and update the defconfig. 449 450 This function parses the given defconfig, the generated .config 451 and include/autoconf.mk searching the target options. 452 Move the config option(s) to the defconfig or do nothing if unneeded. 453 Also, display the log to show what happened to this defconfig. 454 455 Arguments: 456 defconfig: defconfig name. 457 """ 458 459 defconfig_path = os.path.join('configs', defconfig) 460 dotconfig_path = os.path.join(self.build_dir, '.config') 461 autoconf_path = os.path.join(self.build_dir, 'include', 'autoconf.mk') 462 results = [] 463 464 with open(defconfig_path) as f: 465 defconfig_lines = f.readlines() 466 467 with open(dotconfig_path) as f: 468 dotconfig_lines = f.readlines() 469 470 with open(autoconf_path) as f: 471 autoconf_lines = f.readlines() 472 473 for config_attr in self.config_attrs: 474 result = self.parse_one_config(config_attr, defconfig_lines, 475 dotconfig_lines, autoconf_lines) 476 results.append(result) 477 478 log = '' 479 480 for (action, value) in results: 481 if action == ACTION_MOVE: 482 actlog = "Move '%s'" % value 483 log_color = COLOR_LIGHT_GREEN 484 elif action == ACTION_DEFAULT_VALUE: 485 actlog = "Default value '%s'. Do nothing." % value 486 log_color = COLOR_LIGHT_BLUE 487 elif action == ACTION_ALREADY_EXIST: 488 actlog = "'%s' already defined in Kconfig. Do nothing." % value 489 log_color = COLOR_LIGHT_PURPLE 490 elif action == ACTION_UNDEFINED: 491 actlog = "Undefined. Do nothing." 492 log_color = COLOR_DARK_GRAY 493 else: 494 sys.exit("Internal Error. This should not happen.") 495 496 log += log_msg(self.options.color, log_color, defconfig, actlog) 497 498 # Some threads are running in parallel. 499 # Print log in one shot to not mix up logs from different threads. 500 print log, 501 502 if not self.options.dry_run: 503 with open(defconfig_path, 'a') as f: 504 for (action, value) in results: 505 if action == ACTION_MOVE: 506 f.write(value + '\n') 507 508 os.remove(os.path.join(self.build_dir, 'include', 'config', 'auto.conf')) 509 os.remove(autoconf_path) 510 511class Slot: 512 513 """A slot to store a subprocess. 514 515 Each instance of this class handles one subprocess. 516 This class is useful to control multiple threads 517 for faster processing. 518 """ 519 520 def __init__(self, config_attrs, options, devnull, make_cmd): 521 """Create a new process slot. 522 523 Arguments: 524 config_attrs: A list of dictionaris, each of them includes the name, 525 the type, and the default value of the target config. 526 options: option flags. 527 devnull: A file object of '/dev/null'. 528 make_cmd: command name of GNU Make. 529 """ 530 self.options = options 531 self.build_dir = tempfile.mkdtemp() 532 self.devnull = devnull 533 self.make_cmd = (make_cmd, 'O=' + self.build_dir) 534 self.parser = KconfigParser(config_attrs, options, self.build_dir) 535 self.state = STATE_IDLE 536 self.failed_boards = [] 537 538 def __del__(self): 539 """Delete the working directory 540 541 This function makes sure the temporary directory is cleaned away 542 even if Python suddenly dies due to error. It should be done in here 543 because it is guranteed the destructor is always invoked when the 544 instance of the class gets unreferenced. 545 546 If the subprocess is still running, wait until it finishes. 547 """ 548 if self.state != STATE_IDLE: 549 while self.ps.poll() == None: 550 pass 551 shutil.rmtree(self.build_dir) 552 553 def add(self, defconfig): 554 """Assign a new subprocess for defconfig and add it to the slot. 555 556 If the slot is vacant, create a new subprocess for processing the 557 given defconfig and add it to the slot. Just returns False if 558 the slot is occupied (i.e. the current subprocess is still running). 559 560 Arguments: 561 defconfig: defconfig name. 562 563 Returns: 564 Return True on success or False on failure 565 """ 566 if self.state != STATE_IDLE: 567 return False 568 cmd = list(self.make_cmd) 569 cmd.append(defconfig) 570 self.ps = subprocess.Popen(cmd, stdout=self.devnull) 571 self.defconfig = defconfig 572 self.state = STATE_DEFCONFIG 573 return True 574 575 def poll(self): 576 """Check the status of the subprocess and handle it as needed. 577 578 Returns True if the slot is vacant (i.e. in idle state). 579 If the configuration is successfully finished, assign a new 580 subprocess to build include/autoconf.mk. 581 If include/autoconf.mk is generated, invoke the parser to 582 parse the .config and the include/autoconf.mk, and then set the 583 slot back to the idle state. 584 585 Returns: 586 Return True if the subprocess is terminated, False otherwise 587 """ 588 if self.state == STATE_IDLE: 589 return True 590 591 if self.ps.poll() == None: 592 return False 593 594 if self.ps.poll() != 0: 595 596 print >> sys.stderr, log_msg(self.options.color, 597 COLOR_LIGHT_RED, 598 self.defconfig, 599 "failed to process.") 600 if self.options.exit_on_error: 601 sys.exit("Exit on error.") 602 else: 603 # If --exit-on-error flag is not set, 604 # skip this board and continue. 605 # Record the failed board. 606 self.failed_boards.append(self.defconfig) 607 self.state = STATE_IDLE 608 return True 609 610 if self.state == STATE_AUTOCONF: 611 self.parser.update_defconfig(self.defconfig) 612 self.state = STATE_IDLE 613 return True 614 615 cross_compile = self.parser.get_cross_compile() 616 cmd = list(self.make_cmd) 617 if cross_compile: 618 cmd.append('CROSS_COMPILE=%s' % cross_compile) 619 cmd.append('include/config/auto.conf') 620 self.ps = subprocess.Popen(cmd, stdout=self.devnull) 621 self.state = STATE_AUTOCONF 622 return False 623 624 def get_failed_boards(self): 625 """Returns a list of failed boards (defconfigs) in this slot. 626 """ 627 return self.failed_boards 628 629class Slots: 630 631 """Controller of the array of subprocess slots.""" 632 633 def __init__(self, config_attrs, options): 634 """Create a new slots controller. 635 636 Arguments: 637 config_attrs: A list of dictionaris containing the name, the type, 638 and the default value of the target CONFIG. 639 options: option flags. 640 """ 641 self.options = options 642 self.slots = [] 643 devnull = get_devnull() 644 make_cmd = get_make_cmd() 645 for i in range(options.jobs): 646 self.slots.append(Slot(config_attrs, options, devnull, make_cmd)) 647 648 def add(self, defconfig): 649 """Add a new subprocess if a vacant slot is found. 650 651 Arguments: 652 defconfig: defconfig name to be put into. 653 654 Returns: 655 Return True on success or False on failure 656 """ 657 for slot in self.slots: 658 if slot.add(defconfig): 659 return True 660 return False 661 662 def available(self): 663 """Check if there is a vacant slot. 664 665 Returns: 666 Return True if at lease one vacant slot is found, False otherwise. 667 """ 668 for slot in self.slots: 669 if slot.poll(): 670 return True 671 return False 672 673 def empty(self): 674 """Check if all slots are vacant. 675 676 Returns: 677 Return True if all the slots are vacant, False otherwise. 678 """ 679 ret = True 680 for slot in self.slots: 681 if not slot.poll(): 682 ret = False 683 return ret 684 685 def show_failed_boards(self): 686 """Display all of the failed boards (defconfigs).""" 687 failed_boards = [] 688 689 for slot in self.slots: 690 failed_boards += slot.get_failed_boards() 691 692 if len(failed_boards) > 0: 693 msg = [ "The following boards were not processed due to error:" ] 694 msg += failed_boards 695 for line in msg: 696 print >> sys.stderr, color_text(self.options.color, 697 COLOR_LIGHT_RED, line) 698 699def move_config(config_attrs, options): 700 """Move config options to defconfig files. 701 702 Arguments: 703 config_attrs: A list of dictionaris, each of them includes the name, 704 the type, and the default value of the target config. 705 options: option flags 706 """ 707 check_top_directory() 708 709 if len(config_attrs) == 0: 710 print 'Nothing to do. exit.' 711 sys.exit(0) 712 713 print 'Move the following CONFIG options (jobs: %d)' % options.jobs 714 for config_attr in config_attrs: 715 print ' %s (type: %s, default: %s)' % (config_attr['config'], 716 config_attr['type'], 717 config_attr['default']) 718 719 # All the defconfig files to be processed 720 defconfigs = [] 721 for (dirpath, dirnames, filenames) in os.walk('configs'): 722 dirpath = dirpath[len('configs') + 1:] 723 for filename in fnmatch.filter(filenames, '*_defconfig'): 724 defconfigs.append(os.path.join(dirpath, filename)) 725 726 slots = Slots(config_attrs, options) 727 728 # Main loop to process defconfig files: 729 # Add a new subprocess into a vacant slot. 730 # Sleep if there is no available slot. 731 for defconfig in defconfigs: 732 while not slots.add(defconfig): 733 while not slots.available(): 734 # No available slot: sleep for a while 735 time.sleep(SLEEP_TIME) 736 737 # wait until all the subprocesses finish 738 while not slots.empty(): 739 time.sleep(SLEEP_TIME) 740 741 slots.show_failed_boards() 742 743 cleanup_headers(config_attrs, options.dry_run) 744 745def bad_recipe(filename, linenum, msg): 746 """Print error message with the file name and the line number and exit.""" 747 sys.exit("%s: line %d: error : " % (filename, linenum) + msg) 748 749def parse_recipe(filename): 750 """Parse the recipe file and retrieve the config attributes. 751 752 This function parses the given recipe file and gets the name, 753 the type, and the default value of the target config options. 754 755 Arguments: 756 filename: path to file to be parsed. 757 Returns: 758 A list of dictionaris, each of them includes the name, 759 the type, and the default value of the target config. 760 """ 761 config_attrs = [] 762 linenum = 1 763 764 for line in open(filename): 765 tokens = line.split() 766 if len(tokens) != 3: 767 bad_recipe(filename, linenum, 768 "%d fields in this line. Each line must contain 3 fields" 769 % len(tokens)) 770 771 (config, type, default) = tokens 772 773 # prefix the option name with CONFIG_ if missing 774 if not config.startswith('CONFIG_'): 775 config = 'CONFIG_' + config 776 777 # sanity check of default values 778 if type == 'bool': 779 if not default in ('y', 'n'): 780 bad_recipe(filename, linenum, 781 "default for bool type must be either y or n") 782 elif type == 'tristate': 783 if not default in ('y', 'm', 'n'): 784 bad_recipe(filename, linenum, 785 "default for tristate type must be y, m, or n") 786 elif type == 'string': 787 if default[0] != '"' or default[-1] != '"': 788 bad_recipe(filename, linenum, 789 "default for string type must be surrounded by double-quotations") 790 elif type == 'int': 791 try: 792 int(default) 793 except: 794 bad_recipe(filename, linenum, 795 "type is int, but default value is not decimal") 796 elif type == 'hex': 797 if len(default) < 2 or default[:2] != '0x': 798 bad_recipe(filename, linenum, 799 "default for hex type must be prefixed with 0x") 800 try: 801 int(default, 16) 802 except: 803 bad_recipe(filename, linenum, 804 "type is hex, but default value is not hexadecimal") 805 else: 806 bad_recipe(filename, linenum, 807 "unsupported type '%s'. type must be one of bool, tristate, string, int, hex" 808 % type) 809 810 config_attrs.append({'config': config, 'type': type, 'default': default}) 811 linenum += 1 812 813 return config_attrs 814 815def main(): 816 try: 817 cpu_count = multiprocessing.cpu_count() 818 except NotImplementedError: 819 cpu_count = 1 820 821 parser = optparse.OptionParser() 822 # Add options here 823 parser.add_option('-c', '--color', action='store_true', default=False, 824 help='display the log in color') 825 parser.add_option('-n', '--dry-run', action='store_true', default=False, 826 help='perform a trial run (show log with no changes)') 827 parser.add_option('-e', '--exit-on-error', action='store_true', 828 default=False, 829 help='exit immediately on any error') 830 parser.add_option('-j', '--jobs', type='int', default=cpu_count, 831 help='the number of jobs to run simultaneously') 832 parser.usage += ' recipe_file\n\n' + \ 833 'The recipe_file should describe config options you want to move.\n' + \ 834 'Each line should contain config_name, type, default_value\n\n' + \ 835 'Example:\n' + \ 836 'CONFIG_FOO bool n\n' + \ 837 'CONFIG_BAR int 100\n' + \ 838 'CONFIG_BAZ string "hello"\n' 839 840 (options, args) = parser.parse_args() 841 842 if len(args) != 1: 843 parser.print_usage() 844 sys.exit(1) 845 846 config_attrs = parse_recipe(args[0]) 847 848 update_cross_compile() 849 850 move_config(config_attrs, options) 851 852if __name__ == '__main__': 853 main() 854