1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0+ 3# 4# Author: Masahiro Yamada <yamada.masahiro@socionext.com> 5# 6 7""" 8Move config options from headers to defconfig files. 9 10See doc/develop/moveconfig.rst for documentation. 11""" 12 13from argparse import ArgumentParser 14import asteval 15import collections 16from contextlib import ExitStack 17import copy 18import difflib 19import doctest 20import filecmp 21import fnmatch 22import glob 23import multiprocessing 24import os 25import queue 26import re 27import shutil 28import subprocess 29import sys 30import tempfile 31import threading 32import time 33import unittest 34 35from buildman import bsettings 36from buildman import kconfiglib 37from buildman import toolchain 38 39SHOW_GNU_MAKE = 'scripts/show-gnu-make' 40SLEEP_TIME=0.03 41 42STATE_IDLE = 0 43STATE_DEFCONFIG = 1 44STATE_AUTOCONF = 2 45STATE_SAVEDEFCONFIG = 3 46 47ACTION_MOVE = 0 48ACTION_NO_ENTRY = 1 49ACTION_NO_ENTRY_WARN = 2 50ACTION_NO_CHANGE = 3 51 52COLOR_BLACK = '0;30' 53COLOR_RED = '0;31' 54COLOR_GREEN = '0;32' 55COLOR_BROWN = '0;33' 56COLOR_BLUE = '0;34' 57COLOR_PURPLE = '0;35' 58COLOR_CYAN = '0;36' 59COLOR_LIGHT_GRAY = '0;37' 60COLOR_DARK_GRAY = '1;30' 61COLOR_LIGHT_RED = '1;31' 62COLOR_LIGHT_GREEN = '1;32' 63COLOR_YELLOW = '1;33' 64COLOR_LIGHT_BLUE = '1;34' 65COLOR_LIGHT_PURPLE = '1;35' 66COLOR_LIGHT_CYAN = '1;36' 67COLOR_WHITE = '1;37' 68 69AUTO_CONF_PATH = 'include/config/auto.conf' 70CONFIG_DATABASE = 'moveconfig.db' 71 72CONFIG_LEN = len('CONFIG_') 73 74SIZES = { 75 'SZ_1': 0x00000001, 'SZ_2': 0x00000002, 76 'SZ_4': 0x00000004, 'SZ_8': 0x00000008, 77 'SZ_16': 0x00000010, 'SZ_32': 0x00000020, 78 'SZ_64': 0x00000040, 'SZ_128': 0x00000080, 79 'SZ_256': 0x00000100, 'SZ_512': 0x00000200, 80 'SZ_1K': 0x00000400, 'SZ_2K': 0x00000800, 81 'SZ_4K': 0x00001000, 'SZ_8K': 0x00002000, 82 'SZ_16K': 0x00004000, 'SZ_32K': 0x00008000, 83 'SZ_64K': 0x00010000, 'SZ_128K': 0x00020000, 84 'SZ_256K': 0x00040000, 'SZ_512K': 0x00080000, 85 'SZ_1M': 0x00100000, 'SZ_2M': 0x00200000, 86 'SZ_4M': 0x00400000, 'SZ_8M': 0x00800000, 87 'SZ_16M': 0x01000000, 'SZ_32M': 0x02000000, 88 'SZ_64M': 0x04000000, 'SZ_128M': 0x08000000, 89 'SZ_256M': 0x10000000, 'SZ_512M': 0x20000000, 90 'SZ_1G': 0x40000000, 'SZ_2G': 0x80000000, 91 'SZ_4G': 0x100000000 92} 93 94RE_REMOVE_DEFCONFIG = re.compile(r'(.*)_defconfig') 95 96### helper functions ### 97def remove_defconfig(defc): 98 """Drop the _defconfig suffix on a string 99 100 Args: 101 defc (str): String to convert 102 103 Returns: 104 str: string with the '_defconfig' suffix removed 105 """ 106 return RE_REMOVE_DEFCONFIG.match(defc)[1] 107 108def check_top_directory(): 109 """Exit if we are not at the top of source directory.""" 110 for fname in 'README', 'Licenses': 111 if not os.path.exists(fname): 112 sys.exit('Please run at the top of source directory.') 113 114def check_clean_directory(): 115 """Exit if the source tree is not clean.""" 116 for fname in '.config', 'include/config': 117 if os.path.exists(fname): 118 sys.exit("source tree is not clean, please run 'make mrproper'") 119 120def get_make_cmd(): 121 """Get the command name of GNU Make. 122 123 U-Boot needs GNU Make for building, but the command name is not 124 necessarily "make". (for example, "gmake" on FreeBSD). 125 Returns the most appropriate command name on your system. 126 """ 127 with subprocess.Popen([SHOW_GNU_MAKE], stdout=subprocess.PIPE) as proc: 128 ret = proc.communicate() 129 if proc.returncode: 130 sys.exit('GNU Make not found') 131 return ret[0].rstrip() 132 133def get_matched_defconfig(line): 134 """Get the defconfig files that match a pattern 135 136 Args: 137 line (str): Path or filename to match, e.g. 'configs/snow_defconfig' or 138 'k2*_defconfig'. If no directory is provided, 'configs/' is 139 prepended 140 141 Returns: 142 list of str: a list of matching defconfig files 143 """ 144 dirname = os.path.dirname(line) 145 if dirname: 146 pattern = line 147 else: 148 pattern = os.path.join('configs', line) 149 return glob.glob(pattern) + glob.glob(pattern + '_defconfig') 150 151def get_matched_defconfigs(defconfigs_file): 152 """Get all the defconfig files that match the patterns in a file. 153 154 Args: 155 defconfigs_file (str): File containing a list of defconfigs to process, 156 or '-' to read the list from stdin 157 158 Returns: 159 list of str: A list of paths to defconfig files, with no duplicates 160 """ 161 defconfigs = [] 162 with ExitStack() as stack: 163 if defconfigs_file == '-': 164 inf = sys.stdin 165 defconfigs_file = 'stdin' 166 else: 167 inf = stack.enter_context(open(defconfigs_file, encoding='utf-8')) 168 for i, line in enumerate(inf): 169 line = line.strip() 170 if not line: 171 continue # skip blank lines silently 172 if ' ' in line: 173 line = line.split(' ')[0] # handle 'git log' input 174 matched = get_matched_defconfig(line) 175 if not matched: 176 print(f"warning: {defconfigs_file}:{i + 1}: no defconfig matched '{line}'", 177 file=sys.stderr) 178 179 defconfigs += matched 180 181 # use set() to drop multiple matching 182 return [defconfig[len('configs') + 1:] for defconfig in set(defconfigs)] 183 184def get_all_defconfigs(): 185 """Get all the defconfig files under the configs/ directory. 186 187 Returns: 188 list of str: List of paths to defconfig files 189 """ 190 defconfigs = [] 191 for (dirpath, _, filenames) in os.walk('configs'): 192 dirpath = dirpath[len('configs') + 1:] 193 for filename in fnmatch.filter(filenames, '*_defconfig'): 194 defconfigs.append(os.path.join(dirpath, filename)) 195 196 return defconfigs 197 198def color_text(color_enabled, color, string): 199 """Return colored string.""" 200 if color_enabled: 201 # LF should not be surrounded by the escape sequence. 202 # Otherwise, additional whitespace or line-feed might be printed. 203 return '\n'.join([ '\033[' + color + 'm' + s + '\033[0m' if s else '' 204 for s in string.split('\n') ]) 205 return string 206 207def show_diff(alines, blines, file_path, color_enabled): 208 """Show unidified diff. 209 210 Args: 211 alines (list of str): A list of lines (before) 212 blines (list of str): A list of lines (after) 213 file_path (str): Path to the file 214 color_enabled (bool): Display the diff in color 215 """ 216 diff = difflib.unified_diff(alines, blines, 217 fromfile=os.path.join('a', file_path), 218 tofile=os.path.join('b', file_path)) 219 220 for line in diff: 221 if line.startswith('-') and not line.startswith('--'): 222 print(color_text(color_enabled, COLOR_RED, line)) 223 elif line.startswith('+') and not line.startswith('++'): 224 print(color_text(color_enabled, COLOR_GREEN, line)) 225 else: 226 print(line) 227 228def extend_matched_lines(lines, matched, pre_patterns, post_patterns, 229 extend_pre, extend_post): 230 """Extend matched lines if desired patterns are found before/after already 231 matched lines. 232 233 Args: 234 lines (list of str): list of lines handled. 235 matched (list of int): list of line numbers that have been already 236 matched (will be updated by this function) 237 pre_patterns (list of re.Pattern): list of regular expression that should 238 be matched as preamble 239 post_patterns (list of re.Pattern): list of regular expression that should 240 be matched as postamble 241 extend_pre (bool): Add the line number of matched preamble to the matched 242 list 243 extend_post (bool): Add the line number of matched postamble to the 244 matched list 245 """ 246 extended_matched = [] 247 248 j = matched[0] 249 250 for i in matched: 251 if i == 0 or i < j: 252 continue 253 j = i 254 while j in matched: 255 j += 1 256 if j >= len(lines): 257 break 258 259 for pat in pre_patterns: 260 if pat.search(lines[i - 1]): 261 break 262 else: 263 # not matched 264 continue 265 266 for pat in post_patterns: 267 if pat.search(lines[j]): 268 break 269 else: 270 # not matched 271 continue 272 273 if extend_pre: 274 extended_matched.append(i - 1) 275 if extend_post: 276 extended_matched.append(j) 277 278 matched += extended_matched 279 matched.sort() 280 281def confirm(args, prompt): 282 """Ask the user to confirm something 283 284 Args: 285 args (Namespace ): program arguments 286 287 Returns: 288 bool: True to confirm, False to cancel/stop 289 """ 290 if not args.yes: 291 while True: 292 choice = input(f'{prompt} [y/n]: ') 293 choice = choice.lower() 294 print(choice) 295 if choice in ('y', 'n'): 296 break 297 298 if choice == 'n': 299 return False 300 301 return True 302 303def write_file(fname, data): 304 """Write data to a file 305 306 Args: 307 fname (str): Filename to write to 308 data (list of str): Lines to write (with or without trailing newline); 309 or str to write 310 """ 311 with open(fname, 'w', encoding='utf-8') as out: 312 if isinstance(data, list): 313 for line in data: 314 print(line.rstrip('\n'), file=out) 315 else: 316 out.write(data) 317 318def read_file(fname, as_lines=True, skip_unicode=False): 319 """Read a file and return the contents 320 321 Args: 322 fname (str): Filename to read from 323 as_lines: Return file contents as a list of lines 324 skip_unicode (bool): True to report unicode errors and continue 325 326 Returns: 327 iter of str: List of ;ines from the file with newline removed; str if 328 as_lines is False with newlines intact; or None if a unicode error 329 occurred 330 331 Raises: 332 UnicodeDecodeError: Unicode error occurred when reading 333 """ 334 with open(fname, encoding='utf-8') as inf: 335 try: 336 if as_lines: 337 return [line.rstrip('\n') for line in inf.readlines()] 338 else: 339 return inf.read() 340 except UnicodeDecodeError as e: 341 if not skip_unicode: 342 raises 343 print("Failed on file %s': %s" % (fname, e)) 344 return None 345 346def cleanup_empty_blocks(header_path, args): 347 """Clean up empty conditional blocks 348 349 Args: 350 header_path (str): path to the cleaned file. 351 args (Namespace): program arguments 352 """ 353 pattern = re.compile(r'^\s*#\s*if.*$\n^\s*#\s*endif.*$\n*', flags=re.M) 354 data = read_file(header_path, as_lines=False, skip_unicode=True) 355 if data is None: 356 return 357 358 new_data = pattern.sub('\n', data) 359 360 show_diff(data.splitlines(True), new_data.splitlines(True), header_path, 361 args.color) 362 363 if args.dry_run: 364 return 365 366 if new_data != data: 367 write_file(header_path, new_data) 368 369def cleanup_one_header(header_path, patterns, args): 370 """Clean regex-matched lines away from a file. 371 372 Args: 373 header_path: path to the cleaned file. 374 patterns: list of regex patterns. Any lines matching to these 375 patterns are deleted. 376 args (Namespace): program arguments 377 """ 378 lines = read_file(header_path, skip_unicode=True) 379 if lines is None: 380 return 381 382 matched = [] 383 for i, line in enumerate(lines): 384 if i - 1 in matched and lines[i - 1].endswith('\\'): 385 matched.append(i) 386 continue 387 for pattern in patterns: 388 if pattern.search(line): 389 matched.append(i) 390 break 391 392 if not matched: 393 return 394 395 # remove empty #ifdef ... #endif, successive blank lines 396 pattern_if = re.compile(r'#\s*if(def|ndef)?\b') # #if, #ifdef, #ifndef 397 pattern_elif = re.compile(r'#\s*el(if|se)\b') # #elif, #else 398 pattern_endif = re.compile(r'#\s*endif\b') # #endif 399 pattern_blank = re.compile(r'^\s*$') # empty line 400 401 while True: 402 old_matched = copy.copy(matched) 403 extend_matched_lines(lines, matched, [pattern_if], 404 [pattern_endif], True, True) 405 extend_matched_lines(lines, matched, [pattern_elif], 406 [pattern_elif, pattern_endif], True, False) 407 extend_matched_lines(lines, matched, [pattern_if, pattern_elif], 408 [pattern_blank], False, True) 409 extend_matched_lines(lines, matched, [pattern_blank], 410 [pattern_elif, pattern_endif], True, False) 411 extend_matched_lines(lines, matched, [pattern_blank], 412 [pattern_blank], True, False) 413 if matched == old_matched: 414 break 415 416 tolines = copy.copy(lines) 417 418 for i in reversed(matched): 419 tolines.pop(i) 420 421 show_diff(lines, tolines, header_path, args.color) 422 423 if args.dry_run: 424 return 425 426 write_file(header_path, tolines) 427 428def cleanup_headers(configs, args): 429 """Delete config defines from board headers. 430 431 Args: 432 configs: A list of CONFIGs to remove. 433 args (Namespace): program arguments 434 """ 435 if not confirm(args, 'Clean up headers?'): 436 return 437 438 patterns = [] 439 for config in configs: 440 patterns.append(re.compile(r'#\s*define\s+%s\b' % config)) 441 patterns.append(re.compile(r'#\s*undef\s+%s\b' % config)) 442 443 for dir in 'include', 'arch', 'board': 444 for (dirpath, dirnames, filenames) in os.walk(dir): 445 if dirpath == os.path.join('include', 'generated'): 446 continue 447 for filename in filenames: 448 if not filename.endswith(('~', '.dts', '.dtsi', '.bin', 449 '.elf','.aml','.dat')): 450 header_path = os.path.join(dirpath, filename) 451 # This file contains UTF-16 data and no CONFIG symbols 452 if header_path == 'include/video_font_data.h': 453 continue 454 cleanup_one_header(header_path, patterns, args) 455 cleanup_empty_blocks(header_path, args) 456 457def cleanup_one_extra_option(defconfig_path, configs, args): 458 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in one defconfig file. 459 460 Args: 461 defconfig_path: path to the cleaned defconfig file. 462 configs: A list of CONFIGs to remove. 463 args (Namespace): program arguments 464 """ 465 466 start = 'CONFIG_SYS_EXTRA_OPTIONS="' 467 end = '"' 468 469 lines = read_file(defconfig_path) 470 471 for i, line in enumerate(lines): 472 if line.startswith(start) and line.endswith(end): 473 break 474 else: 475 # CONFIG_SYS_EXTRA_OPTIONS was not found in this defconfig 476 return 477 478 old_tokens = line[len(start):-len(end)].split(',') 479 new_tokens = [] 480 481 for token in old_tokens: 482 pos = token.find('=') 483 if not (token[:pos] if pos >= 0 else token) in configs: 484 new_tokens.append(token) 485 486 if new_tokens == old_tokens: 487 return 488 489 tolines = copy.copy(lines) 490 491 if new_tokens: 492 tolines[i] = start + ','.join(new_tokens) + end 493 else: 494 tolines.pop(i) 495 496 show_diff(lines, tolines, defconfig_path, args.color) 497 498 if args.dry_run: 499 return 500 501 write_file(defconfig_path, tolines) 502 503def cleanup_extra_options(configs, args): 504 """Delete config defines in CONFIG_SYS_EXTRA_OPTIONS in defconfig files. 505 506 Args: 507 configs: A list of CONFIGs to remove. 508 args (Namespace): program arguments 509 """ 510 if not confirm(args, 'Clean up CONFIG_SYS_EXTRA_OPTIONS?'): 511 return 512 513 configs = [ config[len('CONFIG_'):] for config in configs ] 514 515 defconfigs = get_all_defconfigs() 516 517 for defconfig in defconfigs: 518 cleanup_one_extra_option(os.path.join('configs', defconfig), configs, 519 args) 520 521def cleanup_whitelist(configs, args): 522 """Delete config whitelist entries 523 524 Args: 525 configs: A list of CONFIGs to remove. 526 args (Namespace): program arguments 527 """ 528 if not confirm(args, 'Clean up whitelist entries?'): 529 return 530 531 lines = read_file(os.path.join('scripts', 'config_whitelist.txt')) 532 533 lines = [x for x in lines if x.strip() not in configs] 534 535 write_file(os.path.join('scripts', 'config_whitelist.txt'), lines) 536 537def find_matching(patterns, line): 538 for pat in patterns: 539 if pat.search(line): 540 return True 541 return False 542 543def cleanup_readme(configs, args): 544 """Delete config description in README 545 546 Args: 547 configs: A list of CONFIGs to remove. 548 args (Namespace): program arguments 549 """ 550 if not confirm(args, 'Clean up README?'): 551 return 552 553 patterns = [] 554 for config in configs: 555 patterns.append(re.compile(r'^\s+%s' % config)) 556 557 lines = read_file('README') 558 559 found = False 560 newlines = [] 561 for line in lines: 562 if not found: 563 found = find_matching(patterns, line) 564 if found: 565 continue 566 567 if found and re.search(r'^\s+CONFIG', line): 568 found = False 569 570 if not found: 571 newlines.append(line) 572 573 write_file('README', newlines) 574 575def try_expand(line): 576 """If value looks like an expression, try expanding it 577 Otherwise just return the existing value 578 """ 579 if line.find('=') == -1: 580 return line 581 582 try: 583 aeval = asteval.Interpreter( usersyms=SIZES, minimal=True ) 584 cfg, val = re.split("=", line) 585 val= val.strip('\"') 586 if re.search(r'[*+-/]|<<|SZ_+|\(([^\)]+)\)', val): 587 newval = hex(aeval(val)) 588 print('\tExpanded expression %s to %s' % (val, newval)) 589 return cfg+'='+newval 590 except: 591 print('\tFailed to expand expression in %s' % line) 592 593 return line 594 595 596### classes ### 597class Progress: 598 599 """Progress Indicator""" 600 601 def __init__(self, total): 602 """Create a new progress indicator. 603 604 Args: 605 total: A number of defconfig files to process. 606 """ 607 self.current = 0 608 self.total = total 609 610 def inc(self): 611 """Increment the number of processed defconfig files.""" 612 613 self.current += 1 614 615 def show(self): 616 """Display the progress.""" 617 print(' %d defconfigs out of %d\r' % (self.current, self.total), end=' ') 618 sys.stdout.flush() 619 620 621class KconfigScanner: 622 """Kconfig scanner.""" 623 624 def __init__(self): 625 """Scan all the Kconfig files and create a Config object.""" 626 # Define environment variables referenced from Kconfig 627 os.environ['srctree'] = os.getcwd() 628 os.environ['UBOOTVERSION'] = 'dummy' 629 os.environ['KCONFIG_OBJDIR'] = '' 630 self.conf = kconfiglib.Kconfig() 631 632 633class KconfigParser: 634 635 """A parser of .config and include/autoconf.mk.""" 636 637 re_arch = re.compile(r'CONFIG_SYS_ARCH="(.*)"') 638 re_cpu = re.compile(r'CONFIG_SYS_CPU="(.*)"') 639 640 def __init__(self, configs, args, build_dir): 641 """Create a new parser. 642 643 Args: 644 configs: A list of CONFIGs to move. 645 args (Namespace): program arguments 646 build_dir: Build directory. 647 """ 648 self.configs = configs 649 self.args = args 650 self.dotconfig = os.path.join(build_dir, '.config') 651 self.autoconf = os.path.join(build_dir, 'include', 'autoconf.mk') 652 self.spl_autoconf = os.path.join(build_dir, 'spl', 'include', 653 'autoconf.mk') 654 self.config_autoconf = os.path.join(build_dir, AUTO_CONF_PATH) 655 self.defconfig = os.path.join(build_dir, 'defconfig') 656 657 def get_arch(self): 658 """Parse .config file and return the architecture. 659 660 Returns: 661 Architecture name (e.g. 'arm'). 662 """ 663 arch = '' 664 cpu = '' 665 for line in read_file(self.dotconfig): 666 m = self.re_arch.match(line) 667 if m: 668 arch = m.group(1) 669 continue 670 m = self.re_cpu.match(line) 671 if m: 672 cpu = m.group(1) 673 674 if not arch: 675 return None 676 677 # fix-up for aarch64 678 if arch == 'arm' and cpu == 'armv8': 679 arch = 'aarch64' 680 681 return arch 682 683 def parse_one_config(self, config, dotconfig_lines, autoconf_lines): 684 """Parse .config, defconfig, include/autoconf.mk for one config. 685 686 This function looks for the config options in the lines from 687 defconfig, .config, and include/autoconf.mk in order to decide 688 which action should be taken for this defconfig. 689 690 Args: 691 config: CONFIG name to parse. 692 dotconfig_lines: lines from the .config file. 693 autoconf_lines: lines from the include/autoconf.mk file. 694 695 Returns: 696 A tupple of the action for this defconfig and the line 697 matched for the config. 698 """ 699 not_set = '# %s is not set' % config 700 701 for line in autoconf_lines: 702 line = line.rstrip() 703 if line.startswith(config + '='): 704 new_val = line 705 break 706 else: 707 new_val = not_set 708 709 new_val = try_expand(new_val) 710 711 for line in dotconfig_lines: 712 line = line.rstrip() 713 if line.startswith(config + '=') or line == not_set: 714 old_val = line 715 break 716 else: 717 if new_val == not_set: 718 return (ACTION_NO_ENTRY, config) 719 else: 720 return (ACTION_NO_ENTRY_WARN, config) 721 722 # If this CONFIG is neither bool nor trisate 723 if old_val[-2:] != '=y' and old_val[-2:] != '=m' and old_val != not_set: 724 # tools/scripts/define2mk.sed changes '1' to 'y'. 725 # This is a problem if the CONFIG is int type. 726 # Check the type in Kconfig and handle it correctly. 727 if new_val[-2:] == '=y': 728 new_val = new_val[:-1] + '1' 729 730 return (ACTION_NO_CHANGE if old_val == new_val else ACTION_MOVE, 731 new_val) 732 733 def update_dotconfig(self): 734 """Parse files for the config options and update the .config. 735 736 This function parses the generated .config and include/autoconf.mk 737 searching the target options. 738 Move the config option(s) to the .config as needed. 739 740 Args: 741 defconfig: defconfig name. 742 743 Returns: 744 Return a tuple of (updated flag, log string). 745 The "updated flag" is True if the .config was updated, False 746 otherwise. The "log string" shows what happend to the .config. 747 """ 748 749 results = [] 750 updated = False 751 suspicious = False 752 rm_files = [self.config_autoconf, self.autoconf] 753 754 if self.args.spl: 755 if os.path.exists(self.spl_autoconf): 756 autoconf_path = self.spl_autoconf 757 rm_files.append(self.spl_autoconf) 758 else: 759 for f in rm_files: 760 os.remove(f) 761 return (updated, suspicious, 762 color_text(self.args.color, COLOR_BROWN, 763 "SPL is not enabled. Skipped.") + '\n') 764 else: 765 autoconf_path = self.autoconf 766 767 dotconfig_lines = read_file(self.dotconfig) 768 769 autoconf_lines = read_file(autoconf_path) 770 771 for config in self.configs: 772 result = self.parse_one_config(config, dotconfig_lines, 773 autoconf_lines) 774 results.append(result) 775 776 log = '' 777 778 for (action, value) in results: 779 if action == ACTION_MOVE: 780 actlog = "Move '%s'" % value 781 log_color = COLOR_LIGHT_GREEN 782 elif action == ACTION_NO_ENTRY: 783 actlog = '%s is not defined in Kconfig. Do nothing.' % value 784 log_color = COLOR_LIGHT_BLUE 785 elif action == ACTION_NO_ENTRY_WARN: 786 actlog = '%s is not defined in Kconfig (suspicious). Do nothing.' % value 787 log_color = COLOR_YELLOW 788 suspicious = True 789 elif action == ACTION_NO_CHANGE: 790 actlog = "'%s' is the same as the define in Kconfig. Do nothing." \ 791 % value 792 log_color = COLOR_LIGHT_PURPLE 793 elif action == ACTION_SPL_NOT_EXIST: 794 actlog = 'SPL is not enabled for this defconfig. Skip.' 795 log_color = COLOR_PURPLE 796 else: 797 sys.exit('Internal Error. This should not happen.') 798 799 log += color_text(self.args.color, log_color, actlog) + '\n' 800 801 with open(self.dotconfig, 'a', encoding='utf-8') as out: 802 for (action, value) in results: 803 if action == ACTION_MOVE: 804 out.write(value + '\n') 805 updated = True 806 807 self.results = results 808 for f in rm_files: 809 os.remove(f) 810 811 return (updated, suspicious, log) 812 813 def check_defconfig(self): 814 """Check the defconfig after savedefconfig 815 816 Returns: 817 Return additional log if moved CONFIGs were removed again by 818 'make savedefconfig'. 819 """ 820 821 log = '' 822 823 defconfig_lines = read_file(self.defconfig) 824 825 for (action, value) in self.results: 826 if action != ACTION_MOVE: 827 continue 828 if not value in defconfig_lines: 829 log += color_text(self.args.color, COLOR_YELLOW, 830 "'%s' was removed by savedefconfig.\n" % 831 value) 832 833 return log 834 835 836class DatabaseThread(threading.Thread): 837 """This thread processes results from Slot threads. 838 839 It collects the data in the master config directary. There is only one 840 result thread, and this helps to serialise the build output. 841 """ 842 def __init__(self, config_db, db_queue): 843 """Set up a new result thread 844 845 Args: 846 builder: Builder which will be sent each result 847 """ 848 threading.Thread.__init__(self) 849 self.config_db = config_db 850 self.db_queue= db_queue 851 852 def run(self): 853 """Called to start up the result thread. 854 855 We collect the next result job and pass it on to the build. 856 """ 857 while True: 858 defconfig, configs = self.db_queue.get() 859 self.config_db[defconfig] = configs 860 self.db_queue.task_done() 861 862 863class Slot: 864 865 """A slot to store a subprocess. 866 867 Each instance of this class handles one subprocess. 868 This class is useful to control multiple threads 869 for faster processing. 870 """ 871 872 def __init__(self, toolchains, configs, args, progress, devnull, 873 make_cmd, reference_src_dir, db_queue): 874 """Create a new process slot. 875 876 Args: 877 toolchains: Toolchains object containing toolchains. 878 configs: A list of CONFIGs to move. 879 args: Program arguments 880 progress: A progress indicator. 881 devnull: A file object of '/dev/null'. 882 make_cmd: command name of GNU Make. 883 reference_src_dir: Determine the true starting config state from this 884 source tree. 885 db_queue: output queue to write config info for the database 886 """ 887 self.toolchains = toolchains 888 self.args = args 889 self.progress = progress 890 self.build_dir = tempfile.mkdtemp() 891 self.devnull = devnull 892 self.make_cmd = (make_cmd, 'O=' + self.build_dir) 893 self.reference_src_dir = reference_src_dir 894 self.db_queue = db_queue 895 self.parser = KconfigParser(configs, args, self.build_dir) 896 self.state = STATE_IDLE 897 self.failed_boards = set() 898 self.suspicious_boards = set() 899 900 def __del__(self): 901 """Delete the working directory 902 903 This function makes sure the temporary directory is cleaned away 904 even if Python suddenly dies due to error. It should be done in here 905 because it is guaranteed the destructor is always invoked when the 906 instance of the class gets unreferenced. 907 908 If the subprocess is still running, wait until it finishes. 909 """ 910 if self.state != STATE_IDLE: 911 while self.ps.poll() == None: 912 pass 913 shutil.rmtree(self.build_dir) 914 915 def add(self, defconfig): 916 """Assign a new subprocess for defconfig and add it to the slot. 917 918 If the slot is vacant, create a new subprocess for processing the 919 given defconfig and add it to the slot. Just returns False if 920 the slot is occupied (i.e. the current subprocess is still running). 921 922 Args: 923 defconfig: defconfig name. 924 925 Returns: 926 Return True on success or False on failure 927 """ 928 if self.state != STATE_IDLE: 929 return False 930 931 self.defconfig = defconfig 932 self.log = '' 933 self.current_src_dir = self.reference_src_dir 934 self.do_defconfig() 935 return True 936 937 def poll(self): 938 """Check the status of the subprocess and handle it as needed. 939 940 Returns True if the slot is vacant (i.e. in idle state). 941 If the configuration is successfully finished, assign a new 942 subprocess to build include/autoconf.mk. 943 If include/autoconf.mk is generated, invoke the parser to 944 parse the .config and the include/autoconf.mk, moving 945 config options to the .config as needed. 946 If the .config was updated, run "make savedefconfig" to sync 947 it, update the original defconfig, and then set the slot back 948 to the idle state. 949 950 Returns: 951 Return True if the subprocess is terminated, False otherwise 952 """ 953 if self.state == STATE_IDLE: 954 return True 955 956 if self.ps.poll() == None: 957 return False 958 959 if self.ps.poll() != 0: 960 self.handle_error() 961 elif self.state == STATE_DEFCONFIG: 962 if self.reference_src_dir and not self.current_src_dir: 963 self.do_savedefconfig() 964 else: 965 self.do_autoconf() 966 elif self.state == STATE_AUTOCONF: 967 if self.current_src_dir: 968 self.current_src_dir = None 969 self.do_defconfig() 970 elif self.args.build_db: 971 self.do_build_db() 972 else: 973 self.do_savedefconfig() 974 elif self.state == STATE_SAVEDEFCONFIG: 975 self.update_defconfig() 976 else: 977 sys.exit('Internal Error. This should not happen.') 978 979 return True if self.state == STATE_IDLE else False 980 981 def handle_error(self): 982 """Handle error cases.""" 983 984 self.log += color_text(self.args.color, COLOR_LIGHT_RED, 985 'Failed to process.\n') 986 if self.args.verbose: 987 self.log += color_text(self.args.color, COLOR_LIGHT_CYAN, 988 self.ps.stderr.read().decode()) 989 self.finish(False) 990 991 def do_defconfig(self): 992 """Run 'make <board>_defconfig' to create the .config file.""" 993 994 cmd = list(self.make_cmd) 995 cmd.append(self.defconfig) 996 self.ps = subprocess.Popen(cmd, stdout=self.devnull, 997 stderr=subprocess.PIPE, 998 cwd=self.current_src_dir) 999 self.state = STATE_DEFCONFIG 1000
1001 def do_autoconf(self): 1002 """Run 'make AUTO_CONF_PATH'.""" 1003 1004 arch = self.parser.get_arch() 1005 try: 1006 toolchain = self.toolchains.Select(arch) 1007 except ValueError: 1008 self.log += color_text(self.args.color, COLOR_YELLOW, 1009 "Tool chain for '%s' is missing. Do nothing.\n" % arch) 1010 self.finish(False) 1011 return 1012 env = toolchain.MakeEnvironment(False) 1013 1014 cmd = list(self.make_cmd) 1015 cmd.append('KCONFIG_IGNORE_DUPLICATES=1') 1016 cmd.append(AUTO_CONF_PATH) 1017 self.ps = subprocess.Popen(cmd, stdout=self.devnull, env=env, 1018 stderr=subprocess.PIPE, 1019 cwd=self.current_src_dir) 1020 self.state = STATE_AUTOCONF 1021 1022 def do_build_db(self): 1023 """Add the board to the database""" 1024 configs = {} 1025 for line in read_file(os.path.join(self.build_dir, AUTO_CONF_PATH)): 1026 if line.startswith('CONFIG'): 1027 config, value = line.split('=', 1) 1028 configs[config] = value.rstrip() 1029 self.db_queue.put([self.defconfig, configs]) 1030 self.finish(True) 1031 1032 def do_savedefconfig(self): 1033 """Update the .config and run 'make savedefconfig'.""" 1034 1035 (updated, suspicious, log) = self.parser.update_dotconfig() 1036 if suspicious: 1037 self.suspicious_boards.add(self.defconfig) 1038 self.log += log 1039 1040 if not self.args.force_sync and not updated: 1041 self.finish(True) 1042 return 1043 if updated: 1044 self.log += color_text(self.args.color, COLOR_LIGHT_GREEN, 1045 'Syncing by savedefconfig...\n') 1046 else: 1047 self.log += 'Syncing by savedefconfig (forced by option)...\n' 1048 1049 cmd = list(self.make_cmd) 1050 cmd.append('savedefconfig') 1051 self.ps = subprocess.Popen(cmd, stdout=self.devnull, 1052 stderr=subprocess.PIPE) 1053 self.state = STATE_SAVEDEFCONFIG 1054 1055 def update_defconfig(self): 1056 """Update the input defconfig and go back to the idle state.""" 1057 1058 log = self.parser.check_defconfig() 1059 if log: 1060 self.suspicious_boards.add(self.defconfig) 1061 self.log += log 1062 orig_defconfig = os.path.join('configs', self.defconfig) 1063 new_defconfig = os.path.join(self.build_dir, 'defconfig') 1064 updated = not filecmp.cmp(orig_defconfig, new_defconfig) 1065 1066 if updated: 1067 self.log += color_text(self.args.color, COLOR_LIGHT_BLUE, 1068 'defconfig was updated.\n') 1069 1070 if not self.args.dry_run and updated: 1071 shutil.move(new_defconfig, orig_defconfig) 1072 self.finish(True) 1073 1074 def finish(self, success): 1075 """Display log along with progress and go to the idle state. 1076 1077 Args: 1078 success: Should be True when the defconfig was processed 1079 successfully, or False when it fails. 1080 """ 1081 # output at least 30 characters to hide the "* defconfigs out of *". 1082 log = self.defconfig.ljust(30) + '\n' 1083 1084 log += '\n'.join([ ' ' + s for s in self.log.split('\n') ]) 1085 # Some threads are running in parallel. 1086 # Print log atomically to not mix up logs from different threads. 1087 print(log, file=(sys.stdout if success else sys.stderr)) 1088 1089 if not success: 1090 if self.args.exit_on_error: 1091 sys.exit('Exit on error.') 1092 # If --exit-on-error flag is not set, skip this board and continue. 1093 # Record the failed board. 1094 self.failed_boards.add(self.defconfig) 1095 1096 self.progress.inc() 1097 self.progress.show() 1098 self.state = STATE_IDLE 1099 1100 def get_failed_boards(self): 1101 """Returns a set of failed boards (defconfigs) in this slot. 1102 """ 1103 return self.failed_boards 1104 1105 def get_suspicious_boards(self): 1106 """Returns a set of boards (defconfigs) with possible misconversion. 1107 """ 1108 return self.suspicious_boards - self.failed_boards 1109 1110class Slots: 1111 1112 """Controller of the array of subprocess slots.""" 1113 1114 def __init__(self, toolchains, configs, args, progress, 1115 reference_src_dir, db_queue): 1116 """Create a new slots controller. 1117 1118 Args: 1119 toolchains: Toolchains object containing toolchains. 1120 configs: A list of CONFIGs to move. 1121 args: Program arguments 1122 progress: A progress indicator. 1123 reference_src_dir: Determine the true starting config state from this 1124 source tree. 1125 db_queue: output queue to write config info for the database 1126 """ 1127 self.args = args 1128 self.slots = [] 1129 devnull = subprocess.DEVNULL 1130 make_cmd = get_make_cmd() 1131 for i in range(args.jobs): 1132 self.slots.append(Slot(toolchains, configs, args, progress, 1133 devnull, make_cmd, reference_src_dir, 1134 db_queue)) 1135 1136 def add(self, defconfig): 1137 """Add a new subprocess if a vacant slot is found. 1138 1139 Args: 1140 defconfig: defconfig name to be put into. 1141 1142 Returns: 1143 Return True on success or False on failure 1144 """ 1145 for slot in self.slots: 1146 if slot.add(defconfig): 1147 return True 1148 return False 1149 1150 def available(self): 1151 """Check if there is a vacant slot. 1152 1153 Returns: 1154 Return True if at lease one vacant slot is found, False otherwise. 1155 """ 1156 for slot in self.slots: 1157 if slot.poll(): 1158 return True 1159 return False 1160 1161 def empty(self): 1162 """Check if all slots are vacant. 1163 1164 Returns: 1165 Return True if all the slots are vacant, False otherwise. 1166 """ 1167 ret = True 1168 for slot in self.slots: 1169 if not slot.poll(): 1170 ret = False 1171 return ret 1172 1173 def show_failed_boards(self): 1174 """Display all of the failed boards (defconfigs).""" 1175 boards = set() 1176 output_file = 'moveconfig.failed' 1177 1178 for slot in self.slots: 1179 boards |= slot.get_failed_boards() 1180 1181 if boards: 1182 boards = '\n'.join(boards) + '\n' 1183 msg = 'The following boards were not processed due to error:\n' 1184 msg += boards 1185 msg += '(the list has been saved in %s)\n' % output_file 1186 print(color_text(self.args.color, COLOR_LIGHT_RED, 1187 msg), file=sys.stderr) 1188 1189 write_file(output_file, boards) 1190 1191 def show_suspicious_boards(self): 1192 """Display all boards (defconfigs) with possible misconversion.""" 1193 boards = set() 1194 output_file = 'moveconfig.suspicious' 1195 1196 for slot in self.slots: 1197 boards |= slot.get_suspicious_boards() 1198 1199 if boards: 1200 boards = '\n'.join(boards) + '\n' 1201 msg = 'The following boards might have been converted incorrectly.\n' 1202 msg += 'It is highly recommended to check them manually:\n' 1203 msg += boards 1204 msg += '(the list has been saved in %s)\n' % output_file 1205 print(color_text(self.args.color, COLOR_YELLOW, 1206 msg), file=sys.stderr) 1207 1208 write_file(output_file, boards) 1209 1210class ReferenceSource: 1211 1212 """Reference source against which original configs should be parsed.""" 1213 1214 def __init__(self, commit): 1215 """Create a reference source directory based on a specified commit. 1216 1217 Args: 1218 commit: commit to git-clone 1219 """ 1220 self.src_dir = tempfile.mkdtemp() 1221 print('Cloning git repo to a separate work directory...') 1222 subprocess.check_output(['git', 'clone', os.getcwd(), '.'], 1223 cwd=self.src_dir) 1224 print("Checkout '%s' to build the original autoconf.mk." % \ 1225 subprocess.check_output(['git', 'rev-parse', '--short', commit]).strip()) 1226 subprocess.check_output(['git', 'checkout', commit], 1227 stderr=subprocess.STDOUT, cwd=self.src_dir) 1228 1229 def __del__(self): 1230 """Delete the reference source directory 1231 1232 This function makes sure the temporary directory is cleaned away 1233 even if Python suddenly dies due to error. It should be done in here 1234 because it is guaranteed the destructor is always invoked when the 1235 instance of the class gets unreferenced. 1236 """ 1237 shutil.rmtree(self.src_dir) 1238 1239 def get_dir(self): 1240 """Return the absolute path to the reference source directory.""" 1241 1242 return self.src_dir 1243 1244def move_config(toolchains, configs, args, db_queue): 1245 """Move config options to defconfig files. 1246 1247 Args: 1248 configs: A list of CONFIGs to move. 1249 args: Program arguments 1250 """ 1251 if len(configs) == 0: 1252 if args.force_sync: 1253 print('No CONFIG is specified. You are probably syncing defconfigs.', end=' ') 1254 elif args.build_db: 1255 print('Building %s database' % CONFIG_DATABASE) 1256 else: 1257 print('Neither CONFIG nor --force-sync is specified. Nothing will happen.', end=' ') 1258 else: 1259 print('Move ' + ', '.join(configs), end=' ') 1260 print('(jobs: %d)\n' % args.jobs) 1261 1262 if args.git_ref: 1263 reference_src = ReferenceSource(args.git_ref) 1264 reference_src_dir = reference_src.get_dir() 1265 else: 1266 reference_src_dir = None 1267 1268 if args.defconfigs: 1269 defconfigs = get_matched_defconfigs(args.defconfigs) 1270 else: 1271 defconfigs = get_all_defconfigs() 1272 1273 progress = Progress(len(defconfigs)) 1274 slots = Slots(toolchains, configs, args, progress, reference_src_dir, 1275 db_queue) 1276 1277 # Main loop to process defconfig files: 1278 # Add a new subprocess into a vacant slot. 1279 # Sleep if there is no available slot. 1280 for defconfig in defconfigs: 1281 while not slots.add(defconfig): 1282 while not slots.available(): 1283 # No available slot: sleep for a while 1284 time.sleep(SLEEP_TIME) 1285 1286 # wait until all the subprocesses finish 1287 while not slots.empty(): 1288 time.sleep(SLEEP_TIME) 1289 1290 print('') 1291 slots.show_failed_boards() 1292 slots.show_suspicious_boards() 1293 1294def find_kconfig_rules(kconf, config, imply_config): 1295 """Check whether a config has a 'select' or 'imply' keyword 1296 1297 Args: 1298 kconf: Kconfiglib.Kconfig object 1299 config: Name of config to check (without CONFIG_ prefix) 1300 imply_config: Implying config (without CONFIG_ prefix) which may or 1301 may not have an 'imply' for 'config') 1302 1303 Returns: 1304 Symbol object for 'config' if found, else None 1305 """ 1306 sym = kconf.syms.get(imply_config) 1307 if sym: 1308 for sel, cond in (sym.selects + sym.implies): 1309 if sel.name == config: 1310 return sym 1311 return None 1312 1313def check_imply_rule(kconf, config, imply_config): 1314 """Check if we can add an 'imply' option 1315 1316 This finds imply_config in the Kconfig and looks to see if it is possible 1317 to add an 'imply' for 'config' to that part of the Kconfig. 1318 1319 Args: 1320 kconf: Kconfiglib.Kconfig object 1321 config: Name of config to check (without CONFIG_ prefix) 1322 imply_config: Implying config (without CONFIG_ prefix) which may or 1323 may not have an 'imply' for 'config') 1324 1325 Returns: 1326 tuple: 1327 filename of Kconfig file containing imply_config, or None if none 1328 line number within the Kconfig file, or 0 if none 1329 message indicating the result 1330 """ 1331 sym = kconf.syms.get(imply_config) 1332 if not sym: 1333 return 'cannot find sym' 1334 nodes = sym.nodes 1335 if len(nodes) != 1: 1336 return '%d locations' % len(nodes) 1337 node = nodes[0] 1338 fname, linenum = node.filename, node.linenr 1339 cwd = os.getcwd() 1340 if cwd and fname.startswith(cwd): 1341 fname = fname[len(cwd) + 1:] 1342 file_line = ' at %s:%d' % (fname, linenum) 1343 data = read_file(fname) 1344 if data[linenum - 1] != 'config %s' % imply_config: 1345 return None, 0, 'bad sym format %s%s' % (data[linenum], file_line) 1346 return fname, linenum, 'adding%s' % file_line 1347 1348def add_imply_rule(config, fname, linenum): 1349 """Add a new 'imply' option to a Kconfig 1350 1351 Args: 1352 config: config option to add an imply for (without CONFIG_ prefix) 1353 fname: Kconfig filename to update 1354 linenum: Line number to place the 'imply' before 1355 1356 Returns: 1357 Message indicating the result 1358 """ 1359 file_line = ' at %s:%d' % (fname, linenum) 1360 data = read_file(fname) 1361 linenum -= 1 1362 1363 for offset, line in enumerate(data[linenum:]): 1364 if line.strip().startswith('help') or not line: 1365 data.insert(linenum + offset, '\timply %s' % config) 1366 write_file(fname, data) 1367 return 'added%s' % file_line 1368 1369 return 'could not insert%s' 1370 1371(IMPLY_MIN_2, IMPLY_TARGET, IMPLY_CMD, IMPLY_NON_ARCH_BOARD) = ( 1372 1, 2, 4, 8) 1373 1374IMPLY_FLAGS = { 1375 'min2': [IMPLY_MIN_2, 'Show options which imply >2 boards (normally >5)'], 1376 'target': [IMPLY_TARGET, 'Allow CONFIG_TARGET_... options to imply'], 1377 'cmd': [IMPLY_CMD, 'Allow CONFIG_CMD_... to imply'], 1378 'non-arch-board': [ 1379 IMPLY_NON_ARCH_BOARD, 1380 'Allow Kconfig options outside arch/ and /board/ to imply'], 1381} 1382 1383 1384def read_database(): 1385 """Read in the config database 1386 1387 Returns: 1388 tuple: 1389 set of all config options seen (each a str) 1390 set of all defconfigs seen (each a str) 1391 dict of configs for each defconfig: 1392 key: defconfig name, e.g. "MPC8548CDS_legacy_defconfig" 1393 value: dict: 1394 key: CONFIG option 1395 value: Value of option 1396 dict of defconfigs for each config: 1397 key: CONFIG option 1398 value: set of boards using that option 1399 1400 """ 1401 configs = {} 1402 1403 # key is defconfig name, value is dict of (CONFIG_xxx, value) 1404 config_db = {} 1405 1406 # Set of all config options we have seen 1407 all_configs = set() 1408 1409 # Set of all defconfigs we have seen 1410 all_defconfigs = set() 1411 1412 defconfig_db = collections.defaultdict(set) 1413 for line in read_file(CONFIG_DATABASE): 1414 line = line.rstrip() 1415 if not line: # Separator between defconfigs 1416 config_db[defconfig] = configs 1417 all_defconfigs.add(defconfig) 1418 configs = {} 1419 elif line[0] == ' ': # CONFIG line 1420 config, value = line.strip().split('=', 1) 1421 configs[config] = value 1422 defconfig_db[config].add(defconfig) 1423 all_configs.add(config) 1424 else: # New defconfig 1425 defconfig = line 1426 1427 return all_configs, all_defconfigs, config_db, defconfig_db 1428 1429 1430def do_imply_config(config_list, add_imply, imply_flags, skip_added, 1431 check_kconfig=True, find_superset=False): 1432 """Find CONFIG options which imply those in the list 1433 1434 Some CONFIG options can be implied by others and this can help to reduce 1435 the size of the defconfig files. For example, CONFIG_X86 implies 1436 CONFIG_CMD_IRQ, so we can put 'imply CMD_IRQ' under 'config X86' and 1437 all x86 boards will have that option, avoiding adding CONFIG_CMD_IRQ to 1438 each of the x86 defconfig files. 1439 1440 This function uses the moveconfig database to find such options. It 1441 displays a list of things that could possibly imply those in the list. 1442 The algorithm ignores any that start with CONFIG_TARGET since these 1443 typically refer to only a few defconfigs (often one). It also does not 1444 display a config with less than 5 defconfigs. 1445 1446 The algorithm works using sets. For each target config in config_list: 1447 - Get the set 'defconfigs' which use that target config 1448 - For each config (from a list of all configs): 1449 - Get the set 'imply_defconfig' of defconfigs which use that config 1450 - 1451 - If imply_defconfigs contains anything not in defconfigs then 1452 this config does not imply the target config 1453 1454 Params: 1455 config_list: List of CONFIG options to check (each a string) 1456 add_imply: Automatically add an 'imply' for each config. 1457 imply_flags: Flags which control which implying configs are allowed 1458 (IMPLY_...) 1459 skip_added: Don't show options which already have an imply added. 1460 check_kconfig: Check if implied symbols already have an 'imply' or 1461 'select' for the target config, and show this information if so. 1462 find_superset: True to look for configs which are a superset of those 1463 already found. So for example if CONFIG_EXYNOS5 implies an option, 1464 but CONFIG_EXYNOS covers a larger set of defconfigs and also 1465 implies that option, this will drop the former in favour of the 1466 latter. In practice this option has not proved very used. 1467 1468 Note the terminoloy: 1469 config - a CONFIG_XXX options (a string, e.g. 'CONFIG_CMD_EEPROM') 1470 defconfig - a defconfig file (a string, e.g. 'configs/snow_defconfig') 1471 """ 1472 kconf = KconfigScanner().conf if check_kconfig else None 1473 if add_imply and add_imply != 'all': 1474 add_imply = add_imply.split(',') 1475 1476 all_configs, all_defconfigs, config_db, defconfig_db = read_database() 1477 1478 # Work through each target config option in turn, independently 1479 for config in config_list: 1480 defconfigs = defconfig_db.get(config) 1481 if not defconfigs: 1482 print('%s not found in any defconfig' % config) 1483 continue 1484 1485 # Get the set of defconfigs without this one (since a config cannot 1486 # imply itself) 1487 non_defconfigs = all_defconfigs - defconfigs 1488 num_defconfigs = len(defconfigs) 1489 print('%s found in %d/%d defconfigs' % (config, num_defconfigs, 1490 len(all_configs))) 1491 1492 # This will hold the results: key=config, value=defconfigs containing it 1493 imply_configs = {} 1494 rest_configs = all_configs - set([config]) 1495 1496 # Look at every possible config, except the target one 1497 for imply_config in rest_configs: 1498 if 'ERRATUM' in imply_config: 1499 continue 1500 if not imply_flags & IMPLY_CMD: 1501 if 'CONFIG_CMD' in imply_config: 1502 continue 1503 if not imply_flags & IMPLY_TARGET: 1504 if 'CONFIG_TARGET' in imply_config: 1505 continue 1506 1507 # Find set of defconfigs that have this config 1508 imply_defconfig = defconfig_db[imply_config] 1509 1510 # Get the intersection of this with defconfigs containing the 1511 # target config 1512 common_defconfigs = imply_defconfig & defconfigs 1513 1514 # Get the set of defconfigs containing this config which DO NOT 1515 # also contain the taret config. If this set is non-empty it means 1516 # that this config affects other defconfigs as well as (possibly) 1517 # the ones affected by the target config. This means it implies 1518 # things we don't want to imply. 1519 not_common_defconfigs = imply_defconfig & non_defconfigs 1520 if not_common_defconfigs: 1521 continue 1522 1523 # If there are common defconfigs, imply_config may be useful 1524 if common_defconfigs: 1525 skip = False 1526 if find_superset: 1527 for prev in list(imply_configs.keys()): 1528 prev_count = len(imply_configs[prev]) 1529 count = len(common_defconfigs) 1530 if (prev_count > count and 1531 (imply_configs[prev] & common_defconfigs == 1532 common_defconfigs)): 1533 # skip imply_config because prev is a superset 1534 skip = True 1535 break 1536 elif count > prev_count: 1537 # delete prev because imply_config is a superset 1538 del imply_configs[prev] 1539 if not skip: 1540 imply_configs[imply_config] = common_defconfigs 1541 1542 # Now we have a dict imply_configs of configs which imply each config 1543 # The value of each dict item is the set of defconfigs containing that 1544 # config. Rank them so that we print the configs that imply the largest 1545 # number of defconfigs first. 1546 ranked_iconfigs = sorted(imply_configs, 1547 key=lambda k: len(imply_configs[k]), reverse=True) 1548 kconfig_info = '' 1549 cwd = os.getcwd() 1550 add_list = collections.defaultdict(list) 1551 for iconfig in ranked_iconfigs: 1552 num_common = len(imply_configs[iconfig]) 1553 1554 # Don't bother if there are less than 5 defconfigs affected. 1555 if num_common < (2 if imply_flags & IMPLY_MIN_2 else 5): 1556 continue 1557 missing = defconfigs - imply_configs[iconfig] 1558 missing_str = ', '.join(missing) if missing else 'all' 1559 missing_str = '' 1560 show = True 1561 if kconf: 1562 sym = find_kconfig_rules(kconf, config[CONFIG_LEN:], 1563 iconfig[CONFIG_LEN:]) 1564 kconfig_info = '' 1565 if sym: 1566 nodes = sym.nodes 1567 if len(nodes) == 1: 1568 fname, linenum = nodes[0].filename, nodes[0].linenr 1569 if cwd and fname.startswith(cwd): 1570 fname = fname[len(cwd) + 1:] 1571 kconfig_info = '%s:%d' % (fname, linenum) 1572 if skip_added: 1573 show = False 1574 else: 1575 sym = kconf.syms.get(iconfig[CONFIG_LEN:]) 1576 fname = '' 1577 if sym: 1578 nodes = sym.nodes 1579 if len(nodes) == 1: 1580 fname, linenum = nodes[0].filename, nodes[0].linenr 1581 if cwd and fname.startswith(cwd): 1582 fname = fname[len(cwd) + 1:] 1583 in_arch_board = not sym or (fname.startswith('arch') or 1584 fname.startswith('board')) 1585 if (not in_arch_board and 1586 not imply_flags & IMPLY_NON_ARCH_BOARD): 1587 continue 1588 1589 if add_imply and (add_imply == 'all' or 1590 iconfig in add_imply): 1591 fname, linenum, kconfig_info = (check_imply_rule(kconf, 1592 config[CONFIG_LEN:], iconfig[CONFIG_LEN:])) 1593 if fname: 1594 add_list[fname].append(linenum) 1595 1596 if show and kconfig_info != 'skip': 1597 print('%5d : %-30s%-25s %s' % (num_common, iconfig.ljust(30), 1598 kconfig_info, missing_str)) 1599 1600 # Having collected a list of things to add, now we add them. We process 1601 # each file from the largest line number to the smallest so that 1602 # earlier additions do not affect our line numbers. E.g. if we added an 1603 # imply at line 20 it would change the position of each line after 1604 # that. 1605 for fname, linenums in add_list.items(): 1606 for linenum in sorted(linenums, reverse=True): 1607 add_imply_rule(config[CONFIG_LEN:], fname, linenum) 1608 1609def defconfig_matches(configs, re_match): 1610 """Check if any CONFIG option matches a regex 1611 1612 The match must be complete, i.e. from the start to end of the CONFIG option. 1613 1614 Args: 1615 configs (dict): Dict of CONFIG options: 1616 key: CONFIG option 1617 value: Value of option 1618 re_match (re.Pattern): Match to check 1619 1620 Returns: 1621 bool: True if any CONFIG matches the regex 1622 """ 1623 for cfg in configs: 1624 m_cfg = re_match.match(cfg) 1625 if m_cfg and m_cfg.span()[1] == len(cfg): 1626 return True 1627 return False 1628 1629def do_find_config(config_list): 1630 """Find boards with a given combination of CONFIGs 1631 1632 Params: 1633 config_list: List of CONFIG options to check (each a regex consisting 1634 of a config option, with or without a CONFIG_ prefix. If an option 1635 is preceded by a tilde (~) then it must be false, otherwise it must 1636 be true) 1637 """ 1638 all_configs, all_defconfigs, config_db, defconfig_db = read_database() 1639 1640 # Get the whitelist 1641 adhoc_configs = set(read_file('scripts/config_whitelist.txt')) 1642 1643 # Start with all defconfigs 1644 out = all_defconfigs 1645 1646 # Work through each config in turn 1647 adhoc = [] 1648 for item in config_list: 1649 # Get the real config name and whether we want this config or not 1650 cfg = item 1651 want = True 1652 if cfg[0] == '~': 1653 want = False 1654 cfg = cfg[1:] 1655 1656 if cfg in adhoc_configs: 1657 adhoc.append(cfg) 1658 continue 1659 1660 # Search everything that is still in the running. If it has a config 1661 # that we want, or doesn't have one that we don't, add it into the 1662 # running for the next stage 1663 in_list = out 1664 out = set() 1665 re_match = re.compile(cfg) 1666 for defc in in_list: 1667 has_cfg = defconfig_matches(config_db[defc], re_match) 1668 if has_cfg == want: 1669 out.add(defc) 1670 if adhoc: 1671 print(f"Error: Not in Kconfig: %s" % ' '.join(adhoc)) 1672 else: 1673 print(f'{len(out)} matches') 1674 print(' '.join([remove_defconfig(item) for item in out])) 1675 1676 1677def prefix_config(cfg): 1678 """Prefix a config with CONFIG_ if needed 1679 1680 This handles ~ operator, which indicates that the CONFIG should be disabled 1681 1682 >>> prefix_config('FRED') 1683 'CONFIG_FRED' 1684 >>> prefix_config('CONFIG_FRED') 1685 'CONFIG_FRED' 1686 >>> prefix_config('~FRED') 1687 '~CONFIG_FRED' 1688 >>> prefix_config('~CONFIG_FRED') 1689 '~CONFIG_FRED' 1690 >>> prefix_config('A123') 1691 'CONFIG_A123' 1692 """ 1693 op = '' 1694 if cfg[0] == '~': 1695 op = cfg[0] 1696 cfg = cfg[1:] 1697 if not cfg.startswith('CONFIG_'): 1698 cfg = 'CONFIG_' + cfg 1699 return op + cfg 1700 1701 1702def main(): 1703 try: 1704 cpu_count = multiprocessing.cpu_count() 1705 except NotImplementedError: 1706 cpu_count = 1 1707 1708 epilog = '''Move config options from headers to defconfig files. See 1709doc/develop/moveconfig.rst for documentation.''' 1710 1711 parser = ArgumentParser(epilog=epilog) 1712 # Add arguments here 1713 parser.add_argument('-a', '--add-imply', type=str, default='', 1714 help='comma-separated list of CONFIG options to add ' 1715 "an 'imply' statement to for the CONFIG in -i") 1716 parser.add_argument('-A', '--skip-added', action='store_true', default=False, 1717 help="don't show options which are already marked as " 1718 'implying others') 1719 parser.add_argument('-b', '--build-db', action='store_true', default=False, 1720 help='build a CONFIG database') 1721 parser.add_argument('-c', '--color', action='store_true', default=False, 1722 help='display the log in color') 1723 parser.add_argument('-C', '--commit', action='store_true', default=False, 1724 help='Create a git commit for the operation') 1725 parser.add_argument('-d', '--defconfigs', type=str, 1726 help='a file containing a list of defconfigs to move, ' 1727 "one per line (for example 'snow_defconfig') " 1728 "or '-' to read from stdin") 1729 parser.add_argument('-e', '--exit-on-error', action='store_true', 1730 default=False, 1731 help='exit immediately on any error') 1732 parser.add_argument('-f', '--find', action='store_true', default=False, 1733 help='Find boards with a given config combination') 1734 parser.add_argument('-H', '--headers-only', dest='cleanup_headers_only', 1735 action='store_true', default=False, 1736 help='only cleanup the headers') 1737 parser.add_argument('-i', '--imply', action='store_true', default=False, 1738 help='find options which imply others') 1739 parser.add_argument('-I', '--imply-flags', type=str, default='', 1740 help="control the -i option ('help' for help") 1741 parser.add_argument('-j', '--jobs', type=int, default=cpu_count, 1742 help='the number of jobs to run simultaneously') 1743 parser.add_argument('-n', '--dry-run', action='store_true', default=False, 1744 help='perform a trial run (show log with no changes)') 1745 parser.add_argument('-r', '--git-ref', type=str, 1746 help='the git ref to clone for building the autoconf.mk') 1747 parser.add_argument('-s', '--force-sync', action='store_true', default=False, 1748 help='force sync by savedefconfig') 1749 parser.add_argument('-S', '--spl', action='store_true', default=False, 1750 help='parse config options defined for SPL build') 1751 parser.add_argument('-t', '--test', action='store_true', default=False, 1752 help='run unit tests') 1753 parser.add_argument('-y', '--yes', action='store_true', default=False, 1754 help="respond 'yes' to any prompts") 1755 parser.add_argument('-v', '--verbose', action='store_true', default=False, 1756 help='show any build errors as boards are built') 1757 parser.add_argument('configs', nargs='*') 1758 1759 args = parser.parse_args() 1760 configs = args.configs 1761 1762 if args.test: 1763 sys.argv = [sys.argv[0]] 1764 fail, count = doctest.testmod() 1765 if fail: 1766 return 1 1767 unittest.main() 1768 1769 if not any((len(configs), args.force_sync, args.build_db, args.imply, 1770 args.find)): 1771 parser.print_usage() 1772 sys.exit(1) 1773 1774 # prefix the option name with CONFIG_ if missing 1775 configs = [prefix_config(cfg) for cfg in configs] 1776 1777 check_top_directory() 1778 1779 if args.imply: 1780 imply_flags = 0 1781 if args.imply_flags == 'all': 1782 imply_flags = -1 1783 1784 elif args.imply_flags: 1785 for flag in args.imply_flags.split(','): 1786 bad = flag not in IMPLY_FLAGS 1787 if bad: 1788 print("Invalid flag '%s'" % flag) 1789 if flag == 'help' or bad: 1790 print("Imply flags: (separate with ',')") 1791 for name, info in IMPLY_FLAGS.items(): 1792 print(' %-15s: %s' % (name, info[1])) 1793 parser.print_usage() 1794 sys.exit(1) 1795 imply_flags |= IMPLY_FLAGS[flag][0] 1796 1797 do_imply_config(configs, args.add_imply, imply_flags, args.skip_added) 1798 return 1799 1800 if args.find: 1801 do_find_config(configs) 1802 return 1803 1804 config_db = {} 1805 db_queue = queue.Queue() 1806 t = DatabaseThread(config_db, db_queue) 1807 t.setDaemon(True) 1808 t.start() 1809 1810 if not args.cleanup_headers_only: 1811 check_clean_directory() 1812 bsettings.Setup('') 1813 toolchains = toolchain.Toolchains() 1814 toolchains.GetSettings() 1815 toolchains.Scan(verbose=False) 1816 move_config(toolchains, configs, args, db_queue) 1817 db_queue.join() 1818 1819 if configs: 1820 cleanup_headers(configs, args) 1821 cleanup_extra_options(configs, args) 1822 cleanup_whitelist(configs, args) 1823 cleanup_readme(configs, args) 1824 1825 if args.commit: 1826 subprocess.call(['git', 'add', '-u']) 1827 if configs: 1828 msg = 'Convert %s %sto Kconfig' % (configs[0], 1829 'et al ' if len(configs) > 1 else '') 1830 msg += ('\n\nThis converts the following to Kconfig:\n %s\n' % 1831 '\n '.join(configs)) 1832 else: 1833 msg = 'configs: Resync with savedefconfig' 1834 msg += '\n\nRsync all defconfig files using moveconfig.py' 1835 subprocess.call(['git', 'commit', '-s', '-m', msg]) 1836 1837 if args.build_db: 1838 with open(CONFIG_DATABASE, 'w', encoding='utf-8') as fd: 1839 for defconfig, configs in config_db.items(): 1840 fd.write('%s\n' % defconfig) 1841 for config in sorted(configs.keys()): 1842 fd.write(' %s=%s\n' % (config, configs[config])) 1843 fd.write('\n') 1844 1845if __name__ == '__main__': 1846 sys.exit(main()) 1847

