From: Breno Rodrigues Alves <breno3011alves@gmail.com>
To: linux-kernel@vger.kernel.org
Cc: Andy Whitcroft <apw@canonical.com>, Joe Perches <joe@perches.com>,
Dwaipayan Ray <dwaipayanray1@gmail.com>,
Lukas Bulwahn <lukas.bulwahn@gmail.com>,
Breno Rodrigues Alves <breno3011alves@gmail.com>
Subject: [RFC PATCH v2 5/7] scripts: convert kconfig-sym-check.pl to Python
Date: Sun, 18 Oct 2026 10:57:20 -0300 [thread overview]
Message-ID: <20261018135722.27410-6-breno3011alves@gmail.com> (raw)
In-Reply-To: <20261018135722.27410-1-breno3011alves@gmail.com>
Faithful re-implementation of kconfig-sym-check.pl in Python 3
(stdlib only). Same Kconfig parsing, same symbol-reference
detection, same excludes-file handling, same exit code.
Verified byte-for-byte identical to the Perl version on the
full kernel tree (~/linux).
Signed-off-by: Breno Rodrigues Alves <breno3011alves@gmail.com>
---
scripts/kconfig/kconfig-sym-check.py | 177 +++++++++++++++++++++++++++
1 file changed, 177 insertions(+)
create mode 100755 scripts/kconfig/kconfig-sym-check.py
diff --git a/scripts/kconfig/kconfig-sym-check.py b/scripts/kconfig/kconfig-sym-check.py
new file mode 100755
index 000000000..f15b25535
--- /dev/null
+++ b/scripts/kconfig/kconfig-sym-check.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+#
+# Detect unknown symbols referenced in Kconfig files.
+# Usage: kconfig-sym-check.py <srctree> [excludes file]
+
+import os
+import re
+import subprocess
+import sys
+
+VALID = re.compile(r'^[a-zA-Z0-9_]+$')
+RE_DEF = re.compile(r'^\s*(?:config|menuconfig)\s+([a-zA-Z0-9_]+)\s*(?:#.*)?$')
+RE_HELP = re.compile(r'^\s*(?:help|---help---)$')
+RE_REF = re.compile(
+ r'^\s*(?:default|def_bool|def_tristate|select|depends\s+on|imply|'
+ r'visible\s+if|range|if|bool|tristate|int|hex|string|prompt)\s+(.+)\s*$')
+RE_STRING = re.compile(r'"(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)*\'')
+RE_SKIP = re.compile(r'^\s*(?:#.*)?$')
+
+
+def indent_depth(ws):
+ col = 0
+ for c in ws:
+ if c == '\t':
+ col = (col // 8) * 8 + 8
+ else:
+ col += 1
+ return col
+
+
+def find_kconfig_files(srctree):
+ try:
+ out = subprocess.check_output(
+ ['git', '-C', srctree, 'ls-files', '*Kconfig*'],
+ text=True, stderr=subprocess.DEVNULL)
+ files = [ln.strip() for ln in out.splitlines() if ln.strip()]
+ if files:
+ return [os.path.join(srctree, f) for f in files]
+ except (subprocess.CalledProcessError, FileNotFoundError):
+ pass
+ try:
+ out = subprocess.check_output(
+ ['find', srctree, '-name', '*Kconfig*'], text=True)
+ return [ln.strip() for ln in out.splitlines() if ln.strip()]
+ except (subprocess.CalledProcessError, FileNotFoundError):
+ return []
+
+
+def parse_kconfig(path, configs, refs):
+ with open(path) as f:
+ raw = f.readlines()
+
+ merged = []
+ i = 0
+ while i < len(raw):
+ line = raw[i].rstrip('\n')
+ while line.endswith('\\') and i + 1 < len(raw):
+ line = line[:-1] + ' ' + raw[i + 1].rstrip('\n')
+ i += 1
+ merged.append(line)
+ i += 1
+
+ help_mode = False
+ help_level = 0
+ i = 0
+ while i < len(merged):
+ line = merged[i]
+
+ if not line.strip():
+ i += 1
+ continue
+ if line.lstrip().startswith('#'):
+ i += 1
+ continue
+
+ level = indent_depth(re.match(r'^(\s*)', line).group(1))
+
+ if help_mode and level < help_level:
+ help_mode = False
+ if help_mode:
+ i += 1
+ continue
+
+ if RE_HELP.match(line):
+ help_mode = True
+ j = i + 1
+ while j < len(merged) and RE_SKIP.match(merged[j]):
+ j += 1
+ if j >= len(merged):
+ break
+ next_level = indent_depth(
+ re.match(r'^(\s*)', merged[j]).group(1))
+ if next_level >= level:
+ help_level = next_level
+ else:
+ help_mode = False
+ i = j
+ continue
+
+ m = RE_DEF.match(line)
+ if m:
+ configs[m.group(1)] = configs.get(m.group(1), 0) + 1
+ i += 1
+ continue
+
+ m = RE_REF.match(line)
+ if m:
+ s = m.group(1)
+ s = RE_STRING.sub('', s)
+ s = re.sub(r'#.*', '', s)
+ s = re.sub(r'\$\((?:[^()]*|\((?:[^()]*|\([^()]*\))*\))*\)', '', s)
+ s = re.sub(r'%%[^%]*%%', '', s)
+ for sym in re.split(r'[^a-zA-Z0-9_]+', s):
+ if not sym or not sym[0].isalpha():
+ continue
+ if sym in ('if', 'y', 'n', 'm'):
+ continue
+ if re.match(r'^0[xX][0-9a-fA-F]+$', sym):
+ continue
+ refs[sym] = refs.get(sym, 0) + 1
+ i += 1
+
+
+def main(argv):
+ if not argv:
+ s = ''
+ try:
+ s = subprocess.check_output(
+ ['git', 'rev-parse', '--show-toplevel'],
+ text=True, stderr=subprocess.DEVNULL).strip()
+ except (subprocess.CalledProcessError, FileNotFoundError):
+ pass
+ msg = f'Usage: {sys.argv[0]} <srctree> [excludes file]\n'
+ msg += 'Please provide <srctree>.'
+ if s:
+ msg += f" Is it '{s}'?"
+ sys.exit(msg + '\n')
+
+ srctree = argv[0]
+ excludes = argv[1] if len(argv) > 1 else None
+
+ files = [f for f in find_kconfig_files(srctree)
+ if '/scripts/kconfig/tests/' not in f]
+
+ configs = {}
+ refs = {}
+ for f in files:
+ parse_kconfig(f, configs, refs)
+
+ known = {}
+ if excludes:
+ with open(excludes) as f:
+ for line in f:
+ line = line.rstrip('\n')
+ if not line.strip() or line.lstrip().startswith('#'):
+ continue
+ m = re.match(r'^\s*([a-zA-Z0-9_]+)\s*(?:#.*)?$', line)
+ if m:
+ known[m.group(1)] = 1
+
+ ret = 0
+ for k in sorted(refs):
+ if k in configs or k in known:
+ continue
+ print(k, end='')
+ if k in ('N', 'Y', 'M'):
+ print(f" - warning: '{k}' is probably not what you want; "
+ f"Kconfig tristate literals are always lowercase "
+ f"('n', 'y', 'm')", end='')
+ print()
+ ret = 1
+ return ret
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
\ No newline at end of file
--
2.43.0
next prev parent reply other threads:[~2026-10-18 13:57 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-18 11:28 [RFC PATCH 0/7] scripts: convert remaining Perl utilities in scripts/ Breno Rodrigues Alves
2026-10-18 11:28 ` [RFC PATCH 1/7] scripts: convert checkstack.pl to Python Breno Rodrigues Alves
2026-10-18 11:28 ` [RFC PATCH 2/7] scripts: convert extract-module-sig.pl " Breno Rodrigues Alves
2026-10-18 11:28 ` [RFC PATCH 3/7] scripts: convert extract-sys-certs.pl " Breno Rodrigues Alves
2026-10-18 11:28 ` [RFC PATCH 4/7] scripts: convert headerdep.pl " Breno Rodrigues Alves
2026-10-18 11:28 ` [RFC PATCH 5/7] scripts: convert kconfig-sym-check.pl " Breno Rodrigues Alves
2026-10-18 11:28 ` [RFC PATCH 6/7] scripts: convert parse-maintainers.pl " Breno Rodrigues Alves
2026-10-18 11:28 ` [RFC PATCH 7/7] scripts: convert streamline_config.pl " Breno Rodrigues Alves
2026-10-18 13:57 ` [RFC PATCH v2 0/7] scripts: convert remaining Perl utilities in scripts/ Breno Rodrigues Alves
2026-10-18 13:57 ` [RFC PATCH v2 1/7] scripts: convert checkstack.pl to Python Breno Rodrigues Alves
2026-10-18 13:57 ` [RFC PATCH v2 2/7] scripts: convert extract-module-sig.pl " Breno Rodrigues Alves
2026-10-18 13:57 ` [RFC PATCH v2 3/7] scripts: convert extract-sys-certs.pl " Breno Rodrigues Alves
2026-10-18 13:57 ` [RFC PATCH v2 4/7] scripts: convert headerdep.pl " Breno Rodrigues Alves
2026-10-18 13:57 ` Breno Rodrigues Alves [this message]
2026-10-18 13:57 ` [RFC PATCH v2 6/7] scripts: convert parse-maintainers.pl " Breno Rodrigues Alves
2026-10-18 13:57 ` [RFC PATCH v2 7/7] scripts: convert streamline_config.pl " Breno Rodrigues Alves
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261018135722.27410-6-breno3011alves@gmail.com \
--to=breno3011alves@gmail.com \
--cc=apw@canonical.com \
--cc=dwaipayanray1@gmail.com \
--cc=joe@perches.com \
--cc=linux-kernel@vger.kernel.org \
--cc=lukas.bulwahn@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®