mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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


  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®