Blame view

scripts/checkkconfigsymbols.py 15.4 KB
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
1
  #!/usr/bin/env python3
4f19048fd   Thomas Gleixner   treewide: Replace...
2
  # SPDX-License-Identifier: GPL-2.0-only
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
3

b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
4
  """Find Kconfig symbols that are referenced but not defined."""
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
5

8e8e33317   Valentin Rothberg   checkkconfigsymbo...
6
  # (c) 2014-2017 Valentin Rothberg <valentinrothberg@gmail.com>
cc641d552   Valentin Rothberg   checkkconfigsymbo...
7
  # (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de>
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
8
  #
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
9

14390e316   Valentin Rothberg   checkkconfigsymbo...
10
  import argparse
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
11
  import difflib
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
12
13
  import os
  import re
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
14
  import signal
f175ba174   Valentin Rothberg   checkkconfigsymbo...
15
  import subprocess
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
16
  import sys
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
17
  from multiprocessing import Pool, cpu_count
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
18

cc641d552   Valentin Rothberg   checkkconfigsymbo...
19
20
  
  # regex expressions
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
21
  OPERATORS = r"&|\(|\)|\||\!"
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
22
23
24
  SYMBOL = r"(?:\w*[A-Z0-9]\w*){2,}"
  DEF = r"^\s*(?:menu){,1}config\s+(" + SYMBOL + r")\s*"
  EXPR = r"(?:" + OPERATORS + r"|\s|" + SYMBOL + r")+"
0bd38ae35   Valentin Rothberg   scripts/checkkcon...
25
  DEFAULT = r"default\s+.*?(?:if\s.+){,1}"
3b28f4f2c   Valentin Rothberg   checkkconfigsymbo...
26
  STMT = r"^\s*(?:if|select|imply|depends\s+on|(?:" + DEFAULT + r"))\s+" + EXPR
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
27
  SOURCE_SYMBOL = r"(?:\W|\b)+[D]{,1}CONFIG_(" + SYMBOL + r")"
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
28

cc641d552   Valentin Rothberg   checkkconfigsymbo...
29
  # regex objects
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
30
  REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$")
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
31
32
  REGEX_SYMBOL = re.compile(r'(?!\B)' + SYMBOL + r'(?!\B)')
  REGEX_SOURCE_SYMBOL = re.compile(SOURCE_SYMBOL)
cc641d552   Valentin Rothberg   checkkconfigsymbo...
33
  REGEX_KCONFIG_DEF = re.compile(DEF)
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
34
35
  REGEX_KCONFIG_EXPR = re.compile(EXPR)
  REGEX_KCONFIG_STMT = re.compile(STMT)
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
36
  REGEX_FILTER_SYMBOLS = re.compile(r"[A-Za-z0-9]$")
0bd38ae35   Valentin Rothberg   scripts/checkkcon...
37
  REGEX_NUMERIC = re.compile(r"0[xX][0-9a-fA-F]+|[0-9]+")
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
38
  REGEX_QUOTES = re.compile("(\"(.*?)\")")
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
39

b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
40
41
  def parse_options():
      """The user interface of this module."""
14390e316   Valentin Rothberg   checkkconfigsymbo...
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
      usage = "Run this tool to detect Kconfig symbols that are referenced but " \
              "not defined in Kconfig.  If no option is specified, "             \
              "checkkconfigsymbols defaults to check your current tree.  "       \
              "Please note that specifying commits will 'git reset --hard\' "    \
              "your current tree!  You may save uncommitted changes to avoid "   \
              "losing data."
  
      parser = argparse.ArgumentParser(description=usage)
  
      parser.add_argument('-c', '--commit', dest='commit', action='store',
                          default="",
                          help="check if the specified commit (hash) introduces "
                               "undefined Kconfig symbols")
  
      parser.add_argument('-d', '--diff', dest='diff', action='store',
                          default="",
                          help="diff undefined symbols between two commits "
                               "(e.g., -d commmit1..commit2)")
  
      parser.add_argument('-f', '--find', dest='find', action='store_true',
                          default=False,
                          help="find and show commits that may cause symbols to be "
                               "missing (required to run with --diff)")
  
      parser.add_argument('-i', '--ignore', dest='ignore', action='store',
                          default="",
                          help="ignore files matching this Python regex "
                               "(e.g., -i '.*defconfig')")
  
      parser.add_argument('-s', '--sim', dest='sim', action='store', default="",
                          help="print a list of max. 10 string-similar symbols")
  
      parser.add_argument('--force', dest='force', action='store_true',
                          default=False,
                          help="reset current Git tree even when it's dirty")
  
      parser.add_argument('--no-color', dest='color', action='store_false',
                          default=True,
                          help="don't print colored output (default when not "
                               "outputting to a terminal)")
  
      args = parser.parse_args()
  
      if args.commit and args.diff:
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
86
          sys.exit("Please specify only one option at once.")
0d18c1928   Valentin Rothberg   checkkconfigsymbo...
87
      if args.diff and not re.match(r"^[\w\-\.\^]+\.\.[\w\-\.\^]+$", args.diff):
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
88
          sys.exit("Please specify valid input in the following format: "
38cbfe4fe   Andreas Ziegler   checkkconfigsymbo...
89
                   "\'commit1..commit2\'")
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
90

14390e316   Valentin Rothberg   checkkconfigsymbo...
91
92
      if args.commit or args.diff:
          if not args.force and tree_is_dirty():
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
93
94
95
96
97
98
99
100
101
              sys.exit("The current Git tree is dirty (see 'git status').  "
                       "Running this script may
  delete important data since it "
                       "calls 'git reset --hard' for some performance
  reasons. "
                       " Please run this script in a clean Git tree or pass "
                       "'--force' if you
  want to ignore this warning and "
                       "continue.")
14390e316   Valentin Rothberg   checkkconfigsymbo...
102
      if args.commit:
d62d5aed3   Ariel Marcovitch   checkkconfigsymbo...
103
104
          if args.commit.startswith('HEAD'):
              sys.exit("The --commit option can't use the HEAD ref")
14390e316   Valentin Rothberg   checkkconfigsymbo...
105
          args.find = False
a42fa92ce   Valentin Rothberg   checkkconfigsymbo...
106

14390e316   Valentin Rothberg   checkkconfigsymbo...
107
      if args.ignore:
cf132e4a8   Valentin Rothberg   checkkconfigsymbo...
108
          try:
14390e316   Valentin Rothberg   checkkconfigsymbo...
109
              re.match(args.ignore, "this/is/just/a/test.c")
cf132e4a8   Valentin Rothberg   checkkconfigsymbo...
110
111
          except:
              sys.exit("Please specify a valid Python regex.")
14390e316   Valentin Rothberg   checkkconfigsymbo...
112
      return args
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
113

24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
114
115
  def main():
      """Main function of this module."""
14390e316   Valentin Rothberg   checkkconfigsymbo...
116
      args = parse_options()
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
117

36c79c7fa   Valentin Rothberg   checkkconfigsymbo...
118
119
      global COLOR
      COLOR = args.color and sys.stdout.isatty()
4c73c0882   Andrew Donnellan   checkkconfigsymbo...
120

14390e316   Valentin Rothberg   checkkconfigsymbo...
121
122
      if args.sim and not args.commit and not args.diff:
          sims = find_sims(args.sim, args.ignore)
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
123
          if sims:
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
124
              print("%s: %s" % (yel("Similar symbols"), ', '.join(sims)))
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
125
          else:
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
126
              print("%s: no similar symbols found" % yel("Similar symbols"))
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
127
128
129
130
131
          sys.exit(0)
  
      # dictionary of (un)defined symbols
      defined = {}
      undefined = {}
14390e316   Valentin Rothberg   checkkconfigsymbo...
132
      if args.commit or args.diff:
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
133
134
135
136
137
          head = get_head()
  
          # get commit range
          commit_a = None
          commit_b = None
