def generate_globals_bindings(name, cpp_globals): includes = [] bindings_functions = [] objects = [] for global_name, cpp_global in cpp_globals.items(): if cpp_global.flags.nobind: continue export_name = format_name(cpp_global.name) log.info(f" Generating bindings for global {global_name}") includes.append(get_include_file(cpp_global)) objects.append({ "bindings": f"Global{export_name}", "identifier": f"{cpp_global.namespace}::{cpp_global.name}", "load_priority": cpp_global.flags.load_priority, }) state_view = flavour.STATE_VIEW binding_function_signature = f"void LoadGlobal{export_name}({state_view} state)" namespace_access = fetch_table(name) + "\n" binding_function_body = namespace_access + flavour.GLOBAL_BODY.format( namespace=name.split("::")[-1], global_name=cpp_global.name, global_ptr=global_name, ) bindings_functions.append(f"{binding_function_signature}\n{{\n" f"{binding_function_body}\n}}") return { "includes": includes, "bindings_functions": bindings_functions, "objects": objects, }
def parse_doxygen_files(path_to_doc, cpp_db): doxygen_index = parse_doxygen_index( os.path.join(path_to_doc, "docbuild", "xml", "index.xml")) log.info("Loading classes info...") for currentDir, _, files in os.walk( os.path.join(path_to_doc, "docbuild/xml/")): for f in sorted(files): if any((f.startswith(f"class{item['namespace']}") or f.startswith(f"struct{item['namespace']}")) for item in SOURCE_DIRECTORIES): class_filepath = os.path.join(currentDir, f) log.debug(f" Parsing class {class_filepath}") tree = etree.parse(class_filepath) class_model = parse_class_from_xml( tree.xpath("/doxygen/compounddef")[0], doxygen_index) cpp_db.classes["::".join( [class_model.namespace, class_model.name])] = class_model elif any( f.startswith(f"namespace{item['namespace']}") for item in SOURCE_DIRECTORIES): namespace_filepath = os.path.join(currentDir, f) log.debug(f" Parsing namespace {namespace_filepath}") parse_namespace_from_xml(namespace_filepath, cpp_db, doxygen_index) else: log.warning(f"Ignoring file {f}")
def generate_enums_bindings(name: str, enums: List[EnumModel]): includes = [] bindings_functions = [] objects = [] for enum_name, enum in enums.items(): log.info(f" Generating bindings for enum {enum_name}") enum_path = strip_include(enum.location.file).replace(os.path.sep, "/") includes.append(f"#include <{enum_path}>") state_view = flavour.STATE_VIEW export_name = format_name(enum.name) binding_function_signature = f"void LoadEnum{export_name}({state_view} state)" table_access = fetch_table(name) + "\n" binding_function_body = table_access + flavour.ENUM_BODY.format( namespace=name.split("::")[-1], enum_type=enum_name, enum_name=enum.name, enum_fields=generate_enum_fields(enum_name, enum), ) bindings_functions.append(f"{binding_function_signature}\n{{\n" f"{binding_function_body}\n}}") objects.append({ "bindings": f"Enum{export_name}", "identifier": f"{enum.namespace}::{enum.name}", "load_priority": enum.flags.load_priority, }) return { "includes": includes, "bindings_functions": bindings_functions, "objects": objects, }
def generate_bindings_for_namespace(namespace_name, namespace): log.info(f"Generating bindings for namespace {namespace_name}") split_name = "/".join(namespace_name.split("::")) source = match_namespace_with_source(namespace_name) location = LOCATIONS[source["output_location"]] os.makedirs( os.path.join(OUTPUT_DIRECTORY, location["headers"], split_name), exist_ok=True, ) os.makedirs( os.path.join(OUTPUT_DIRECTORY, location["sources"], split_name), exist_ok=True, ) class_bindings = generate_classes_bindings(namespace.classes) enum_bindings = generate_enums_bindings(namespace_name, namespace.enums) functions_bindings = generate_functions_bindings(namespace.functions) globals_bindings = generate_globals_bindings(namespace_name, namespace.globals) generated_objects = (class_bindings["objects"] + enum_bindings["objects"] + functions_bindings["objects"] + globals_bindings["objects"]) bindings_header = os.path.join( split_name, f"{namespace_name.split('::')[-1]}.hpp").replace(os.path.sep, "/") if GENERATE_BINDINGS: make_bindings_header(bindings_header, namespace_name, generated_objects) namespace_data = { "includes": namespace.namespaces.flags.additional_includes if namespace.namespaces.flags.additional_includes else [], "bindings_functions": [], } bindings_source = os.path.join( split_name, f"{namespace_name.split('::')[-1]}.cpp").replace(os.path.sep, "/") FILES_TO_FORMAT.append(os.path.join(location["headers"], bindings_header)) FILES_TO_FORMAT.append(os.path.join(location["sources"], bindings_source)) if GENERATE_BINDINGS: bindings_header_include_path = strip_include( os.path.join(location["headers"], bindings_header)).replace("\\", "/") make_bindings_sources( namespace_name, bindings_source, bindings_header_include_path, enum_bindings, class_bindings, functions_bindings, globals_bindings, namespace_data, ) return generated_objects, bindings_header, bindings_source
def check_git_directory(): global PATH_TO_OBENGINE if PATH_TO_OBENGINE is not None: log.debug(f"Found existing ÖbEngine repository in {PATH_TO_OBENGINE}") else: log.debug("Cloning ÖbEngine repository...") PATH_TO_OBENGINE = set_obengine_git_directory(clone_obengine_repo()) log.debug("Checking ÖbEngine repository validity...") if not check_obengine_repo(PATH_TO_OBENGINE): raise InvalidObEngineGitRepositoryException(PATH_TO_OBENGINE) log.info(f"Using ÖbEngine repository in {PATH_TO_OBENGINE}") return PATH_TO_OBENGINE
def generate_bindings(cpp_db: CppDatabase, write_files: bool = True): global GENERATE_BINDINGS GENERATE_BINDINGS = write_files log.info("===== Generating bindings for ÖbEngine ====") discard_placeholders(cpp_db) inject_ref_in_function_parameters(cpp_db) patch_const_ref_return_type(cpp_db) namespaces = group_bindings_by_namespace(cpp_db) generated_objects = {} for namespace_name, namespace in namespaces.items(): copy_parent_bindings(cpp_db, namespace.classes) copy_parent_bases(cpp_db, namespace.classes) flag_abstract_classes(cpp_db, namespace.classes) apply_proxies(cpp_db, namespace.functions) generation_results = generate_bindings_for_namespace( namespace_name, namespace) generated_objects[namespace_name] = { "objects": generation_results[0], "header": generation_results[1], "source": generation_results[2], } if GENERATE_BINDINGS: for location, source_namespaces in SOURCE_DIRECTORIES_BY_OUTPUT.items( ): source_generated_objects = { namespace: generated_object for namespace, generated_object in generated_objects.items() if any( namespace.startswith(source_namespace) for source_namespace in source_namespaces) } source_path = LOCATIONS[location]["sources"] with open( os.path.join(OUTPUT_DIRECTORY, f"{source_path}/index.cpp"), "w", ) as bindings_index: bindings_index.write( generated_bindings_index(location, source_generated_objects)) FILES_TO_FORMAT.append(f"{source_path}/index.cpp") if GENERATE_BINDINGS: clang_format_files(FILES_TO_FORMAT) return generated_objects
def generate_functions_bindings(functions): objects = [] includes = [] bindings_functions = [] for function_name, function_value in functions.items(): log.info(f" Generating bindings for function {function_name}") func_type, short_name = get_real_function_name( function_name.split("::")[-1], function_value) real_function_name = format_name(short_name) if isinstance(function_value, FunctionOverloadModel): identifier = ( f"{function_value.overloads[0].namespace}::{function_value.name}" ) else: identifier = f"{function_value.namespace}::{function_value.name}" objects.append({ "bindings": f"{func_type}{real_function_name}", "identifier": identifier, "load_priority": function_value.flags.load_priority, }) if isinstance(function_value, FunctionOverloadModel): for overload in function_value.overloads: includes.append(get_include_file(overload)) else: includes.append(get_include_file(function_value)) state_view = flavour.STATE_VIEW binding_function_signature = ( f"void LoadFunction{real_function_name}({state_view} state)") binding_function = ( f"{binding_function_signature}\n{{\n" f"{generate_function_bindings(function_name, function_value)}}}") bindings_functions.append(binding_function) return { "includes": includes, "objects": objects, "bindings_functions": bindings_functions, }
def generate_classes_bindings(classes): objects = [] includes = [] bindings_functions = [] for class_name, class_value in classes.items(): includes_for_class = [] if class_value.flags.nobind: continue log.info(f" Generating bindings for class {class_name}") real_class_name = class_name.split("::")[-1] real_class_name = format_name(real_class_name) objects.append({ "bindings": f"Class{real_class_name}", "identifier": f"{class_value.namespace}::{class_value.name}", "load_priority": class_value.flags.load_priority, }) class_path = strip_include(class_value.location.file) class_path = class_path.replace(os.path.sep, "/") class_path = f"#include <{class_path}>" includes_for_class.append(class_path) state_view = flavour.STATE_VIEW binding_function_signature = ( f"void LoadClass{real_class_name}({state_view} state)") binding_function = (f"{binding_function_signature}\n{{\n" f"{generate_class_bindings(class_value)}\n}}") if "_fs" in binding_function: includes_for_class.append("#include <System/Path.hpp>") if class_value.flags.additional_includes: includes_for_class += class_value.flags.additional_includes bindings_functions.append(binding_function) includes.append("\n".join(includes_for_class)) return { "includes": includes, "objects": objects, "bindings_functions": bindings_functions, }
def generate_hints(cpp_db: CppDatabase, path_to_doc: str): log.info("Discarding placeholders") discard_placeholders(cpp_db) log.info("Converting all types") convert_all_types(cpp_db) cpp_db.classes |= _build_table_for_events(cpp_db.classes) cpp_db.classes |= _generate_dynamic_types() all_elements = [ item for item_type in cpp_db.__dict__.keys() for item in getattr(cpp_db, item_type).values() if not item.flags.nobind ] _add_return_type_to_constructors(cpp_db) _remove_operators(cpp_db) _fix_bind_as(all_elements) _setup_methods_as_attributes(cpp_db.classes) log.info("Generating hints") write_hints(_get_namespace_tables(all_elements), all_elements)
def main(): parser = argparse.ArgumentParser() # Starting Obidog log.info("Obidog starting...") # Creating databases cpp_db = CppDatabase() # Checking OBENGINE_GIT_DIRECTORY path_to_obengine = check_git_directory() # Generating Doxygen documentation log.info("Building Doxygen XML documentation...") path_to_doc = build_doxygen_documentation(path_to_obengine) # Processing all files in Doxygen documentation parse_doxygen_files(path_to_doc, cpp_db) cwd = tempfile.mkdtemp() log.info(f"Working directory : {cwd}") parser.add_argument( "mode", help="Resource you want to generate", choices=["documentation", "bindings", "hints"], ) args = parser.parse_args() if args.mode == "documentation": generate_documentation(cpp_db, path_to_doc) elif args.mode == "bindings": generate_bindings(cpp_db) if args.mode == "hints": generate_hints(cpp_db, path_to_doc)
def generate_documentation(cpp_db: CppDatabase, path_to_doc: str): doxygen_index = parse_doxygen_index( os.path.join(path_to_doc, "docbuild", "xml", "index.xml")) log.info("Preparing database") bindings_results = generate_bindings( cpp_db, False) # TODO: Don't forget to put this to false ! log.info("Converting all types") convert_all_types(cpp_db) all_elements = ([ item for item_type in cpp_db.__dict__.keys() for item in getattr(cpp_db, item_type).values() if not item.flags.nobind ] + [ method for class_value in cpp_db.classes.values() for method in class_value.methods.values() if not method.flags.nobind ] + [ attribute for class_value in cpp_db.classes.values() for attribute in class_value.attributes.values() if not attribute.flags.nobind ]) log.info("Retrieving urls for all elements") for element in all_elements: fill_element_urls( element, doxygen_index=doxygen_index, bindings_results=bindings_results, ) log.info("Grouping namespace") namespaces = group_bindings_by_namespace(cpp_db) log.info("Generate namespaces documentation") for namespace_value in namespaces.values(): if not namespace_value.flags.nobind: document_item(namespace_value) log.info("Generate classes documentation") for class_value in cpp_db.classes.values(): if not class_value.flags.nobind: document_item(class_value) """log.info("Generate full database") with open( os.path.join("export", "db.json"), "w", encoding="utf-8" ) as db_export: json.dump( cpp_db.__dict__, db_export, indent=4, ensure_ascii=False, cls=DefaultEncoder, ) """ log.info("Generate search database") generate_search_db(cpp_db)