184 lines
5.6 KiB
Python
184 lines
5.6 KiB
Python
import json
|
|
import sys
|
|
|
|
|
|
def parse_build_profile(profile_filepath, api):
|
|
if profile_filepath == "":
|
|
return {}
|
|
|
|
with open(profile_filepath, encoding="utf-8") as profile_file:
|
|
profile = json.load(profile_file)
|
|
|
|
api_dict = {}
|
|
parents = {}
|
|
children = {}
|
|
for engine_class in api["classes"]:
|
|
api_dict[engine_class["name"]] = engine_class
|
|
parent = engine_class.get("inherits", "")
|
|
child = engine_class["name"]
|
|
parents[child] = parent
|
|
if parent == "":
|
|
continue
|
|
children[parent] = children.get(parent, [])
|
|
children[parent].append(child)
|
|
|
|
included = []
|
|
front = list(profile.get("enabled_classes", []))
|
|
if front:
|
|
# These must always be included
|
|
front.append("WorkerThreadPool")
|
|
front.append("ClassDB")
|
|
front.append("ClassDBSingleton")
|
|
# In src/classes/low_level.cpp
|
|
front.append("FileAccess")
|
|
front.append("Image")
|
|
front.append("XMLParser")
|
|
# In include/godot_cpp/templates/thread_work_pool.hpp
|
|
front.append("Semaphore")
|
|
while front:
|
|
cls = front.pop()
|
|
if cls in included:
|
|
continue
|
|
included.append(cls)
|
|
parent = parents.get(cls, "")
|
|
if parent:
|
|
front.append(parent)
|
|
|
|
excluded = []
|
|
front = list(profile.get("disabled_classes", []))
|
|
while front:
|
|
cls = front.pop()
|
|
if cls in excluded:
|
|
continue
|
|
excluded.append(cls)
|
|
front += children.get(cls, [])
|
|
|
|
if included and excluded:
|
|
print(
|
|
"WARNING: Cannot specify both 'enabled_classes' and 'disabled_classes' in build profile. 'disabled_classes' will be ignored."
|
|
)
|
|
|
|
return {
|
|
"enabled_classes": included,
|
|
"disabled_classes": excluded,
|
|
}
|
|
|
|
|
|
def generate_trimmed_api(source_api_filepath, profile_filepath):
|
|
with open(source_api_filepath, encoding="utf-8") as api_file:
|
|
api = json.load(api_file)
|
|
|
|
if profile_filepath == "":
|
|
return api
|
|
|
|
build_profile = parse_build_profile(profile_filepath, api)
|
|
|
|
engine_classes = {}
|
|
for class_api in api["classes"]:
|
|
engine_classes[class_api["name"]] = class_api["is_refcounted"]
|
|
for native_struct in api["native_structures"]:
|
|
if native_struct["name"] == "ObjectID":
|
|
continue
|
|
engine_classes[native_struct["name"]] = False
|
|
|
|
classes = []
|
|
for class_api in api["classes"]:
|
|
if not is_class_included(class_api["name"], build_profile):
|
|
continue
|
|
if "methods" in class_api:
|
|
methods = []
|
|
for method in class_api["methods"]:
|
|
if not is_method_included(method, build_profile, engine_classes):
|
|
continue
|
|
methods.append(method)
|
|
class_api["methods"] = methods
|
|
classes.append(class_api)
|
|
api["classes"] = classes
|
|
|
|
return api
|
|
|
|
|
|
def is_class_included(class_name, build_profile):
|
|
"""
|
|
Check if an engine class should be included.
|
|
This removes classes according to a build profile of enabled or disabled classes.
|
|
"""
|
|
included = build_profile.get("enabled_classes", [])
|
|
excluded = build_profile.get("disabled_classes", [])
|
|
if included:
|
|
return class_name in included
|
|
if excluded:
|
|
return class_name not in excluded
|
|
return True
|
|
|
|
|
|
def is_method_included(method, build_profile, engine_classes):
|
|
"""
|
|
Check if an engine class method should be included.
|
|
This removes methods according to a build profile of enabled or disabled classes.
|
|
"""
|
|
included = build_profile.get("enabled_classes", [])
|
|
excluded = build_profile.get("disabled_classes", [])
|
|
ref_cls = set()
|
|
rtype = get_base_type(method.get("return_value", {}).get("type", ""))
|
|
args = [get_base_type(a["type"]) for a in method.get("arguments", [])]
|
|
if rtype in engine_classes:
|
|
ref_cls.add(rtype)
|
|
elif is_enum(rtype) and get_enum_class(rtype) in engine_classes:
|
|
ref_cls.add(get_enum_class(rtype))
|
|
for arg in args:
|
|
if arg in engine_classes:
|
|
ref_cls.add(arg)
|
|
elif is_enum(arg) and get_enum_class(arg) in engine_classes:
|
|
ref_cls.add(get_enum_class(arg))
|
|
for acls in ref_cls:
|
|
if len(included) > 0 and acls not in included:
|
|
return False
|
|
elif len(excluded) > 0 and acls in excluded:
|
|
return False
|
|
return True
|
|
|
|
|
|
def is_enum(type_name):
|
|
return type_name.startswith("enum::") or type_name.startswith("bitfield::")
|
|
|
|
|
|
def get_enum_class(enum_name: str):
|
|
if "." in enum_name:
|
|
if is_bitfield(enum_name):
|
|
return enum_name.replace("bitfield::", "").split(".")[0]
|
|
else:
|
|
return enum_name.replace("enum::", "").split(".")[0]
|
|
else:
|
|
return "GlobalConstants"
|
|
|
|
|
|
def get_base_type(type_name):
|
|
if type_name.startswith("const "):
|
|
type_name = type_name[6:]
|
|
if type_name.endswith("*"):
|
|
type_name = type_name[:-1]
|
|
if type_name.startswith("typedarray::"):
|
|
type_name = type_name.replace("typedarray::", "")
|
|
return type_name
|
|
|
|
|
|
def is_bitfield(type_name):
|
|
return type_name.startswith("bitfield::")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 3 or len(sys.argv) > 4:
|
|
print("Usage: %s BUILD_PROFILE INPUT_JSON [OUTPUT_JSON]" % (sys.argv[0]))
|
|
sys.exit(1)
|
|
profile = sys.argv[1]
|
|
infile = sys.argv[2]
|
|
outfile = sys.argv[3] if len(sys.argv) > 3 else ""
|
|
api = generate_trimmed_api(infile, profile)
|
|
|
|
if outfile:
|
|
with open(outfile, "w", encoding="utf-8") as f:
|
|
json.dump(api, f)
|
|
else:
|
|
json.dump(api, sys.stdout)
|