17c5227afSValentin Rothberg#!/usr/bin/env python3 224fe1f03SValentin Rothberg 3b1a3f243SValentin Rothberg"""Find Kconfig symbols that are referenced but not defined.""" 424fe1f03SValentin Rothberg 58e8e3331SValentin Rothberg# (c) 2014-2017 Valentin Rothberg <valentinrothberg@gmail.com> 6cc641d55SValentin Rothberg# (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de> 724fe1f03SValentin Rothberg# 8cc641d55SValentin Rothberg# Licensed under the terms of the GNU GPL License version 2 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) 3724fe1f03SValentin RothbergREGEX_KCONFIG_HELP = re.compile(r"^\s+(help|---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: 10514390e31SValentin Rothberg args.find = False 106a42fa92cSValentin Rothberg 10714390e31SValentin Rothberg if args.ignore: 108cf132e4aSValentin Rothberg try: 10914390e31SValentin Rothberg re.match(args.ignore, "this/is/just/a/test.c") 110cf132e4aSValentin Rothberg except: 111cf132e4aSValentin Rothberg sys.exit("Please specify a valid Python regex.") 112cf132e4aSValentin Rothberg 11314390e31SValentin Rothberg return args 114b1a3f243SValentin Rothberg 115b1a3f243SValentin Rothberg 11624fe1f03SValentin Rothbergdef main(): 11724fe1f03SValentin Rothberg """Main function of this module.""" 11814390e31SValentin Rothberg args = parse_options() 119b1a3f243SValentin Rothberg 12036c79c7fSValentin Rothberg global COLOR 12136c79c7fSValentin Rothberg COLOR = args.color and sys.stdout.isatty() 1224c73c088SAndrew Donnellan 12314390e31SValentin Rothberg if args.sim and not args.commit and not args.diff: 12414390e31SValentin Rothberg sims = find_sims(args.sim, args.ignore) 1251b2c8414SValentin Rothberg if sims: 1267c5227afSValentin Rothberg print("%s: %s" % (yel("Similar symbols"), ', '.join(sims))) 1271b2c8414SValentin Rothberg else: 1287c5227afSValentin Rothberg print("%s: no similar symbols found" % yel("Similar symbols")) 1291b2c8414SValentin Rothberg sys.exit(0) 1301b2c8414SValentin Rothberg 1311b2c8414SValentin Rothberg # dictionary of (un)defined symbols 1321b2c8414SValentin Rothberg defined = {} 1331b2c8414SValentin Rothberg undefined = {} 1341b2c8414SValentin Rothberg 13514390e31SValentin Rothberg if args.commit or args.diff: 136b1a3f243SValentin Rothberg head = get_head() 137b1a3f243SValentin Rothberg 138b1a3f243SValentin Rothberg # get commit range 139b1a3f243SValentin Rothberg commit_a = None 140b1a3f243SValentin Rothberg commit_b = None 14114390e31SValentin Rothberg if args.commit: 14214390e31SValentin Rothberg commit_a = args.commit + "~" 14314390e31SValentin Rothberg commit_b = args.commit 14414390e31SValentin Rothberg elif args.diff: 14514390e31SValentin Rothberg split = args.diff.split("..") 146b1a3f243SValentin Rothberg commit_a = split[0] 147b1a3f243SValentin Rothberg commit_b = split[1] 148b1a3f243SValentin Rothberg undefined_a = {} 149b1a3f243SValentin Rothberg undefined_b = {} 150b1a3f243SValentin Rothberg 151b1a3f243SValentin Rothberg # get undefined items before the commit 1522f9cc12bSValentin Rothberg reset(commit_a) 15314390e31SValentin Rothberg undefined_a, _ = check_symbols(args.ignore) 154b1a3f243SValentin Rothberg 155b1a3f243SValentin Rothberg # get undefined items for the commit 1562f9cc12bSValentin Rothberg reset(commit_b) 15714390e31SValentin Rothberg undefined_b, defined = check_symbols(args.ignore) 158b1a3f243SValentin Rothberg 159b1a3f243SValentin Rothberg # report cases that are present for the commit but not before 160ef3f5543SValentin Rothberg for symbol in sorted(undefined_b): 161ef3f5543SValentin Rothberg # symbol has not been undefined before 162ef3f5543SValentin Rothberg if symbol not in undefined_a: 163ef3f5543SValentin Rothberg files = sorted(undefined_b.get(symbol)) 164ef3f5543SValentin Rothberg undefined[symbol] = files 165ef3f5543SValentin Rothberg # check if there are new files that reference the undefined symbol 166b1a3f243SValentin Rothberg else: 167ef3f5543SValentin Rothberg files = sorted(undefined_b.get(symbol) - 168ef3f5543SValentin Rothberg undefined_a.get(symbol)) 169b1a3f243SValentin Rothberg if files: 170ef3f5543SValentin Rothberg undefined[symbol] = files 171b1a3f243SValentin Rothberg 172b1a3f243SValentin Rothberg # reset to head 1732f9cc12bSValentin Rothberg reset(head) 174b1a3f243SValentin Rothberg 175b1a3f243SValentin Rothberg # default to check the entire tree 176b1a3f243SValentin Rothberg else: 17714390e31SValentin Rothberg undefined, defined = check_symbols(args.ignore) 1781b2c8414SValentin Rothberg 1791b2c8414SValentin Rothberg # now print the output 180ef3f5543SValentin Rothberg for symbol in sorted(undefined): 181ef3f5543SValentin Rothberg print(red(symbol)) 1821b2c8414SValentin Rothberg 183ef3f5543SValentin Rothberg files = sorted(undefined.get(symbol)) 1847c5227afSValentin Rothberg print("%s: %s" % (yel("Referencing files"), ", ".join(files))) 1851b2c8414SValentin Rothberg 186ef3f5543SValentin Rothberg sims = find_sims(symbol, args.ignore, defined) 1871b2c8414SValentin Rothberg sims_out = yel("Similar symbols") 1881b2c8414SValentin Rothberg if sims: 1897c5227afSValentin Rothberg print("%s: %s" % (sims_out, ', '.join(sims))) 1901b2c8414SValentin Rothberg else: 1917c5227afSValentin Rothberg print("%s: %s" % (sims_out, "no similar symbols found")) 1921b2c8414SValentin Rothberg 19314390e31SValentin Rothberg if args.find: 1947c5227afSValentin Rothberg print("%s:" % yel("Commits changing symbol")) 195ef3f5543SValentin Rothberg commits = find_commits(symbol, args.diff) 1961b2c8414SValentin Rothberg if commits: 1971b2c8414SValentin Rothberg for commit in commits: 1981b2c8414SValentin Rothberg commit = commit.split(" ", 1) 1997c5227afSValentin Rothberg print("\t- %s (\"%s\")" % (yel(commit[0]), commit[1])) 2001b2c8414SValentin Rothberg else: 2017c5227afSValentin Rothberg print("\t- no commit found") 2027c5227afSValentin Rothberg print() # new line 203c7455663SValentin Rothberg 204c7455663SValentin Rothberg 2052f9cc12bSValentin Rothbergdef reset(commit): 2062f9cc12bSValentin Rothberg """Reset current git tree to %commit.""" 2072f9cc12bSValentin Rothberg execute(["git", "reset", "--hard", commit]) 2082f9cc12bSValentin Rothberg 2092f9cc12bSValentin Rothberg 210c7455663SValentin Rothbergdef yel(string): 211c7455663SValentin Rothberg """ 212c7455663SValentin Rothberg Color %string yellow. 213c7455663SValentin Rothberg """ 21436c79c7fSValentin Rothberg return "\033[33m%s\033[0m" % string if COLOR else string 215c7455663SValentin Rothberg 216c7455663SValentin Rothberg 217c7455663SValentin Rothbergdef red(string): 218c7455663SValentin Rothberg """ 219c7455663SValentin Rothberg Color %string red. 220c7455663SValentin Rothberg """ 22136c79c7fSValentin Rothberg return "\033[31m%s\033[0m" % string if COLOR else string 222b1a3f243SValentin Rothberg 223b1a3f243SValentin Rothberg 224b1a3f243SValentin Rothbergdef execute(cmd): 225b1a3f243SValentin Rothberg """Execute %cmd and return stdout. Exit in case of error.""" 226f175ba17SValentin Rothberg try: 2272f9cc12bSValentin Rothberg stdout = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=False) 2287c5227afSValentin Rothberg stdout = stdout.decode(errors='replace') 229f175ba17SValentin Rothberg except subprocess.CalledProcessError as fail: 2302f9cc12bSValentin Rothberg exit(fail) 231b1a3f243SValentin Rothberg return stdout 232b1a3f243SValentin Rothberg 233b1a3f243SValentin Rothberg 234a42fa92cSValentin Rothbergdef find_commits(symbol, diff): 235a42fa92cSValentin Rothberg """Find commits changing %symbol in the given range of %diff.""" 2362f9cc12bSValentin Rothberg commits = execute(["git", "log", "--pretty=oneline", 2372f9cc12bSValentin Rothberg "--abbrev-commit", "-G", 2382f9cc12bSValentin Rothberg symbol, diff]) 2391b2c8414SValentin Rothberg return [x for x in commits.split("\n") if x] 240a42fa92cSValentin Rothberg 241a42fa92cSValentin Rothberg 242b1a3f243SValentin Rothbergdef tree_is_dirty(): 243b1a3f243SValentin Rothberg """Return true if the current working tree is dirty (i.e., if any file has 244b1a3f243SValentin Rothberg been added, deleted, modified, renamed or copied but not committed).""" 2452f9cc12bSValentin Rothberg stdout = execute(["git", "status", "--porcelain"]) 246b1a3f243SValentin Rothberg for line in stdout: 247b1a3f243SValentin Rothberg if re.findall(r"[URMADC]{1}", line[:2]): 248b1a3f243SValentin Rothberg return True 249b1a3f243SValentin Rothberg return False 250b1a3f243SValentin Rothberg 251b1a3f243SValentin Rothberg 252b1a3f243SValentin Rothbergdef get_head(): 253b1a3f243SValentin Rothberg """Return commit hash of current HEAD.""" 2542f9cc12bSValentin Rothberg stdout = execute(["git", "rev-parse", "HEAD"]) 255b1a3f243SValentin Rothberg return stdout.strip('\n') 256b1a3f243SValentin Rothberg 257b1a3f243SValentin Rothberg 258e2042a8aSValentin Rothbergdef partition(lst, size): 259e2042a8aSValentin Rothberg """Partition list @lst into eveni-sized lists of size @size.""" 2607c5227afSValentin Rothberg return [lst[i::size] for i in range(size)] 261e2042a8aSValentin Rothberg 262e2042a8aSValentin Rothberg 263e2042a8aSValentin Rothbergdef init_worker(): 264e2042a8aSValentin Rothberg """Set signal handler to ignore SIGINT.""" 265e2042a8aSValentin Rothberg signal.signal(signal.SIGINT, signal.SIG_IGN) 266e2042a8aSValentin Rothberg 267e2042a8aSValentin Rothberg 2681b2c8414SValentin Rothbergdef find_sims(symbol, ignore, defined=[]): 2691b2c8414SValentin Rothberg """Return a list of max. ten Kconfig symbols that are string-similar to 2701b2c8414SValentin Rothberg @symbol.""" 2711b2c8414SValentin Rothberg if defined: 2728e8e3331SValentin Rothberg return difflib.get_close_matches(symbol, set(defined), 10) 2731b2c8414SValentin Rothberg 2741b2c8414SValentin Rothberg pool = Pool(cpu_count(), init_worker) 2751b2c8414SValentin Rothberg kfiles = [] 2761b2c8414SValentin Rothberg for gitfile in get_files(): 2771b2c8414SValentin Rothberg if REGEX_FILE_KCONFIG.match(gitfile): 2781b2c8414SValentin Rothberg kfiles.append(gitfile) 2791b2c8414SValentin Rothberg 2801b2c8414SValentin Rothberg arglist = [] 2811b2c8414SValentin Rothberg for part in partition(kfiles, cpu_count()): 2821b2c8414SValentin Rothberg arglist.append((part, ignore)) 2831b2c8414SValentin Rothberg 2841b2c8414SValentin Rothberg for res in pool.map(parse_kconfig_files, arglist): 2851b2c8414SValentin Rothberg defined.extend(res[0]) 2861b2c8414SValentin Rothberg 2878e8e3331SValentin Rothberg return difflib.get_close_matches(symbol, set(defined), 10) 2881b2c8414SValentin Rothberg 2891b2c8414SValentin Rothberg 2901b2c8414SValentin Rothbergdef get_files(): 2911b2c8414SValentin Rothberg """Return a list of all files in the current git directory.""" 2921b2c8414SValentin Rothberg # use 'git ls-files' to get the worklist 2932f9cc12bSValentin Rothberg stdout = execute(["git", "ls-files"]) 2941b2c8414SValentin Rothberg if len(stdout) > 0 and stdout[-1] == "\n": 2951b2c8414SValentin Rothberg stdout = stdout[:-1] 2961b2c8414SValentin Rothberg 2971b2c8414SValentin Rothberg files = [] 2981b2c8414SValentin Rothberg for gitfile in stdout.rsplit("\n"): 2991b2c8414SValentin Rothberg if ".git" in gitfile or "ChangeLog" in gitfile or \ 3001b2c8414SValentin Rothberg ".log" in gitfile or os.path.isdir(gitfile) or \ 3011b2c8414SValentin Rothberg gitfile.startswith("tools/"): 3021b2c8414SValentin Rothberg continue 3031b2c8414SValentin Rothberg files.append(gitfile) 3041b2c8414SValentin Rothberg return files 3051b2c8414SValentin Rothberg 3061b2c8414SValentin Rothberg 307cf132e4aSValentin Rothbergdef check_symbols(ignore): 308b1a3f243SValentin Rothberg """Find undefined Kconfig symbols and return a dict with the symbol as key 309cf132e4aSValentin Rothberg and a list of referencing files as value. Files matching %ignore are not 310cf132e4aSValentin Rothberg checked for undefined symbols.""" 311e2042a8aSValentin Rothberg pool = Pool(cpu_count(), init_worker) 312e2042a8aSValentin Rothberg try: 313e2042a8aSValentin Rothberg return check_symbols_helper(pool, ignore) 314e2042a8aSValentin Rothberg except KeyboardInterrupt: 315e2042a8aSValentin Rothberg pool.terminate() 316e2042a8aSValentin Rothberg pool.join() 317e2042a8aSValentin Rothberg sys.exit(1) 318e2042a8aSValentin Rothberg 319e2042a8aSValentin Rothberg 320e2042a8aSValentin Rothbergdef check_symbols_helper(pool, ignore): 321e2042a8aSValentin Rothberg """Helper method for check_symbols(). Used to catch keyboard interrupts in 322e2042a8aSValentin Rothberg check_symbols() in order to properly terminate running worker processes.""" 32324fe1f03SValentin Rothberg source_files = [] 32424fe1f03SValentin Rothberg kconfig_files = [] 325ef3f5543SValentin Rothberg defined_symbols = [] 326ef3f5543SValentin Rothberg referenced_symbols = dict() # {file: [symbols]} 32724fe1f03SValentin Rothberg 3281b2c8414SValentin Rothberg for gitfile in get_files(): 32924fe1f03SValentin Rothberg if REGEX_FILE_KCONFIG.match(gitfile): 33024fe1f03SValentin Rothberg kconfig_files.append(gitfile) 33124fe1f03SValentin Rothberg else: 332e2042a8aSValentin Rothberg if ignore and not re.match(ignore, gitfile): 333e2042a8aSValentin Rothberg continue 334e2042a8aSValentin Rothberg # add source files that do not match the ignore pattern 33524fe1f03SValentin Rothberg source_files.append(gitfile) 33624fe1f03SValentin Rothberg 337e2042a8aSValentin Rothberg # parse source files 338e2042a8aSValentin Rothberg arglist = partition(source_files, cpu_count()) 339e2042a8aSValentin Rothberg for res in pool.map(parse_source_files, arglist): 340ef3f5543SValentin Rothberg referenced_symbols.update(res) 34124fe1f03SValentin Rothberg 342e2042a8aSValentin Rothberg # parse kconfig files 343e2042a8aSValentin Rothberg arglist = [] 344e2042a8aSValentin Rothberg for part in partition(kconfig_files, cpu_count()): 345e2042a8aSValentin Rothberg arglist.append((part, ignore)) 346e2042a8aSValentin Rothberg for res in pool.map(parse_kconfig_files, arglist): 347ef3f5543SValentin Rothberg defined_symbols.extend(res[0]) 348ef3f5543SValentin Rothberg referenced_symbols.update(res[1]) 349ef3f5543SValentin Rothberg defined_symbols = set(defined_symbols) 350e2042a8aSValentin Rothberg 351ef3f5543SValentin Rothberg # inverse mapping of referenced_symbols to dict(symbol: [files]) 352e2042a8aSValentin Rothberg inv_map = dict() 353ef3f5543SValentin Rothberg for _file, symbols in referenced_symbols.items(): 354ef3f5543SValentin Rothberg for symbol in symbols: 355ef3f5543SValentin Rothberg inv_map[symbol] = inv_map.get(symbol, set()) 356ef3f5543SValentin Rothberg inv_map[symbol].add(_file) 357ef3f5543SValentin Rothberg referenced_symbols = inv_map 35824fe1f03SValentin Rothberg 359ef3f5543SValentin Rothberg undefined = {} # {symbol: [files]} 360ef3f5543SValentin Rothberg for symbol in sorted(referenced_symbols): 361cc641d55SValentin Rothberg # filter some false positives 362ef3f5543SValentin Rothberg if symbol == "FOO" or symbol == "BAR" or \ 363ef3f5543SValentin Rothberg symbol == "FOO_BAR" or symbol == "XXX": 364cc641d55SValentin Rothberg continue 365ef3f5543SValentin Rothberg if symbol not in defined_symbols: 366ef3f5543SValentin Rothberg if symbol.endswith("_MODULE"): 367cc641d55SValentin Rothberg # avoid false positives for kernel modules 368ef3f5543SValentin Rothberg if symbol[:-len("_MODULE")] in defined_symbols: 36924fe1f03SValentin Rothberg continue 370ef3f5543SValentin Rothberg undefined[symbol] = referenced_symbols.get(symbol) 371ef3f5543SValentin Rothberg return undefined, defined_symbols 37224fe1f03SValentin Rothberg 37324fe1f03SValentin Rothberg 374e2042a8aSValentin Rothbergdef parse_source_files(source_files): 375e2042a8aSValentin Rothberg """Parse each source file in @source_files and return dictionary with source 376e2042a8aSValentin Rothberg files as keys and lists of references Kconfig symbols as values.""" 377ef3f5543SValentin Rothberg referenced_symbols = dict() 378e2042a8aSValentin Rothberg for sfile in source_files: 379ef3f5543SValentin Rothberg referenced_symbols[sfile] = parse_source_file(sfile) 380ef3f5543SValentin Rothberg return referenced_symbols 381e2042a8aSValentin Rothberg 382e2042a8aSValentin Rothberg 383e2042a8aSValentin Rothbergdef parse_source_file(sfile): 384ef3f5543SValentin Rothberg """Parse @sfile and return a list of referenced Kconfig symbols.""" 38524fe1f03SValentin Rothberg lines = [] 386e2042a8aSValentin Rothberg references = [] 387e2042a8aSValentin Rothberg 388e2042a8aSValentin Rothberg if not os.path.exists(sfile): 389e2042a8aSValentin Rothberg return references 390e2042a8aSValentin Rothberg 3917c5227afSValentin Rothberg with open(sfile, "r", encoding='utf-8', errors='replace') as stream: 39224fe1f03SValentin Rothberg lines = stream.readlines() 39324fe1f03SValentin Rothberg 39424fe1f03SValentin Rothberg for line in lines: 39536c79c7fSValentin Rothberg if "CONFIG_" not in line: 39624fe1f03SValentin Rothberg continue 397ef3f5543SValentin Rothberg symbols = REGEX_SOURCE_SYMBOL.findall(line) 398ef3f5543SValentin Rothberg for symbol in symbols: 399ef3f5543SValentin Rothberg if not REGEX_FILTER_SYMBOLS.search(symbol): 40024fe1f03SValentin Rothberg continue 401ef3f5543SValentin Rothberg references.append(symbol) 402e2042a8aSValentin Rothberg 403e2042a8aSValentin Rothberg return references 40424fe1f03SValentin Rothberg 40524fe1f03SValentin Rothberg 406ef3f5543SValentin Rothbergdef get_symbols_in_line(line): 407ef3f5543SValentin Rothberg """Return mentioned Kconfig symbols in @line.""" 408ef3f5543SValentin Rothberg return REGEX_SYMBOL.findall(line) 40924fe1f03SValentin Rothberg 41024fe1f03SValentin Rothberg 411e2042a8aSValentin Rothbergdef parse_kconfig_files(args): 412e2042a8aSValentin Rothberg """Parse kconfig files and return tuple of defined and references Kconfig 413e2042a8aSValentin Rothberg symbols. Note, @args is a tuple of a list of files and the @ignore 414e2042a8aSValentin Rothberg pattern.""" 415e2042a8aSValentin Rothberg kconfig_files = args[0] 416e2042a8aSValentin Rothberg ignore = args[1] 417ef3f5543SValentin Rothberg defined_symbols = [] 418ef3f5543SValentin Rothberg referenced_symbols = dict() 419e2042a8aSValentin Rothberg 420e2042a8aSValentin Rothberg for kfile in kconfig_files: 421e2042a8aSValentin Rothberg defined, references = parse_kconfig_file(kfile) 422ef3f5543SValentin Rothberg defined_symbols.extend(defined) 423e2042a8aSValentin Rothberg if ignore and re.match(ignore, kfile): 424e2042a8aSValentin Rothberg # do not collect references for files that match the ignore pattern 425e2042a8aSValentin Rothberg continue 426ef3f5543SValentin Rothberg referenced_symbols[kfile] = references 427ef3f5543SValentin Rothberg return (defined_symbols, referenced_symbols) 428e2042a8aSValentin Rothberg 429e2042a8aSValentin Rothberg 430e2042a8aSValentin Rothbergdef parse_kconfig_file(kfile): 431ef3f5543SValentin Rothberg """Parse @kfile and update symbol definitions and references.""" 43224fe1f03SValentin Rothberg lines = [] 433e2042a8aSValentin Rothberg defined = [] 434e2042a8aSValentin Rothberg references = [] 43524fe1f03SValentin Rothberg skip = False 43624fe1f03SValentin Rothberg 437e2042a8aSValentin Rothberg if not os.path.exists(kfile): 438e2042a8aSValentin Rothberg return defined, references 439e2042a8aSValentin Rothberg 4407c5227afSValentin Rothberg with open(kfile, "r", encoding='utf-8', errors='replace') as stream: 44124fe1f03SValentin Rothberg lines = stream.readlines() 44224fe1f03SValentin Rothberg 44324fe1f03SValentin Rothberg for i in range(len(lines)): 44424fe1f03SValentin Rothberg line = lines[i] 44524fe1f03SValentin Rothberg line = line.strip('\n') 446cc641d55SValentin Rothberg line = line.split("#")[0] # ignore comments 44724fe1f03SValentin Rothberg 44824fe1f03SValentin Rothberg if REGEX_KCONFIG_DEF.match(line): 449ef3f5543SValentin Rothberg symbol_def = REGEX_KCONFIG_DEF.findall(line) 450ef3f5543SValentin Rothberg defined.append(symbol_def[0]) 45124fe1f03SValentin Rothberg skip = False 45224fe1f03SValentin Rothberg elif REGEX_KCONFIG_HELP.match(line): 45324fe1f03SValentin Rothberg skip = True 45424fe1f03SValentin Rothberg elif skip: 455cc641d55SValentin Rothberg # ignore content of help messages 45624fe1f03SValentin Rothberg pass 45724fe1f03SValentin Rothberg elif REGEX_KCONFIG_STMT.match(line): 458e2042a8aSValentin Rothberg line = REGEX_QUOTES.sub("", line) 459ef3f5543SValentin Rothberg symbols = get_symbols_in_line(line) 460cc641d55SValentin Rothberg # multi-line statements 46124fe1f03SValentin Rothberg while line.endswith("\\"): 46224fe1f03SValentin Rothberg i += 1 46324fe1f03SValentin Rothberg line = lines[i] 46424fe1f03SValentin Rothberg line = line.strip('\n') 465ef3f5543SValentin Rothberg symbols.extend(get_symbols_in_line(line)) 466ef3f5543SValentin Rothberg for symbol in set(symbols): 467ef3f5543SValentin Rothberg if REGEX_NUMERIC.match(symbol): 4680bd38ae3SValentin Rothberg # ignore numeric values 4690bd38ae3SValentin Rothberg continue 470ef3f5543SValentin Rothberg references.append(symbol) 471e2042a8aSValentin Rothberg 472e2042a8aSValentin Rothberg return defined, references 47324fe1f03SValentin Rothberg 47424fe1f03SValentin Rothberg 47524fe1f03SValentin Rothbergif __name__ == "__main__": 47624fe1f03SValentin Rothberg main() 477