diff --git a/.editorconfig b/.editorconfig index 6272e0b3..9614ca84 100644 --- a/.editorconfig +++ b/.editorconfig @@ -29,3 +29,7 @@ indent_size = 4 [*.bat] end_of_line = crlf + +[.luarc.json] +indent_style = space +indent_size = 4 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fca3b65d..ed203866 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -264,7 +264,7 @@ jobs: scons-version: $SCONS_VERSION - name: Copy artifacts to build folder run: | - scons lua_runtime=${{ matrix.lua-runtime }} addons_files + scons lua_runtime=${{ matrix.lua-runtime }} addons_files lua_api cp -r ${{ steps.download.outputs.download-path }}/**/lib* addons/lua-gdextension/build - name: Upload artifact uses: actions/upload-artifact@v4 @@ -272,6 +272,7 @@ jobs: name: ${{ matrix.lua-runtime == 'lua' && 'lua-gdextension' || format('lua-gdextension+{0}', matrix.lua-runtime) }} path: | LICENSE + .luarc.json addons/lua-gdextension/** addons/lua-gdextension/build/.gdignore include-hidden-files: true diff --git a/.gitignore b/.gitignore index 2f39df8c..c94f712d 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,11 @@ test/godot .vs/ .vscode/ -# Addons files +# Addons files generated by build addons/lua-gdextension/CHANGELOG.md addons/lua-gdextension/README.md addons/lua-gdextension/LICENSE +addons/lua-gdextension/lua_api_definitions/builtin_classes.lua +addons/lua-gdextension/lua_api_definitions/classes.lua +addons/lua-gdextension/lua_api_definitions/global_enums.lua +addons/lua-gdextension/lua_api_definitions/utility_functions.lua diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 00000000..752b905a --- /dev/null +++ b/.luarc.json @@ -0,0 +1,7 @@ +{ + "workspace": { + "library": [ + "addons/lua-gdextension/lua_api_definitions" + ] + } +} diff --git a/addons/lua-gdextension/lua_api_definitions/.gdignore b/addons/lua-gdextension/lua_api_definitions/.gdignore new file mode 100644 index 00000000..e69de29b diff --git a/addons/lua-gdextension/lua_api_definitions/lua_script_language.lua b/addons/lua-gdextension/lua_api_definitions/lua_script_language.lua new file mode 100644 index 00000000..e4ebeb67 --- /dev/null +++ b/addons/lua-gdextension/lua_api_definitions/lua_script_language.lua @@ -0,0 +1,83 @@ +--- @meta + +----------------------------------------------------------- +-- Properties +----------------------------------------------------------- + +--- @class LuaScriptProperty +--- Property definition for Lua scripts. +LuaScriptProperty = {} + +--- Used to define custom properties in Lua scripts. +--- If you pass a table, the following keys are used (all are optional): +--- + `1`: if it's a Variant type or Class (like `Dictionary` or `Node2D`) it represents the property type. +--- Otherwise, it represents the property's default value. +--- + `type`: should be a Variant type or a Class, such as `Vector2` or `RefCounted`. +--- + `hint`: property hint (check out the `PropertyHint` enum for available values) +--- + `hint_string`: property hint string, depends on the value of `hint` +--- + `usage`: property usage flags (check out the `PropertyUsage` enum for available values) +--- + `class_name`: the name of the Class, filled automatically from `type` if it's a Class type +--- + `default`: the default value of the property +--- + `get`: getter function, should be either a Lua function or a string containing the getter method name +--- + `set`: setter function, should be either a Lua function or a string containing the setter method name +--- +--- In case `t` is not a table, the table `{t}` will be used instead. +--- @param t table | any +--- @return LuaScriptProperty +function property(t) end + +--- Used to define exported properties in Lua scripts. +--- This is the same as `property`, but always adds `PROPERTY_USAGE_EDITOR` to the property's usage flags. +--- +--- @see property +--- @param t table | any +--- @return LuaScriptProperty +function export(t) end + + +----------------------------------------------------------- +-- Signals +----------------------------------------------------------- + +--- @class LuaScriptSignal +--- Signal definition for Lua scripts. +LuaScriptSignal = {} + +--- Used to define custom signals in Lua scripts. +--- For now there is no way to pass type information for arguments, only their names. +--- ``` +--- local MyClass = {} +--- MyClass.some_signal = signal("argument1", "argument2") +--- return MyClass +--- ``` +--- @param ... string +--- @return LuaScriptSignal +function signal(...) end + + +----------------------------------------------------------- +-- RPC configuration +----------------------------------------------------------- + +--- Similar to GDScript's `@rpc` annotation, should be used to initialize the special `rpc_config`. +--- Example: +--- ``` +--- local MyClass = {} +--- +--- function MyClass:some_method() end +--- function MyClass:some_other_method() end +--- +--- MyClass.rpc_config = { +--- "some_method" = rpc("any_peer", "call_local", "reliable", 0), +--- "some_other_method" = rpc("reliable", 1), +--- } +--- +--- return MyClass +--- ``` +--- See [@rpc](https://docs.godotengine.org/en/stable/classes/class_@gdscript.html#class-gdscript-annotation-rpc) for more information. +--- +--- @param mode "any_peer" | "authority" | nil +--- @param sync "call_remote" | "call_local" | nil +--- @param transfer_mode "unreliable" | "unreliable_ordered" | "reliable" | nil +--- @param transfer_channel integer? +function rpc(mode, sync, transfer_mode, transfer_channel) end diff --git a/addons/lua-gdextension/lua_api_definitions/manually_defined_globals.lua b/addons/lua-gdextension/lua_api_definitions/manually_defined_globals.lua new file mode 100644 index 00000000..9d56bcc5 --- /dev/null +++ b/addons/lua-gdextension/lua_api_definitions/manually_defined_globals.lua @@ -0,0 +1,23 @@ +--- @meta + +--- Yields the current coroutine until the passed signal is emitted. +--- If an Object is passed, awaits for its 'completed' signal. +--- This function should only be called inside a coroutine. +--- +--- Note: only available if `GODOT_UTILITY_FUNCTIONS` library is open in the LuaState. +--- @param awaitable Object | Signal +--- @return any +function await(awaitable) end + + +--- Returns the Variant type of the passed value. +--- Contrary to GDScript's `typeof`, in Lua this does not return the enum like `TYPE_BOOL` or `TYPE_DICTIONARY`, but rather the actual class type like `bool` or `Dictionary`. +--- ``` +--- if typeof(some_value) == Dictionary then +--- -- ... +--- end +--- ``` +--- Note: only available if `GODOT_VARIANT` library is open in the LuaState. +--- @param value any +--- @return userdata? +function typeof(value) end diff --git a/tools/code_generation/generate_cpp_code.py b/tools/code_generation/generate_cpp_code.py index a3dc926d..51628063 100644 --- a/tools/code_generation/generate_cpp_code.py +++ b/tools/code_generation/generate_cpp_code.py @@ -26,7 +26,7 @@ def _to_variant_type(s: str) -> str: def generate_utility_functions(utility_functions): lines = [ - "// This file was automatically generated by generate_code.py\n#undef register_utility_functions\n#define register_utility_functions(state)", + "// This file was automatically generated by generate_cpp_code.py\n#undef register_utility_functions\n#define register_utility_functions(state)", ] for f in utility_functions: name = f["name"] @@ -47,7 +47,7 @@ def generate_utility_functions(utility_functions): def generate_enums(global_enums): lines = [ - "// This file was automatically generated by generate_code.py\n#undef register_global_enums\n#define register_global_enums(state)", + "// This file was automatically generated by generate_cpp_code.py\n#undef register_global_enums\n#define register_global_enums(state)", ] for enum in global_enums: lines.append(f"\t/* {enum['name']} */") @@ -58,7 +58,7 @@ def generate_enums(global_enums): def generate_package_searcher(): lines = [ - "// This file was automatically generated by generate_code.py", + "// This file was automatically generated by generate_cpp_code.py", "const char package_searcher_lua[] = ", ] with open(PACKAGE_SEARCHER_SRC, "r", encoding="utf-8") as f: @@ -71,7 +71,7 @@ def generate_package_searcher(): def generate_lua_script_globals(): lines = [ - "// This file was automatically generated by generate_code.py", + "// This file was automatically generated by generate_cpp_code.py", "const char lua_script_globals[] = ", ] with open(LUA_SCRIPT_GLOBALS_SRC, "r", encoding="utf-8") as f: @@ -84,7 +84,7 @@ def generate_lua_script_globals(): def generate_variant_type_constants(builtin_classes): lines = [ - "// This file was automatically generated by generate_code.py", + "// This file was automatically generated by generate_cpp_code.py", "#include ", "using namespace godot;", "", diff --git a/tools/code_generation/generate_lua_godot_api.py b/tools/code_generation/generate_lua_godot_api.py new file mode 100644 index 00000000..56c9d91a --- /dev/null +++ b/tools/code_generation/generate_lua_godot_api.py @@ -0,0 +1,375 @@ +""" +Generate a Lua file with Lua Language Server annotations based on extension_api.json +""" + +import os +import json +from textwrap import dedent + +from json_types import * + + +SRC_DIR = os.path.dirname(__file__) +DEST_DIR = os.path.join(SRC_DIR, "..", "..", "addons", "lua-gdextension", "lua_api_definitions") +API_JSON_PATH = os.path.join(SRC_DIR, "..", "..", "lib", "godot-cpp", "gdextension", "extension_api.json") + +OPERATOR_MAP = { + ## LLS doesn't really support equality operators: https://github.com/LuaLS/lua-language-server/issues/1882 + # "==": "eq", + # "<": "lt", + # "<=": "le", + + "+": "add", + "-": "sub", + "*": "mul", + "/": "div", + "%": "mod", + "**": "pow", + "unary-": "unm", + + "&": "band", + "|": "bor", + "^": "bxor", + "~": "bnot", + "<<": "shl", + ">>": "shr", +} + +LUA_KEYWORD_MAP = { + "end": "_end", + "function": "_function", + "in": "_in", + "local": "_local", + "repeat": "_repeat", +} + +MANUALLY_DEFINED_UTILITY_FUNCTIONS = { + "typeof", +} + + +def main(): + with open(API_JSON_PATH, encoding="utf-8") as f: + extension_api: ExtensionApi = json.load(f) + + with open(os.path.join(DEST_DIR, "global_enums.lua"), "w") as f: + _write_to_file(f, generate_global_enums(extension_api["global_enums"])) + + with open(os.path.join(DEST_DIR, "builtin_classes.lua"), "w") as f: + _write_to_file(f, generate_builtin_classes(extension_api["builtin_classes"])) + + with open(os.path.join(DEST_DIR, "classes.lua"), "w") as f: + _write_to_file(f, generate_classes(extension_api["classes"], extension_api["singletons"])) + + with open(os.path.join(DEST_DIR, "utility_functions.lua"), "w") as f: + _write_to_file(f, generate_utility_functions(extension_api["utility_functions"])) + + +def _write_to_file(f, lines: list[str]): + preamble = [ + "--- This file was automatically generated by generate_lua_godot_api.py", + "--- @meta", + ] + f.writelines(f"{l}\n" for l in preamble + lines) + + +def _generate_section(name: str) -> str: + return dedent(f""" + ----------------------------------------------------------- + -- {name} + ----------------------------------------------------------- + """) + + +def _arg_name(name: str) -> str: + return LUA_KEYWORD_MAP.get(name, name) + + +def _arg_type(name: str, has_default: Any = False) -> str: + if name == "Variant": + arg_type = "any" + elif name.startswith("typedarray::"): + arg_type = f"Array[{name[len("typedarray::"):]}]" + else: + arg_type = name.replace(",", " | ").replace("enum::", "").replace("bitfield::", "") + + if has_default: + arg_type += '?' + return arg_type + + +def generate_global_enums( + enums: list[GlobalEnumOrEnum], +) -> list[str]: + lines = [_generate_section("Global Enums")] + for enum in enums: + lines.append(f"--- @alias {enum['name']} {' | '.join(f'`{value["name"]}`' for value in enum['values'])}") + for value in enum["values"]: + lines.append(f"{value['name']} = {value['value']}") + lines.append("") + return lines + + +def generate_builtin_classes( + builtin_classes: list[BuiltinClass], +) -> list[str]: + lines = [ + "--- @diagnostic disable: param-type-mismatch", + "--- @diagnostic disable: redundant-parameter", + _generate_section("Builtin Classes (a.k.a. Variants)"), + ] + + # First, the definition of Variant + lines.append(dedent(""" + --- @class Variant + --- @overload fun(): Variant + --- @overload fun(from: any): Variant + --- @operator concat(any): String + Variant = {} + + --- @param self any + --- @return bool + function Variant.booleanize(self) end + + --- @param self any + --- @return Variant + function Variant.duplicate(self) end + + --- @param method string + --- @return Variant + function Variant:call(method, ...) end + + --- @param method string + --- @return Variant + function Variant:pcall(method, ...) end + + --- @param self any + --- @return Variant.Type + function Variant.get_type(self) end + + --- @param self any + --- @return string + function Variant.get_type_name(self) end + + --- @param self any + --- @return integer + function Variant.hash(self) end + + --- @param self any + --- @param recursion_count integer + --- @return integer + function Variant.recursive_hash(self, recursion_count) end + + --- @param other any + --- @return bool + function Variant.hash_compare(self, other) end + + --- @param self any + --- @param type any + --- @return bool + function Variant.is(self, type) end + """).lstrip()) + + # Now its specializations + for cls in builtin_classes: + if cls["name"] == "Nil": + continue + + lines.append(f"{_generate_section(cls['name'])}") + if cls["name"] == "bool": + lines.append("--- @alias bool boolean") + lines.append("--- @return bool") + lines.append("function bool() end") + elif cls["name"] == "int": + lines.append("--- @alias int integer") + lines.append("--- @return int") + lines.append("function int() end") + elif cls["name"] == "float": + lines.append("--- @alias float number") + lines.append("--- @return float") + lines.append("function float() end") + else: + can_construct_from_table = cls["name"] in ["Dictionary", "Array"] + is_string = cls["name"] in ["String", "StringName"] + + # Header + if is_string: + lines.append(f"--- @alias {cls['name']} string") + lines.append(f"{cls['name']} = string") + else: + inherits = ["Variant"] + if indexing_return_type := cls.get("indexing_return_type"): + key_type = "any" if cls["is_keyed"] else "int" + inherits.append(f"{{ [{key_type}]: {_arg_type(indexing_return_type, indexing_return_type != "Variant")} }}") + + lines.append(f"--- @class {cls['name']}: {', '.join(inherits)}") + + # Fields + for member in cls.get("members", []): + lines.append(f"--- @field {member['name']} {member['type']}") + + # Constructors + if can_construct_from_table: + lines.append(f"--- @overload fun(from: table): {cls['name']}") + for ctor in cls["constructors"]: + lines.append(f"--- @overload fun({', '.join(f"{arg['name']}: {arg['type']}" for arg in ctor.get('arguments', []))}): {cls['name']}") + + # Other operators + for op in cls["operators"]: + if op["name"] in OPERATOR_MAP: + lines.append(f"--- @operator {OPERATOR_MAP[op['name']]}({op.get('right_type', "")}): {op['return_type']}") + + lines.append(f"{cls['name']} = {{}}") + + # Constants + if constants := cls.get("constants", []): + lines.append("") + for constant in constants: + lines.append(f"{cls['name']}.{constant['name']} = {constant['value'].replace("inf", "math.huge")}") + + # Enums + for enum in cls.get("enums", []): + lines.append("") + lines.append(f"--- @enum {cls['name']}.{enum['name']}") + lines.append(f"{cls['name']}.{enum['name']} = {{") + for value in enum["values"]: + lines.append(f"\t{value['name']} = {value['value']},") + lines.append("}") + + # Methods + # We don't repeat StringName methods because they are the same as String's, which are both aliases to `string` + if cls["name"] != "StringName": + for method in cls.get("methods", []): + # Just skip methods that have names that are keywords in Lua + if method["name"] in LUA_KEYWORD_MAP: + continue + + lines.append("") + if method["is_static"]: + lines.append("--- static") + if is_string: + lines.append(f"--- @param self string") + for arg in method.get('arguments', []): + lines.append(f"--- @param {_arg_name(arg['name'])} {_arg_type(arg['type'], arg.get('default_value'))}{' Default: ' + arg.get('default_value', '') if arg.get('default_value') else ''}") + if return_type := method.get("return_type"): + lines.append(f"--- @return {_arg_type(return_type)}") + args = [_arg_name(arg["name"]) for arg in method.get("arguments", [])] + if is_string: + args.insert(0, "self") + if method["is_vararg"]: + args.append("...") + lines.append(f"""function { + 'string' if is_string else cls['name'] + }{ + '.' if is_string else ':' + }{ + method['name'] + }({ + ', '.join(args) + }) end""") + + lines.append("") + + lines.append("") + return lines + + +def generate_classes( + classes: list[Class], + singletons: list[ArgumentOrSingletonOrMember], +) -> list[str]: + lines = [] + + # Now its specializations + for cls in classes: + # Header + lines.append(f"{_generate_section(cls['name'])}") + lines.append(f"--- @class {cls['name']}: {cls.get('inherits', 'Variant')}, {{ [string]: any }}") + + # Properties + for property in cls.get("properties", []): + lines.append(f"--- @field {property['name']} {_arg_type(property['type'])}") + + lines.append(f"{cls['name']} = {{}}") + + # Constructor + if not get_class_singleton_name(cls, singletons) and cls["is_instantiable"]: + lines.append("") + lines.append(f"--- @return {cls['name']}") + lines.append(f"function {cls['name']}:new() end") + + # Constants + if constants := cls.get("constants", []): + lines.append("") + for constant in constants: + lines.append(f"{cls['name']}.{constant['name']} = {constant['value']}") + + # Enums + for enum in cls.get("enums", []): + lines.append("") + lines.append(f"--- @alias {cls['name']}.{enum['name']} {' | '.join(f"`{cls['name']}.{value['name']}`" for value in enum['values'])}") + for value in enum["values"]: + lines.append(f"{cls['name']}.{value['name']} = {value['value']}") + + # Signals + if signals := cls.get("signals", []): + lines.append("") + for signal in signals: + lines.append(f"{cls['name']}.{signal['name']} = Signal()") + + # Methods + for method in cls.get("methods", []): + # Just skip methods that have names that are keywords in Lua + if method["name"] in LUA_KEYWORD_MAP: + continue + + lines.append("") + if method["is_static"]: + lines.append("--- static") + for arg in method.get('arguments', []): + lines.append(f"--- @param {_arg_name(arg['name'])} {_arg_type(arg['type'], arg.get('default_value'))}{' Default: ' + arg.get('default_value', '') if arg.get('default_value') else ''}") + if return_value := method.get("return_value"): + lines.append(f"--- @return {_arg_type(return_value['type'])}") + args = [_arg_name(arg["name"]) for arg in method.get("arguments", [])] + if method['is_vararg']: + args.append("...") + lines.append(f"""function { + cls['name'] + }:{ + method['name'] + }({ + ', '.join(args) + }) end""") + + lines.append("") + + lines.append("") + return lines + + +def generate_utility_functions( + utility_functions: list[UtilityFunction], +) -> list[str]: + lines = [] + for f in utility_functions: + if f["name"] in MANUALLY_DEFINED_UTILITY_FUNCTIONS: + continue + + lines.append("") + for arg in f.get('arguments', []): + lines.append(f"--- @param {_arg_name(arg['name'])} {_arg_type(arg['type'])}") + if return_type := f.get("return_type"): + lines.append(f"--- @return {_arg_type(return_type)}") + args = [_arg_name(arg["name"]) for arg in f.get("arguments", [])] + if f['is_vararg']: + args.append("...") + lines.append(f"""function { + f['name'] + }({ + ', '.join(args) + }) end""") + return lines + + +if __name__ == "__main__": + main() diff --git a/tools/code_generation/json_types.py b/tools/code_generation/json_types.py new file mode 100644 index 00000000..f97e0eb6 --- /dev/null +++ b/tools/code_generation/json_types.py @@ -0,0 +1,196 @@ +""" +extension_api.json JSON schema as Python TypedDict subclasses + +Automatically generated (with caveats) by json2pyi + https://github.com/Gowee/json2pyi +""" + +from __future__ import annotations + +try: + from typing import Any, List, TypedDict, NotRequired +except ImportError: + from typing import Any, List + from typing_extensions import TypedDict, NotRequired + + +class ExtensionApi(TypedDict): + header: Header + builtin_class_sizes: List[BuiltinClassSize] + builtin_class_member_offsets: List[BuiltinClassMemberOffset] + global_constants: List[Any] + global_enums: List[GlobalEnumOrEnum] + utility_functions: List[UtilityFunction] + builtin_classes: List[BuiltinClass] + classes: List[Class] + singletons: List[ArgumentOrSingletonOrMember] + native_structures: List[NativeStructure] + + +class Header(TypedDict): + version_major: int + version_minor: int + version_patch: int + version_status: str + version_build: str + version_full_name: str + + +class BuiltinClassSize(TypedDict): + build_configuration: str + sizes: List[Size] + + +class Size(TypedDict): + name: str + size: int + + +class BuiltinClassMemberOffset(TypedDict): + build_configuration: str + classes: List[MemberOffset] + + +class MemberOffset(TypedDict): + name: str + members: List[Member] + + +class Member(TypedDict): + member: str + offset: int + meta: str + + +class GlobalEnumOrEnum(TypedDict): + name: str + is_bitfield: bool + values: List[ValueOrConstant] + + +class ValueOrConstant(TypedDict): + name: str + value: int + + +class UtilityFunction(TypedDict): + name: str + return_type: NotRequired[str] + category: str + is_vararg: bool + hash: int + arguments: NotRequired[List[ArgumentOrSingletonOrMember]] + + +class ArgumentOrSingletonOrMember(TypedDict): + name: str + type: str + + +class BuiltinClass(TypedDict): + name: str + indexing_return_type: NotRequired[str] + is_keyed: bool + operators: List[Operator] + methods: NotRequired[List[BuiltinClassMethod]] + constructors: List[Constructor] + has_destructor: bool + members: NotRequired[List[ArgumentOrSingletonOrMember]] + constants: NotRequired[List[Constant]] + enums: NotRequired[List[Enum]] + + +class Operator(TypedDict): + name: str + right_type: NotRequired[str] + return_type: str + + +class BuiltinClassMethod(TypedDict): + name: str + return_type: NotRequired[str] + is_vararg: bool + is_const: bool + is_static: bool + hash: int + arguments: NotRequired[List[Argument]] + + +class Constructor(TypedDict): + index: int + arguments: NotRequired[List[ArgumentOrSingletonOrMember]] + + +class Constant(TypedDict): + name: str + type: str + value: str + + +class Enum(TypedDict): + name: str + values: List[ValueOrConstant] + + +class Class(TypedDict): + name: str + is_refcounted: bool + is_instantiable: bool + inherits: NotRequired[str] + api_type: str + enums: NotRequired[List[GlobalEnumOrEnum]] + methods: NotRequired[List[Method]] + properties: NotRequired[List[Property]] + signals: NotRequired[List[Signal]] + constants: NotRequired[List[ValueOrConstant]] + + +class Method(TypedDict): + name: str + is_const: bool + is_vararg: bool + is_static: bool + is_virtual: bool + hash: NotRequired[int] + arguments: NotRequired[List[Argument]] + return_value: NotRequired[ReturnValue] + + +class Argument(TypedDict): + name: str + type: str + meta: NotRequired[str] + default_value: NotRequired[str] + + +class ReturnValue(TypedDict): + type: str + meta: NotRequired[str] + + +class Property(TypedDict): + type: str + name: str + setter: NotRequired[str] + getter: str + index: NotRequired[int] + + +class Signal(TypedDict): + name: str + arguments: NotRequired[List[ArgumentOrSingletonOrMember]] + + +class NativeStructure(TypedDict): + name: str + format: str + + +def get_class_singleton_name( + cls: Class, + singletons: list[ArgumentOrSingletonOrMember], +) -> ArgumentOrSingletonOrMember | None: + for singleton in singletons: + if singleton["type"] == cls["name"]: + return singleton + return None diff --git a/tools/code_generator.py b/tools/code_generator.py index fab113fd..5271885b 100644 --- a/tools/code_generator.py +++ b/tools/code_generator.py @@ -25,3 +25,19 @@ def generate(env): ], action=python_bin + " $SOURCE", ) + # Lua API metadata file to use in Lua Language Server + godot_lua_api = env.Command( + [ + "addons/lua-gdextension/lua_api_definitions/builtin_classes.lua", + "addons/lua-gdextension/lua_api_definitions/classes.lua", + "addons/lua-gdextension/lua_api_definitions/global_enums.lua", + "addons/lua-gdextension/lua_api_definitions/utility_functions.lua", + ], + [ + "tools/code_generation/generate_lua_godot_api.py", + "lib/godot-cpp/gdextension/extension_api.json", + ], + action=python_bin + " $SOURCE", + ) + env.Default(godot_lua_api) + env.Alias("lua_api", godot_lua_api)