Tools: Update binding generator.

This commit is contained in:
Joao Matos
2025-02-25 00:35:24 +00:00
parent 58dc95a2f2
commit 2e879c8982
4 changed files with 39 additions and 18 deletions

View File

@@ -156,13 +156,13 @@ macro(generate_from_py BASE_NAME)
if(NOT EXISTS "${SOURCE_CPP_PATH}")
# assures the source files are generated at least once
message(STATUS "${SOURCE_CPP_PATH}")
execute_process(COMMAND "${PYTHON_EXECUTABLE}" "${TOOL_NATIVE_PATH}" --outputPath "${OUTPUT_NATIVE_PATH}" "${SOURCE_NATIVE_PATH}"
execute_process(COMMAND "${Python3_EXECUTABLE}" "${TOOL_NATIVE_PATH}" --outputPath "${OUTPUT_NATIVE_PATH}" "${SOURCE_NATIVE_PATH}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMMAND_ERROR_IS_FATAL ANY
)
endif()
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${BASE_NAME}_.h" "${CMAKE_CURRENT_BINARY_DIR}/${BASE_NAME}_.cpp"
COMMAND ${PYTHON_EXECUTABLE} "${TOOL_NATIVE_PATH}" --outputPath "${OUTPUT_NATIVE_PATH}" ${BASE_NAME}.pyi
COMMAND ${Python3_EXECUTABLE} "${TOOL_NATIVE_PATH}" --outputPath "${OUTPUT_NATIVE_PATH}" ${BASE_NAME}.pyi
MAIN_DEPENDENCY "${BASE_NAME}.pyi"
DEPENDS
"${CMAKE_SOURCE_DIR}/src/Tools/bindings/templates/templateClassPyExport.py"

View File

@@ -57,7 +57,7 @@ def generate(filename, outputPath):
Export.outputDir = outputPath + "/"
Export.inputDir = os.path.dirname(filename) + "/"
Export.export = GenerateModelInst.PythonExport[0]
Export.is_python = filename.endswith(".py")
Export.is_python = filename.endswith(".pyi")
Export.Generate()
if Export.is_python:
Export.Compare()

View File

@@ -210,10 +210,17 @@ def _parse_methods(class_node: ast.ClassDef) -> List[Methode]:
"""
methods = []
for stmt in class_node.body:
if not isinstance(stmt, ast.FunctionDef):
continue
def collect_function_defs(nodes):
funcs = []
for node in nodes:
if isinstance(node, ast.FunctionDef):
funcs.append(node)
elif isinstance(node, ast.If):
funcs.extend(collect_function_defs(node.body))
funcs.extend(collect_function_defs(node.orelse))
return funcs
for stmt in collect_function_defs(class_node.body):
# Skip methods decorated with @overload
skip_method = False
for deco in stmt.decorator_list:
@@ -542,28 +549,42 @@ def _parse_class(class_node, source_code: str, path: str, imports_mapping: dict)
def parse_python_code(path: str) -> GenerateModel:
"""
Parse the given Python source code and build a GenerateModel containing
PythonExport entries for each class that inherits from a relevant binding class.
PythonExport entries. If any class is explicitly exported using @export,
only those classes are used. If no classes have the @export decorator,
then a single non-exported class is assumed to be the export. If there
are multiple non-exported classes, an exception is raised.
"""
source_code = None
with open(path, "r") as file:
source_code = file.read()
tree = ast.parse(source_code)
imports_mapping = _parse_imports(tree)
model = GenerateModel()
explicit_exports = []
non_explicit_exports = []
for node in tree.body:
if isinstance(node, ast.ClassDef):
py_export = _parse_class(node, source_code, path, imports_mapping)
model.PythonExport.append(py_export)
if py_export.IsExplicitlyExported:
explicit_exports.append(py_export)
else:
non_explicit_exports.append(py_export)
# Check for multiple non explicitly exported classes
non_exported_classes = [
item for item in model.PythonExport if not getattr(item, "IsExplicitlyExported", False)
]
if len(non_exported_classes) > 1:
raise Exception("Multiple non explicitly-exported classes were found, please use @export.")
model = GenerateModel()
if explicit_exports:
# Use only explicitly exported classes.
model.PythonExport.extend(explicit_exports)
else:
# No explicit exports; allow only one non-exported class.
if len(non_explicit_exports) == 1:
model.PythonExport.append(non_explicit_exports[0])
elif len(non_explicit_exports) > 1:
raise Exception(
"Multiple non explicitly-exported classes were found, please use @export."
)
else:
raise Exception("No classes found for export.")
return model

View File

@@ -15,7 +15,7 @@ def compareFiles(file1, file2):
# Check if files exist
for file in (file1, file2):
if not os.path.exists(file):
raise FileNotFoundError(f"File not found: {file1} {file2}")
raise FileNotFoundError(f"File not found: {file}")
# Read file contents
with open(file1, "r", encoding="utf-8") as f1, open(file2, "r", encoding="utf-8") as f2: