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.
This commit is contained in:
Andrii Doroshenko (Xrayez) 2020-10-23 22:28:21 +03:00
parent b67ccf1a6f
commit a3c2c1e18a
2 changed files with 77 additions and 18 deletions

View file

@ -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