diff options
| author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2022-09-08 14:09:53 +0200 |
|---|---|---|
| committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2022-09-15 14:38:33 +0200 |
| commit | 327350779203bdde7728a87e513e7e27dfc8e876 (patch) | |
| tree | 3636930460336b9f4b032824abd4b6d6f8fd4293 /tools/doc_modules.py | |
| parent | 5b0918c6c6fa575a16b3ec1637281397e951f62b (diff) | |
Generate documentation for all modules independently
Previously, only the modules that were built in PySide were considered
for documentation, which required one to create a full build just to
build the documentation. One reason for this was that the inheritance
diagram creation determined the base classes by loading the
classes/modules and do introspection. This has been replaced by a
mechanism were shiboken creates a JSON inheritance file.
It is then sufficient to have the modules built in Qt so that the
includes exist for shiboken and qdoc finds the source.
Add a script that retrieves the Qt modules built by looking at the Qt
include path, rearranges them by dependency order and use that as an
input to the documentation checks.
With that, a minimal build of PySide6 suffices to generate the complete
documentation.
The script can be used standalone with an option to print warnings
about modules not built in Qt.
Task-number: PYSIDE-1106
Change-Id: Iade4c6f1d3d255f7052be94d0a6fd647f5ee4ed3
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Diffstat (limited to 'tools/doc_modules.py')
| -rw-r--r-- | tools/doc_modules.py | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/tools/doc_modules.py b/tools/doc_modules.py new file mode 100644 index 000000000..9b22c23a1 --- /dev/null +++ b/tools/doc_modules.py @@ -0,0 +1,120 @@ +# Copyright (C) 2022 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 + +import os +import subprocess +import sys +from argparse import ArgumentParser, RawTextHelpFormatter +from pathlib import Path +import xml.sax +from xml.sax.handler import ContentHandler + +DESC = """Print a list of module short names ordered by typesystem dependencies +for which documentation can be built by intersecting the PySide6 modules with +the modules built in Qt.""" + + +ROOT_DIR = Path(__file__).parents[1].resolve() +SOURCE_DIR = ROOT_DIR / "sources" / "pyside6" / "PySide6" + + +class TypeSystemContentHandler(ContentHandler): + """XML SAX content handler that extracts required modules from the + "load-typesystem" elements of the typesystem_file. Nodes that start + with Qt and are marked as generate == "no" are considered required.""" + + def __init__(self): + self.required_modules = [] + + def startElement(self, name, attrs): + if name == "load-typesystem": + generate = attrs.get("generate", "").lower() + if generate == "no" or generate == "false": + load_file_name = attrs.get("name") # "QtGui/typesystem_gui.xml" + if load_file_name.startswith("Qt"): + slash = load_file_name.find("/") + if slash > 0: + self.required_modules.append(load_file_name[:slash]) + + +def required_typesystems(module): + """Determine the required Qt modules by looking at the "load-typesystem" + elements of the typesystem_file.""" + name = module[2:].lower() + typesystem_file = SOURCE_DIR / module / f"typesystem_{name}.xml" + # Use a SAX parser since that works despite undefined entity + # errors for typesystem entities. + handler = TypeSystemContentHandler() + try: + parser = xml.sax.make_parser() + parser.setContentHandler(handler) + parser.parse(typesystem_file) + except Exception as e: + print(f"Error parsing {typesystem_file}: {e}", file=sys.stderr) + return handler.required_modules + + +def sort_modules(dependency_dict): + """Sort the modules by dependencies using brute force: Keep adding + modules all of whose requirements are present to the result list + until done.""" + result = [] + while True: + found = False + for module, dependencies in dependency_dict.items(): + if module not in result: + if all(dependency in result for dependency in dependencies): + result.append(module) + found = True + if not found: + break + + if len(result) < len(dependency_dict) and verbose: + for desired_module in dependency_dict.keys(): + if desired_module not in result: + print(f"Not documenting {desired_module} (missing dependency)", + file=sys.stderr) + return result + + +if __name__ == "__main__": + argument_parser = ArgumentParser(description=DESC, + formatter_class=RawTextHelpFormatter) + argument_parser.add_argument("--verbose", "-v", action="store_true", + help="Verbose") + argument_parser.add_argument("qt_include_dir", help="Qt Include dir", + nargs='?', type=str) + options = argument_parser.parse_args() + verbose = options.verbose + qt_include_dir = None + if options.qt_include_dir: + qt_include_dir = Path(options.qt_include_dir) + if not qt_include_dir.is_dir(): + print(f"Invalid include directory passed: {options.qt_include_dir}", + file=sys.stderr) + sys.exit(-1) + else: + verbose = True # Called by hand to find out about available modules + query_cmd = ["qtpaths", "-query", "QT_INSTALL_HEADERS"] + output = subprocess.check_output(query_cmd, stderr=subprocess.STDOUT, + universal_newlines=True) + qt_include_dir = Path(output.strip()) + if not qt_include_dir.is_dir(): + print("Cannot determine include directory", file=sys.stderr) + sys.exit(-1) + + # Build a typesystem dependency dict of the available modules in order + # to be able to sort_modules by dependencies. This is required as + # otherwise shiboken will read the required typesystems with + # generate == "no" and thus omit modules. + module_dependency_dict = {} + for m in SOURCE_DIR.glob("Qt*"): + module = m.name + qt_include_path = qt_include_dir / module + if qt_include_path.is_dir(): + module_dependency_dict[module] = required_typesystems(module) + elif verbose: + print(f"Not documenting {module} (not built)", file=sys.stderr) + + modules = sort_modules(module_dependency_dict) + print(" ".join([m[2:] for m in modules])) |
