18c2ecf20Sopenharmony_ci#!/usr/bin/env python3 28c2ecf20Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0-only 38c2ecf20Sopenharmony_ci 48c2ecf20Sopenharmony_ci"""Find Kconfig symbols that are referenced but not defined.""" 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci# (c) 2014-2017 Valentin Rothberg <valentinrothberg@gmail.com> 78c2ecf20Sopenharmony_ci# (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de> 88c2ecf20Sopenharmony_ci# 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ciimport argparse 128c2ecf20Sopenharmony_ciimport difflib 138c2ecf20Sopenharmony_ciimport os 148c2ecf20Sopenharmony_ciimport re 158c2ecf20Sopenharmony_ciimport signal 168c2ecf20Sopenharmony_ciimport subprocess 178c2ecf20Sopenharmony_ciimport sys 188c2ecf20Sopenharmony_cifrom multiprocessing import Pool, cpu_count 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci# regex expressions 228c2ecf20Sopenharmony_ciOPERATORS = r"&|\(|\)|\||\!" 238c2ecf20Sopenharmony_ciSYMBOL = r"(?:\w*[A-Z0-9]\w*){2,}" 248c2ecf20Sopenharmony_ciDEF = r"^\s*(?:menu){,1}config\s+(" + SYMBOL + r")\s*" 258c2ecf20Sopenharmony_ciEXPR = r"(?:" + OPERATORS + r"|\s|" + SYMBOL + r")+" 268c2ecf20Sopenharmony_ciDEFAULT = r"default\s+.*?(?:if\s.+){,1}" 278c2ecf20Sopenharmony_ciSTMT = r"^\s*(?:if|select|imply|depends\s+on|(?:" + DEFAULT + r"))\s+" + EXPR 288c2ecf20Sopenharmony_ciSOURCE_SYMBOL = r"(?:\W|\b)+[D]{,1}CONFIG_(" + SYMBOL + r")" 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci# regex objects 318c2ecf20Sopenharmony_ciREGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$") 328c2ecf20Sopenharmony_ciREGEX_SYMBOL = re.compile(r'(?!\B)' + SYMBOL + r'(?!\B)') 338c2ecf20Sopenharmony_ciREGEX_SOURCE_SYMBOL = re.compile(SOURCE_SYMBOL) 348c2ecf20Sopenharmony_ciREGEX_KCONFIG_DEF = re.compile(DEF) 358c2ecf20Sopenharmony_ciREGEX_KCONFIG_EXPR = re.compile(EXPR) 368c2ecf20Sopenharmony_ciREGEX_KCONFIG_STMT = re.compile(STMT) 378c2ecf20Sopenharmony_ciREGEX_KCONFIG_HELP = re.compile(r"^\s+help\s*$") 388c2ecf20Sopenharmony_ciREGEX_FILTER_SYMBOLS = re.compile(r"[A-Za-z0-9]$") 398c2ecf20Sopenharmony_ciREGEX_NUMERIC = re.compile(r"0[xX][0-9a-fA-F]+|[0-9]+") 408c2ecf20Sopenharmony_ciREGEX_QUOTES = re.compile("(\"(.*?)\")") 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cidef parse_options(): 448c2ecf20Sopenharmony_ci """The user interface of this module.""" 458c2ecf20Sopenharmony_ci usage = "Run this tool to detect Kconfig symbols that are referenced but " \ 468c2ecf20Sopenharmony_ci "not defined in Kconfig. If no option is specified, " \ 478c2ecf20Sopenharmony_ci "checkkconfigsymbols defaults to check your current tree. " \ 488c2ecf20Sopenharmony_ci "Please note that specifying commits will 'git reset --hard\' " \ 498c2ecf20Sopenharmony_ci "your current tree! You may save uncommitted changes to avoid " \ 508c2ecf20Sopenharmony_ci "losing data." 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci parser = argparse.ArgumentParser(description=usage) 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci parser.add_argument('-c', '--commit', dest='commit', action='store', 558c2ecf20Sopenharmony_ci default="", 568c2ecf20Sopenharmony_ci help="check if the specified commit (hash) introduces " 578c2ecf20Sopenharmony_ci "undefined Kconfig symbols") 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci parser.add_argument('-d', '--diff', dest='diff', action='store', 608c2ecf20Sopenharmony_ci default="", 618c2ecf20Sopenharmony_ci help="diff undefined symbols between two commits " 628c2ecf20Sopenharmony_ci "(e.g., -d commmit1..commit2)") 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci parser.add_argument('-f', '--find', dest='find', action='store_true', 658c2ecf20Sopenharmony_ci default=False, 668c2ecf20Sopenharmony_ci help="find and show commits that may cause symbols to be " 678c2ecf20Sopenharmony_ci "missing (required to run with --diff)") 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci parser.add_argument('-i', '--ignore', dest='ignore', action='store', 708c2ecf20Sopenharmony_ci default="", 718c2ecf20Sopenharmony_ci help="ignore files matching this Python regex " 728c2ecf20Sopenharmony_ci "(e.g., -i '.*defconfig')") 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci parser.add_argument('-s', '--sim', dest='sim', action='store', default="", 758c2ecf20Sopenharmony_ci help="print a list of max. 10 string-similar symbols") 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci parser.add_argument('--force', dest='force', action='store_true', 788c2ecf20Sopenharmony_ci default=False, 798c2ecf20Sopenharmony_ci help="reset current Git tree even when it's dirty") 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci parser.add_argument('--no-color', dest='color', action='store_false', 828c2ecf20Sopenharmony_ci default=True, 838c2ecf20Sopenharmony_ci help="don't print colored output (default when not " 848c2ecf20Sopenharmony_ci "outputting to a terminal)") 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci args = parser.parse_args() 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci if args.commit and args.diff: 898c2ecf20Sopenharmony_ci sys.exit("Please specify only one option at once.") 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci if args.diff and not re.match(r"^[\w\-\.\^]+\.\.[\w\-\.\^]+$", args.diff): 928c2ecf20Sopenharmony_ci sys.exit("Please specify valid input in the following format: " 938c2ecf20Sopenharmony_ci "\'commit1..commit2\'") 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci if args.commit or args.diff: 968c2ecf20Sopenharmony_ci if not args.force and tree_is_dirty(): 978c2ecf20Sopenharmony_ci sys.exit("The current Git tree is dirty (see 'git status'). " 988c2ecf20Sopenharmony_ci "Running this script may\ndelete important data since it " 998c2ecf20Sopenharmony_ci "calls 'git reset --hard' for some performance\nreasons. " 1008c2ecf20Sopenharmony_ci " Please run this script in a clean Git tree or pass " 1018c2ecf20Sopenharmony_ci "'--force' if you\nwant to ignore this warning and " 1028c2ecf20Sopenharmony_ci "continue.") 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci if args.commit: 1058c2ecf20Sopenharmony_ci args.find = False 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci if args.ignore: 1088c2ecf20Sopenharmony_ci try: 1098c2ecf20Sopenharmony_ci re.match(args.ignore, "this/is/just/a/test.c") 1108c2ecf20Sopenharmony_ci except: 1118c2ecf20Sopenharmony_ci sys.exit("Please specify a valid Python regex.") 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci return args 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_cidef print_undefined_symbols(): 1178c2ecf20Sopenharmony_ci """Main function of this module.""" 1188c2ecf20Sopenharmony_ci args = parse_options() 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci global COLOR 1218c2ecf20Sopenharmony_ci COLOR = args.color and sys.stdout.isatty() 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci if args.sim and not args.commit and not args.diff: 1248c2ecf20Sopenharmony_ci sims = find_sims(args.sim, args.ignore) 1258c2ecf20Sopenharmony_ci if sims: 1268c2ecf20Sopenharmony_ci print("%s: %s" % (yel("Similar symbols"), ', '.join(sims))) 1278c2ecf20Sopenharmony_ci else: 1288c2ecf20Sopenharmony_ci print("%s: no similar symbols found" % yel("Similar symbols")) 1298c2ecf20Sopenharmony_ci sys.exit(0) 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci # dictionary of (un)defined symbols 1328c2ecf20Sopenharmony_ci defined = {} 1338c2ecf20Sopenharmony_ci undefined = {} 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci if args.commit or args.diff: 1368c2ecf20Sopenharmony_ci head = get_head() 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci # get commit range 1398c2ecf20Sopenharmony_ci commit_a = None 1408c2ecf20Sopenharmony_ci commit_b = None 1418c2ecf20Sopenharmony_ci if args.commit: 1428c2ecf20Sopenharmony_ci commit_a = args.commit + "~" 1438c2ecf20Sopenharmony_ci commit_b = args.commit 1448c2ecf20Sopenharmony_ci elif args.diff: 1458c2ecf20Sopenharmony_ci split = args.diff.split("..") 1468c2ecf20Sopenharmony_ci commit_a = split[0] 1478c2ecf20Sopenharmony_ci commit_b = split[1] 1488c2ecf20Sopenharmony_ci undefined_a = {} 1498c2ecf20Sopenharmony_ci undefined_b = {} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci # get undefined items before the commit 1528c2ecf20Sopenharmony_ci reset(commit_a) 1538c2ecf20Sopenharmony_ci undefined_a, _ = check_symbols(args.ignore) 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci # get undefined items for the commit 1568c2ecf20Sopenharmony_ci reset(commit_b) 1578c2ecf20Sopenharmony_ci undefined_b, defined = check_symbols(args.ignore) 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci # report cases that are present for the commit but not before 1608c2ecf20Sopenharmony_ci for symbol in sorted(undefined_b): 1618c2ecf20Sopenharmony_ci # symbol has not been undefined before 1628c2ecf20Sopenharmony_ci if symbol not in undefined_a: 1638c2ecf20Sopenharmony_ci files = sorted(undefined_b.get(symbol)) 1648c2ecf20Sopenharmony_ci undefined[symbol] = files 1658c2ecf20Sopenharmony_ci # check if there are new files that reference the undefined symbol 1668c2ecf20Sopenharmony_ci else: 1678c2ecf20Sopenharmony_ci files = sorted(undefined_b.get(symbol) - 1688c2ecf20Sopenharmony_ci undefined_a.get(symbol)) 1698c2ecf20Sopenharmony_ci if files: 1708c2ecf20Sopenharmony_ci undefined[symbol] = files 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci # reset to head 1738c2ecf20Sopenharmony_ci reset(head) 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci # default to check the entire tree 1768c2ecf20Sopenharmony_ci else: 1778c2ecf20Sopenharmony_ci undefined, defined = check_symbols(args.ignore) 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci # now print the output 1808c2ecf20Sopenharmony_ci for symbol in sorted(undefined): 1818c2ecf20Sopenharmony_ci print(red(symbol)) 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci files = sorted(undefined.get(symbol)) 1848c2ecf20Sopenharmony_ci print("%s: %s" % (yel("Referencing files"), ", ".join(files))) 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci sims = find_sims(symbol, args.ignore, defined) 1878c2ecf20Sopenharmony_ci sims_out = yel("Similar symbols") 1888c2ecf20Sopenharmony_ci if sims: 1898c2ecf20Sopenharmony_ci print("%s: %s" % (sims_out, ', '.join(sims))) 1908c2ecf20Sopenharmony_ci else: 1918c2ecf20Sopenharmony_ci print("%s: %s" % (sims_out, "no similar symbols found")) 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if args.find: 1948c2ecf20Sopenharmony_ci print("%s:" % yel("Commits changing symbol")) 1958c2ecf20Sopenharmony_ci commits = find_commits(symbol, args.diff) 1968c2ecf20Sopenharmony_ci if commits: 1978c2ecf20Sopenharmony_ci for commit in commits: 1988c2ecf20Sopenharmony_ci commit = commit.split(" ", 1) 1998c2ecf20Sopenharmony_ci print("\t- %s (\"%s\")" % (yel(commit[0]), commit[1])) 2008c2ecf20Sopenharmony_ci else: 2018c2ecf20Sopenharmony_ci print("\t- no commit found") 2028c2ecf20Sopenharmony_ci print() # new line 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cidef reset(commit): 2068c2ecf20Sopenharmony_ci """Reset current git tree to %commit.""" 2078c2ecf20Sopenharmony_ci execute(["git", "reset", "--hard", commit]) 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_cidef yel(string): 2118c2ecf20Sopenharmony_ci """ 2128c2ecf20Sopenharmony_ci Color %string yellow. 2138c2ecf20Sopenharmony_ci """ 2148c2ecf20Sopenharmony_ci return "\033[33m%s\033[0m" % string if COLOR else string 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_cidef red(string): 2188c2ecf20Sopenharmony_ci """ 2198c2ecf20Sopenharmony_ci Color %string red. 2208c2ecf20Sopenharmony_ci """ 2218c2ecf20Sopenharmony_ci return "\033[31m%s\033[0m" % string if COLOR else string 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_cidef execute(cmd): 2258c2ecf20Sopenharmony_ci """Execute %cmd and return stdout. Exit in case of error.""" 2268c2ecf20Sopenharmony_ci try: 2278c2ecf20Sopenharmony_ci stdout = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=False) 2288c2ecf20Sopenharmony_ci stdout = stdout.decode(errors='replace') 2298c2ecf20Sopenharmony_ci except subprocess.CalledProcessError as fail: 2308c2ecf20Sopenharmony_ci exit(fail) 2318c2ecf20Sopenharmony_ci return stdout 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_cidef find_commits(symbol, diff): 2358c2ecf20Sopenharmony_ci """Find commits changing %symbol in the given range of %diff.""" 2368c2ecf20Sopenharmony_ci commits = execute(["git", "log", "--pretty=oneline", 2378c2ecf20Sopenharmony_ci "--abbrev-commit", "-G", 2388c2ecf20Sopenharmony_ci symbol, diff]) 2398c2ecf20Sopenharmony_ci return [x for x in commits.split("\n") if x] 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_cidef tree_is_dirty(): 2438c2ecf20Sopenharmony_ci """Return true if the current working tree is dirty (i.e., if any file has 2448c2ecf20Sopenharmony_ci been added, deleted, modified, renamed or copied but not committed).""" 2458c2ecf20Sopenharmony_ci stdout = execute(["git", "status", "--porcelain"]) 2468c2ecf20Sopenharmony_ci for line in stdout: 2478c2ecf20Sopenharmony_ci if re.findall(r"[URMADC]{1}", line[:2]): 2488c2ecf20Sopenharmony_ci return True 2498c2ecf20Sopenharmony_ci return False 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_cidef get_head(): 2538c2ecf20Sopenharmony_ci """Return commit hash of current HEAD.""" 2548c2ecf20Sopenharmony_ci stdout = execute(["git", "rev-parse", "HEAD"]) 2558c2ecf20Sopenharmony_ci return stdout.strip('\n') 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_cidef partition(lst, size): 2598c2ecf20Sopenharmony_ci """Partition list @lst into eveni-sized lists of size @size.""" 2608c2ecf20Sopenharmony_ci return [lst[i::size] for i in range(size)] 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_cidef init_worker(): 2648c2ecf20Sopenharmony_ci """Set signal handler to ignore SIGINT.""" 2658c2ecf20Sopenharmony_ci signal.signal(signal.SIGINT, signal.SIG_IGN) 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_cidef find_sims(symbol, ignore, defined=[]): 2698c2ecf20Sopenharmony_ci """Return a list of max. ten Kconfig symbols that are string-similar to 2708c2ecf20Sopenharmony_ci @symbol.""" 2718c2ecf20Sopenharmony_ci if defined: 2728c2ecf20Sopenharmony_ci return difflib.get_close_matches(symbol, set(defined), 10) 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci pool = Pool(cpu_count(), init_worker) 2758c2ecf20Sopenharmony_ci kfiles = [] 2768c2ecf20Sopenharmony_ci for gitfile in get_files(): 2778c2ecf20Sopenharmony_ci if REGEX_FILE_KCONFIG.match(gitfile): 2788c2ecf20Sopenharmony_ci kfiles.append(gitfile) 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci arglist = [] 2818c2ecf20Sopenharmony_ci for part in partition(kfiles, cpu_count()): 2828c2ecf20Sopenharmony_ci arglist.append((part, ignore)) 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci for res in pool.map(parse_kconfig_files, arglist): 2858c2ecf20Sopenharmony_ci defined.extend(res[0]) 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci return difflib.get_close_matches(symbol, set(defined), 10) 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_cidef get_files(): 2918c2ecf20Sopenharmony_ci """Return a list of all files in the current git directory.""" 2928c2ecf20Sopenharmony_ci # use 'git ls-files' to get the worklist 2938c2ecf20Sopenharmony_ci stdout = execute(["git", "ls-files"]) 2948c2ecf20Sopenharmony_ci if len(stdout) > 0 and stdout[-1] == "\n": 2958c2ecf20Sopenharmony_ci stdout = stdout[:-1] 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci files = [] 2988c2ecf20Sopenharmony_ci for gitfile in stdout.rsplit("\n"): 2998c2ecf20Sopenharmony_ci if ".git" in gitfile or "ChangeLog" in gitfile or \ 3008c2ecf20Sopenharmony_ci ".log" in gitfile or os.path.isdir(gitfile) or \ 3018c2ecf20Sopenharmony_ci gitfile.startswith("tools/"): 3028c2ecf20Sopenharmony_ci continue 3038c2ecf20Sopenharmony_ci files.append(gitfile) 3048c2ecf20Sopenharmony_ci return files 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_cidef check_symbols(ignore): 3088c2ecf20Sopenharmony_ci """Find undefined Kconfig symbols and return a dict with the symbol as key 3098c2ecf20Sopenharmony_ci and a list of referencing files as value. Files matching %ignore are not 3108c2ecf20Sopenharmony_ci checked for undefined symbols.""" 3118c2ecf20Sopenharmony_ci pool = Pool(cpu_count(), init_worker) 3128c2ecf20Sopenharmony_ci try: 3138c2ecf20Sopenharmony_ci return check_symbols_helper(pool, ignore) 3148c2ecf20Sopenharmony_ci except KeyboardInterrupt: 3158c2ecf20Sopenharmony_ci pool.terminate() 3168c2ecf20Sopenharmony_ci pool.join() 3178c2ecf20Sopenharmony_ci sys.exit(1) 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_cidef check_symbols_helper(pool, ignore): 3218c2ecf20Sopenharmony_ci """Helper method for check_symbols(). Used to catch keyboard interrupts in 3228c2ecf20Sopenharmony_ci check_symbols() in order to properly terminate running worker processes.""" 3238c2ecf20Sopenharmony_ci source_files = [] 3248c2ecf20Sopenharmony_ci kconfig_files = [] 3258c2ecf20Sopenharmony_ci defined_symbols = [] 3268c2ecf20Sopenharmony_ci referenced_symbols = dict() # {file: [symbols]} 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci for gitfile in get_files(): 3298c2ecf20Sopenharmony_ci if REGEX_FILE_KCONFIG.match(gitfile): 3308c2ecf20Sopenharmony_ci kconfig_files.append(gitfile) 3318c2ecf20Sopenharmony_ci else: 3328c2ecf20Sopenharmony_ci if ignore and not re.match(ignore, gitfile): 3338c2ecf20Sopenharmony_ci continue 3348c2ecf20Sopenharmony_ci # add source files that do not match the ignore pattern 3358c2ecf20Sopenharmony_ci source_files.append(gitfile) 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci # parse source files 3388c2ecf20Sopenharmony_ci arglist = partition(source_files, cpu_count()) 3398c2ecf20Sopenharmony_ci for res in pool.map(parse_source_files, arglist): 3408c2ecf20Sopenharmony_ci referenced_symbols.update(res) 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci # parse kconfig files 3438c2ecf20Sopenharmony_ci arglist = [] 3448c2ecf20Sopenharmony_ci for part in partition(kconfig_files, cpu_count()): 3458c2ecf20Sopenharmony_ci arglist.append((part, ignore)) 3468c2ecf20Sopenharmony_ci for res in pool.map(parse_kconfig_files, arglist): 3478c2ecf20Sopenharmony_ci defined_symbols.extend(res[0]) 3488c2ecf20Sopenharmony_ci referenced_symbols.update(res[1]) 3498c2ecf20Sopenharmony_ci defined_symbols = set(defined_symbols) 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci # inverse mapping of referenced_symbols to dict(symbol: [files]) 3528c2ecf20Sopenharmony_ci inv_map = dict() 3538c2ecf20Sopenharmony_ci for _file, symbols in referenced_symbols.items(): 3548c2ecf20Sopenharmony_ci for symbol in symbols: 3558c2ecf20Sopenharmony_ci inv_map[symbol] = inv_map.get(symbol, set()) 3568c2ecf20Sopenharmony_ci inv_map[symbol].add(_file) 3578c2ecf20Sopenharmony_ci referenced_symbols = inv_map 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci undefined = {} # {symbol: [files]} 3608c2ecf20Sopenharmony_ci for symbol in sorted(referenced_symbols): 3618c2ecf20Sopenharmony_ci # filter some false positives 3628c2ecf20Sopenharmony_ci if symbol == "FOO" or symbol == "BAR" or \ 3638c2ecf20Sopenharmony_ci symbol == "FOO_BAR" or symbol == "XXX": 3648c2ecf20Sopenharmony_ci continue 3658c2ecf20Sopenharmony_ci if symbol not in defined_symbols: 3668c2ecf20Sopenharmony_ci if symbol.endswith("_MODULE"): 3678c2ecf20Sopenharmony_ci # avoid false positives for kernel modules 3688c2ecf20Sopenharmony_ci if symbol[:-len("_MODULE")] in defined_symbols: 3698c2ecf20Sopenharmony_ci continue 3708c2ecf20Sopenharmony_ci undefined[symbol] = referenced_symbols.get(symbol) 3718c2ecf20Sopenharmony_ci return undefined, defined_symbols 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_cidef parse_source_files(source_files): 3758c2ecf20Sopenharmony_ci """Parse each source file in @source_files and return dictionary with source 3768c2ecf20Sopenharmony_ci files as keys and lists of references Kconfig symbols as values.""" 3778c2ecf20Sopenharmony_ci referenced_symbols = dict() 3788c2ecf20Sopenharmony_ci for sfile in source_files: 3798c2ecf20Sopenharmony_ci referenced_symbols[sfile] = parse_source_file(sfile) 3808c2ecf20Sopenharmony_ci return referenced_symbols 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_cidef parse_source_file(sfile): 3848c2ecf20Sopenharmony_ci """Parse @sfile and return a list of referenced Kconfig symbols.""" 3858c2ecf20Sopenharmony_ci lines = [] 3868c2ecf20Sopenharmony_ci references = [] 3878c2ecf20Sopenharmony_ci 3888c2ecf20Sopenharmony_ci if not os.path.exists(sfile): 3898c2ecf20Sopenharmony_ci return references 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci with open(sfile, "r", encoding='utf-8', errors='replace') as stream: 3928c2ecf20Sopenharmony_ci lines = stream.readlines() 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_ci for line in lines: 3958c2ecf20Sopenharmony_ci if "CONFIG_" not in line: 3968c2ecf20Sopenharmony_ci continue 3978c2ecf20Sopenharmony_ci symbols = REGEX_SOURCE_SYMBOL.findall(line) 3988c2ecf20Sopenharmony_ci for symbol in symbols: 3998c2ecf20Sopenharmony_ci if not REGEX_FILTER_SYMBOLS.search(symbol): 4008c2ecf20Sopenharmony_ci continue 4018c2ecf20Sopenharmony_ci references.append(symbol) 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_ci return references 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_cidef get_symbols_in_line(line): 4078c2ecf20Sopenharmony_ci """Return mentioned Kconfig symbols in @line.""" 4088c2ecf20Sopenharmony_ci return REGEX_SYMBOL.findall(line) 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_cidef parse_kconfig_files(args): 4128c2ecf20Sopenharmony_ci """Parse kconfig files and return tuple of defined and references Kconfig 4138c2ecf20Sopenharmony_ci symbols. Note, @args is a tuple of a list of files and the @ignore 4148c2ecf20Sopenharmony_ci pattern.""" 4158c2ecf20Sopenharmony_ci kconfig_files = args[0] 4168c2ecf20Sopenharmony_ci ignore = args[1] 4178c2ecf20Sopenharmony_ci defined_symbols = [] 4188c2ecf20Sopenharmony_ci referenced_symbols = dict() 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_ci for kfile in kconfig_files: 4218c2ecf20Sopenharmony_ci defined, references = parse_kconfig_file(kfile) 4228c2ecf20Sopenharmony_ci defined_symbols.extend(defined) 4238c2ecf20Sopenharmony_ci if ignore and re.match(ignore, kfile): 4248c2ecf20Sopenharmony_ci # do not collect references for files that match the ignore pattern 4258c2ecf20Sopenharmony_ci continue 4268c2ecf20Sopenharmony_ci referenced_symbols[kfile] = references 4278c2ecf20Sopenharmony_ci return (defined_symbols, referenced_symbols) 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_ci 4308c2ecf20Sopenharmony_cidef parse_kconfig_file(kfile): 4318c2ecf20Sopenharmony_ci """Parse @kfile and update symbol definitions and references.""" 4328c2ecf20Sopenharmony_ci lines = [] 4338c2ecf20Sopenharmony_ci defined = [] 4348c2ecf20Sopenharmony_ci references = [] 4358c2ecf20Sopenharmony_ci skip = False 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_ci if not os.path.exists(kfile): 4388c2ecf20Sopenharmony_ci return defined, references 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci with open(kfile, "r", encoding='utf-8', errors='replace') as stream: 4418c2ecf20Sopenharmony_ci lines = stream.readlines() 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci for i in range(len(lines)): 4448c2ecf20Sopenharmony_ci line = lines[i] 4458c2ecf20Sopenharmony_ci line = line.strip('\n') 4468c2ecf20Sopenharmony_ci line = line.split("#")[0] # ignore comments 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci if REGEX_KCONFIG_DEF.match(line): 4498c2ecf20Sopenharmony_ci symbol_def = REGEX_KCONFIG_DEF.findall(line) 4508c2ecf20Sopenharmony_ci defined.append(symbol_def[0]) 4518c2ecf20Sopenharmony_ci skip = False 4528c2ecf20Sopenharmony_ci elif REGEX_KCONFIG_HELP.match(line): 4538c2ecf20Sopenharmony_ci skip = True 4548c2ecf20Sopenharmony_ci elif skip: 4558c2ecf20Sopenharmony_ci # ignore content of help messages 4568c2ecf20Sopenharmony_ci pass 4578c2ecf20Sopenharmony_ci elif REGEX_KCONFIG_STMT.match(line): 4588c2ecf20Sopenharmony_ci line = REGEX_QUOTES.sub("", line) 4598c2ecf20Sopenharmony_ci symbols = get_symbols_in_line(line) 4608c2ecf20Sopenharmony_ci # multi-line statements 4618c2ecf20Sopenharmony_ci while line.endswith("\\"): 4628c2ecf20Sopenharmony_ci i += 1 4638c2ecf20Sopenharmony_ci line = lines[i] 4648c2ecf20Sopenharmony_ci line = line.strip('\n') 4658c2ecf20Sopenharmony_ci symbols.extend(get_symbols_in_line(line)) 4668c2ecf20Sopenharmony_ci for symbol in set(symbols): 4678c2ecf20Sopenharmony_ci if REGEX_NUMERIC.match(symbol): 4688c2ecf20Sopenharmony_ci # ignore numeric values 4698c2ecf20Sopenharmony_ci continue 4708c2ecf20Sopenharmony_ci references.append(symbol) 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci return defined, references 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_cidef main(): 4768c2ecf20Sopenharmony_ci try: 4778c2ecf20Sopenharmony_ci print_undefined_symbols() 4788c2ecf20Sopenharmony_ci except BrokenPipeError: 4798c2ecf20Sopenharmony_ci # Python flushes standard streams on exit; redirect remaining output 4808c2ecf20Sopenharmony_ci # to devnull to avoid another BrokenPipeError at shutdown 4818c2ecf20Sopenharmony_ci devnull = os.open(os.devnull, os.O_WRONLY) 4828c2ecf20Sopenharmony_ci os.dup2(devnull, sys.stdout.fileno()) 4838c2ecf20Sopenharmony_ci sys.exit(1) # Python exits with error code 1 on EPIPE 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ciif __name__ == "__main__": 4878c2ecf20Sopenharmony_ci main() 488