14390e316   Valentin Rothberg   checkkconfigsymbo...
138
139
140
141
142
          if args.commit:
              commit_a = args.commit + "~"
              commit_b = args.commit
          elif args.diff:
              split = args.diff.split("..")
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
143
144
145
146
147
148
              commit_a = split[0]
              commit_b = split[1]
              undefined_a = {}
              undefined_b = {}
  
          # get undefined items before the commit
2f9cc12bb   Valentin Rothberg   checkkconfigsymbo...
149
          reset(commit_a)
14390e316   Valentin Rothberg   checkkconfigsymbo...
150
          undefined_a, _ = check_symbols(args.ignore)
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
151
152
  
          # get undefined items for the commit
2f9cc12bb   Valentin Rothberg   checkkconfigsymbo...
153
          reset(commit_b)
14390e316   Valentin Rothberg   checkkconfigsymbo...
154
          undefined_b, defined = check_symbols(args.ignore)
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
155
156
  
          # report cases that are present for the commit but not before
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
157
158
159
160
161
162
          for symbol in sorted(undefined_b):
              # symbol has not been undefined before
              if symbol not in undefined_a:
                  files = sorted(undefined_b.get(symbol))
                  undefined[symbol] = files
              # check if there are new files that reference the undefined symbol
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
163
              else:
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
164
165
                  files = sorted(undefined_b.get(symbol) -
                                 undefined_a.get(symbol))
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
166
                  if files:
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
167
                      undefined[symbol] = files
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
168
169
  
          # reset to head
2f9cc12bb   Valentin Rothberg   checkkconfigsymbo...
170
          reset(head)
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
171
172
173
  
      # default to check the entire tree
      else:
14390e316   Valentin Rothberg   checkkconfigsymbo...
174
          undefined, defined = check_symbols(args.ignore)
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
175
176
  
      # now print the output
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
177
178
      for symbol in sorted(undefined):
          print(red(symbol))
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
179

ef3f55438   Valentin Rothberg   checkkconfigsymbl...
180
          files = sorted(undefined.get(symbol))
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
181
          print("%s: %s" % (yel("Referencing files"), ", ".join(files)))
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
182

ef3f55438   Valentin Rothberg   checkkconfigsymbl...
183
          sims = find_sims(symbol, args.ignore, defined)
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
184
185
          sims_out = yel("Similar symbols")
          if sims:
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
186
              print("%s: %s" % (sims_out, ', '.join(sims)))
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
187
          else:
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
188
              print("%s: %s" % (sims_out, "no similar symbols found"))
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
189

14390e316   Valentin Rothberg   checkkconfigsymbo...
190
          if args.find:
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
191
              print("%s:" % yel("Commits changing symbol"))
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
192
              commits = find_commits(symbol, args.diff)
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
193
194
195
              if commits:
                  for commit in commits:
                      commit = commit.split(" ", 1)
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
196
                      print("\t- %s (\"%s\")" % (yel(commit[0]), commit[1]))
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
197
              else:
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
198
                  print("\t- no commit found")
36c79c7fa   Valentin Rothberg   checkkconfigsymbo...
199
          print()  # new line
c74556630   Valentin Rothberg   checkkconfigsymbo...
200

2f9cc12bb   Valentin Rothberg   checkkconfigsymbo...
201
202
203
  def reset(commit):
      """Reset current git tree to %commit."""
      execute(["git", "reset", "--hard", commit])
c74556630   Valentin Rothberg   checkkconfigsymbo...
204
205
206
207
  def yel(string):
      """
      Color %string yellow.
      """
36c79c7fa   Valentin Rothberg   checkkconfigsymbo...
208
      return "\033[33m%s\033[0m" % string if COLOR else string
c74556630   Valentin Rothberg   checkkconfigsymbo...
209
210
211
212
213
214
  
  
  def red(string):
      """
      Color %string red.
      """
