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) 37ef3f5543SValentin RothbergREGEX_FILTER_SYMBOLS = re.compile(r"[A-Za-z0-9]$") 380bd38ae3SValentin RothbergREGEX_NUMERIC = re.compile(r"0[xX][0-9a-fA-F]+|[0-9]+") 39e2042a8aSValentin RothbergREGEX_QUOTES = re.compile("(\"(.*?)\")") 4024fe1f03SValentin Rothberg 4124fe1f03SValentin Rothberg 42b1a3f243SValentin Rothbergdef parse_options(): 43b1a3f243SValentin Rothberg """The user interface of this module.""" 4414390e31SValentin Rothberg usage = "Run this tool to detect Kconfig symbols that are referenced but " \ 4514390e31SValentin Rothberg "not defined in Kconfig. If no option is specified, " \ 4614390e31SValentin Rothberg "checkkconfigsymbols defaults to check your current tree. " \ 4714390e31SValentin Rothberg "Please note that specifying commits will 'git reset --hard\' " \ 4814390e31SValentin Rothberg "your current tree! You may save uncommitted changes to avoid " \ 4914390e31SValentin Rothberg "losing data." 50b1a3f243SValentin Rothberg 5114390e31SValentin Rothberg parser = argparse.ArgumentParser(description=usage) 52b1a3f243SValentin Rothberg 5314390e31SValentin Rothberg parser.add_argument('-c', '--commit', dest='commit', action='store', 54b1a3f243SValentin Rothberg default="", 5514390e31SValentin Rothberg help="check if the specified commit (hash) introduces " 5614390e31SValentin Rothberg "undefined Kconfig symbols") 57b1a3f243SValentin Rothberg 5814390e31SValentin Rothberg parser.add_argument('-d', '--diff', dest='diff', action='store', 59b1a3f243SValentin Rothberg default="", 6014390e31SValentin Rothberg help="diff undefined symbols between two commits " 6114390e31SValentin Rothberg "(e.g., -d commmit1..commit2)") 62b1a3f243SValentin Rothberg 6314390e31SValentin Rothberg parser.add_argument('-f', '--find', dest='find', action='store_true', 64a42fa92cSValentin Rothberg default=False, 6514390e31SValentin Rothberg help="find and show commits that may cause symbols to be " 6614390e31SValentin Rothberg "missing (required to run with --diff)") 67a42fa92cSValentin Rothberg 6814390e31SValentin Rothberg parser.add_argument('-i', '--ignore', dest='ignore', action='store', 69cf132e4aSValentin Rothberg default="", 7014390e31SValentin Rothberg help="ignore files matching this Python regex " 7114390e31SValentin Rothberg "(e.g., -i '.*defconfig')") 72cf132e4aSValentin Rothberg 7314390e31SValentin Rothberg parser.add_argument('-s', '--sim', dest='sim', action='store', default="", 7414390e31SValentin Rothberg help="print a list of max. 10 string-similar symbols") 751b2c8414SValentin Rothberg 7614390e31SValentin Rothberg parser.add_argument('--force', dest='force', action='store_true', 77b1a3f243SValentin Rothberg default=False, 7814390e31SValentin Rothberg help="reset current Git tree even when it's dirty") 79b1a3f243SValentin Rothberg 8014390e31SValentin Rothberg parser.add_argument('--no-color', dest='color', action='store_false', 814c73c088SAndrew Donnellan default=True, 8214390e31SValentin Rothberg help="don't print colored output (default when not " 8314390e31SValentin Rothberg "outputting to a terminal)") 844c73c088SAndrew Donnellan 8514390e31SValentin Rothberg args = parser.parse_args() 86b1a3f243SValentin Rothberg 8714390e31SValentin Rothberg if args.commit and args.diff: 88b1a3f243SValentin Rothberg sys.exit("Please specify only one option at once.") 89b1a3f243SValentin Rothberg 900d18c192SValentin Rothberg if args.diff and not re.match(r"^[\w\-\.\^]+\.\.[\w\-\.\^]+$", args.diff): 91b1a3f243SValentin Rothberg sys.exit("Please specify valid input in the following format: " 9238cbfe4fSAndreas Ziegler "\'commit1..commit2\'") 93b1a3f243SValentin Rothberg 9414390e31SValentin Rothberg if args.commit or args.diff: 9514390e31SValentin Rothberg if not args.force and tree_is_dirty(): 96b1a3f243SValentin Rothberg sys.exit("The current Git tree is dirty (see 'git status'). " 97b1a3f243SValentin Rothberg "Running this script may\ndelete important data since it " 98b1a3f243SValentin Rothberg "calls 'git reset --hard' for some performance\nreasons. " 99b1a3f243SValentin Rothberg " Please run this script in a clean Git tree or pass " 100b1a3f243SValentin Rothberg "'--force' if you\nwant to ignore this warning and " 101b1a3f243SValentin Rothberg "continue.") 102b1a3f243SValentin Rothberg 10314390e31SValentin Rothberg if args.commit: 104d62d5aedSAriel Marcovitch if args.commit.startswith('HEAD'): 105d62d5aedSAriel Marcovitch sys.exit("The --commit option can't use the HEAD ref") 106d62d5aedSAriel Marcovitch 10714390e31SValentin Rothberg args.find = False 108a42fa92cSValentin Rothberg 10914390e31SValentin Rothberg if args.ignore: 110cf132e4aSValentin Rothberg try: 11114390e31SValentin Rothberg re.match(args.ignore, "this/is/just/a/test.c") 112cf132e4aSValentin Rothberg except: 113cf132e4aSValentin Rothberg sys.exit("Please specify a valid Python regex.") 114cf132e4aSValentin Rothberg 11514390e31SValentin Rothberg return args 116b1a3f243SValentin Rothberg 117b1a3f243SValentin Rothberg 118*87c7ee67SMasahiro Yamadadef print_undefined_symbols(): 11924fe1f03SValentin Rothberg """Main function of this module.""" 12014390e31SValentin Rothberg args = parse_options() 121b1a3f243SValentin Rothberg 12236c79c7fSValentin Rothberg global COLOR 12336c79c7fSValentin Rothberg COLOR = args.color and sys.stdout.isatty() 1244c73c088SAndrew Donnellan 12514390e31SValentin Rothberg if args.sim and not args.commit and not args.diff: 12614390e31SValentin Rothberg sims = find_sims(args.sim, args.ignore) 1271b2c8414SValentin Rothberg if sims: 1287c5227afSValentin Rothberg print("%s: %s" % (yel("Similar symbols"), ', '.join(sims))) 1291b2c8414SValentin Rothberg else: 1307c5227afSValentin Rothberg print("%s: no similar symbols found" % yel("Similar symbols")) 1311b2c8414SValentin Rothberg sys.exit(0) 1321b2c8414SValentin Rothberg 1331b2c8414SValentin Rothberg # dictionary of (un)defined symbols 1341b2c8414SValentin Rothberg defined = {} 1351b2c8414SValentin Rothberg undefined = {} 1361b2c8414SValentin Rothberg 13714390e31SValentin Rothberg if args.commit or args.diff: 138b1a3f243SValentin Rothberg head = get_head() 139b1a3f243SValentin Rothberg 140b1a3f243SValentin Rothberg # get commit range 141b1a3f243SValentin Rothberg commit_a = None 142b1a3f243SValentin Rothberg commit_b = None 14314390e31SValentin Rothberg if args.commit: 14414390e31SValentin Rothberg commit_a = args.commit + "~" 14514390e31SValentin Rothberg commit_b = args.commit 14614390e31SValentin Rothberg elif args.diff: 14714390e31SValentin Rothberg split = args.diff.split("..") 148b1a3f243SValentin Rothberg commit_a = split[0] 149b1a3f243SValentin Rothberg commit_b = split[1] 150b1a3f243SValentin Rothberg undefined_a = {} 151b1a3f243SValentin Rothberg undefined_b = {} 152b1a3f243SValentin Rothberg 153b1a3f243SValentin Rothberg # get undefined items before the commit 1542f9cc12bSValentin Rothberg reset(commit_a) 15514390e31SValentin Rothberg undefined_a, _ = check_symbols(args.ignore) 156b1a3f243SValentin Rothberg 157b1a3f243SValentin Rothberg # get undefined items for the commit 1582f9cc12bSValentin Rothberg reset(commit_b) 15914390e31SValentin Rothberg undefined_b, defined = check_symbols(args.ignore) 160b1a3f243SValentin Rothberg 161b1a3f243SValentin Rothberg # report cases that are present for the commit but not before 162ef3f5543SValentin Rothberg for symbol in sorted(undefined_b): 163ef3f5543SValentin Rothberg # symbol has not been undefined before 164ef3f5543SValentin Rothberg if symbol not in undefined_a: 165ef3f5543SValentin Rothberg files = sorted(undefined_b.get(symbol)) 166ef3f5543SValentin Rothberg undefined[symbol] = files 167ef3f5543SValentin Rothberg # check if there are new files that reference the undefined symbol 168b1a3f243SValentin Rothberg else: 169ef3f5543SValentin Rothberg files = sorted(undefined_b.get(symbol) - 170ef3f5543SValentin Rothberg undefined_a.get(symbol)) 171b1a3f243SValentin Rothberg if files: 172ef3f5543SValentin Rothberg undefined[symbol] = files 173b1a3f243SValentin Rothberg 174b1a3f243SValentin Rothberg # reset to head 1752f9cc12bSValentin Rothberg reset(head) 176b1a3f243SValentin Rothberg 177b1a3f243SValentin Rothberg # default to check the entire tree 178b1a3f243SValentin Rothberg else: 17914390e31SValentin Rothberg undefined, defined = check_symbols(args.ignore) 1801b2c8414SValentin Rothberg 1811b2c8414SValentin Rothberg # now print the output 182ef3f5543SValentin Rothberg for symbol in sorted(undefined): 183ef3f5543SValentin Rothberg print(red(symbol)) 1841b2c8414SValentin Rothberg 185ef3f5543SValentin Rothberg files = sorted(undefined.get(symbol)) 1867c5227afSValentin Rothberg print("%s: %s" % (yel("Referencing files"), ", ".join(files))) 1871b2c8414SValentin Rothberg 188ef3f5543SValentin Rothberg sims = find_sims(symbol, args.ignore, defined) 1891b2c8414SValentin Rothberg sims_out = yel("Similar symbols") 1901b2c8414SValentin Rothberg if sims: 1917c5227afSValentin Rothberg print("%s: %s" % (sims_out, ', '.join(sims))) 1921b2c8414SValentin Rothberg else: 1937c5227afSValentin Rothberg print("%s: %s" % (sims_out, "no similar symbols found")) 1941b2c8414SValentin Rothberg 19514390e31SValentin Rothberg if args.find: 1967c5227afSValentin Rothberg print("%s:" % yel("Commits changing symbol")) 197ef3f5543SValentin Rothberg commits = find_commits(symbol, args.diff) 1981b2c8414SValentin Rothberg if commits: 1991b2c8414SValentin Rothberg for commit in commits: 2001b2c8414SValentin Rothberg commit = commit.split(" ", 1) 2017c5227afSValentin Rothberg print("\t- %s (\"%s\")" % (yel(commit[0]), commit[1])) 2021b2c8414SValentin Rothberg else: 2037c5227afSValentin Rothberg print("\t- no commit found") 2047c5227afSValentin Rothberg print() # new line 205c7455663SValentin Rothberg 206c7455663SValentin Rothberg 2072f9cc12bSValentin Rothbergdef reset(commit): 2082f9cc12bSValentin Rothberg """Reset current git tree to %commit.""" 2092f9cc12bSValentin Rothberg execute(["git", "reset", "--hard", commit]) 2102f9cc12bSValentin Rothberg 2112f9cc12bSValentin Rothberg 212c7455663SValentin Rothbergdef yel(string): 213c7455663SValentin Rothberg """ 214c7455663SValentin Rothberg Color %string yellow. 215c7455663SValentin Rothberg """ 21636c79c7fSValentin Rothberg return "\033[33m%s\033[0m" % string if COLOR else string 217c7455663SValentin Rothberg 218c7455663SValentin Rothberg 219c7455663SValentin Rothbergdef red(string): 220c7455663SValentin Rothberg """ 221c7455663SValentin Rothberg Color %string red. 222c7455663SValentin Rothberg """ 22336c79c7fSValentin Rothberg return "\033[31m%s\033[0m" % string if COLOR else string 224b1a3f243SValentin Rothberg 225b1a3f243SValentin Rothberg 226b1a3f243SValentin Rothbergdef execute(cmd): 227b1a3f243SValentin Rothberg """Execute %cmd and return stdout. Exit in case of error.""" 228f175ba17SValentin Rothberg try: 2292f9cc12bSValentin Rothberg stdout = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=False) 2307c5227afSValentin Rothberg stdout = stdout.decode(errors='replace') 231f175ba17SValentin Rothberg except subprocess.CalledProcessError as fail: 2322f9cc12bSValentin Rothberg exit(fail) 233b1a3f243SValentin Rothberg return stdout 234b1a3f243SValentin Rothberg 235b1a3f243SValentin Rothberg 236a42fa92cSValentin Rothbergdef find_commits(symbol, diff): 237a42fa92cSValentin Rothberg """Find commits changing %symbol in the given range of %diff.""" 2382f9cc12bSValentin Rothberg commits = execute(["git", "log", "--pretty=oneline", 2392f9cc12bSValentin Rothberg "--abbrev-commit", "-G", 2402f9cc12bSValentin Rothberg symbol, diff]) 2411b2c8414SValentin Rothberg return [x for x in commits.split("\n") if x] 242a42fa92cSValentin Rothberg 243a42fa92cSValentin Rothberg 244b1a3f243SValentin Rothbergdef tree_is_dirty(): 245b1a3f243SValentin Rothberg """Return true if the current working tree is dirty (i.e., if any file has 246b1a3f243SValentin Rothberg been added, deleted, modified, renamed or copied but not committed).""" 2472f9cc12bSValentin Rothberg stdout = execute(["git", "status", "--porcelain"]) 248b1a3f243SValentin Rothberg for line in stdout: 249b1a3f243SValentin Rothberg if re.findall(r"[URMADC]{1}", line[:2]): 250b1a3f243SValentin Rothberg return True 251b1a3f243SValentin Rothberg return False 252b1a3f243SValentin Rothberg 253b1a3f243SValentin Rothberg 254b1a3f243SValentin Rothbergdef get_head(): 255b1a3f243SValentin Rothberg """Return commit hash of current HEAD.""" 2562f9cc12bSValentin Rothberg stdout = execute(["git", "rev-parse", "HEAD"]) 257b1a3f243SValentin Rothberg return stdout.strip('\n') 258b1a3f243SValentin Rothberg 259b1a3f243SValentin Rothberg 260e2042a8aSValentin Rothbergdef partition(lst, size): 261e2042a8aSValentin Rothberg """Partition list @lst into eveni-sized lists of size @size.""" 2627c5227afSValentin Rothberg return [lst[i::size] for i in range(size)] 263e2042a8aSValentin Rothberg 264e2042a8aSValentin Rothberg 265e2042a8aSValentin Rothbergdef init_worker(): 266e2042a8aSValentin Rothberg """Set signal handler to ignore SIGINT.""" 267e2042a8aSValentin Rothberg signal.signal(signal.SIGINT, signal.SIG_IGN) 268e2042a8aSValentin Rothberg 269e2042a8aSValentin Rothberg 2701b2c8414SValentin Rothbergdef find_sims(symbol, ignore, defined=[]): 2711b2c8414SValentin Rothberg """Return a list of max. ten Kconfig symbols that are string-similar to 2721b2c8414SValentin Rothberg @symbol.""" 2731b2c8414SValentin Rothberg if defined: 2748e8e3331SValentin Rothberg return difflib.get_close_matches(symbol, set(defined), 10) 2751b2c8414SValentin Rothberg 2761b2c8414SValentin Rothberg pool = Pool(cpu_count(), init_worker) 2771b2c8414SValentin Rothberg kfiles = [] 2781b2c8414SValentin Rothberg for gitfile in get_files(): 2791b2c8414SValentin Rothberg if REGEX_FILE_KCONFIG.match(gitfile): 2801b2c8414SValentin Rothberg kfiles.append(gitfile) 2811b2c8414SValentin Rothberg 2821b2c8414SValentin Rothberg arglist = [] 2831b2c8414SValentin Rothberg for part in partition(kfiles, cpu_count()): 2841b2c8414SValentin Rothberg arglist.append((part, ignore)) 2851b2c8414SValentin Rothberg 2861b2c8414SValentin Rothberg for res in pool.map(parse_kconfig_files, arglist): 2871b2c8414SValentin Rothberg defined.extend(res[0]) 2881b2c8414SValentin Rothberg 2898e8e3331SValentin Rothberg return difflib.get_close_matches(symbol, set(defined), 10) 2901b2c8414SValentin Rothberg 2911b2c8414SValentin Rothberg 2921b2c8414SValentin Rothbergdef get_files(): 2931b2c8414SValentin Rothberg """Return a list of all files in the current git directory.""" 2941b2c8414SValentin Rothberg # use 'git ls-files' to get the worklist 2952f9cc12bSValentin Rothberg stdout = execute(["git", "ls-files"]) 2961b2c8414SValentin Rothberg if len(stdout) > 0 and stdout[-1] == "\n": 2971b2c8414SValentin Rothberg stdout = stdout[:-1] 2981b2c8414SValentin Rothberg 2991b2c8414SValentin Rothberg files = [] 3001b2c8414SValentin Rothberg for gitfile in stdout.rsplit("\n"): 3011b2c8414SValentin Rothberg if ".git" in gitfile or "ChangeLog" in gitfile or \ 3021b2c8414SValentin Rothberg ".log" in gitfile or os.path.isdir(gitfile) or \ 3031b2c8414SValentin Rothberg gitfile.startswith("tools/"): 3041b2c8414SValentin Rothberg continue 3051b2c8414SValentin Rothberg files.append(gitfile) 3061b2c8414SValentin Rothberg return files 3071b2c8414SValentin Rothberg 3081b2c8414SValentin Rothberg 309cf132e4aSValentin Rothbergdef check_symbols(ignore): 310b1a3f243SValentin Rothberg """Find undefined Kconfig symbols and return a dict with the symbol as key 311cf132e4aSValentin Rothberg and a list of referencing files as value. Files matching %ignore are not 312cf132e4aSValentin Rothberg checked for undefined symbols.""" 313e2042a8aSValentin Rothberg pool = Pool(cpu_count(), init_worker) 314e2042a8aSValentin Rothberg try: 315e2042a8aSValentin Rothberg return check_symbols_helper(pool, ignore) 316e2042a8aSValentin Rothberg except KeyboardInterrupt: 317e2042a8aSValentin Rothberg pool.terminate() 318e2042a8aSValentin Rothberg pool.join() 319e2042a8aSValentin Rothberg sys.exit(1) 320e2042a8aSValentin Rothberg 321e2042a8aSValentin Rothberg 322e2042a8aSValentin Rothbergdef check_symbols_helper(pool, ignore): 323e2042a8aSValentin Rothberg """Helper method for check_symbols(). Used to catch keyboard interrupts in 324e2042a8aSValentin Rothberg check_symbols() in order to properly terminate running worker processes.""" 32524fe1f03SValentin Rothberg source_files = [] 32624fe1f03SValentin Rothberg kconfig_files = [] 327ef3f5543SValentin Rothberg defined_symbols = [] 328ef3f5543SValentin Rothberg referenced_symbols = dict() # {file: [symbols]} 32924fe1f03SValentin Rothberg 3301b2c8414SValentin Rothberg for gitfile in get_files(): 33124fe1f03SValentin Rothberg if REGEX_FILE_KCONFIG.match(gitfile): 33224fe1f03SValentin Rothberg kconfig_files.append(gitfile) 33324fe1f03SValentin Rothberg else: 3341439ebd2SAriel Marcovitch if ignore and re.match(ignore, gitfile): 335e2042a8aSValentin Rothberg continue 336e2042a8aSValentin Rothberg # add source files that do not match the ignore pattern 33724fe1f03SValentin Rothberg source_files.append(gitfile) 33824fe1f03SValentin Rothberg 339e2042a8aSValentin Rothberg # parse source files 340e2042a8aSValentin Rothberg arglist = partition(source_files, cpu_count()) 341e2042a8aSValentin Rothberg for res in pool.map(parse_source_files, arglist): 342ef3f5543SValentin Rothberg referenced_symbols.update(res) 34324fe1f03SValentin Rothberg 344e2042a8aSValentin Rothberg # parse kconfig files 345e2042a8aSValentin Rothberg arglist = [] 346e2042a8aSValentin Rothberg for part in partition(kconfig_files, cpu_count()): 347e2042a8aSValentin Rothberg arglist.append((part, ignore)) 348e2042a8aSValentin Rothberg for res in pool.map(parse_kconfig_files, arglist): 349ef3f5543SValentin Rothberg defined_symbols.extend(res[0]) 350ef3f5543SValentin Rothberg referenced_symbols.update(res[1]) 351ef3f5543SValentin Rothberg defined_symbols = set(defined_symbols) 352e2042a8aSValentin Rothberg 353ef3f5543SValentin Rothberg # inverse mapping of referenced_symbols to dict(symbol: [files]) 354e2042a8aSValentin Rothberg inv_map = dict() 355ef3f5543SValentin Rothberg for _file, symbols in referenced_symbols.items(): 356ef3f5543SValentin Rothberg for symbol in symbols: 357ef3f5543SValentin Rothberg inv_map[symbol] = inv_map.get(symbol, set()) 358ef3f5543SValentin Rothberg inv_map[symbol].add(_file) 359ef3f5543SValentin Rothberg referenced_symbols = inv_map 36024fe1f03SValentin Rothberg 361ef3f5543SValentin Rothberg undefined = {} # {symbol: [files]} 362ef3f5543SValentin Rothberg for symbol in sorted(referenced_symbols): 363cc641d55SValentin Rothberg # filter some false positives 364ef3f5543SValentin Rothberg if symbol == "FOO" or symbol == "BAR" or \ 365ef3f5543SValentin Rothberg symbol == "FOO_BAR" or symbol == "XXX": 366cc641d55SValentin Rothberg continue 367ef3f5543SValentin Rothberg if symbol not in defined_symbols: 368ef3f5543SValentin Rothberg if symbol.endswith("_MODULE"): 369cc641d55SValentin Rothberg # avoid false positives for kernel modules 370ef3f5543SValentin Rothberg if symbol[:-len("_MODULE")] in defined_symbols: 37124fe1f03SValentin Rothberg continue 372ef3f5543SValentin Rothberg undefined[symbol] = referenced_symbols.get(symbol) 373ef3f5543SValentin Rothberg return undefined, defined_symbols 37424fe1f03SValentin Rothberg 37524fe1f03SValentin Rothberg 376e2042a8aSValentin Rothbergdef parse_source_files(source_files): 377e2042a8aSValentin Rothberg """Parse each source file in @source_files and return dictionary with source 378e2042a8aSValentin Rothberg files as keys and lists of references Kconfig symbols as values.""" 379ef3f5543SValentin Rothberg referenced_symbols = dict() 380e2042a8aSValentin Rothberg for sfile in source_files: 381ef3f5543SValentin Rothberg referenced_symbols[sfile] = parse_source_file(sfile) 382ef3f5543SValentin Rothberg return referenced_symbols 383e2042a8aSValentin Rothberg 384e2042a8aSValentin Rothberg 385e2042a8aSValentin Rothbergdef parse_source_file(sfile): 386ef3f5543SValentin Rothberg """Parse @sfile and return a list of referenced Kconfig symbols.""" 38724fe1f03SValentin Rothberg lines = [] 388e2042a8aSValentin Rothberg references = [] 389e2042a8aSValentin Rothberg 390e2042a8aSValentin Rothberg if not os.path.exists(sfile): 391e2042a8aSValentin Rothberg return references 392e2042a8aSValentin Rothberg 3937c5227afSValentin Rothberg with open(sfile, "r", encoding='utf-8', errors='replace') as stream: 39424fe1f03SValentin Rothberg lines = stream.readlines() 39524fe1f03SValentin Rothberg 39624fe1f03SValentin Rothberg for line in lines: 39736c79c7fSValentin Rothberg if "CONFIG_" not in line: 39824fe1f03SValentin Rothberg continue 399ef3f5543SValentin Rothberg symbols = REGEX_SOURCE_SYMBOL.findall(line) 400ef3f5543SValentin Rothberg for symbol in symbols: 401ef3f5543SValentin Rothberg if not REGEX_FILTER_SYMBOLS.search(symbol): 40224fe1f03SValentin Rothberg continue 403ef3f5543SValentin Rothberg references.append(symbol) 404e2042a8aSValentin Rothberg 405e2042a8aSValentin Rothberg return references 40624fe1f03SValentin Rothberg 40724fe1f03SValentin Rothberg 408ef3f5543SValentin Rothbergdef get_symbols_in_line(line): 409ef3f5543SValentin Rothberg """Return mentioned Kconfig symbols in @line.""" 410ef3f5543SValentin Rothberg return REGEX_SYMBOL.findall(line) 41124fe1f03SValentin Rothberg 41224fe1f03SValentin Rothberg 413e2042a8aSValentin Rothbergdef parse_kconfig_files(args): 414e2042a8aSValentin Rothberg """Parse kconfig files and return tuple of defined and references Kconfig 415e2042a8aSValentin Rothberg symbols. Note, @args is a tuple of a list of files and the @ignore 416e2042a8aSValentin Rothberg pattern.""" 417e2042a8aSValentin Rothberg kconfig_files = args[0] 418e2042a8aSValentin Rothberg ignore = args[1] 419ef3f5543SValentin Rothberg defined_symbols = [] 420ef3f5543SValentin Rothberg referenced_symbols = dict() 421e2042a8aSValentin Rothberg 422e2042a8aSValentin Rothberg for kfile in kconfig_files: 423e2042a8aSValentin Rothberg defined, references = parse_kconfig_file(kfile) 424ef3f5543SValentin Rothberg defined_symbols.extend(defined) 425e2042a8aSValentin Rothberg if ignore and re.match(ignore, kfile): 426e2042a8aSValentin Rothberg # do not collect references for files that match the ignore pattern 427e2042a8aSValentin Rothberg continue 428ef3f5543SValentin Rothberg referenced_symbols[kfile] = references 429ef3f5543SValentin Rothberg return (defined_symbols, referenced_symbols) 430e2042a8aSValentin Rothberg 431e2042a8aSValentin Rothberg 432e2042a8aSValentin Rothbergdef parse_kconfig_file(kfile): 433ef3f5543SValentin Rothberg """Parse @kfile and update symbol definitions and references.""" 43424fe1f03SValentin Rothberg lines = [] 435e2042a8aSValentin Rothberg defined = [] 436e2042a8aSValentin Rothberg references = [] 43724fe1f03SValentin Rothberg 438e2042a8aSValentin Rothberg if not os.path.exists(kfile): 439e2042a8aSValentin Rothberg return defined, references 440e2042a8aSValentin Rothberg 4417c5227afSValentin Rothberg with open(kfile, "r", encoding='utf-8', errors='replace') as stream: 44224fe1f03SValentin Rothberg lines = stream.readlines() 44324fe1f03SValentin Rothberg 44424fe1f03SValentin Rothberg for i in range(len(lines)): 44524fe1f03SValentin Rothberg line = lines[i] 44624fe1f03SValentin Rothberg line = line.strip('\n') 447cc641d55SValentin Rothberg line = line.split("#")[0] # ignore comments 44824fe1f03SValentin Rothberg 44924fe1f03SValentin Rothberg if REGEX_KCONFIG_DEF.match(line): 450ef3f5543SValentin Rothberg symbol_def = REGEX_KCONFIG_DEF.findall(line) 451ef3f5543SValentin Rothberg defined.append(symbol_def[0]) 45224fe1f03SValentin Rothberg elif REGEX_KCONFIG_STMT.match(line): 453e2042a8aSValentin Rothberg line = REGEX_QUOTES.sub("", line) 454ef3f5543SValentin Rothberg symbols = get_symbols_in_line(line) 455cc641d55SValentin Rothberg # multi-line statements 45624fe1f03SValentin Rothberg while line.endswith("\\"): 45724fe1f03SValentin Rothberg i += 1 45824fe1f03SValentin Rothberg line = lines[i] 45924fe1f03SValentin Rothberg line = line.strip('\n') 460ef3f5543SValentin Rothberg symbols.extend(get_symbols_in_line(line)) 461ef3f5543SValentin Rothberg for symbol in set(symbols): 462ef3f5543SValentin Rothberg if REGEX_NUMERIC.match(symbol): 4630bd38ae3SValentin Rothberg # ignore numeric values 4640bd38ae3SValentin Rothberg continue 465ef3f5543SValentin Rothberg references.append(symbol) 466e2042a8aSValentin Rothberg 467e2042a8aSValentin Rothberg return defined, references 46824fe1f03SValentin Rothberg 46924fe1f03SValentin Rothberg 470*87c7ee67SMasahiro Yamadadef main(): 471*87c7ee67SMasahiro Yamada try: 472*87c7ee67SMasahiro Yamada print_undefined_symbols() 473*87c7ee67SMasahiro Yamada except BrokenPipeError: 474*87c7ee67SMasahiro Yamada # Python flushes standard streams on exit; redirect remaining output 475*87c7ee67SMasahiro Yamada # to devnull to avoid another BrokenPipeError at shutdown 476*87c7ee67SMasahiro Yamada devnull = os.open(os.devnull, os.O_WRONLY) 477*87c7ee67SMasahiro Yamada os.dup2(devnull, sys.stdout.fileno()) 478*87c7ee67SMasahiro Yamada sys.exit(1) # Python exits with error code 1 on EPIPE 479*87c7ee67SMasahiro Yamada 480*87c7ee67SMasahiro Yamada 48124fe1f03SValentin Rothbergif __name__ == "__main__": 48224fe1f03SValentin Rothberg main() 483