diff options
Diffstat (limited to 'tools/doc_modules.py')
-rw-r--r-- | tools/doc_modules.py | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/tools/doc_modules.py b/tools/doc_modules.py new file mode 100644 index 000000000..d46f4db02 --- /dev/null +++ b/tools/doc_modules.py @@ -0,0 +1,209 @@ +# 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" + + +qt_version = None +qt_include_dir = None + + +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"Warning: XML error parsing {typesystem_file}: {e}", file=sys.stderr) + return handler.required_modules + + +def query_qtpaths(keyword): + query_cmd = ["qtpaths", "-query", keyword] + output = subprocess.check_output(query_cmd, stderr=subprocess.STDOUT, + universal_newlines=True) + return output.strip() + + +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 + + +def _write_type_system(modules, file): + """Helper to write the type system for shiboken. It needs to be in + dependency order to prevent shiboken from loading the included + typesystems with generate="no", which causes those modules to be + missing.""" + for module in modules: + name = module[2:].lower() + filename = f"{module}/typesystem_{name}.xml" + print(f' <load-typesystem name="{filename}" generate="yes"/>', + file=file) + print("</typesystem>", file=file) + + +def write_type_system(modules, filename): + """Write the type system for shiboken in dependency order.""" + if filename == "-": + _write_type_system(modules, sys.stdout) + else: + path = Path(filename) + exists = path.exists() + with path.open(mode="a") as f: + if not exists: + print('<typesystem package="PySide">', file=f) + _write_type_system(modules, f) + + +def _write_global_header(modules, file): + """Helper to write the global header for shiboken.""" + for module in modules: + print(f"#include <{module}/{module}>", file=file) + + +def write_global_header(modules, filename): + """Write the global header for shiboken.""" + if filename == "-": + _write_global_header(modules, sys.stdout) + else: + with Path(filename).open(mode="a") as f: + _write_global_header(modules, f) + + +def _write_docconf(modules, file): + """Helper to write the include paths for the .qdocconf file.""" + # @TODO fix this for macOS frameworks. + for module in modules: + root = f" -I/{qt_include_dir}/{module}" + print(f"{root} \\", file=file) + print(f"{root}/{qt_version} \\", file=file) + print(f"{root}/{qt_version}/{module} \\", file=file) + + +def write_docconf(modules, filename): + """Write the include paths for the .qdocconf file.""" + if filename == "-": + _write_docconf(modules, sys.stdout) + else: + with Path(filename).open(mode="a") as f: + _write_docconf(modules, f) + + +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) + argument_parser.add_argument("qt_version", help="Qt version string", + nargs='?', type=str) + argument_parser.add_argument("--typesystem", "-t", help="Typesystem file to write", + action="store", type=str) + argument_parser.add_argument("--global-header", "-g", help="Global header to write", + action="store", type=str) + argument_parser.add_argument("--docconf", "-d", help="docconf file to write", + action="store", type=str) + + options = argument_parser.parse_args() + verbose = options.verbose + 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"] + qt_include_dir = Path(query_qtpaths("QT_INSTALL_HEADERS")) + if not qt_include_dir.is_dir(): + print("Cannot determine include directory", file=sys.stderr) + sys.exit(-1) + + qt_version = options.qt_version if options.qt_version else query_qtpaths("QT_VERSION") + + # 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 + # QtGraphs duplicates symbols from QtDataVisualization causing shiboken errors + if module == "QtDataVisualization": + continue + 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])) + + if options.typesystem: + write_type_system(modules, options.typesystem) + if options.global_header: + write_global_header(modules, options.global_header) + if options.docconf: + write_docconf(modules, options.docconf) |