@@ -197,12 +197,14 @@ def generate_virtuals(target):
197197 f .write (txt )
198198
199199
200- def get_file_list (api_filepath , output_dir , headers = False , sources = False ):
200+ def get_file_list (api_filepath , output_dir , headers = False , sources = False , profile_filepath = "" ):
201201 api = {}
202202 files = []
203203 with open (api_filepath , encoding = "utf-8" ) as api_file :
204204 api = json .load (api_file )
205205
206+ build_profile = parse_build_profile (profile_filepath , api )
207+
206208 core_gen_folder = Path (output_dir ) / "gen" / "include" / "godot_cpp" / "core"
207209 include_gen_folder = Path (output_dir ) / "gen" / "include" / "godot_cpp"
208210 source_gen_folder = Path (output_dir ) / "gen" / "src"
@@ -233,7 +235,7 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
233235 source_filename = source_gen_folder / "classes" / (camel_to_snake (engine_class ["name" ]) + ".cpp" )
234236 if headers :
235237 files .append (str (header_filename .as_posix ()))
236- if sources :
238+ if sources and is_class_included ( engine_class [ "name" ], build_profile ) :
237239 files .append (str (source_filename .as_posix ()))
238240
239241 for native_struct in api ["native_structures" ]:
@@ -265,12 +267,105 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
265267 return files
266268
267269
268- def print_file_list (api_filepath , output_dir , headers = False , sources = False ):
269- print (* get_file_list (api_filepath , output_dir , headers , sources ), sep = ";" , end = None )
270+ def print_file_list (api_filepath , output_dir , headers = False , sources = False , profile_filepath = "" ):
271+ print (* get_file_list (api_filepath , output_dir , headers , sources , profile_filepath ), sep = ";" , end = None )
272+
273+
274+ def parse_build_profile (profile_filepath , api ):
275+ if profile_filepath == "" :
276+ return {}
277+ print ("Using feature build profile: " + profile_filepath )
278+
279+ with open (profile_filepath , encoding = "utf-8" ) as profile_file :
280+ profile = json .load (profile_file )
281+
282+ api_dict = {}
283+ parents = {}
284+ children = {}
285+ for engine_class in api ["classes" ]:
286+ api_dict [engine_class ["name" ]] = engine_class
287+ parent = engine_class .get ("inherits" , "" )
288+ child = engine_class ["name" ]
289+ parents [child ] = parent
290+ if parent == "" :
291+ continue
292+ children [parent ] = children .get (parent , [])
293+ children [parent ].append (child )
294+
295+ # Parse methods dependencies
296+ deps = {}
297+ reverse_deps = {}
298+ for name , engine_class in api_dict .items ():
299+ ref_cls = set ()
300+ for method in engine_class .get ("methods" , []):
301+ rtype = method .get ("return_value" , {}).get ("type" , "" )
302+ args = [a ["type" ] for a in method .get ("arguments" , [])]
303+ if rtype in api_dict :
304+ ref_cls .add (rtype )
305+ elif is_enum (rtype ) and get_enum_class (rtype ) in api_dict :
306+ ref_cls .add (get_enum_class (rtype ))
307+ for arg in args :
308+ if arg in api_dict :
309+ ref_cls .add (arg )
310+ elif is_enum (arg ) and get_enum_class (arg ) in api_dict :
311+ ref_cls .add (get_enum_class (arg ))
312+ deps [engine_class ["name" ]] = set (filter (lambda x : x != name , ref_cls ))
313+ for acls in ref_cls :
314+ if acls == name :
315+ continue
316+ reverse_deps [acls ] = reverse_deps .get (acls , set ())
317+ reverse_deps [acls ].add (name )
318+
319+ included = []
320+ front = list (profile .get ("enabled_classes" , []))
321+ if front :
322+ # These must always be included
323+ front .append ("WorkerThreadPool" )
324+ front .append ("ClassDB" )
325+ front .append ("ClassDBSingleton" )
326+ while front :
327+ cls = front .pop ()
328+ if cls in included :
329+ continue
330+ included .append (cls )
331+ parent = parents .get (cls , "" )
332+ if parent :
333+ front .append (parent )
334+ for rcls in deps .get (cls , set ()):
335+ if rcls in included or rcls in front :
336+ continue
337+ front .append (rcls )
338+
339+ excluded = []
340+ front = list (profile .get ("disabled_classes" , []))
341+ while front :
342+ cls = front .pop ()
343+ if cls in excluded :
344+ continue
345+ excluded .append (cls )
346+ front += children .get (cls , [])
347+ for rcls in reverse_deps .get (cls , set ()):
348+ if rcls in excluded or rcls in front :
349+ continue
350+ front .append (rcls )
351+
352+ if included and excluded :
353+ print (
354+ "WARNING: Cannot specify both 'enabled_classes' and 'disabled_classes' in build profile. 'disabled_classes' will be ignored."
355+ )
356+
357+ return {
358+ "enabled_classes" : included ,
359+ "disabled_classes" : excluded ,
360+ }
270361
271362
272363def scons_emit_files (target , source , env ):
273- files = [env .File (f ) for f in get_file_list (str (source [0 ]), target [0 ].abspath , True , True )]
364+ profile_filepath = env .get ("build_profile" , "" )
365+ if profile_filepath and not Path (profile_filepath ).is_absolute ():
366+ profile_filepath = str ((Path (env .Dir ("#" ).abspath ) / profile_filepath ).as_posix ())
367+
368+ files = [env .File (f ) for f in get_file_list (str (source [0 ]), target [0 ].abspath , True , True , profile_filepath )]
274369 env .Clean (target , files )
275370 env ["godot_cpp_gen_dir" ] = target [0 ].abspath
276371 return files , source
@@ -2547,6 +2642,20 @@ def is_refcounted(type_name):
25472642 return type_name in engine_classes and engine_classes [type_name ]
25482643
25492644
2645+ def is_class_included (class_name , build_profile ):
2646+ """
2647+ Check if an engine class should be included.
2648+ This removes classes according to a build profile of enabled or disabled classes.
2649+ """
2650+ included = build_profile .get ("enabled_classes" , [])
2651+ excluded = build_profile .get ("disabled_classes" , [])
2652+ if included :
2653+ return class_name in included
2654+ if excluded :
2655+ return class_name not in excluded
2656+ return True
2657+
2658+
25502659def is_included (type_name , current_type ):
25512660 """
25522661 Check if a builtin type should be included.
0 commit comments