aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2024-04-19 15:23:58 +0200
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2024-04-19 17:07:36 +0200
commit1251a23cfd13bb0652f38ef3b36087034f552d57 (patch)
treef81cb5cd4a401da38bae7b3b5edf8530d6e3d605
parentd477f2d99df41a6a0e53e2aaf447cc41c4a0f783 (diff)
Long live scanqtclasses.py!
Add a script which scans C++ headers and typesystem files and prints missing classes. Task-number: PYSIDE-2620 Change-Id: Ibd2d1aab8debc19e72d9847af180fd425c17db9d Reviewed-by: Shyamnath Premnadh <Shyamnath.Premnadh@qt.io>
-rw-r--r--tools/scanqtclasses.py122
1 files changed, 122 insertions, 0 deletions
diff --git a/tools/scanqtclasses.py b/tools/scanqtclasses.py
new file mode 100644
index 000000000..0f87d80bd
--- /dev/null
+++ b/tools/scanqtclasses.py
@@ -0,0 +1,122 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+from pathlib import Path
+import os
+import re
+import subprocess
+import sys
+
+"""Scan the Qt C++ headers per module for classes that should be present
+ in the matching type system and print the missing classes."""
+
+
+VALUE_TYPE = re.compile(r'^\s*<value-type name="([^"]+)"')
+
+
+OBJECT_TYPE = re.compile(r'^\s*<object-type name="([^"]+)"')
+
+
+def query_qtpaths(keyword):
+ """Query qtpaths for a keyword."""
+ query_cmd = ["qtpaths", "-query", keyword]
+ output = subprocess.check_output(query_cmd, stderr=subprocess.STDOUT,
+ universal_newlines=True)
+ return output.strip()
+
+
+def is_class_exluded(name):
+ """Check for excluded classes that do not make sense in a typesystem."""
+ if len(name) < 2:
+ return True
+ if "Iterator" in name or "iterator" in name:
+ return True
+ if name.startswith("If") or name.startswith("Is") or name.startswith("When"):
+ return True
+ if name[:1].islower():
+ return True
+ if name.startswith("QOpenGLFunctions") and name.endswith("Backend"):
+ return True
+ return False
+
+
+def class_from_header_line(line):
+ """Extract a class name from a C++ header line."""
+ def _is_macro(token):
+ return "EXPORT" in token or "API" in token
+
+ def _fix_class_name(name):
+ pos = name.find('<') # Some template specialization "class Name<TemplateParam>"
+ if pos > 0:
+ name = name[:pos]
+ if name.endswith(':'):
+ name = name[:-1]
+ return name
+
+ if line.startswith('//') or line.endswith(';'): # comment/forward decl
+ return None
+ line = line.strip()
+ if not line.startswith("class ") and not line.startswith("struct "):
+ return None
+ tokens = line.split()
+ pos = 1
+ while pos < len(tokens) and _is_macro(tokens[pos]):
+ pos += 1
+ return _fix_class_name(tokens[pos]) if pos < len(tokens) else None
+
+
+def classes_from_header(header):
+ """Extract classes from C++ header file."""
+ result = []
+ for line in header.read_text("utf-8").splitlines():
+ name = class_from_header_line(line)
+ if name and not is_class_exluded(name):
+ result.append(name)
+ return sorted(result)
+
+
+def classes_from_typesystem(typesystem):
+ """Extract classes from typesystem XML file."""
+ result = []
+ for line in typesystem.read_text("utf-8").splitlines():
+ match = VALUE_TYPE.search(line) or OBJECT_TYPE.search(line)
+ if match:
+ result.append(match.group(1))
+ return sorted(result)
+
+
+def check_classes(qt_module_inc_dir, pyside_dir):
+ """Check classes of a module."""
+ module_name = qt_module_inc_dir.name
+ sys.stderr.write(f"Checking {module_name} ")
+ cpp_classes = []
+ typesystem_classes = []
+ for header in qt_module_inc_dir.glob("q*.h"):
+ if not header.name.endswith("_p.h"):
+ cpp_classes.extend(classes_from_header(header))
+ for typesystem in pyside_dir.glob("*.xml"):
+ typesystem_classes.extend(classes_from_typesystem(typesystem))
+
+ cpp_count = len(cpp_classes)
+ typesystem_count = len(typesystem_classes)
+ sys.stderr.write(f"found {cpp_count} C++ / {typesystem_count} typesystem classes")
+ if cpp_count <= typesystem_count:
+ sys.stderr.write(" ok\n")
+ else:
+ sys.stderr.write(f", {cpp_count-typesystem_count} missing\n")
+ for cpp_class in cpp_classes:
+ if cpp_class not in typesystem_classes:
+ wrapper_name = cpp_class.lower() + "_wrapper.cpp"
+ print(f"{module_name}:{cpp_class}:{wrapper_name}")
+
+
+if __name__ == '__main__':
+ qt_version = query_qtpaths("QT_VERSION")
+ qt_inc_dir = Path(query_qtpaths("QT_INSTALL_HEADERS"))
+ print(f"Qt {qt_version} at {os.fspath(qt_inc_dir.parent)}", file=sys.stderr)
+
+ dir = Path(__file__).parents[1].resolve()
+ for module_dir in (dir / "sources" / "pyside6" / "PySide6").glob("Qt*"):
+ qt_module_inc_dir = qt_inc_dir / module_dir.name
+ if qt_module_inc_dir.is_dir():
+ check_classes(qt_module_inc_dir, module_dir)