From a3c2c1e18a3b1ebcd06aabd71e98c53fd0e5e998 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Fri, 23 Oct 2020 22:28:21 +0300 Subject: [PATCH] SCons: Add an option to detect C++ modules recursively This adds `custom_modules_recursive` which allows to detect and collect all nested C++ modules which may reside in any directory specified by `custom_modules` option. The detection logic is made to be more strict because `SCSub` may be used for organizing hierarchical builds within a module itself, so the existence of `register_types.h` and `config.py` is checked as well (these are all required for a C++ module to be compiled by Godot). For performance reasons, built-in modules are not checked recursively, and there's no benefit of doing so in the first place. It's now possible to specify a directory path pointing to a *single* module, as it may contain nested modules which are detected recursively. --- SConstruct | 9 +++++- methods.py | 86 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/SConstruct b/SConstruct index f134dfddac..46e0da89e8 100644 --- a/SConstruct +++ b/SConstruct @@ -123,6 +123,7 @@ opts.Add(BoolVariable("deprecated", "Enable deprecated features", True)) opts.Add(BoolVariable("minizip", "Enable ZIP archive support using minizip", True)) opts.Add(BoolVariable("xaudio2", "Enable the XAudio2 audio driver", False)) opts.Add("custom_modules", "A list of comma-separated directory paths containing custom modules to build.", "") +opts.Add(BoolVariable("custom_modules_recursive", "Detect custom modules recursively for each specified path.", True)) # Advanced options opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False)) @@ -199,8 +200,14 @@ if env_base["custom_modules"]: Exit(255) for path in module_search_paths: + if path == "modules": + # Built-in modules don't have nested modules, + # so save the time it takes to parse directories. + modules = methods.detect_modules(path, recursive=False) + else: # External. + modules = methods.detect_modules(path, env_base["custom_modules_recursive"]) # Note: custom modules can override built-in ones. - modules_detected.update(methods.detect_modules(path)) + modules_detected.update(modules) include_path = os.path.dirname(path) if include_path: env_base.Prepend(CPPPATH=[include_path]) diff --git a/methods.py b/methods.py index f7134b472b..0010ad93a5 100644 --- a/methods.py +++ b/methods.py @@ -145,34 +145,88 @@ def parse_cg_file(fname, uniforms, sizes, conditionals): fs.close() -def detect_modules(at_path): - module_list = OrderedDict() # name : path +def detect_modules(search_path, recursive=False): + """Detects and collects a list of C++ modules at specified path - modules_glob = os.path.join(at_path, "*") - files = glob.glob(modules_glob) - files.sort() # so register_module_types does not change that often, and also plugins are registered in alphabetic order + `search_path` - a directory path containing modules. The path may point to + a single module, which may have other nested modules. A module must have + "register_types.h", "SCsub", "config.py" files created to be detected. - for x in files: - if not is_module(x): - continue - name = os.path.basename(x) - path = x.replace("\\", "/") # win32 - module_list[name] = path + `recursive` - if `True`, then all subdirectories are searched for modules as + specified by the `search_path`, otherwise collects all modules under the + `search_path` directory. If the `search_path` is a module, it is collected + in all cases. - return module_list + Returns an `OrderedDict` with module names as keys, and directory paths as + values. If a path is relative, then it is a built-in module. If a path is + absolute, then it is a custom module collected outside of the engine source. + """ + modules = OrderedDict() + + def add_module(path): + module_name = os.path.basename(path) + module_path = path.replace("\\", "/") # win32 + modules[module_name] = module_path + + def is_engine(path): + # Prevent recursively detecting modules in self and other + # Godot sources when using `custom_modules` build option. + version_path = os.path.join(path, "version.py") + if os.path.exists(version_path): + with open(version_path) as f: + version = {} + exec(f.read(), version) + if version.get("short_name") == "godot": + return True + return False + + def get_files(path): + files = glob.glob(os.path.join(path, "*")) + # Sort so that `register_module_types` does not change that often, + # and plugins are registered in alphabetic order as well. + files.sort() + return files + + if not recursive: + if is_module(search_path): + add_module(search_path) + for path in get_files(search_path): + if is_engine(path): + continue + if is_module(path): + add_module(path) + else: + to_search = [search_path] + while to_search: + path = to_search.pop() + if is_module(path): + add_module(path) + for child in get_files(path): + if not os.path.isdir(child): + continue + if is_engine(child): + continue + to_search.insert(0, child) + return modules def is_module(path): - return os.path.isdir(path) and os.path.exists(os.path.join(path, "SCsub")) + if not os.path.isdir(path): + return False + must_exist = ["register_types.h", "SCsub", "config.py"] + for f in must_exist: + if not os.path.exists(os.path.join(path, f)): + return False + return True -def write_modules(module_list): +def write_modules(modules): includes_cpp = "" preregister_cpp = "" register_cpp = "" unregister_cpp = "" - for name, path in module_list.items(): + for name, path in modules.items(): try: with open(os.path.join(path, "register_types.h")): includes_cpp += '#include "' + path + '/register_types.h"\n' @@ -230,8 +284,6 @@ def convert_custom_modules_path(path): raise ValueError(err_msg % "point to an existing directory.") if path == os.path.realpath("modules"): raise ValueError(err_msg % "be a directory other than built-in `modules` directory.") - if is_module(path): - raise ValueError(err_msg % "point to a directory with modules, not a single module.") return path