17c5227afSValentin Rothberg#!/usr/bin/env python3 24f19048fSThomas Gleixner# SPDX-License-Identifier: GPL-2.0-only 324fe1f03SValentin Rothberg 4b1a3f243SValentin Rothberg"""Find Kconfig symbols that are referenced but not defined.""" 524fe1f03SValentin Rothberg 68e8e3331SValentin Rothberg# (c) 2014-2017 Valentin Rothberg <valentinrothberg@gmail.com> 7cc641d55SValentin Rothberg# (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de> 824fe1f03SValentin Rothberg# 924fe1f03SValentin Rothberg 1024fe1f03SValentin Rothberg 1114390e31SValentin Rothbergimport argparse 121b2c8414SValentin Rothbergimport difflib 1324fe1f03SValentin Rothbergimport os 1424fe1f03SValentin Rothbergimport re 15e2042a8aSValentin Rothbergimport signal 16f175ba17SValentin Rothbergimport subprocess 17b1a3f243SValentin Rothbergimport sys 18e2042a8aSValentin Rothbergfrom multiprocessing import Pool, cpu_count 1924fe1f03SValentin Rothberg 20cc641d55SValentin Rothberg 21cc641d55SValentin Rothberg# regex expressions 2224fe1f03SValentin RothbergOPERATORS = r"&|\(|\)|\||\!" 23ef3f5543SValentin RothbergSYMBOL = r"(?:\w*[A-Z0-9]\w*){2,}" 24ef3f5543SValentin RothbergDEF = r"^\s*(?:menu){,1}config\s+(" + SYMBOL + r")\s*" 25ef3f5543SValentin RothbergEXPR = r"(?:" + OPERATORS + r"|\s|" + SYMBOL + r")+" 260bd38ae3SValentin RothbergDEFAULT = r"default\s+.*?(?:if\s.+){,1}" 273b28f4f2SValentin RothbergSTMT = r"^\s*(?:if|select|imply|depends\s+on|(?:" + DEFAULT + r"))\s+" + EXPR 28ef3f5543SValentin RothbergSOURCE_SYMBOL = r"(?:\W|\b)+[D]{,1}CONFIG_(" + SYMBOL + r")" 2924fe1f03SValentin Rothberg 30cc641d55SValentin Rothberg# regex objects 3124fe1f03SValentin RothbergREGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$") 32ef3f5543SValentin RothbergREGEX_SYMBOL = re.compile(r'(?!\B)' + SYMBOL + r'(?!\B)') 33ef3f5543SValentin RothbergREGEX_SOURCE_SYMBOL = re.compile(SOURCE_SYMBOL) 34cc641d55SValentin RothbergREGEX_KCONFIG_DEF = re.compile(DEF) 3524fe1f03SValentin RothbergREGEX_KCONFIG_EXPR = re.compile(EXPR) 3624fe1f03SValentin RothbergREGEX_KCONFIG_STMT = re.compile(STMT) 37f70f74d1SMasahiro YamadaREGEX_KCONFIG_HELP = re.compile(r"^\s+help\s*$") 38ef3f5543SValentin RothbergREGEX_FILTER_SYMBOLS = re.compile(r"[A-Za-z0-9]$") 390bd38ae3SValentin RothbergREGEX_NUMERIC = re.compile(r"0[xX][0-9a-fA-F]+|[0-9]+") 40e2042a8aSValentin RothbergREGEX_QUOTES = re.compile("(\"(.*?)\")") 4124fe1f03SValentin Rothberg 4224fe1f03SValentin Rothberg 43b1a3f243SValentin Rothbergdef parse_options(): 44b1a3f243SValentin Rothberg """The user interface of this module.""" 4514390e31SValentin Rothberg usage = "Run this tool to detect Kconfig symbols that are referenced but " \ 4614390e31SValentin Rothberg "not defined in Kconfig. If no option is specified, " \ 4714390e31SValentin Rothberg "checkkconfigsymbols defaults to check your current tree. " \ 4814390e31SValentin Rothberg "Please note that specifying commits will 'git reset --hard\' " \ 4914390e31SValentin Rothberg "your current tree! You may save uncommitted changes to avoid " \ 5014390e31SValentin Rothberg "losing data." 51b1a3f243SValentin Rothberg 5214390e31SValentin Rothberg parser = argparse.ArgumentParser(description=usage) 53b1a3f243SValentin Rothberg 5414390e31SValentin Rothberg parser.add_argument('-c', '--commit', dest='commit', action='store', 55b1a3f243SValentin Rothberg default="", 5614390e31SValentin Rothberg help="check if the specified commit (hash) introduces " 5714390e31SValentin Rothberg "undefined Kconfig symbols") 58b1a3f243SValentin Rothberg 5914390e31SValentin Rothberg parser.add_argument('-d', '--diff', dest='diff', action='store', 60b1a3f243SValentin Rothberg default="", 6114390e31SValentin Rothberg help="diff undefined symbols between two commits " 6214390e31SValentin Rothberg "(e.g., -d commmit1..commit2)") 63b1a3f243SValentin Rothberg 6414390e31SValentin Rothberg parser.add_argument('-f', '--find', dest='find', action='store_true', 65a42fa92cSValentin Rothberg default=False, 6614390e31SValentin Rothberg help="find and show commits that may cause symbols to be " 6714390e31SValentin Rothberg "missing (required to run with --diff)") 68a42fa92cSValentin Rothberg 6914390e31SValentin Rothberg parser.add_argument('-i', '--ignore', dest='ignore', action='store', 70cf132e4aSValentin Rothberg default="", 7114390e31SValentin Rothberg help="ignore files matching this Python regex " 7214390e31SValentin Rothberg "(e.g., -i '.*defconfig')") 73cf132e4aSValentin Rothberg 7414390e31SValentin Rothberg parser.add_argument('-s', '--sim', dest='sim', action='store', default="", 7514390e31SValentin Rothberg help="print a list of max. 10 string-similar symbols") 761b2c8414SValentin Rothberg 7714390e31SValentin Rothberg parser.add_argument('--force', dest='force', action='store_true', 78b1a3f243SValentin Rothberg default=False, 7914390e31SValentin Rothberg help="reset current Git tree even when it's dirty") 80b1a3f243SValentin Rothberg 8114390e31SValentin Rothberg parser.add_argument('--no-color', dest='color', action='store_false', 824c73c088SAndrew Donnellan default=True, 8314390e31SValentin Rothberg help="don't print colored output (default when not " 8414390e31SValentin Rothberg "outputting to a terminal)") 854c73c088SAndrew Donnellan 8614390e31SValentin Rothberg args = parser.parse_args() 87b1a3f243SValentin Rothberg 8814390e31SValentin Rothberg if args.commit and args.diff: 89b1a3f243SValentin Rothberg sys.exit("Please specify only one option at once.") 90b1a3f243SValentin Rothberg 910d18c192SValentin Rothberg if args.diff and not re.match(r"^[\w\-\.\^]+\.\.[\w\-\.\^]+$", args.diff): 92b1a3f243SValentin Rothberg sys.exit("Please specify valid input in the following format: " 9338cbfe4fSAndreas Ziegler "\'commit1..commit2\'") 94b1a3f243SValentin Rothberg 9514390e31SValentin Rothberg if args.commit or args.diff: 9614390e31SValentin Rothberg if not args.force and tree_is_dirty(): 97b1a3f243SValentin Rothberg sys.exit("The current Git tree is dirty (see 'git status'). " 98b1a3f243SValentin Rothberg "Running this script may\ndelete important data since it " 99b1a3f243SValentin Rothberg "calls 'git reset --hard' for some performance\nreasons. " 100b1a3f243SValentin Rothberg " Please run this script in a clean Git tree or pass " 101b1a3f243SValentin Rothberg "'--force' if you\nwant to ignore this warning and " 102b1a3f243SValentin Rothberg "continue.") 103b1a3f243SValentin Rothberg 10414390e31SValentin Rothberg if args.commit: 105*d62d5aedSAriel Marcovitch if args.commit.startswith('HEAD'): 106*d62d5aedSAriel Marcovitch sys.exit("The --commit option can't use the HEAD ref") 107*d62d5aedSAriel Marcovitch 10814390e31SValentin Rothberg args.find = False 109a42fa92cSValentin Rothberg 11014390e31SValentin Rothberg if args.ignore: 111cf132e4aSValentin Rothberg try: 11214390e31SValentin Rothberg re.match(args.ignore, "this/is/just/a/test.c") 113cf132e4aSValentin Rothberg except: 114cf132e4aSValentin Rothberg sys.exit("Please specify a valid Python regex.") 115cf132e4aSValentin Rothberg 11614390e31SValentin Rothberg return args 117b1a3f243SValentin Rothberg 118b1a3f243SValentin Rothberg 11924fe1f03SValentin Rothbergdef main(): 12024fe1f03SValentin Rothberg """Main function of this module.""" 12114390e31SValentin Rothberg args = parse_options() 122b1a3f243SValentin Rothberg 12336c79c7fSValentin Rothberg global COLOR 12436c79c7fSValentin Rothberg COLOR = args.color and sys.stdout.isatty() 1254c73c088SAndrew Donnellan 12614390e31SValentin Rothberg if args.sim and not args.commit and not args.diff: 12714390e31SValentin Rothberg sims = find_sims(args.sim, args.ignore) 1281b2c8414SValentin Rothberg if sims: 1297c5227afSValentin Rothberg print("%s: %s" % (yel("Similar symbols"), ', '.join(sims))) 1301b2c8414SValentin Rothberg else: 1317c5227afSValentin Rothberg print("%s: no similar symbols found" % yel("Similar symbols")) 1321b2c8414SValentin Rothberg sys.exit(0) 1331b2c8414SValentin Rothberg 1341b2c8414SValentin Rothberg # dictionary of (un)defined symbols 1351b2c8414SValentin Rothberg defined = {} 1361b2c8414SValentin Rothberg undefined = {} 1371b2c8414SValentin Rothberg 13814390e31SValentin Rothberg if args.commit or args.diff: 139b1a3f243SValentin Rothberg head = get_head() 140b1a3f243SValentin Rothberg 141b1a3f243SValentin Rothberg # get commit range 142b1a3f243SValentin Rothberg commit_a = None 143b1a3f243SValentin Rothberg commit_b = None 14414390e31SValentin Rothberg if args.commit: 14514390e31SValentin Rothberg commit_a = args.commit + "~" 14614390e31SValentin Rothberg commit_b = args.commit 14714390e31SValentin Rothberg elif args.diff: 14814390e31SValentin Rothberg split = args.diff.split("..") 149b1a3f243SValentin Rothberg commit_a = split[0] 150b1a3f243SValentin Rothberg commit_b = split[1] 151b1a3f243SValentin Rothberg undefined_a = {} 152b1a3f243SValentin Rothberg undefined_b = {} 153b1a3f243SValentin Rothberg 154b1a3f243SValentin Rothberg # get undefined items before the commit 1552f9cc12bSValentin Rothberg reset(commit_a) 15614390e31SValentin Rothberg undefined_a, _ = check_symbols(args.ignore) 157b1a3f243SValentin Rothberg 158b1a3f243SValentin Rothberg # get undefined items for the commit 1592f9cc12bSValentin Rothberg reset(commit_b) 16014390e31SValentin Rothberg undefined_b, defined = check_symbols(args.ignore) 161b1a3f243SValentin Rothberg 162b1a3f243SValentin Rothberg # report cases that are present for the commit but not before 163ef3f5543SValentin Rothberg for symbol in sorted(undefined_b): 164ef3f5543SValentin Rothberg # symbol has not been undefined before 165ef3f5543SValentin Rothberg if symbol not in undefined_a: 166ef3f5543SValentin Rothberg files = sorted(undefined_b.get(symbol)) 167ef3f5543SValentin Rothberg undefined[symbol] = files 168ef3f5543SValentin Rothberg # check if there are new files that reference the undefined symbol 169b1a3f243SValentin Rothberg else: 170ef3f5543SValentin Rothberg files = sorted(undefined_b.get(symbol) - 171ef3f5543SValentin Rothberg undefined_a.get(symbol)) 172b1a3f243SValentin Rothberg if files: 173ef3f5543SValentin Rothberg undefined[symbol] = files 174b1a3f243SValentin Rothberg 175b1a3f243SValentin Rothberg # reset to head 1762f9cc12bSValentin Rothberg reset(head) 177b1a3f243SValentin Rothberg 178b1a3f243SValentin Rothberg # default to check the entire tree 179b1a3f243SValentin Rothberg else: 18014390e31SValentin Rothberg undefined, defined = check_symbols(args.ignore) 1811b2c8414SValentin Rothberg 1821b2c8414SValentin Rothberg # now print the output 183ef3f5543SValentin Rothberg for symbol in sorted(undefined): 184ef3f5543SValentin Rothberg print(red(symbol)) 1851b2c8414SValentin Rothberg 186ef3f5543SValentin Rothberg files = sorted(undefined.get(symbol)) 1877c5227afSValentin Rothberg print("%s: %s" % (yel("Referencing files"), ", ".join(files))) 1881b2c8414SValentin Rothberg 189ef3f5543SValentin Rothberg sims = find_sims(symbol, args.ignore, defined) 1901b2c8414SValentin Rothberg sims_out = yel("Similar symbols") 1911b2c8414SValentin Rothberg if sims: 1927c5227afSValentin Rothberg print("%s: %s" % (sims_out, ', '.join(sims))) 1931b2c8414SValentin Rothberg else: 1947c5227afSValentin Rothberg print("%s: %s" % (sims_out, "no similar symbols found")) 1951b2c8414SValentin Rothberg 19614390e31SValentin Rothberg if args.find: 1977c5227afSValentin Rothberg print("%s:" % yel("Commits changing symbol")) 198ef3f5543SValentin Rothberg commits = find_commits(symbol, args.diff) 1991b2c8414SValentin Rothberg if commits: 2001b2c8414SValentin Rothberg for commit in commits: 2011b2c8414SValentin Rothberg commit = commit.split(" ", 1) 2027c5227afSValentin Rothberg print("\t- %s (\"%s\")" % (yel(commit[0]), commit[1])) 2031b2c8414SValentin Rothberg else: 2047c5227afSValentin Rothberg print("\t- no commit found") 2057c5227afSValentin Rothberg print() # new line 206c7455663SValentin Rothberg 207c7455663SValentin Rothberg 2082f9cc12bSValentin Rothbergdef reset(commit): 2092f9cc12bSValentin Rothberg """Reset current git tree to %commit.""" 2102f9cc12bSValentin Rothberg execute(["git", "reset", "--hard", commit]) 2112f9cc12bSValentin Rothberg 2122f9cc12bSValentin Rothberg 213c7455663SValentin Rothbergdef yel(string): 214c7455663SValentin Rothberg """ 215c7455663SValentin Rothberg Color %string yellow. 216c7455663SValentin Rothberg """ 21736c79c7fSValentin Rothberg return "\033[33m%s\033[0m" % string if COLOR else string 218c7455663SValentin Rothberg 219c7455663SValentin Rothberg 220c7455663SValentin Rothbergdef red(string): 221c7455663SValentin Rothberg """ 222c7455663SValentin Rothberg Color %string red. 223c7455663SValentin Rothberg """ 22436c79c7fSValentin Rothberg return "\033[31m%s\033[0m" % string if COLOR else string 225b1a3f243SValentin Rothberg 226b1a3f243SValentin Rothberg 227b1a3f243SValentin Rothbergdef execute(cmd): 228b1a3f243SValentin Rothberg """Execute %cmd and return stdout. Exit in case of error.""" 229f175ba17SValentin Rothberg try: 2302f9cc12bSValentin Rothberg stdout = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=False) 2317c5227afSValentin Rothberg stdout = stdout.decode(errors='replace') 232f175ba17SValentin Rothberg except subprocess.CalledProcessError as fail: 2332f9cc12bSValentin Rothberg exit(fail) 234b1a3f243SValentin Rothberg return stdout 235b1a3f243SValentin Rothberg 236b1a3f243SValentin Rothberg 237a42fa92cSValentin Rothbergdef find_commits(symbol, diff): 238a42fa92cSValentin Rothberg """Find commits changing %symbol in the given range of %diff.""" 2392f9cc12bSValentin Rothberg commits = execute(["git", "log", "--pretty=oneline", 2402f9cc12bSValentin Rothberg "--abbrev-commit", "-G", 2412f9cc12bSValentin Rothberg symbol, diff]) 2421b2c8414SValentin Rothberg return [x for x in commits.split("\n") if x] 243a42fa92cSValentin Rothberg 244a42fa92cSValentin Rothberg 245b1a3f243SValentin Rothbergdef tree_is_dirty(): 246b1a3f243SValentin Rothberg """Return true if the current working tree is dirty (i.e., if any file has 247b1a3f243SValentin Rothberg been added, deleted, modified, renamed or copied but not committed).""" 2482f9cc12bSValentin Rothberg stdout = execute(["git", "status", "--porcelain"]) 249b1a3f243SValentin Rothberg for line in stdout: 250b1a3f243SValentin Rothberg if re.findall(r"[URMADC]{1}", line[:2]): 251b1a3f243SValentin Rothberg return True 252b1a3f243SValentin Rothberg return False 253b1a3f243SValentin Rothberg 254b1a3f243SValentin Rothberg 255b1a3f243SValentin Rothbergdef get_head(): 256b1a3f243SValentin Rothberg """Return commit hash of current HEAD.""" 2572f9cc12bSValentin Rothberg stdout = execute(["git", "rev-parse", "HEAD"]) 258b1a3f243SValentin Rothberg return stdout.strip('\n') 259b1a3f243SValentin Rothberg 260b1a3f243SValentin Rothberg 261e2042a8aSValentin Rothbergdef partition(lst, size): 262e2042a8aSValentin Rothberg """Partition list @lst into eveni-sized lists of size @size.""" 2637c5227afSValentin Rothberg return [lst[i::size] for i in range(size)] 264e2042a8aSValentin Rothberg 265e2042a8aSValentin Rothberg 266e2042a8aSValentin Rothbergdef init_worker(): 267e2042a8aSValentin Rothberg """Set signal handler to ignore SIGINT.""" 268e2042a8aSValentin Rothberg signal.signal(signal.SIGINT, signal.SIG_IGN) 269e2042a8aSValentin Rothberg 270e2042a8aSValentin Rothberg 2711b2c8414SValentin Rothbergdef find_sims(symbol, ignore, defined=[]): 2721b2c8414SValentin Rothberg """Return a list of max. ten Kconfig symbols that are string-similar to 2731b2c8414SValentin Rothberg @symbol.""" 2741b2c8414SValentin Rothberg if defined: 2758e8e3331SValentin Rothberg return difflib.get_close_matches(symbol, set(defined), 10) 2761b2c8414SValentin Rothberg 2771b2c8414SValentin Rothberg pool = Pool(cpu_count(), init_worker) 2781b2c8414SValentin Rothberg kfiles = [] 2791b2c8414SValentin Rothberg for gitfile in get_files(): 2801b2c8414SValentin Rothberg if REGEX_FILE_KCONFIG.match(gitfile): 2811b2c8414SValentin Rothberg kfiles.append(gitfile) 2821b2c8414SValentin Rothberg 2831b2c8414SValentin Rothberg arglist = [] 2841b2c8414SValentin Rothberg for part in partition(kfiles, cpu_count()): 2851b2c8414SValentin Rothberg arglist.append((part, ignore)) 2861b2c8414SValentin Rothberg 2871b2c8414SValentin Rothberg for res in pool.map(parse_kconfig_files, arglist): 2881b2c8414SValentin Rothberg defined.extend(res[0]) 2891b2c8414SValentin Rothberg 2908e8e3331SValentin Rothberg return difflib.get_close_matches(symbol, set(defined), 10) 2911b2c8414SValentin Rothberg 2921b2c8414SValentin Rothberg 2931b2c8414SValentin Rothbergdef get_files(): 2941b2c8414SValentin Rothberg """Return a list of all files in the current git directory.""" 2951b2c8414SValentin Rothberg # use 'git ls-files' to get the worklist 2962f9cc12bSValentin Rothberg stdout = execute(["git", "ls-files"]) 2971b2c8414SValentin Rothberg if len(stdout) > 0 and stdout[-1] == "\n": 2981b2c8414SValentin Rothberg stdout = stdout[:-1] 2991b2c8414SValentin Rothberg 3001b2c8414SValentin Rothberg files = [] 3011b2c8414SValentin Rothberg for gitfile in stdout.rsplit("\n"): 3021b2c8414SValentin Rothberg if ".git" in gitfile or "ChangeLog" in gitfile or \ 3031b2c8414SValentin Rothberg ".log" in gitfile or os.path.isdir(gitfile) or \ 3041b2c8414SValentin Rothberg gitfile.startswith("tools/"): 3051b2c8414SValentin Rothberg continue 3061b2c8414SValentin Rothberg files.append(gitfile) 3071b2c8414SValentin Rothberg return files 3081b2c8414SValentin Rothberg 3091b2c8414SValentin Rothberg 310cf132e4aSValentin Rothbergdef check_symbols(ignore): 311b1a3f243SValentin Rothberg """Find undefined Kconfig symbols and return a dict with the symbol as key 312cf132e4aSValentin Rothberg and a list of referencing files as value. Files matching %ignore are not 313cf132e4aSValentin Rothberg checked for undefined symbols.""" 314e2042a8aSValentin Rothberg pool = Pool(cpu_count(), init_worker) 315e2042a8aSValentin Rothberg try: 316e2042a8aSValentin Rothberg return check_symbols_helper(pool, ignore) 317e2042a8aSValentin Rothberg except KeyboardInterrupt: 318e2042a8aSValentin Rothberg pool.terminate() 319e2042a8aSValentin Rothberg pool.join() 320e2042a8aSValentin Rothberg sys.exit(1) 321e2042a8aSValentin Rothberg 322e2042a8aSValentin Rothberg 323e2042a8aSValentin Rothbergdef check_symbols_helper(pool, ignore): 324e2042a8aSValentin Rothberg """Helper method for check_symbols(). Used to catch keyboard interrupts in 325e2042a8aSValentin Rothberg check_symbols() in order to properly terminate running worker processes.""" 32624fe1f03SValentin Rothberg source_files = [] 32724fe1f03SValentin Rothberg kconfig_files = [] 328ef3f5543SValentin Rothberg defined_symbols = [] 329ef3f5543SValentin Rothberg referenced_symbols = dict() # {file: [symbols]} 33024fe1f03SValentin Rothberg 3311b2c8414SValentin Rothberg for gitfile in get_files(): 33224fe1f03SValentin Rothberg if REGEX_FILE_KCONFIG.match(gitfile): 33324fe1f03SValentin Rothberg kconfig_files.append(gitfile) 33424fe1f03SValentin Rothberg else: 3351439ebd2SAriel Marcovitch if ignore and re.match(ignore, gitfile): 336e2042a8aSValentin Rothberg continue 337e2042a8aSValentin Rothberg # add source files that do not match the ignore pattern 33824fe1f03SValentin Rothberg source_files.append(gitfile) 33924fe1f03SValentin Rothberg 340e2042a8aSValentin Rothberg # parse source files 341e2042a8aSValentin Rothberg arglist = partition(source_files, cpu_count()) 342e2042a8aSValentin Rothberg for res in pool.map(parse_source_files, arglist): 343ef3f5543SValentin Rothberg referenced_symbols.update(res) 34424fe1f03SValentin Rothberg 345e2042a8aSValentin Rothberg # parse kconfig files 346e2042a8aSValentin Rothberg arglist = [] 347e2042a8aSValentin Rothberg for part in partition(kconfig_files, cpu_count()): 348e2042a8aSValentin Rothberg arglist.append((part, ignore)) 349e2042a8aSValentin Rothberg for res in pool.map(parse_kconfig_files, arglist): 350ef3f5543SValentin Rothberg defined_symbols.extend(res[0]) 351ef3f5543SValentin Rothberg referenced_symbols.update(res[1]) 352ef3f5543SValentin Rothberg defined_symbols = set(defined_symbols) 353e2042a8aSValentin Rothberg 354ef3f5543SValentin Rothberg # inverse mapping of referenced_symbols to dict(symbol: [files]) 355e2042a8aSValentin Rothberg inv_map = dict() 356ef3f5543SValentin Rothberg for _file, symbols in referenced_symbols.items(): 357ef3f5543SValentin Rothberg for symbol in symbols: 358ef3f5543SValentin Rothberg inv_map[symbol] = inv_map.get(symbol, set()) 359ef3f5543SValentin Rothberg inv_map[symbol].add(_file) 360ef3f5543SValentin Rothberg referenced_symbols = inv_map 36124fe1f03SValentin Rothberg 362ef3f5543SValentin Rothberg undefined = {} # {symbol: [files]} 363ef3f5543SValentin Rothberg for symbol in sorted(referenced_symbols): 364cc641d55SValentin Rothberg # filter some false positives 365ef3f5543SValentin Rothberg if symbol == "FOO" or symbol == "BAR" or \ 366ef3f5543SValentin Rothberg symbol == "FOO_BAR" or symbol == "XXX": 367cc641d55SValentin Rothberg continue 368ef3f5543SValentin Rothberg if symbol not in defined_symbols: 369ef3f5543SValentin Rothberg if symbol.endswith("_MODULE"): 370cc641d55SValentin Rothberg # avoid false positives for kernel modules 371ef3f5543SValentin Rothberg if symbol[:-len("_MODULE")] in defined_symbols: 37224fe1f03SValentin Rothberg continue 373ef3f5543SValentin Rothberg undefined[symbol] = referenced_symbols.get(symbol) 374ef3f5543SValentin Rothberg return undefined, defined_symbols 37524fe1f03SValentin Rothberg 37624fe1f03SValentin Rothberg 377e2042a8aSValentin Rothbergdef parse_source_files(source_files): 378e2042a8aSValentin Rothberg """Parse each source file in @source_files and return dictionary with source 379e2042a8aSValentin Rothberg files as keys and lists of references Kconfig symbols as values.""" 380ef3f5543SValentin Rothberg referenced_symbols = dict() 381e2042a8aSValentin Rothberg for sfile in source_files: 382ef3f5543SValentin Rothberg referenced_symbols[sfile] = parse_source_file(sfile) 383ef3f5543SValentin Rothberg return referenced_symbols 384e2042a8aSValentin Rothberg 385e2042a8aSValentin Rothberg 386e2042a8aSValentin Rothbergdef parse_source_file(sfile): 387ef3f5543SValentin Rothberg """Parse @sfile and return a list of referenced Kconfig symbols.""" 38824fe1f03SValentin Rothberg lines = [] 389e2042a8aSValentin Rothberg references = [] 390e2042a8aSValentin Rothberg 391e2042a8aSValentin Rothberg if not os.path.exists(sfile): 392e2042a8aSValentin Rothberg return references 393e2042a8aSValentin Rothberg 3947c5227afSValentin Rothberg with open(sfile, "r", encoding='utf-8', errors='replace') as stream: 39524fe1f03SValentin Rothberg lines = stream.readlines() 39624fe1f03SValentin Rothberg 39724fe1f03SValentin Rothberg for line in lines: 39836c79c7fSValentin Rothberg if "CONFIG_" not in line: 39924fe1f03SValentin Rothberg continue 400ef3f5543SValentin Rothberg symbols = REGEX_SOURCE_SYMBOL.findall(line) 401ef3f5543SValentin Rothberg for symbol in symbols: 402ef3f5543SValentin Rothberg if not REGEX_FILTER_SYMBOLS.search(symbol): 40324fe1f03SValentin Rothberg continue 404ef3f5543SValentin Rothberg references.append(symbol) 405e2042a8aSValentin Rothberg 406e2042a8aSValentin Rothberg return references 40724fe1f03SValentin Rothberg 40824fe1f03SValentin Rothberg 409ef3f5543SValentin Rothbergdef get_symbols_in_line(line): 410ef3f5543SValentin Rothberg """Return mentioned Kconfig symbols in @line.""" 411ef3f5543SValentin Rothberg return REGEX_SYMBOL.findall(line) 41224fe1f03SValentin Rothberg 41324fe1f03SValentin Rothberg 414e2042a8aSValentin Rothbergdef parse_kconfig_files(args): 415e2042a8aSValentin Rothberg """Parse kconfig files and return tuple of defined and references Kconfig 416e2042a8aSValentin Rothberg symbols. Note, @args is a tuple of a list of files and the @ignore 417e2042a8aSValentin Rothberg pattern.""" 418e2042a8aSValentin Rothberg kconfig_files = args[0] 419e2042a8aSValentin Rothberg ignore = args[1] 420ef3f5543SValentin Rothberg defined_symbols = [] 421ef3f5543SValentin Rothberg referenced_symbols = dict() 422e2042a8aSValentin Rothberg 423e2042a8aSValentin Rothberg for kfile in kconfig_files: 424e2042a8aSValentin Rothberg defined, references = parse_kconfig_file(kfile) 425ef3f5543SValentin Rothberg defined_symbols.extend(defined) 426e2042a8aSValentin Rothberg if ignore and re.match(ignore, kfile): 427e2042a8aSValentin Rothberg # do not collect references for files that match the ignore pattern 428e2042a8aSValentin Rothberg continue 429ef3f5543SValentin Rothberg referenced_symbols[kfile] = references 430ef3f5543SValentin Rothberg return (defined_symbols, referenced_symbols) 431e2042a8aSValentin Rothberg 432e2042a8aSValentin Rothberg 433e2042a8aSValentin Rothbergdef parse_kconfig_file(kfile): 434ef3f5543SValentin Rothberg """Parse @kfile and update symbol definitions and references.""" 43524fe1f03SValentin Rothberg lines = [] 436e2042a8aSValentin Rothberg defined = [] 437e2042a8aSValentin Rothberg references = [] 43824fe1f03SValentin Rothberg skip = False 43924fe1f03SValentin Rothberg 440e2042a8aSValentin Rothberg if not os.path.exists(kfile): 441e2042a8aSValentin Rothberg return defined, references 442e2042a8aSValentin Rothberg 4437c5227afSValentin Rothberg with open(kfile, "r", encoding='utf-8', errors='replace') as stream: 44424fe1f03SValentin Rothberg lines = stream.readlines() 44524fe1f03SValentin Rothberg 44624fe1f03SValentin Rothberg for i in range(len(lines)): 44724fe1f03SValentin Rothberg line = lines[i] 44824fe1f03SValentin Rothberg line = line.strip('\n') 449cc641d55SValentin Rothberg line = line.split("#")[0] # ignore comments 45024fe1f03SValentin Rothberg 45124fe1f03SValentin Rothberg if REGEX_KCONFIG_DEF.match(line): 452ef3f5543SValentin Rothberg symbol_def = REGEX_KCONFIG_DEF.findall(line) 453ef3f5543SValentin Rothberg defined.append(symbol_def[0]) 45424fe1f03SValentin Rothberg skip = False 45524fe1f03SValentin Rothberg elif REGEX_KCONFIG_HELP.match(line): 45624fe1f03SValentin Rothberg skip = True 45724fe1f03SValentin Rothberg elif skip: 458cc641d55SValentin Rothberg # ignore content of help messages 45924fe1f03SValentin Rothberg pass 46024fe1f03SValentin Rothberg elif REGEX_KCONFIG_STMT.match(line): 461e2042a8aSValentin Rothberg line = REGEX_QUOTES.sub("", line) 462ef3f5543SValentin Rothberg symbols = get_symbols_in_line(line) 463cc641d55SValentin Rothberg # multi-line statements 46424fe1f03SValentin Rothberg while line.endswith("\\"): 46524fe1f03SValentin Rothberg i += 1 46624fe1f03SValentin Rothberg line = lines[i] 46724fe1f03SValentin Rothberg line = line.strip('\n') 468ef3f5543SValentin Rothberg symbols.extend(get_symbols_in_line(line)) 469ef3f5543SValentin Rothberg for symbol in set(symbols): 470ef3f5543SValentin Rothberg if REGEX_NUMERIC.match(symbol): 4710bd38ae3SValentin Rothberg # ignore numeric values 4720bd38ae3SValentin Rothberg continue 473ef3f5543SValentin Rothberg references.append(symbol) 474e2042a8aSValentin Rothberg 475e2042a8aSValentin Rothberg return defined, references 47624fe1f03SValentin Rothberg 47724fe1f03SValentin Rothberg 47824fe1f03SValentin Rothbergif __name__ == "__main__": 47924fe1f03SValentin Rothberg main() 480