Path: blob/master/core/extension/make_interface_header.py
20985 views
import difflib1import json2from collections import OrderedDict34import methods56BASE_TYPES = [7"void",8"int8_t",9"uint8_t",10"int16_t",11"uint16_t",12"int32_t",13"uint32_t",14"int64_t",15"uint64_t",16"size_t",17"char",18"char16_t",19"char32_t",20"wchar_t",21"float",22"double",23]242526def run(target, source, env):27filename = str(source[0])28buffer = methods.get_buffer(filename)29data = json.loads(buffer, object_pairs_hook=OrderedDict)30check_formatting(buffer.decode("utf-8"), data, filename)31check_allowed_keys(data, ["_copyright", "$schema", "format_version", "types", "interface"])3233valid_data_types = {}34for type in BASE_TYPES:35valid_data_types[type] = True3637with methods.generated_wrapper(str(target[0])) as file:38file.write("""\39#ifndef __cplusplus40#include <stddef.h>41#include <stdint.h>4243typedef uint32_t char32_t;44typedef uint16_t char16_t;45#else46#include <cstddef>47#include <cstdint>4849extern "C" {50#endif5152""")5354handles = []55type_replacements = []56for type in data["types"]:57kind = type["kind"]5859check_type(kind, type, valid_data_types)60valid_data_types[type["name"]] = type6162if "deprecated" in type:63check_allowed_keys(type["deprecated"], ["since"], ["message", "replace_with"])64if "replace_with" in type["deprecated"]:65type_replacements.append((type["name"], type["deprecated"]["replace_with"]))6667if "description" in type:68write_doc(file, type["description"])6970if kind == "handle":71check_allowed_keys(72type, ["name", "kind"], ["is_const", "is_uninitialized", "parent", "description", "deprecated"]73)74if "parent" in type and type["parent"] not in handles:75raise UnknownTypeError(type["parent"], type["name"])76# @todo In the future, let's write these as `struct *` so the compiler can help us with type checking.77type["type"] = "void*" if not type.get("is_const", False) else "const void*"78write_simple_type(file, type)79handles.append(type["name"])80elif kind == "alias":81check_allowed_keys(type, ["name", "kind", "type"], ["description", "deprecated"])82write_simple_type(file, type)83elif kind == "enum":84check_allowed_keys(type, ["name", "kind", "values"], ["is_bitfield", "description", "deprecated"])85write_enum_type(file, type)86elif kind == "function":87check_allowed_keys(type, ["name", "kind", "arguments"], ["return_value", "description", "deprecated"])88write_function_type(file, type)89elif kind == "struct":90check_allowed_keys(type, ["name", "kind", "members"], ["description", "deprecated"])91write_struct_type(file, type)92else:93raise Exception(f"Unknown kind of type: {kind}")9495for type_name, replace_with in type_replacements:96if replace_with not in valid_data_types:97raise Exception(f"Unknown type '{replace_with}' used as replacement for '{type_name}'")98replacement = valid_data_types[replace_with]99if isinstance(replacement, dict) and "deprecated" in replacement:100raise Exception(101f"Cannot use '{replace_with}' as replacement for '{type_name}' because it's deprecated too"102)103104interface_replacements = []105valid_interfaces = {}106for interface in data["interface"]:107check_type("function", interface, valid_data_types)108check_allowed_keys(109interface,110["name", "arguments", "since", "description"],111["return_value", "see", "legacy_type_name", "deprecated"],112)113valid_interfaces[interface["name"]] = interface114if "deprecated" in interface:115check_allowed_keys(interface["deprecated"], ["since"], ["message", "replace_with"])116if "replace_with" in interface["deprecated"]:117interface_replacements.append((interface["name"], interface["deprecated"]["replace_with"]))118write_interface(file, interface)119120for function_name, replace_with in interface_replacements:121if replace_with not in valid_interfaces:122raise Exception(123f"Unknown interface function '{replace_with}' used as replacement for '{function_name}'"124)125replacement = valid_interfaces[replace_with]126if "deprecated" in replacement:127raise Exception(128f"Cannot use '{replace_with}' as replacement for '{function_name}' because it's deprecated too"129)130131file.write("""\132#ifdef __cplusplus133}134#endif135""")136137138# Serialize back into JSON in order to see if the formatting remains the same.139def check_formatting(buffer, data, filename):140buffer2 = json.dumps(data, indent=4)141142lines1 = buffer.splitlines()143lines2 = buffer2.splitlines()144145diff = difflib.unified_diff(146lines1,147lines2,148fromfile="a/" + filename,149tofile="b/" + filename,150lineterm="",151)152153diff = list(diff)154if len(diff) > 0:155print(" *** Apply this patch to fix: ***\n")156print("\n".join(diff))157raise Exception(f"Formatting issues in {filename}")158159160def check_allowed_keys(data, required, optional=[]):161keys = data.keys()162allowed = required + optional163for k in keys:164if k not in allowed:165raise Exception(f"Found unknown key '{k}'")166for r in required:167if r not in keys:168raise Exception(f"Missing required key '{r}'")169170171class UnknownTypeError(Exception):172def __init__(self, unknown, parent, item=None):173self.unknown = unknown174self.parent = parent175if item:176msg = f"Unknown type '{unknown}' for '{item}' used in '{parent}'"177else:178msg = f"Unknown type '{unknown}' used in '{parent}'"179super().__init__(msg)180181182def base_type_name(type_name):183if type_name.startswith("const "):184type_name = type_name[6:]185if type_name.endswith("*"):186type_name = type_name[:-1]187return type_name188189190def format_type_and_name(type, name=None):191ret = type192if ret[-1] == "*":193ret = ret[:-1] + " *"194if name:195if ret[-1] == "*":196ret = ret + name197else:198ret = ret + " " + name199return ret200201202def is_valid_type(type, valid_data_types):203if type in ["void", "const void"]:204# The "void" type can only be used with the pointer modifier.205return False206return base_type_name(type) in valid_data_types207208209def check_type(kind, type, valid_data_types):210if kind == "alias":211if not is_valid_type(type["type"], valid_data_types):212raise UnknownTypeError(type["type"], type["name"])213elif kind == "struct":214for member in type["members"]:215if not is_valid_type(member["type"], valid_data_types):216raise UnknownTypeError(member["type"], type["name"], member["name"])217elif kind == "function":218for arg in type["arguments"]:219if not is_valid_type(arg["type"], valid_data_types):220raise UnknownTypeError(arg["type"], type["name"], arg.get("name"))221if "return_value" in type:222if not is_valid_type(type["return_value"]["type"], valid_data_types):223raise UnknownTypeError(type["return_value"]["type"], type["name"])224225226def write_doc(file, doc, indent=""):227if len(doc) == 1:228file.write(f"{indent}/* {doc[0]} */\n")229return230231first = True232for line in doc:233if first:234file.write(indent + "/*")235first = False236else:237file.write(indent + " *")238239if line != "":240file.write(" " + line)241file.write("\n")242file.write(indent + " */\n")243244245def make_deprecated_message(data):246parts = [247f"Deprecated in Godot {data['since']}.",248data["message"] if "message" in data else "",249f"Use `{data['replace_with']}` instead." if "replace_with" in data else "",250]251return " ".join([x for x in parts if x.strip() != ""])252253254def make_deprecated_comment_for_type(type):255if "deprecated" not in type:256return ""257message = make_deprecated_message(type["deprecated"])258return f" /* {message} */"259260261def write_simple_type(file, type):262file.write(f"typedef {format_type_and_name(type['type'], type['name'])};{make_deprecated_comment_for_type(type)}\n")263264265def write_enum_type(file, enum):266file.write("typedef enum {\n")267for value in enum["values"]:268check_allowed_keys(value, ["name", "value"], ["description", "deprecated"])269if "description" in value:270write_doc(file, value["description"], "\t")271file.write(f"\t{value['name']} = {value['value']},\n")272file.write(f"}} {enum['name']};{make_deprecated_comment_for_type(enum)}\n\n")273274275def make_args_text(args):276combined = []277for arg in args:278check_allowed_keys(arg, ["type"], ["name", "description"])279combined.append(format_type_and_name(arg["type"], arg.get("name")))280return ", ".join(combined)281282283def write_function_type(file, fn):284args_text = make_args_text(fn["arguments"]) if ("arguments" in fn) else ""285name_and_args = f"(*{fn['name']})({args_text})"286return_type = fn["return_value"]["type"] if "return_value" in fn else "void"287file.write(f"typedef {format_type_and_name(return_type, name_and_args)};{make_deprecated_comment_for_type(fn)}\n")288289290def write_struct_type(file, struct):291file.write("typedef struct {\n")292for member in struct["members"]:293check_allowed_keys(member, ["name", "type"], ["description"])294if "description" in member:295write_doc(file, member["description"], "\t")296file.write(f"\t{format_type_and_name(member['type'], member['name'])};\n")297file.write(f"}} {struct['name']};{make_deprecated_comment_for_type(struct)}\n\n")298299300def write_interface(file, interface):301doc = [302f"@name {interface['name']}",303f"@since {interface['since']}",304]305306if "deprecated" in interface:307doc.append(f"@deprecated {make_deprecated_message(interface['deprecated'])}")308309doc += [310"",311interface["description"][0],312]313314if len(interface["description"]) > 1:315doc.append("")316doc += interface["description"][1:]317318if "arguments" in interface:319doc.append("")320for arg in interface["arguments"]:321if "description" not in arg:322raise Exception(f"Interface function {interface['name']} is missing docs for {arg['name']} argument")323arg_doc = " ".join(arg["description"])324doc.append(f"@param {arg['name']} {arg_doc}")325326if "return_value" in interface:327if "description" not in interface["return_value"]:328raise Exception(f"Interface function {interface['name']} is missing docs for return value")329ret_doc = " ".join(interface["return_value"]["description"])330doc.append("")331doc.append(f"@return {ret_doc}")332333if "see" in interface:334doc.append("")335for see in interface["see"]:336doc.append(f"@see {see}")337338file.write("/**\n")339for d in doc:340if d != "":341file.write(f" * {d}\n")342else:343file.write(" *\n")344file.write(" */\n")345346fn = interface.copy()347if "deprecated" in fn:348del fn["deprecated"]349fn["name"] = "GDExtensionInterface" + "".join(word.capitalize() for word in interface["name"].split("_"))350write_function_type(file, fn)351352file.write("\n")353354355