1 #!/usr/bin/env python3 1 #!/usr/bin/env python3 2 # SPDX-License-Identifier: GPL-2.0-only 2 # SPDX-License-Identifier: GPL-2.0-only 3 3 4 """Find Kconfig symbols that are referenced bu 4 """Find Kconfig symbols that are referenced but not defined.""" 5 5 6 # (c) 2014-2017 Valentin Rothberg <valentinroth 6 # (c) 2014-2017 Valentin Rothberg <valentinrothberg@gmail.com> 7 # (c) 2014 Stefan Hengelein <stefan.hengelein@f 7 # (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de> 8 # 8 # 9 9 10 10 11 import argparse 11 import argparse 12 import difflib 12 import difflib 13 import os 13 import os 14 import re 14 import re 15 import signal 15 import signal 16 import subprocess 16 import subprocess 17 import sys 17 import sys 18 from multiprocessing import Pool, cpu_count 18 from multiprocessing import Pool, cpu_count 19 19 20 20 21 # regex expressions 21 # regex expressions 22 OPERATORS = r"&|\(|\)|\||\!" 22 OPERATORS = r"&|\(|\)|\||\!" 23 SYMBOL = r"(?:\w*[A-Z0-9]\w*){2,}" 23 SYMBOL = r"(?:\w*[A-Z0-9]\w*){2,}" 24 DEF = r"^\s*(?:menu){,1}config\s+(" + SYMBOL + 24 DEF = r"^\s*(?:menu){,1}config\s+(" + SYMBOL + r")\s*" 25 EXPR = r"(?:" + OPERATORS + r"|\s|" + SYMBOL + 25 EXPR = r"(?:" + OPERATORS + r"|\s|" + SYMBOL + r")+" 26 DEFAULT = r"default\s+.*?(?:if\s.+){,1}" 26 DEFAULT = r"default\s+.*?(?:if\s.+){,1}" 27 STMT = r"^\s*(?:if|select|imply|depends\s+on|( 27 STMT = r"^\s*(?:if|select|imply|depends\s+on|(?:" + DEFAULT + r"))\s+" + EXPR 28 SOURCE_SYMBOL = r"(?:\W|\b)+[D]{,1}CONFIG_(" + 28 SOURCE_SYMBOL = r"(?:\W|\b)+[D]{,1}CONFIG_(" + SYMBOL + r")" 29 29 30 # regex objects 30 # regex objects 31 REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\. 31 REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$") 32 REGEX_SYMBOL = re.compile(r'(?!\B)' + SYMBOL + 32 REGEX_SYMBOL = re.compile(r'(?!\B)' + SYMBOL + r'(?!\B)') 33 REGEX_SOURCE_SYMBOL = re.compile(SOURCE_SYMBOL 33 REGEX_SOURCE_SYMBOL = re.compile(SOURCE_SYMBOL) 34 REGEX_KCONFIG_DEF = re.compile(DEF) 34 REGEX_KCONFIG_DEF = re.compile(DEF) 35 REGEX_KCONFIG_EXPR = re.compile(EXPR) 35 REGEX_KCONFIG_EXPR = re.compile(EXPR) 36 REGEX_KCONFIG_STMT = re.compile(STMT) 36 REGEX_KCONFIG_STMT = re.compile(STMT) 37 REGEX_FILTER_SYMBOLS = re.compile(r"[A-Za-z0-9 37 REGEX_FILTER_SYMBOLS = re.compile(r"[A-Za-z0-9]$") 38 REGEX_NUMERIC = re.compile(r"0[xX][0-9a-fA-F]+ 38 REGEX_NUMERIC = re.compile(r"0[xX][0-9a-fA-F]+|[0-9]+") 39 REGEX_QUOTES = re.compile("(\"(.*?)\")") 39 REGEX_QUOTES = re.compile("(\"(.*?)\")") 40 40 41 41 42 def parse_options(): 42 def parse_options(): 43 """The user interface of this module.""" 43 """The user interface of this module.""" 44 usage = "Run this tool to detect Kconfig s 44 usage = "Run this tool to detect Kconfig symbols that are referenced but " \ 45 "not defined in Kconfig. If no op 45 "not defined in Kconfig. If no option is specified, " \ 46 "checkkconfigsymbols defaults to c 46 "checkkconfigsymbols defaults to check your current tree. " \ 47 "Please note that specifying commi 47 "Please note that specifying commits will 'git reset --hard\' " \ 48 "your current tree! You may save 48 "your current tree! You may save uncommitted changes to avoid " \ 49 "losing data." 49 "losing data." 50 50 51 parser = argparse.ArgumentParser(descripti 51 parser = argparse.ArgumentParser(description=usage) 52 52 53 parser.add_argument('-c', '--commit', dest 53 parser.add_argument('-c', '--commit', dest='commit', action='store', 54 default="", 54 default="", 55 help="check if the spe 55 help="check if the specified commit (hash) introduces " 56 "undefined Kconfi 56 "undefined Kconfig symbols") 57 57 58 parser.add_argument('-d', '--diff', dest=' 58 parser.add_argument('-d', '--diff', dest='diff', action='store', 59 default="", 59 default="", 60 help="diff undefined s 60 help="diff undefined symbols between two commits " 61 "(e.g., -d commmi 61 "(e.g., -d commmit1..commit2)") 62 62 63 parser.add_argument('-f', '--find', dest=' 63 parser.add_argument('-f', '--find', dest='find', action='store_true', 64 default=False, 64 default=False, 65 help="find and show co 65 help="find and show commits that may cause symbols to be " 66 "missing (require 66 "missing (required to run with --diff)") 67 67 68 parser.add_argument('-i', '--ignore', dest 68 parser.add_argument('-i', '--ignore', dest='ignore', action='store', 69 default="", 69 default="", 70 help="ignore files mat 70 help="ignore files matching this Python regex " 71 "(e.g., -i '.*def 71 "(e.g., -i '.*defconfig')") 72 72 73 parser.add_argument('-s', '--sim', dest='s 73 parser.add_argument('-s', '--sim', dest='sim', action='store', default="", 74 help="print a list of 74 help="print a list of max. 10 string-similar symbols") 75 75 76 parser.add_argument('--force', dest='force 76 parser.add_argument('--force', dest='force', action='store_true', 77 default=False, 77 default=False, 78 help="reset current Gi 78 help="reset current Git tree even when it's dirty") 79 79 80 parser.add_argument('--no-color', dest='co 80 parser.add_argument('--no-color', dest='color', action='store_false', 81 default=True, 81 default=True, 82 help="don't print colo 82 help="don't print colored output (default when not " 83 "outputting to a 83 "outputting to a terminal)") 84 84 85 args = parser.parse_args() 85 args = parser.parse_args() 86 86 87 if args.commit and args.diff: 87 if args.commit and args.diff: 88 sys.exit("Please specify only one opti 88 sys.exit("Please specify only one option at once.") 89 89 90 if args.diff and not re.match(r"^[\w\-\.\^ 90 if args.diff and not re.match(r"^[\w\-\.\^]+\.\.[\w\-\.\^]+$", args.diff): 91 sys.exit("Please specify valid input i 91 sys.exit("Please specify valid input in the following format: " 92 "\'commit1..commit2\'") 92 "\'commit1..commit2\'") 93 93 94 if args.commit or args.diff: 94 if args.commit or args.diff: 95 if not args.force and tree_is_dirty(): 95 if not args.force and tree_is_dirty(): 96 sys.exit("The current Git tree is 96 sys.exit("The current Git tree is dirty (see 'git status'). " 97 "Running this script may\ 97 "Running this script may\ndelete important data since it " 98 "calls 'git reset --hard' 98 "calls 'git reset --hard' for some performance\nreasons. " 99 " Please run this script 99 " Please run this script in a clean Git tree or pass " 100 "'--force' if you\nwant t 100 "'--force' if you\nwant to ignore this warning and " 101 "continue.") 101 "continue.") 102 102 103 if args.commit: 103 if args.commit: 104 if args.commit.startswith('HEAD'): 104 if args.commit.startswith('HEAD'): 105 sys.exit("The --commit option can' 105 sys.exit("The --commit option can't use the HEAD ref") 106 106 107 args.find = False 107 args.find = False 108 108 109 if args.ignore: 109 if args.ignore: 110 try: 110 try: 111 re.match(args.ignore, "this/is/jus 111 re.match(args.ignore, "this/is/just/a/test.c") 112 except: 112 except: 113 sys.exit("Please specify a valid P 113 sys.exit("Please specify a valid Python regex.") 114 114 115 return args 115 return args 116 116 117 117 118 def print_undefined_symbols(): 118 def print_undefined_symbols(): 119 """Main function of this module.""" 119 """Main function of this module.""" 120 args = parse_options() 120 args = parse_options() 121 121 122 global COLOR 122 global COLOR 123 COLOR = args.color and sys.stdout.isatty() 123 COLOR = args.color and sys.stdout.isatty() 124 124 125 if args.sim and not args.commit and not ar 125 if args.sim and not args.commit and not args.diff: 126 sims = find_sims(args.sim, args.ignore 126 sims = find_sims(args.sim, args.ignore) 127 if sims: 127 if sims: 128 print("%s: %s" % (yel("Similar sym 128 print("%s: %s" % (yel("Similar symbols"), ', '.join(sims))) 129 else: 129 else: 130 print("%s: no similar symbols foun 130 print("%s: no similar symbols found" % yel("Similar symbols")) 131 sys.exit(0) 131 sys.exit(0) 132 132 133 # dictionary of (un)defined symbols 133 # dictionary of (un)defined symbols 134 defined = {} 134 defined = {} 135 undefined = {} 135 undefined = {} 136 136 137 if args.commit or args.diff: 137 if args.commit or args.diff: 138 head = get_head() 138 head = get_head() 139 139 140 # get commit range 140 # get commit range 141 commit_a = None 141 commit_a = None 142 commit_b = None 142 commit_b = None 143 if args.commit: 143 if args.commit: 144 commit_a = args.commit + "~" 144 commit_a = args.commit + "~" 145 commit_b = args.commit 145 commit_b = args.commit 146 elif args.diff: 146 elif args.diff: 147 split = args.diff.split("..") 147 split = args.diff.split("..") 148 commit_a = split[0] 148 commit_a = split[0] 149 commit_b = split[1] 149 commit_b = split[1] 150 undefined_a = {} 150 undefined_a = {} 151 undefined_b = {} 151 undefined_b = {} 152 152 153 # get undefined items before the commi 153 # get undefined items before the commit 154 reset(commit_a) 154 reset(commit_a) 155 undefined_a, _ = check_symbols(args.ig 155 undefined_a, _ = check_symbols(args.ignore) 156 156 157 # get undefined items for the commit 157 # get undefined items for the commit 158 reset(commit_b) 158 reset(commit_b) 159 undefined_b, defined = check_symbols(a 159 undefined_b, defined = check_symbols(args.ignore) 160 160 161 # report cases that are present for th 161 # report cases that are present for the commit but not before 162 for symbol in sorted(undefined_b): 162 for symbol in sorted(undefined_b): 163 # symbol has not been undefined be 163 # symbol has not been undefined before 164 if symbol not in undefined_a: 164 if symbol not in undefined_a: 165 files = sorted(undefined_b.get 165 files = sorted(undefined_b.get(symbol)) 166 undefined[symbol] = files 166 undefined[symbol] = files 167 # check if there are new files tha 167 # check if there are new files that reference the undefined symbol 168 else: 168 else: 169 files = sorted(undefined_b.get 169 files = sorted(undefined_b.get(symbol) - 170 undefined_a.get 170 undefined_a.get(symbol)) 171 if files: 171 if files: 172 undefined[symbol] = files 172 undefined[symbol] = files 173 173 174 # reset to head 174 # reset to head 175 reset(head) 175 reset(head) 176 176 177 # default to check the entire tree 177 # default to check the entire tree 178 else: 178 else: 179 undefined, defined = check_symbols(arg 179 undefined, defined = check_symbols(args.ignore) 180 180 181 # now print the output 181 # now print the output 182 for symbol in sorted(undefined): 182 for symbol in sorted(undefined): 183 print(red(symbol)) 183 print(red(symbol)) 184 184 185 files = sorted(undefined.get(symbol)) 185 files = sorted(undefined.get(symbol)) 186 print("%s: %s" % (yel("Referencing fil 186 print("%s: %s" % (yel("Referencing files"), ", ".join(files))) 187 187 188 sims = find_sims(symbol, args.ignore, 188 sims = find_sims(symbol, args.ignore, defined) 189 sims_out = yel("Similar symbols") 189 sims_out = yel("Similar symbols") 190 if sims: 190 if sims: 191 print("%s: %s" % (sims_out, ', '.j 191 print("%s: %s" % (sims_out, ', '.join(sims))) 192 else: 192 else: 193 print("%s: %s" % (sims_out, "no si 193 print("%s: %s" % (sims_out, "no similar symbols found")) 194 194 195 if args.find: 195 if args.find: 196 print("%s:" % yel("Commits changin 196 print("%s:" % yel("Commits changing symbol")) 197 commits = find_commits(symbol, arg 197 commits = find_commits(symbol, args.diff) 198 if commits: 198 if commits: 199 for commit in commits: 199 for commit in commits: 200 commit = commit.split(" ", 200 commit = commit.split(" ", 1) 201 print("\t- %s (\"%s\")" % 201 print("\t- %s (\"%s\")" % (yel(commit[0]), commit[1])) 202 else: 202 else: 203 print("\t- no commit found") 203 print("\t- no commit found") 204 print() # new line 204 print() # new line 205 205 206 206 207 def reset(commit): 207 def reset(commit): 208 """Reset current git tree to %commit.""" 208 """Reset current git tree to %commit.""" 209 execute(["git", "reset", "--hard", commit] 209 execute(["git", "reset", "--hard", commit]) 210 210 211 211 212 def yel(string): 212 def yel(string): 213 """ 213 """ 214 Color %string yellow. 214 Color %string yellow. 215 """ 215 """ 216 return "\033[33m%s\033[0m" % string if COL 216 return "\033[33m%s\033[0m" % string if COLOR else string 217 217 218 218 219 def red(string): 219 def red(string): 220 """ 220 """ 221 Color %string red. 221 Color %string red. 222 """ 222 """ 223 return "\033[31m%s\033[0m" % string if COL 223 return "\033[31m%s\033[0m" % string if COLOR else string 224 224 225 225 226 def execute(cmd): 226 def execute(cmd): 227 """Execute %cmd and return stdout. Exit i 227 """Execute %cmd and return stdout. Exit in case of error.""" 228 try: 228 try: 229 stdout = subprocess.check_output(cmd, 229 stdout = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=False) 230 stdout = stdout.decode(errors='replace 230 stdout = stdout.decode(errors='replace') 231 except subprocess.CalledProcessError as fa 231 except subprocess.CalledProcessError as fail: 232 exit(fail) 232 exit(fail) 233 return stdout 233 return stdout 234 234 235 235 236 def find_commits(symbol, diff): 236 def find_commits(symbol, diff): 237 """Find commits changing %symbol in the gi 237 """Find commits changing %symbol in the given range of %diff.""" 238 commits = execute(["git", "log", "--pretty 238 commits = execute(["git", "log", "--pretty=oneline", 239 "--abbrev-commit", "-G" 239 "--abbrev-commit", "-G", 240 symbol, diff]) 240 symbol, diff]) 241 return [x for x in commits.split("\n") if 241 return [x for x in commits.split("\n") if x] 242 242 243 243 244 def tree_is_dirty(): 244 def tree_is_dirty(): 245 """Return true if the current working tree 245 """Return true if the current working tree is dirty (i.e., if any file has 246 been added, deleted, modified, renamed or 246 been added, deleted, modified, renamed or copied but not committed).""" 247 stdout = execute(["git", "status", "--porc 247 stdout = execute(["git", "status", "--porcelain"]) 248 for line in stdout: 248 for line in stdout: 249 if re.findall(r"[URMADC]{1}", line[:2] 249 if re.findall(r"[URMADC]{1}", line[:2]): 250 return True 250 return True 251 return False 251 return False 252 252 253 253 254 def get_head(): 254 def get_head(): 255 """Return commit hash of current HEAD.""" 255 """Return commit hash of current HEAD.""" 256 stdout = execute(["git", "rev-parse", "HEA 256 stdout = execute(["git", "rev-parse", "HEAD"]) 257 return stdout.strip('\n') 257 return stdout.strip('\n') 258 258 259 259 260 def partition(lst, size): 260 def partition(lst, size): 261 """Partition list @lst into eveni-sized li 261 """Partition list @lst into eveni-sized lists of size @size.""" 262 return [lst[i::size] for i in range(size)] 262 return [lst[i::size] for i in range(size)] 263 263 264 264 265 def init_worker(): 265 def init_worker(): 266 """Set signal handler to ignore SIGINT.""" 266 """Set signal handler to ignore SIGINT.""" 267 signal.signal(signal.SIGINT, signal.SIG_IG 267 signal.signal(signal.SIGINT, signal.SIG_IGN) 268 268 269 269 270 def find_sims(symbol, ignore, defined=[]): 270 def find_sims(symbol, ignore, defined=[]): 271 """Return a list of max. ten Kconfig symbo 271 """Return a list of max. ten Kconfig symbols that are string-similar to 272 @symbol.""" 272 @symbol.""" 273 if defined: 273 if defined: 274 return difflib.get_close_matches(symbo 274 return difflib.get_close_matches(symbol, set(defined), 10) 275 275 276 pool = Pool(cpu_count(), init_worker) 276 pool = Pool(cpu_count(), init_worker) 277 kfiles = [] 277 kfiles = [] 278 for gitfile in get_files(): 278 for gitfile in get_files(): 279 if REGEX_FILE_KCONFIG.match(gitfile): 279 if REGEX_FILE_KCONFIG.match(gitfile): 280 kfiles.append(gitfile) 280 kfiles.append(gitfile) 281 281 282 arglist = [] 282 arglist = [] 283 for part in partition(kfiles, cpu_count()) 283 for part in partition(kfiles, cpu_count()): 284 arglist.append((part, ignore)) 284 arglist.append((part, ignore)) 285 285 286 for res in pool.map(parse_kconfig_files, a 286 for res in pool.map(parse_kconfig_files, arglist): 287 defined.extend(res[0]) 287 defined.extend(res[0]) 288 288 289 return difflib.get_close_matches(symbol, s 289 return difflib.get_close_matches(symbol, set(defined), 10) 290 290 291 291 292 def get_files(): 292 def get_files(): 293 """Return a list of all files in the curre 293 """Return a list of all files in the current git directory.""" 294 # use 'git ls-files' to get the worklist 294 # use 'git ls-files' to get the worklist 295 stdout = execute(["git", "ls-files"]) 295 stdout = execute(["git", "ls-files"]) 296 if len(stdout) > 0 and stdout[-1] == "\n": 296 if len(stdout) > 0 and stdout[-1] == "\n": 297 stdout = stdout[:-1] 297 stdout = stdout[:-1] 298 298 299 files = [] 299 files = [] 300 for gitfile in stdout.rsplit("\n"): 300 for gitfile in stdout.rsplit("\n"): 301 if ".git" in gitfile or "ChangeLog" in 301 if ".git" in gitfile or "ChangeLog" in gitfile or \ 302 ".log" in gitfile or os.path.i 302 ".log" in gitfile or os.path.isdir(gitfile) or \ 303 gitfile.startswith("tools/"): 303 gitfile.startswith("tools/"): 304 continue 304 continue 305 files.append(gitfile) 305 files.append(gitfile) 306 return files 306 return files 307 307 308 308 309 def check_symbols(ignore): 309 def check_symbols(ignore): 310 """Find undefined Kconfig symbols and retu 310 """Find undefined Kconfig symbols and return a dict with the symbol as key 311 and a list of referencing files as value. 311 and a list of referencing files as value. Files matching %ignore are not 312 checked for undefined symbols.""" 312 checked for undefined symbols.""" 313 pool = Pool(cpu_count(), init_worker) 313 pool = Pool(cpu_count(), init_worker) 314 try: 314 try: 315 return check_symbols_helper(pool, igno 315 return check_symbols_helper(pool, ignore) 316 except KeyboardInterrupt: 316 except KeyboardInterrupt: 317 pool.terminate() 317 pool.terminate() 318 pool.join() 318 pool.join() 319 sys.exit(1) 319 sys.exit(1) 320 320 321 321 322 def check_symbols_helper(pool, ignore): 322 def check_symbols_helper(pool, ignore): 323 """Helper method for check_symbols(). Use 323 """Helper method for check_symbols(). Used to catch keyboard interrupts in 324 check_symbols() in order to properly termi 324 check_symbols() in order to properly terminate running worker processes.""" 325 source_files = [] 325 source_files = [] 326 kconfig_files = [] 326 kconfig_files = [] 327 defined_symbols = [] 327 defined_symbols = [] 328 referenced_symbols = dict() # {file: [sym 328 referenced_symbols = dict() # {file: [symbols]} 329 329 330 for gitfile in get_files(): 330 for gitfile in get_files(): 331 if REGEX_FILE_KCONFIG.match(gitfile): 331 if REGEX_FILE_KCONFIG.match(gitfile): 332 kconfig_files.append(gitfile) 332 kconfig_files.append(gitfile) 333 else: 333 else: 334 if ignore and re.match(ignore, git 334 if ignore and re.match(ignore, gitfile): 335 continue 335 continue 336 # add source files that do not mat 336 # add source files that do not match the ignore pattern 337 source_files.append(gitfile) 337 source_files.append(gitfile) 338 338 339 # parse source files 339 # parse source files 340 arglist = partition(source_files, cpu_coun 340 arglist = partition(source_files, cpu_count()) 341 for res in pool.map(parse_source_files, ar 341 for res in pool.map(parse_source_files, arglist): 342 referenced_symbols.update(res) 342 referenced_symbols.update(res) 343 343 344 # parse kconfig files 344 # parse kconfig files 345 arglist = [] 345 arglist = [] 346 for part in partition(kconfig_files, cpu_c 346 for part in partition(kconfig_files, cpu_count()): 347 arglist.append((part, ignore)) 347 arglist.append((part, ignore)) 348 for res in pool.map(parse_kconfig_files, a 348 for res in pool.map(parse_kconfig_files, arglist): 349 defined_symbols.extend(res[0]) 349 defined_symbols.extend(res[0]) 350 referenced_symbols.update(res[1]) 350 referenced_symbols.update(res[1]) 351 defined_symbols = set(defined_symbols) 351 defined_symbols = set(defined_symbols) 352 352 353 # inverse mapping of referenced_symbols to 353 # inverse mapping of referenced_symbols to dict(symbol: [files]) 354 inv_map = dict() 354 inv_map = dict() 355 for _file, symbols in referenced_symbols.i 355 for _file, symbols in referenced_symbols.items(): 356 for symbol in symbols: 356 for symbol in symbols: 357 inv_map[symbol] = inv_map.get(symb 357 inv_map[symbol] = inv_map.get(symbol, set()) 358 inv_map[symbol].add(_file) 358 inv_map[symbol].add(_file) 359 referenced_symbols = inv_map 359 referenced_symbols = inv_map 360 360 361 undefined = {} # {symbol: [files]} 361 undefined = {} # {symbol: [files]} 362 for symbol in sorted(referenced_symbols): 362 for symbol in sorted(referenced_symbols): 363 # filter some false positives 363 # filter some false positives 364 if symbol == "FOO" or symbol == "BAR" 364 if symbol == "FOO" or symbol == "BAR" or \ 365 symbol == "FOO_BAR" or symbol 365 symbol == "FOO_BAR" or symbol == "XXX": 366 continue 366 continue 367 if symbol not in defined_symbols: 367 if symbol not in defined_symbols: 368 if symbol.endswith("_MODULE"): 368 if symbol.endswith("_MODULE"): 369 # avoid false positives for ke 369 # avoid false positives for kernel modules 370 if symbol[:-len("_MODULE")] in 370 if symbol[:-len("_MODULE")] in defined_symbols: 371 continue 371 continue 372 undefined[symbol] = referenced_sym 372 undefined[symbol] = referenced_symbols.get(symbol) 373 return undefined, defined_symbols 373 return undefined, defined_symbols 374 374 375 375 376 def parse_source_files(source_files): 376 def parse_source_files(source_files): 377 """Parse each source file in @source_files 377 """Parse each source file in @source_files and return dictionary with source 378 files as keys and lists of references Kcon 378 files as keys and lists of references Kconfig symbols as values.""" 379 referenced_symbols = dict() 379 referenced_symbols = dict() 380 for sfile in source_files: 380 for sfile in source_files: 381 referenced_symbols[sfile] = parse_sour 381 referenced_symbols[sfile] = parse_source_file(sfile) 382 return referenced_symbols 382 return referenced_symbols 383 383 384 384 385 def parse_source_file(sfile): 385 def parse_source_file(sfile): 386 """Parse @sfile and return a list of refer 386 """Parse @sfile and return a list of referenced Kconfig symbols.""" 387 lines = [] 387 lines = [] 388 references = [] 388 references = [] 389 389 390 if not os.path.exists(sfile): 390 if not os.path.exists(sfile): 391 return references 391 return references 392 392 393 with open(sfile, "r", encoding='utf-8', er 393 with open(sfile, "r", encoding='utf-8', errors='replace') as stream: 394 lines = stream.readlines() 394 lines = stream.readlines() 395 395 396 for line in lines: 396 for line in lines: 397 if "CONFIG_" not in line: 397 if "CONFIG_" not in line: 398 continue 398 continue 399 symbols = REGEX_SOURCE_SYMBOL.findall( 399 symbols = REGEX_SOURCE_SYMBOL.findall(line) 400 for symbol in symbols: 400 for symbol in symbols: 401 if not REGEX_FILTER_SYMBOLS.search 401 if not REGEX_FILTER_SYMBOLS.search(symbol): 402 continue 402 continue 403 references.append(symbol) 403 references.append(symbol) 404 404 405 return references 405 return references 406 406 407 407 408 def get_symbols_in_line(line): 408 def get_symbols_in_line(line): 409 """Return mentioned Kconfig symbols in @li 409 """Return mentioned Kconfig symbols in @line.""" 410 return REGEX_SYMBOL.findall(line) 410 return REGEX_SYMBOL.findall(line) 411 411 412 412 413 def parse_kconfig_files(args): 413 def parse_kconfig_files(args): 414 """Parse kconfig files and return tuple of 414 """Parse kconfig files and return tuple of defined and references Kconfig 415 symbols. Note, @args is a tuple of a list 415 symbols. Note, @args is a tuple of a list of files and the @ignore 416 pattern.""" 416 pattern.""" 417 kconfig_files = args[0] 417 kconfig_files = args[0] 418 ignore = args[1] 418 ignore = args[1] 419 defined_symbols = [] 419 defined_symbols = [] 420 referenced_symbols = dict() 420 referenced_symbols = dict() 421 421 422 for kfile in kconfig_files: 422 for kfile in kconfig_files: 423 defined, references = parse_kconfig_fi 423 defined, references = parse_kconfig_file(kfile) 424 defined_symbols.extend(defined) 424 defined_symbols.extend(defined) 425 if ignore and re.match(ignore, kfile): 425 if ignore and re.match(ignore, kfile): 426 # do not collect references for fi 426 # do not collect references for files that match the ignore pattern 427 continue 427 continue 428 referenced_symbols[kfile] = references 428 referenced_symbols[kfile] = references 429 return (defined_symbols, referenced_symbol 429 return (defined_symbols, referenced_symbols) 430 430 431 431 432 def parse_kconfig_file(kfile): 432 def parse_kconfig_file(kfile): 433 """Parse @kfile and update symbol definiti 433 """Parse @kfile and update symbol definitions and references.""" 434 lines = [] 434 lines = [] 435 defined = [] 435 defined = [] 436 references = [] 436 references = [] 437 437 438 if not os.path.exists(kfile): 438 if not os.path.exists(kfile): 439 return defined, references 439 return defined, references 440 440 441 with open(kfile, "r", encoding='utf-8', er 441 with open(kfile, "r", encoding='utf-8', errors='replace') as stream: 442 lines = stream.readlines() 442 lines = stream.readlines() 443 443 444 for i in range(len(lines)): 444 for i in range(len(lines)): 445 line = lines[i] 445 line = lines[i] 446 line = line.strip('\n') 446 line = line.strip('\n') 447 line = line.split("#")[0] # ignore co 447 line = line.split("#")[0] # ignore comments 448 448 449 if REGEX_KCONFIG_DEF.match(line): 449 if REGEX_KCONFIG_DEF.match(line): 450 symbol_def = REGEX_KCONFIG_DEF.fin 450 symbol_def = REGEX_KCONFIG_DEF.findall(line) 451 defined.append(symbol_def[0]) 451 defined.append(symbol_def[0]) 452 elif REGEX_KCONFIG_STMT.match(line): 452 elif REGEX_KCONFIG_STMT.match(line): 453 line = REGEX_QUOTES.sub("", line) 453 line = REGEX_QUOTES.sub("", line) 454 symbols = get_symbols_in_line(line 454 symbols = get_symbols_in_line(line) 455 # multi-line statements 455 # multi-line statements 456 while line.endswith("\\"): 456 while line.endswith("\\"): 457 i += 1 457 i += 1 458 line = lines[i] 458 line = lines[i] 459 line = line.strip('\n') 459 line = line.strip('\n') 460 symbols.extend(get_symbols_in_ 460 symbols.extend(get_symbols_in_line(line)) 461 for symbol in set(symbols): 461 for symbol in set(symbols): 462 if REGEX_NUMERIC.match(symbol) 462 if REGEX_NUMERIC.match(symbol): 463 # ignore numeric values 463 # ignore numeric values 464 continue 464 continue 465 references.append(symbol) 465 references.append(symbol) 466 466 467 return defined, references 467 return defined, references 468 468 469 469 470 def main(): 470 def main(): 471 try: 471 try: 472 print_undefined_symbols() 472 print_undefined_symbols() 473 except BrokenPipeError: 473 except BrokenPipeError: 474 # Python flushes standard streams on e 474 # Python flushes standard streams on exit; redirect remaining output 475 # to devnull to avoid another BrokenPi 475 # to devnull to avoid another BrokenPipeError at shutdown 476 devnull = os.open(os.devnull, os.O_WRO 476 devnull = os.open(os.devnull, os.O_WRONLY) 477 os.dup2(devnull, sys.stdout.fileno()) 477 os.dup2(devnull, sys.stdout.fileno()) 478 sys.exit(1) # Python exits with error 478 sys.exit(1) # Python exits with error code 1 on EPIPE 479 479 480 480 481 if __name__ == "__main__": 481 if __name__ == "__main__": 482 main() 482 main()
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.