36c79c7fa   Valentin Rothberg   checkkconfigsymbo...
215
      return "\033[31m%s\033[0m" % string if COLOR else string
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
216
217
218
219
  
  
  def execute(cmd):
      """Execute %cmd and return stdout.  Exit in case of error."""
f175ba174   Valentin Rothberg   checkkconfigsymbo...
220
      try:
2f9cc12bb   Valentin Rothberg   checkkconfigsymbo...
221
          stdout = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=False)
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
222
          stdout = stdout.decode(errors='replace')
f175ba174   Valentin Rothberg   checkkconfigsymbo...
223
      except subprocess.CalledProcessError as fail:
2f9cc12bb   Valentin Rothberg   checkkconfigsymbo...
224
          exit(fail)
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
225
      return stdout
a42fa92ce   Valentin Rothberg   checkkconfigsymbo...
226
227
  def find_commits(symbol, diff):
      """Find commits changing %symbol in the given range of %diff."""
2f9cc12bb   Valentin Rothberg   checkkconfigsymbo...
228
229
230
      commits = execute(["git", "log", "--pretty=oneline",
                         "--abbrev-commit", "-G",
                         symbol, diff])
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
231
232
      return [x for x in commits.split("
  ") if x]
a42fa92ce   Valentin Rothberg   checkkconfigsymbo...
233

b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
234
235
236
  def tree_is_dirty():
      """Return true if the current working tree is dirty (i.e., if any file has
      been added, deleted, modified, renamed or copied but not committed)."""
2f9cc12bb   Valentin Rothberg   checkkconfigsymbo...
237
      stdout = execute(["git", "status", "--porcelain"])
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
238
239
240
241
242
243
244
245
      for line in stdout:
          if re.findall(r"[URMADC]{1}", line[:2]):
              return True
      return False
  
  
  def get_head():
      """Return commit hash of current HEAD."""
2f9cc12bb   Valentin Rothberg   checkkconfigsymbo...
246
      stdout = execute(["git", "rev-parse", "HEAD"])
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
247
248
      return stdout.strip('
  ')
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
249
250
  def partition(lst, size):
      """Partition list @lst into eveni-sized lists of size @size."""
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
251
      return [lst[i::size] for i in range(size)]
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
252
253
254
255
256
  
  
  def init_worker():
      """Set signal handler to ignore SIGINT."""
      signal.signal(signal.SIGINT, signal.SIG_IGN)
36c79c7fa   Valentin Rothberg   checkkconfigsymbo...
257
  def find_sims(symbol, ignore, defined=[]):
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
258
259
260
      """Return a list of max. ten Kconfig symbols that are string-similar to
      @symbol."""
      if defined:
8e8e33317   Valentin Rothberg   checkkconfigsymbo...
261
          return difflib.get_close_matches(symbol, set(defined), 10)
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
262
263
264
265
266
267
268
269
270
271
272
273
274
  
      pool = Pool(cpu_count(), init_worker)
      kfiles = []
      for gitfile in get_files():
          if REGEX_FILE_KCONFIG.match(gitfile):
              kfiles.append(gitfile)
  
      arglist = []
      for part in partition(kfiles, cpu_count()):
          arglist.append((part, ignore))
  
      for res in pool.map(parse_kconfig_files, arglist):
          defined.extend(res[0])
8e8e33317   Valentin Rothberg   checkkconfigsymbo...
275
      return difflib.get_close_matches(symbol, set(defined), 10)
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
276
277
278
279
280
  
  
  def get_files():
      """Return a list of all files in the current git directory."""
      # use 'git ls-files' to get the worklist
2f9cc12bb   Valentin Rothberg   checkkconfigsymbo...
281
      stdout = execute(["git", "ls-files"])
1b2c84146   Valentin Rothberg   checkkconfigsymbo...
282
283
284
285
286
287
288
289
290
291
292
293
294
      if len(stdout) > 0 and stdout[-1] == "
  ":
          stdout = stdout[:-1]
  
      files = []
      for gitfile in stdout.rsplit("
  "):
          if ".git" in gitfile or "ChangeLog" in gitfile or      \
                  ".log" in gitfile or os.path.isdir(gitfile) or \
                  gitfile.startswith("tools/"):
              continue
          files.append(gitfile)
      return files
cf132e4a8   Valentin Rothberg   checkkconfigsymbo...
295
  def check_symbols(ignore):
b1a3f2434   Valentin Rothberg   checkkconfigsymbo...
296
      """Find undefined Kconfig symbols and return a dict with the symbol as key
cf132e4a8   Valentin Rothberg   checkkconfigsymbo...
297
298
      and a list of referencing files as value.  Files matching %ignore are not
      checked for undefined symbols."""
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
299
300
301
302
303
304
305
306
307
308
309
310
      pool = Pool(cpu_count(), init_worker)
      try:
          return check_symbols_helper(pool, ignore)
      except KeyboardInterrupt:
          pool.terminate()
          pool.join()
          sys.exit(1)
  
  
  def check_symbols_helper(pool, ignore):
      """Helper method for check_symbols().  Used to catch keyboard interrupts in
      check_symbols() in order to properly terminate running worker processes."""
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
311
312
      source_files = []
      kconfig_files = []
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
313
314
      defined_symbols = []
      referenced_symbols = dict()  # {file: [symbols]}
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
315

1b2c84146   Valentin Rothberg   checkkconfigsymbo...
316
      for gitfile in get_files():
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
317
318
319
          if REGEX_FILE_KCONFIG.match(gitfile):
              kconfig_files.append(gitfile)
          else:
1439ebd2c   Ariel Marcovitch   checkkconfigsymbo...
320
              if ignore and re.match(ignore, gitfile):
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
321
322
                  continue
              # add source files that do not match the ignore pattern
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
323
              source_files.append(gitfile)
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
324
325
326
      # parse source files
      arglist = partition(source_files, cpu_count())
      for res in pool.map(parse_source_files, arglist):
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
327
          referenced_symbols.update(res)
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
328

e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
329
330
331
332
333
      # parse kconfig files
      arglist = []
      for part in partition(kconfig_files, cpu_count()):
          arglist.append((part, ignore))
      for res in pool.map(parse_kconfig_files, arglist):
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
334
335
336
          defined_symbols.extend(res[0])
          referenced_symbols.update(res[1])
      defined_symbols = set(defined_symbols)
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
337

ef3f55438   Valentin Rothberg   checkkconfigsymbl...
338
      # inverse mapping of referenced_symbols to dict(symbol: [files])
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
339
      inv_map = dict()
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
340
341
342
343
344
345
346
347
      for _file, symbols in referenced_symbols.items():
          for symbol in symbols:
              inv_map[symbol] = inv_map.get(symbol, set())
              inv_map[symbol].add(_file)
      referenced_symbols = inv_map
  
      undefined = {}  # {symbol: [files]}
      for symbol in sorted(referenced_symbols):
cc641d552   Valentin Rothberg   checkkconfigsymbo...
348
          # filter some false positives
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
349
350
          if symbol == "FOO" or symbol == "BAR" or \
                  symbol == "FOO_BAR" or symbol == "XXX":
cc641d552   Valentin Rothberg   checkkconfigsymbo...
351
              continue
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
352
353
          if symbol not in defined_symbols:
              if symbol.endswith("_MODULE"):
cc641d552   Valentin Rothberg   checkkconfigsymbo...
354
                  # avoid false positives for kernel modules
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
355
                  if symbol[:-len("_MODULE")] in defined_symbols:
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
356
                      continue
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
357
358
              undefined[symbol] = referenced_symbols.get(symbol)
      return undefined, defined_symbols
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
359

e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
360
361
362
  def parse_source_files(source_files):
      """Parse each source file in @source_files and return dictionary with source
      files as keys and lists of references Kconfig symbols as values."""
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
363
      referenced_symbols = dict()
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
364
      for sfile in source_files:
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
365
366
          referenced_symbols[sfile] = parse_source_file(sfile)
      return referenced_symbols
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
367
368
369
  
  
  def parse_source_file(sfile):
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
370
      """Parse @sfile and return a list of referenced Kconfig symbols."""
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
371
      lines = []
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
372
373
374
375
      references = []
  
      if not os.path.exists(sfile):
          return references
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
376
      with open(sfile, "r", encoding='utf-8', errors='replace') as stream:
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
377
378
379
          lines = stream.readlines()
  
      for line in lines:
36c79c7fa   Valentin Rothberg   checkkconfigsymbo...
380
          if "CONFIG_" not in line:
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
381
              continue
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
382
383
384
          symbols = REGEX_SOURCE_SYMBOL.findall(line)
          for symbol in symbols:
              if not REGEX_FILTER_SYMBOLS.search(symbol):
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
385
                  continue
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
386
              references.append(symbol)
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
387
388
  
      return references
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
389

ef3f55438   Valentin Rothberg   checkkconfigsymbl...
390
391
392
  def get_symbols_in_line(line):
      """Return mentioned Kconfig symbols in @line."""
      return REGEX_SYMBOL.findall(line)
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
393

e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
394
395
396
397
398
399
  def parse_kconfig_files(args):
      """Parse kconfig files and return tuple of defined and references Kconfig
      symbols.  Note, @args is a tuple of a list of files and the @ignore
      pattern."""
      kconfig_files = args[0]
      ignore = args[1]
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
400
401
      defined_symbols = []
      referenced_symbols = dict()
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
402
403
404
  
      for kfile in kconfig_files:
          defined, references = parse_kconfig_file(kfile)
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
405
          defined_symbols.extend(defined)
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
406
407
408
          if ignore and re.match(ignore, kfile):
              # do not collect references for files that match the ignore pattern
              continue
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
409
410
          referenced_symbols[kfile] = references
      return (defined_symbols, referenced_symbols)
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
411
412
413
  
  
  def parse_kconfig_file(kfile):
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
414
      """Parse @kfile and update symbol definitions and references."""
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
415
      lines = []
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
416
417
      defined = []
      references = []
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
418

e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
419
420
      if not os.path.exists(kfile):
          return defined, references
7c5227af2   Valentin Rothberg   checkkconfigsymbo...
421
      with open(kfile, "r", encoding='utf-8', errors='replace') as stream:
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
422
423
424
425
426
427
          lines = stream.readlines()
  
      for i in range(len(lines)):
          line = lines[i]
          line = line.strip('
  ')
cc641d552   Valentin Rothberg   checkkconfigsymbo...
428
          line = line.split("#")[0]  # ignore comments
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
429
430
  
          if REGEX_KCONFIG_DEF.match(line):
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
431
432
              symbol_def = REGEX_KCONFIG_DEF.findall(line)
              defined.append(symbol_def[0])
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
433
          elif REGEX_KCONFIG_STMT.match(line):
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
434
              line = REGEX_QUOTES.sub("", line)
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
435
              symbols = get_symbols_in_line(line)
cc641d552   Valentin Rothberg   checkkconfigsymbo...
436
              # multi-line statements
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
437
438
439
440
441
              while line.endswith("\\"):
                  i += 1
                  line = lines[i]
                  line = line.strip('
  ')
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
442
443
444
                  symbols.extend(get_symbols_in_line(line))
              for symbol in set(symbols):
                  if REGEX_NUMERIC.match(symbol):
0bd38ae35   Valentin Rothberg   scripts/checkkcon...
445
446
                      # ignore numeric values
                      continue
ef3f55438   Valentin Rothberg   checkkconfigsymbl...
447
                  references.append(symbol)
e2042a8a8   Valentin Rothberg   checkkconfigsymbo...
448
449
  
      return defined, references
24fe1f03e   Valentin Rothberg   checkkconfigsymbo...
450
451
452
453
  
  
  if __name__ == "__main__":
      main()