1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 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
from __future__ import annotations
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/QtCharts causing shiboken errors
if module == "QtDataVisualization" or module == "QtCharts":
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)
|