Browse Source

Add support for build profiles.

Allow enabling or disabling specific classes (which will not be built).
Fabio Alessandrelli 2 năm trước cách đây
mục cha
commit
1186c488bd
3 tập tin đã thay đổi với 132 bổ sung5 xóa
  1. 114 5
      binding_generator.py
  2. 9 0
      test/build_profile.json
  3. 9 0
      tools/godotcpp.py

+ 114 - 5
binding_generator.py

@@ -197,12 +197,14 @@ def generate_virtuals(target):
         f.write(txt)
 
 
-def get_file_list(api_filepath, output_dir, headers=False, sources=False):
+def get_file_list(api_filepath, output_dir, headers=False, sources=False, profile_filepath=""):
     api = {}
     files = []
     with open(api_filepath, encoding="utf-8") as api_file:
         api = json.load(api_file)
 
+    build_profile = parse_build_profile(profile_filepath, api)
+
     core_gen_folder = Path(output_dir) / "gen" / "include" / "godot_cpp" / "core"
     include_gen_folder = Path(output_dir) / "gen" / "include" / "godot_cpp"
     source_gen_folder = Path(output_dir) / "gen" / "src"
@@ -233,7 +235,7 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
         source_filename = source_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".cpp")
         if headers:
             files.append(str(header_filename.as_posix()))
-        if sources:
+        if sources and is_class_included(engine_class["name"], build_profile):
             files.append(str(source_filename.as_posix()))
 
     for native_struct in api["native_structures"]:
@@ -265,12 +267,105 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
     return files
 
 
-def print_file_list(api_filepath, output_dir, headers=False, sources=False):
-    print(*get_file_list(api_filepath, output_dir, headers, sources), sep=";", end=None)
+def print_file_list(api_filepath, output_dir, headers=False, sources=False, profile_filepath=""):
+    print(*get_file_list(api_filepath, output_dir, headers, sources, profile_filepath), sep=";", end=None)
+
+
+def parse_build_profile(profile_filepath, api):
+    if profile_filepath == "":
+        return {}
+    print("Using feature build profile: " + profile_filepath)
+
+    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)
+
+    # Parse methods dependencies
+    deps = {}
+    reverse_deps = {}
+    for name, engine_class in api_dict.items():
+        ref_cls = set()
+        for method in engine_class.get("methods", []):
+            rtype = method.get("return_value", {}).get("type", "")
+            args = [a["type"] for a in method.get("arguments", [])]
+            if rtype in api_dict:
+                ref_cls.add(rtype)
+            elif is_enum(rtype) and get_enum_class(rtype) in api_dict:
+                ref_cls.add(get_enum_class(rtype))
+            for arg in args:
+                if arg in api_dict:
+                    ref_cls.add(arg)
+                elif is_enum(arg) and get_enum_class(arg) in api_dict:
+                    ref_cls.add(get_enum_class(arg))
+        deps[engine_class["name"]] = set(filter(lambda x: x != name, ref_cls))
+        for acls in ref_cls:
+            if acls == name:
+                continue
+            reverse_deps[acls] = reverse_deps.get(acls, set())
+            reverse_deps[acls].add(name)
+
+    included = []
+    front = list(profile.get("enabled_classes", []))
+    if front:
+        # These must always be included
+        front.append("WorkerThreadPool")
+        front.append("ClassDB")
+        front.append("ClassDBSingleton")
+    while front:
+        cls = front.pop()
+        if cls in included:
+            continue
+        included.append(cls)
+        parent = parents.get(cls, "")
+        if parent:
+            front.append(parent)
+        for rcls in deps.get(cls, set()):
+            if rcls in included or rcls in front:
+                continue
+            front.append(rcls)
+
+    excluded = []
+    front = list(profile.get("disabled_classes", []))
+    while front:
+        cls = front.pop()
+        if cls in excluded:
+            continue
+        excluded.append(cls)
+        front += children.get(cls, [])
+        for rcls in reverse_deps.get(cls, set()):
+            if rcls in excluded or rcls in front:
+                continue
+            front.append(rcls)
+
+    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 scons_emit_files(target, source, env):
-    files = [env.File(f) for f in get_file_list(str(source[0]), target[0].abspath, True, True)]
+    profile_filepath = env.get("build_profile", "")
+    if profile_filepath and not Path(profile_filepath).is_absolute():
+        profile_filepath = str((Path(env.Dir("#").abspath) / profile_filepath).as_posix())
+
+    files = [env.File(f) for f in get_file_list(str(source[0]), target[0].abspath, True, True, profile_filepath)]
     env.Clean(target, files)
     env["godot_cpp_gen_dir"] = target[0].abspath
     return files, source
@@ -2547,6 +2642,20 @@ def is_refcounted(type_name):
     return type_name in engine_classes and engine_classes[type_name]
 
 
+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_included(type_name, current_type):
     """
     Check if a builtin type should be included.

+ 9 - 0
test/build_profile.json

@@ -0,0 +1,9 @@
+{
+	"enabled_classes": [
+		"Control",
+		"Label",
+		"OS",
+		"TileMap",
+		"InputEventKey"
+	]
+}

+ 9 - 0
tools/godotcpp.py

@@ -285,6 +285,15 @@ def options(opts, env):
         )
     )
 
+    opts.Add(
+        PathVariable(
+            "build_profile",
+            "Path to a file containing a feature build profile",
+            default=env.get("build_profile", None),
+            validator=validate_file,
+        )
+    )
+
     opts.Add(
         BoolVariable(
             key="use_hot_reload",