162306a36Sopenharmony_ci#!/usr/bin/env python3 262306a36Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0-only 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci"""Find Kconfig symbols that are referenced but not defined.""" 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci# (c) 2014-2017 Valentin Rothberg <valentinrothberg@gmail.com> 762306a36Sopenharmony_ci# (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de> 862306a36Sopenharmony_ci# 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ciimport argparse 1262306a36Sopenharmony_ciimport difflib 1362306a36Sopenharmony_ciimport os 1462306a36Sopenharmony_ciimport re 1562306a36Sopenharmony_ciimport signal 1662306a36Sopenharmony_ciimport subprocess 1762306a36Sopenharmony_ciimport sys 1862306a36Sopenharmony_cifrom multiprocessing import Pool, cpu_count 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci# regex expressions 2262306a36Sopenharmony_ciOPERATORS = r"&|\(|\)|\||\!" 2362306a36Sopenharmony_ciSYMBOL = r"(?:\w*[A-Z0-9]\w*){2,}" 2462306a36Sopenharmony_ciDEF = r"^\s*(?:menu){,1}config\s+(" + SYMBOL + r")\s*" 2562306a36Sopenharmony_ciEXPR = r"(?:" + OPERATORS + r"|\s|" + SYMBOL + r")+" 2662306a36Sopenharmony_ciDEFAULT = r"default\s+.*?(?:if\s.+){,1}" 2762306a36Sopenharmony_ciSTMT = r"^\s*(?:if|select|imply|depends\s+on|(?:" + DEFAULT + r"))\s+" + EXPR 2862306a36Sopenharmony_ciSOURCE_SYMBOL = r"(?:\W|\b)+[D]{,1}CONFIG_(" + SYMBOL + r")" 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci# regex objects 3162306a36Sopenharmony_ciREGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$") 3262306a36Sopenharmony_ciREGEX_SYMBOL = re.compile(r'(?!\B)' + SYMBOL + r'(?!\B)') 3362306a36Sopenharmony_ciREGEX_SOURCE_SYMBOL = re.compile(SOURCE_SYMBOL) 3462306a36Sopenharmony_ciREGEX_KCONFIG_DEF = re.compile(DEF) 3562306a36Sopenharmony_ciREGEX_KCONFIG_EXPR = re.compile(EXPR) 3662306a36Sopenharmony_ciREGEX_KCONFIG_STMT = re.compile(STMT) 3762306a36Sopenharmony_ciREGEX_FILTER_SYMBOLS = re.compile(r"[A-Za-z0-9]$") 3862306a36Sopenharmony_ciREGEX_NUMERIC = re.compile(r"0[xX][0-9a-fA-F]+|[0-9]+") 3962306a36Sopenharmony_ciREGEX_QUOTES = re.compile("(\"(.*?)\")") 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cidef parse_options(): 4362306a36Sopenharmony_ci """The user interface of this module.""" 4462306a36Sopenharmony_ci usage = "Run this tool to detect Kconfig symbols that are referenced but " \ 4562306a36Sopenharmony_ci "not defined in Kconfig. If no option is specified, " \ 4662306a36Sopenharmony_ci "checkkconfigsymbols defaults to check your current tree. " \ 4762306a36Sopenharmony_ci "Please note that specifying commits will 'git reset --hard\' " \ 4862306a36Sopenharmony_ci "your current tree! You may save uncommitted changes to avoid " \ 4962306a36Sopenharmony_ci "losing data." 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci parser = argparse.ArgumentParser(description=usage) 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci parser.add_argument('-c', '--commit', dest='commit', action='store', 5462306a36Sopenharmony_ci default="", 5562306a36Sopenharmony_ci help="check if the specified commit (hash) introduces " 5662306a36Sopenharmony_ci "undefined Kconfig symbols") 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci parser.add_argument('-d', '--diff', dest='diff', action='store', 5962306a36Sopenharmony_ci default="", 6062306a36Sopenharmony_ci help="diff undefined symbols between two commits " 6162306a36Sopenharmony_ci "(e.g., -d commmit1..commit2)") 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci parser.add_argument('-f', '--find', dest='find', action='store_true', 6462306a36Sopenharmony_ci default=False, 6562306a36Sopenharmony_ci help="find and show commits that may cause symbols to be " 6662306a36Sopenharmony_ci "missing (required to run with --diff)") 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci parser.add_argument('-i', '--ignore', dest='ignore', action='store', 6962306a36Sopenharmony_ci default="", 7062306a36Sopenharmony_ci help="ignore files matching this Python regex " 7162306a36Sopenharmony_ci "(e.g., -i '.*defconfig')") 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci parser.add_argument('-s', '--sim', dest='sim', action='store', default="", 7462306a36Sopenharmony_ci help="print a list of max. 10 string-similar symbols") 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci parser.add_argument('--force', dest='force', action='store_true', 7762306a36Sopenharmony_ci default=False, 7862306a36Sopenharmony_ci help="reset current Git tree even when it's dirty") 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci parser.add_argument('--no-color', dest='color', action='store_false', 8162306a36Sopenharmony_ci default=True, 8262306a36Sopenharmony_ci help="don't print colored output (default when not " 8362306a36Sopenharmony_ci "outputting to a terminal)") 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci args = parser.parse_args() 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci if args.commit and args.diff: 8862306a36Sopenharmony_ci sys.exit("Please specify only one option at once.") 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci if args.diff and not re.match(r"^[\w\-\.\^]+\.\.[\w\-\.\^]+$", args.diff): 9162306a36Sopenharmony_ci sys.exit("Please specify valid input in the following format: " 9262306a36Sopenharmony_ci "\'commit1..commit2\'") 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci if args.commit or args.diff: 9562306a36Sopenharmony_ci if not args.force and tree_is_dirty(): 9662306a36Sopenharmony_ci sys.exit("The current Git tree is dirty (see 'git status'). " 9762306a36Sopenharmony_ci "Running this script may\ndelete important data since it " 9862306a36Sopenharmony_ci "calls 'git reset --hard' for some performance\nreasons. " 9962306a36Sopenharmony_ci " Please run this script in a clean Git tree or pass " 10062306a36Sopenharmony_ci "'--force' if you\nwant to ignore this warning and " 10162306a36Sopenharmony_ci "continue.") 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci if args.commit: 10462306a36Sopenharmony_ci if args.commit.startswith('HEAD'): 10562306a36Sopenharmony_ci sys.exit("The --commit option can't use the HEAD ref") 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci args.find = False 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci if args.ignore: 11062306a36Sopenharmony_ci try: 11162306a36Sopenharmony_ci re.match(args.ignore, "this/is/just/a/test.c") 11262306a36Sopenharmony_ci except: 11362306a36Sopenharmony_ci sys.exit("Please specify a valid Python regex.") 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci return args 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cidef print_undefined_symbols(): 11962306a36Sopenharmony_ci """Main function of this module.""" 12062306a36Sopenharmony_ci args = parse_options() 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci global COLOR 12362306a36Sopenharmony_ci COLOR = args.color and sys.stdout.isatty() 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if args.sim and not args.commit and not args.diff: 12662306a36Sopenharmony_ci sims = find_sims(args.sim, args.ignore) 12762306a36Sopenharmony_ci if sims: 12862306a36Sopenharmony_ci print("%s: %s" % (yel("Similar symbols"), ', '.join(sims))) 12962306a36Sopenharmony_ci else: 13062306a36Sopenharmony_ci print("%s: no similar symbols found" % yel("Similar symbols")) 13162306a36Sopenharmony_ci sys.exit(0) 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci # dictionary of (un)defined symbols 13462306a36Sopenharmony_ci defined = {} 13562306a36Sopenharmony_ci undefined = {} 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if args.commit or args.diff: 13862306a36Sopenharmony_ci head = get_head() 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci # get commit range 14162306a36Sopenharmony_ci commit_a = None 14262306a36Sopenharmony_ci commit_b = None 14362306a36Sopenharmony_ci if args.commit: 14462306a36Sopenharmony_ci commit_a = args.commit + "~" 14562306a36Sopenharmony_ci commit_b = args.commit 14662306a36Sopenharmony_ci elif args.diff: 14762306a36Sopenharmony_ci split = args.diff.split("..") 14862306a36Sopenharmony_ci commit_a = split[0] 14962306a36Sopenharmony_ci commit_b = split[1] 15062306a36Sopenharmony_ci undefined_a = {} 15162306a36Sopenharmony_ci undefined_b = {} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci # get undefined items before the commit 15462306a36Sopenharmony_ci reset(commit_a) 15562306a36Sopenharmony_ci undefined_a, _ = check_symbols(args.ignore) 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci # get undefined items for the commit 15862306a36Sopenharmony_ci reset(commit_b) 15962306a36Sopenharmony_ci undefined_b, defined = check_symbols(args.ignore) 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci # report cases that are present for the commit but not before 16262306a36Sopenharmony_ci for symbol in sorted(undefined_b): 16362306a36Sopenharmony_ci # symbol has not been undefined before 16462306a36Sopenharmony_ci if symbol not in undefined_a: 16562306a36Sopenharmony_ci files = sorted(undefined_b.get(symbol)) 16662306a36Sopenharmony_ci undefined[symbol] = files 16762306a36Sopenharmony_ci # check if there are new files that reference the undefined symbol 16862306a36Sopenharmony_ci else: 16962306a36Sopenharmony_ci files = sorted(undefined_b.get(symbol) - 17062306a36Sopenharmony_ci undefined_a.get(symbol)) 17162306a36Sopenharmony_ci if files: 17262306a36Sopenharmony_ci undefined[symbol] = files 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci # reset to head 17562306a36Sopenharmony_ci reset(head) 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci # default to check the entire tree 17862306a36Sopenharmony_ci else: 17962306a36Sopenharmony_ci undefined, defined = check_symbols(args.ignore) 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci # now print the output 18262306a36Sopenharmony_ci for symbol in sorted(undefined): 18362306a36Sopenharmony_ci print(red(symbol)) 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci files = sorted(undefined.get(symbol)) 18662306a36Sopenharmony_ci print("%s: %s" % (yel("Referencing files"), ", ".join(files))) 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci sims = find_sims(symbol, args.ignore, defined) 18962306a36Sopenharmony_ci sims_out = yel("Similar symbols") 19062306a36Sopenharmony_ci if sims: 19162306a36Sopenharmony_ci print("%s: %s" % (sims_out, ', '.join(sims))) 19262306a36Sopenharmony_ci else: 19362306a36Sopenharmony_ci print("%s: %s" % (sims_out, "no similar symbols found")) 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci if args.find: 19662306a36Sopenharmony_ci print("%s:" % yel("Commits changing symbol")) 19762306a36Sopenharmony_ci commits = find_commits(symbol, args.diff) 19862306a36Sopenharmony_ci if commits: 19962306a36Sopenharmony_ci for commit in commits: 20062306a36Sopenharmony_ci commit = commit.split(" ", 1) 20162306a36Sopenharmony_ci print("\t- %s (\"%s\")" % (yel(commit[0]), commit[1])) 20262306a36Sopenharmony_ci else: 20362306a36Sopenharmony_ci print("\t- no commit found") 20462306a36Sopenharmony_ci print() # new line 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cidef reset(commit): 20862306a36Sopenharmony_ci """Reset current git tree to %commit.""" 20962306a36Sopenharmony_ci execute(["git", "reset", "--hard", commit]) 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cidef yel(string): 21362306a36Sopenharmony_ci """ 21462306a36Sopenharmony_ci Color %string yellow. 21562306a36Sopenharmony_ci """ 21662306a36Sopenharmony_ci return "\033[33m%s\033[0m" % string if COLOR else string 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cidef red(string): 22062306a36Sopenharmony_ci """ 22162306a36Sopenharmony_ci Color %string red. 22262306a36Sopenharmony_ci """ 22362306a36Sopenharmony_ci return "\033[31m%s\033[0m" % string if COLOR else string 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cidef execute(cmd): 22762306a36Sopenharmony_ci """Execute %cmd and return stdout. Exit in case of error.""" 22862306a36Sopenharmony_ci try: 22962306a36Sopenharmony_ci stdout = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=False) 23062306a36Sopenharmony_ci stdout = stdout.decode(errors='replace') 23162306a36Sopenharmony_ci except subprocess.CalledProcessError as fail: 23262306a36Sopenharmony_ci exit(fail) 23362306a36Sopenharmony_ci return stdout 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cidef find_commits(symbol, diff): 23762306a36Sopenharmony_ci """Find commits changing %symbol in the given range of %diff.""" 23862306a36Sopenharmony_ci commits = execute(["git", "log", "--pretty=oneline", 23962306a36Sopenharmony_ci "--abbrev-commit", "-G", 24062306a36Sopenharmony_ci symbol, diff]) 24162306a36Sopenharmony_ci return [x for x in commits.split("\n") if x] 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cidef tree_is_dirty(): 24562306a36Sopenharmony_ci """Return true if the current working tree is dirty (i.e., if any file has 24662306a36Sopenharmony_ci been added, deleted, modified, renamed or copied but not committed).""" 24762306a36Sopenharmony_ci stdout = execute(["git", "status", "--porcelain"]) 24862306a36Sopenharmony_ci for line in stdout: 24962306a36Sopenharmony_ci if re.findall(r"[URMADC]{1}", line[:2]): 25062306a36Sopenharmony_ci return True 25162306a36Sopenharmony_ci return False 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cidef get_head(): 25562306a36Sopenharmony_ci """Return commit hash of current HEAD.""" 25662306a36Sopenharmony_ci stdout = execute(["git", "rev-parse", "HEAD"]) 25762306a36Sopenharmony_ci return stdout.strip('\n') 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_cidef partition(lst, size): 26162306a36Sopenharmony_ci """Partition list @lst into eveni-sized lists of size @size.""" 26262306a36Sopenharmony_ci return [lst[i::size] for i in range(size)] 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cidef init_worker(): 26662306a36Sopenharmony_ci """Set signal handler to ignore SIGINT.""" 26762306a36Sopenharmony_ci signal.signal(signal.SIGINT, signal.SIG_IGN) 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cidef find_sims(symbol, ignore, defined=[]): 27162306a36Sopenharmony_ci """Return a list of max. ten Kconfig symbols that are string-similar to 27262306a36Sopenharmony_ci @symbol.""" 27362306a36Sopenharmony_ci if defined: 27462306a36Sopenharmony_ci return difflib.get_close_matches(symbol, set(defined), 10) 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci pool = Pool(cpu_count(), init_worker) 27762306a36Sopenharmony_ci kfiles = [] 27862306a36Sopenharmony_ci for gitfile in get_files(): 27962306a36Sopenharmony_ci if REGEX_FILE_KCONFIG.match(gitfile): 28062306a36Sopenharmony_ci kfiles.append(gitfile) 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci arglist = [] 28362306a36Sopenharmony_ci for part in partition(kfiles, cpu_count()): 28462306a36Sopenharmony_ci arglist.append((part, ignore)) 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci for res in pool.map(parse_kconfig_files, arglist): 28762306a36Sopenharmony_ci defined.extend(res[0]) 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci return difflib.get_close_matches(symbol, set(defined), 10) 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_cidef get_files(): 29362306a36Sopenharmony_ci """Return a list of all files in the current git directory.""" 29462306a36Sopenharmony_ci # use 'git ls-files' to get the worklist 29562306a36Sopenharmony_ci stdout = execute(["git", "ls-files"]) 29662306a36Sopenharmony_ci if len(stdout) > 0 and stdout[-1] == "\n": 29762306a36Sopenharmony_ci stdout = stdout[:-1] 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci files = [] 30062306a36Sopenharmony_ci for gitfile in stdout.rsplit("\n"): 30162306a36Sopenharmony_ci if ".git" in gitfile or "ChangeLog" in gitfile or \ 30262306a36Sopenharmony_ci ".log" in gitfile or os.path.isdir(gitfile) or \ 30362306a36Sopenharmony_ci gitfile.startswith("tools/"): 30462306a36Sopenharmony_ci continue 30562306a36Sopenharmony_ci files.append(gitfile) 30662306a36Sopenharmony_ci return files 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_cidef check_symbols(ignore): 31062306a36Sopenharmony_ci """Find undefined Kconfig symbols and return a dict with the symbol as key 31162306a36Sopenharmony_ci and a list of referencing files as value. Files matching %ignore are not 31262306a36Sopenharmony_ci checked for undefined symbols.""" 31362306a36Sopenharmony_ci pool = Pool(cpu_count(), init_worker) 31462306a36Sopenharmony_ci try: 31562306a36Sopenharmony_ci return check_symbols_helper(pool, ignore) 31662306a36Sopenharmony_ci except KeyboardInterrupt: 31762306a36Sopenharmony_ci pool.terminate() 31862306a36Sopenharmony_ci pool.join() 31962306a36Sopenharmony_ci sys.exit(1) 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_cidef check_symbols_helper(pool, ignore): 32362306a36Sopenharmony_ci """Helper method for check_symbols(). Used to catch keyboard interrupts in 32462306a36Sopenharmony_ci check_symbols() in order to properly terminate running worker processes.""" 32562306a36Sopenharmony_ci source_files = [] 32662306a36Sopenharmony_ci kconfig_files = [] 32762306a36Sopenharmony_ci defined_symbols = [] 32862306a36Sopenharmony_ci referenced_symbols = dict() # {file: [symbols]} 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci for gitfile in get_files(): 33162306a36Sopenharmony_ci if REGEX_FILE_KCONFIG.match(gitfile): 33262306a36Sopenharmony_ci kconfig_files.append(gitfile) 33362306a36Sopenharmony_ci else: 33462306a36Sopenharmony_ci if ignore and re.match(ignore, gitfile): 33562306a36Sopenharmony_ci continue 33662306a36Sopenharmony_ci # add source files that do not match the ignore pattern 33762306a36Sopenharmony_ci source_files.append(gitfile) 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci # parse source files 34062306a36Sopenharmony_ci arglist = partition(source_files, cpu_count()) 34162306a36Sopenharmony_ci for res in pool.map(parse_source_files, arglist): 34262306a36Sopenharmony_ci referenced_symbols.update(res) 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci # parse kconfig files 34562306a36Sopenharmony_ci arglist = [] 34662306a36Sopenharmony_ci for part in partition(kconfig_files, cpu_count()): 34762306a36Sopenharmony_ci arglist.append((part, ignore)) 34862306a36Sopenharmony_ci for res in pool.map(parse_kconfig_files, arglist): 34962306a36Sopenharmony_ci defined_symbols.extend(res[0]) 35062306a36Sopenharmony_ci referenced_symbols.update(res[1]) 35162306a36Sopenharmony_ci defined_symbols = set(defined_symbols) 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci # inverse mapping of referenced_symbols to dict(symbol: [files]) 35462306a36Sopenharmony_ci inv_map = dict() 35562306a36Sopenharmony_ci for _file, symbols in referenced_symbols.items(): 35662306a36Sopenharmony_ci for symbol in symbols: 35762306a36Sopenharmony_ci inv_map[symbol] = inv_map.get(symbol, set()) 35862306a36Sopenharmony_ci inv_map[symbol].add(_file) 35962306a36Sopenharmony_ci referenced_symbols = inv_map 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci undefined = {} # {symbol: [files]} 36262306a36Sopenharmony_ci for symbol in sorted(referenced_symbols): 36362306a36Sopenharmony_ci # filter some false positives 36462306a36Sopenharmony_ci if symbol == "FOO" or symbol == "BAR" or \ 36562306a36Sopenharmony_ci symbol == "FOO_BAR" or symbol == "XXX": 36662306a36Sopenharmony_ci continue 36762306a36Sopenharmony_ci if symbol not in defined_symbols: 36862306a36Sopenharmony_ci if symbol.endswith("_MODULE"): 36962306a36Sopenharmony_ci # avoid false positives for kernel modules 37062306a36Sopenharmony_ci if symbol[:-len("_MODULE")] in defined_symbols: 37162306a36Sopenharmony_ci continue 37262306a36Sopenharmony_ci undefined[symbol] = referenced_symbols.get(symbol) 37362306a36Sopenharmony_ci return undefined, defined_symbols 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_cidef parse_source_files(source_files): 37762306a36Sopenharmony_ci """Parse each source file in @source_files and return dictionary with source 37862306a36Sopenharmony_ci files as keys and lists of references Kconfig symbols as values.""" 37962306a36Sopenharmony_ci referenced_symbols = dict() 38062306a36Sopenharmony_ci for sfile in source_files: 38162306a36Sopenharmony_ci referenced_symbols[sfile] = parse_source_file(sfile) 38262306a36Sopenharmony_ci return referenced_symbols 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_cidef parse_source_file(sfile): 38662306a36Sopenharmony_ci """Parse @sfile and return a list of referenced Kconfig symbols.""" 38762306a36Sopenharmony_ci lines = [] 38862306a36Sopenharmony_ci references = [] 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci if not os.path.exists(sfile): 39162306a36Sopenharmony_ci return references 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci with open(sfile, "r", encoding='utf-8', errors='replace') as stream: 39462306a36Sopenharmony_ci lines = stream.readlines() 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci for line in lines: 39762306a36Sopenharmony_ci if "CONFIG_" not in line: 39862306a36Sopenharmony_ci continue 39962306a36Sopenharmony_ci symbols = REGEX_SOURCE_SYMBOL.findall(line) 40062306a36Sopenharmony_ci for symbol in symbols: 40162306a36Sopenharmony_ci if not REGEX_FILTER_SYMBOLS.search(symbol): 40262306a36Sopenharmony_ci continue 40362306a36Sopenharmony_ci references.append(symbol) 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci return references 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_cidef get_symbols_in_line(line): 40962306a36Sopenharmony_ci """Return mentioned Kconfig symbols in @line.""" 41062306a36Sopenharmony_ci return REGEX_SYMBOL.findall(line) 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_cidef parse_kconfig_files(args): 41462306a36Sopenharmony_ci """Parse kconfig files and return tuple of defined and references Kconfig 41562306a36Sopenharmony_ci symbols. Note, @args is a tuple of a list of files and the @ignore 41662306a36Sopenharmony_ci pattern.""" 41762306a36Sopenharmony_ci kconfig_files = args[0] 41862306a36Sopenharmony_ci ignore = args[1] 41962306a36Sopenharmony_ci defined_symbols = [] 42062306a36Sopenharmony_ci referenced_symbols = dict() 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci for kfile in kconfig_files: 42362306a36Sopenharmony_ci defined, references = parse_kconfig_file(kfile) 42462306a36Sopenharmony_ci defined_symbols.extend(defined) 42562306a36Sopenharmony_ci if ignore and re.match(ignore, kfile): 42662306a36Sopenharmony_ci # do not collect references for files that match the ignore pattern 42762306a36Sopenharmony_ci continue 42862306a36Sopenharmony_ci referenced_symbols[kfile] = references 42962306a36Sopenharmony_ci return (defined_symbols, referenced_symbols) 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_cidef parse_kconfig_file(kfile): 43362306a36Sopenharmony_ci """Parse @kfile and update symbol definitions and references.""" 43462306a36Sopenharmony_ci lines = [] 43562306a36Sopenharmony_ci defined = [] 43662306a36Sopenharmony_ci references = [] 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci if not os.path.exists(kfile): 43962306a36Sopenharmony_ci return defined, references 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci with open(kfile, "r", encoding='utf-8', errors='replace') as stream: 44262306a36Sopenharmony_ci lines = stream.readlines() 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci for i in range(len(lines)): 44562306a36Sopenharmony_ci line = lines[i] 44662306a36Sopenharmony_ci line = line.strip('\n') 44762306a36Sopenharmony_ci line = line.split("#")[0] # ignore comments 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci if REGEX_KCONFIG_DEF.match(line): 45062306a36Sopenharmony_ci symbol_def = REGEX_KCONFIG_DEF.findall(line) 45162306a36Sopenharmony_ci defined.append(symbol_def[0]) 45262306a36Sopenharmony_ci elif REGEX_KCONFIG_STMT.match(line): 45362306a36Sopenharmony_ci line = REGEX_QUOTES.sub("", line) 45462306a36Sopenharmony_ci symbols = get_symbols_in_line(line) 45562306a36Sopenharmony_ci # multi-line statements 45662306a36Sopenharmony_ci while line.endswith("\\"): 45762306a36Sopenharmony_ci i += 1 45862306a36Sopenharmony_ci line = lines[i] 45962306a36Sopenharmony_ci line = line.strip('\n') 46062306a36Sopenharmony_ci symbols.extend(get_symbols_in_line(line)) 46162306a36Sopenharmony_ci for symbol in set(symbols): 46262306a36Sopenharmony_ci if REGEX_NUMERIC.match(symbol): 46362306a36Sopenharmony_ci # ignore numeric values 46462306a36Sopenharmony_ci continue 46562306a36Sopenharmony_ci references.append(symbol) 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci return defined, references 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_cidef main(): 47162306a36Sopenharmony_ci try: 47262306a36Sopenharmony_ci print_undefined_symbols() 47362306a36Sopenharmony_ci except BrokenPipeError: 47462306a36Sopenharmony_ci # Python flushes standard streams on exit; redirect remaining output 47562306a36Sopenharmony_ci # to devnull to avoid another BrokenPipeError at shutdown 47662306a36Sopenharmony_ci devnull = os.open(os.devnull, os.O_WRONLY) 47762306a36Sopenharmony_ci os.dup2(devnull, sys.stdout.fileno()) 47862306a36Sopenharmony_ci sys.exit(1) # Python exits with error code 1 on EPIPE 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ciif __name__ == "__main__": 48262306a36Sopenharmony_ci main() 483