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