Merge pull request #39040 from Xrayez/modules-search-path-3.2
[3.2] Add `custom_modules` build option to compile external user modules
This commit is contained in:
commit
7c332d9386
73
SConstruct
73
SConstruct
|
@ -48,8 +48,6 @@ for x in sorted(glob.glob("platform/*")):
|
||||||
sys.path.remove(tmppath)
|
sys.path.remove(tmppath)
|
||||||
sys.modules.pop('detect')
|
sys.modules.pop('detect')
|
||||||
|
|
||||||
module_list = methods.detect_modules()
|
|
||||||
|
|
||||||
methods.save_active_platforms(active_platforms, active_platform_ids)
|
methods.save_active_platforms(active_platforms, active_platform_ids)
|
||||||
|
|
||||||
custom_tools = ['default']
|
custom_tools = ['default']
|
||||||
|
@ -121,6 +119,7 @@ opts.Add(BoolVariable('deprecated', "Enable deprecated features", True))
|
||||||
opts.Add(BoolVariable('gdscript', "Enable GDScript support", True))
|
opts.Add(BoolVariable('gdscript', "Enable GDScript support", True))
|
||||||
opts.Add(BoolVariable('minizip', "Enable ZIP archive support using minizip", True))
|
opts.Add(BoolVariable('minizip', "Enable ZIP archive support using minizip", True))
|
||||||
opts.Add(BoolVariable('xaudio2', "Enable the XAudio2 audio driver", False))
|
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.", "")
|
||||||
|
|
||||||
# Advanced options
|
# Advanced options
|
||||||
opts.Add(BoolVariable('verbose', "Enable verbose output for the compilation", False))
|
opts.Add(BoolVariable('verbose', "Enable verbose output for the compilation", False))
|
||||||
|
@ -177,17 +176,41 @@ for k in platform_opts.keys():
|
||||||
for o in opt_list:
|
for o in opt_list:
|
||||||
opts.Add(o)
|
opts.Add(o)
|
||||||
|
|
||||||
for x in module_list:
|
# Detect modules.
|
||||||
module_enabled = True
|
modules_detected = {}
|
||||||
tmppath = "./modules/" + x
|
module_search_paths = ["modules"] # Built-in path.
|
||||||
sys.path.insert(0, tmppath)
|
|
||||||
|
if ARGUMENTS.get("custom_modules"):
|
||||||
|
paths = ARGUMENTS.get("custom_modules").split(",")
|
||||||
|
for p in paths:
|
||||||
|
try:
|
||||||
|
module_search_paths.append(methods.convert_custom_modules_path(p))
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
sys.exit(255)
|
||||||
|
|
||||||
|
for path in module_search_paths:
|
||||||
|
# Note: custom modules can override built-in ones.
|
||||||
|
modules_detected.update(methods.detect_modules(path))
|
||||||
|
include_path = os.path.dirname(path)
|
||||||
|
if include_path:
|
||||||
|
env_base.Prepend(CPPPATH=[include_path])
|
||||||
|
|
||||||
|
# Add module options
|
||||||
|
for name, path in modules_detected.items():
|
||||||
|
enabled = True
|
||||||
|
sys.path.insert(0, path)
|
||||||
import config
|
import config
|
||||||
enabled_attr = getattr(config, "is_enabled", None)
|
|
||||||
if (callable(enabled_attr) and not config.is_enabled()):
|
try:
|
||||||
module_enabled = False
|
enabled = config.is_enabled()
|
||||||
sys.path.remove(tmppath)
|
except AttributeError:
|
||||||
sys.modules.pop('config')
|
pass
|
||||||
opts.Add(BoolVariable('module_' + x + '_enabled', "Enable module '%s'" % (x, ), module_enabled))
|
sys.path.remove(path)
|
||||||
|
sys.modules.pop("config")
|
||||||
|
opts.Add(BoolVariable("module_" + name + "_enabled", "Enable module '%s'" % (name,), enabled))
|
||||||
|
|
||||||
|
methods.write_modules(modules_detected)
|
||||||
|
|
||||||
opts.Update(env_base) # update environment
|
opts.Update(env_base) # update environment
|
||||||
Help(opts.GenerateHelpText(env_base)) # generate help
|
Help(opts.GenerateHelpText(env_base)) # generate help
|
||||||
|
@ -427,16 +450,15 @@ if selected_platform in platform_list:
|
||||||
sys.path.remove(tmppath)
|
sys.path.remove(tmppath)
|
||||||
sys.modules.pop('detect')
|
sys.modules.pop('detect')
|
||||||
|
|
||||||
env.module_list = []
|
modules_enabled = {}
|
||||||
env.module_icons_paths = []
|
env.module_icons_paths = []
|
||||||
env.doc_class_path = {}
|
env.doc_class_path = {}
|
||||||
|
|
||||||
for x in module_list:
|
for name, path in sorted(modules_detected.items()):
|
||||||
if not env['module_' + x + '_enabled']:
|
if not env["module_" + name + "_enabled"]:
|
||||||
continue
|
continue
|
||||||
tmppath = "./modules/" + x
|
sys.path.insert(0, path)
|
||||||
sys.path.insert(0, tmppath)
|
env.current_module = name
|
||||||
env.current_module = x
|
|
||||||
import config
|
import config
|
||||||
# can_build changed number of arguments between 3.0 (1) and 3.1 (2),
|
# can_build changed number of arguments between 3.0 (1) and 3.1 (2),
|
||||||
# so try both to preserve compatibility for 3.0 modules
|
# so try both to preserve compatibility for 3.0 modules
|
||||||
|
@ -450,26 +472,27 @@ if selected_platform in platform_list:
|
||||||
can_build = config.can_build(selected_platform)
|
can_build = config.can_build(selected_platform)
|
||||||
if (can_build):
|
if (can_build):
|
||||||
config.configure(env)
|
config.configure(env)
|
||||||
env.module_list.append(x)
|
|
||||||
|
|
||||||
# Get doc classes paths (if present)
|
# Get doc classes paths (if present)
|
||||||
try:
|
try:
|
||||||
doc_classes = config.get_doc_classes()
|
doc_classes = config.get_doc_classes()
|
||||||
doc_path = config.get_doc_path()
|
doc_path = config.get_doc_path()
|
||||||
for c in doc_classes:
|
for c in doc_classes:
|
||||||
env.doc_class_path[c] = "modules/" + x + "/" + doc_path
|
env.doc_class_path[c] = path + "/" + doc_path
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
# Get icon paths (if present)
|
# Get icon paths (if present)
|
||||||
try:
|
try:
|
||||||
icons_path = config.get_icons_path()
|
icons_path = config.get_icons_path()
|
||||||
env.module_icons_paths.append("modules/" + x + "/" + icons_path)
|
env.module_icons_paths.append(path + "/" + icons_path)
|
||||||
except:
|
except:
|
||||||
# Default path for module icons
|
# Default path for module icons
|
||||||
env.module_icons_paths.append("modules/" + x + "/" + "icons")
|
env.module_icons_paths.append(path + "/" + "icons")
|
||||||
|
modules_enabled[name] = path
|
||||||
|
|
||||||
sys.path.remove(tmppath)
|
sys.path.remove(path)
|
||||||
sys.modules.pop('config')
|
sys.modules.pop("config")
|
||||||
|
|
||||||
|
env.module_list = modules_enabled
|
||||||
|
|
||||||
methods.update_version(env.module_version_string)
|
methods.update_version(env.module_version_string)
|
||||||
|
|
||||||
|
|
28
editor/SCsub
28
editor/SCsub
|
@ -6,6 +6,7 @@ env.editor_sources = []
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
|
import glob
|
||||||
from platform_methods import run_in_subprocess
|
from platform_methods import run_in_subprocess
|
||||||
from compat import open_utf8
|
from compat import open_utf8
|
||||||
import editor_builders
|
import editor_builders
|
||||||
|
@ -41,20 +42,21 @@ if env['tools']:
|
||||||
f.write(reg_exporters_inc)
|
f.write(reg_exporters_inc)
|
||||||
f.write(reg_exporters)
|
f.write(reg_exporters)
|
||||||
|
|
||||||
# API documentation
|
# Core API documentation.
|
||||||
docs = []
|
docs = []
|
||||||
doc_dirs = ["doc/classes"]
|
docs += Glob("#doc/classes/*.xml")
|
||||||
|
|
||||||
for p in env.doc_class_path.values():
|
# Module API documentation.
|
||||||
if p not in doc_dirs:
|
module_dirs = []
|
||||||
doc_dirs.append(p)
|
for d in env.doc_class_path.values():
|
||||||
|
if d not in module_dirs:
|
||||||
|
module_dirs.append(d)
|
||||||
|
|
||||||
for d in doc_dirs:
|
for d in module_dirs:
|
||||||
try:
|
if not os.path.isabs(d):
|
||||||
for f in os.listdir(os.path.join(env.Dir('#').abspath, d)):
|
docs += Glob("#" + d + "/*.xml") # Built-in.
|
||||||
docs.append("#" + os.path.join(d, f))
|
else:
|
||||||
except OSError:
|
docs += Glob(d + "/*.xml") # Custom.
|
||||||
pass
|
|
||||||
|
|
||||||
_make_doc_data_class_path(os.path.join(env.Dir('#').abspath, "editor/doc"))
|
_make_doc_data_class_path(os.path.join(env.Dir('#').abspath, "editor/doc"))
|
||||||
|
|
||||||
|
@ -62,9 +64,7 @@ if env['tools']:
|
||||||
env.Depends("#editor/doc_data_compressed.gen.h", docs)
|
env.Depends("#editor/doc_data_compressed.gen.h", docs)
|
||||||
env.CommandNoCache("#editor/doc_data_compressed.gen.h", docs, run_in_subprocess(editor_builders.make_doc_header))
|
env.CommandNoCache("#editor/doc_data_compressed.gen.h", docs, run_in_subprocess(editor_builders.make_doc_header))
|
||||||
|
|
||||||
import glob
|
path = env.Dir(".").abspath
|
||||||
|
|
||||||
path = env.Dir('.').abspath
|
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
tlist = glob.glob(path + "/translations/*.po")
|
tlist = glob.glob(path + "/translations/*.po")
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
Import('env')
|
Import('env')
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from platform_methods import run_in_subprocess
|
from platform_methods import run_in_subprocess
|
||||||
import editor_icons_builders
|
import editor_icons_builders
|
||||||
|
|
||||||
|
@ -15,7 +17,10 @@ env['BUILDERS']['MakeEditorIconsBuilder'] = make_editor_icons_builder
|
||||||
icon_sources = Glob("*.svg")
|
icon_sources = Glob("*.svg")
|
||||||
|
|
||||||
# Module icons
|
# Module icons
|
||||||
for module_icons in env.module_icons_paths:
|
for path in env.module_icons_paths:
|
||||||
icon_sources += Glob('#' + module_icons + "/*.svg")
|
if not os.path.isabs(path):
|
||||||
|
icon_sources += Glob("#" + path + "/*.svg") # Built-in.
|
||||||
|
else:
|
||||||
|
icon_sources += Glob(path + "/*.svg") # Custom.
|
||||||
|
|
||||||
env.Alias('editor_icons', [env.MakeEditorIconsBuilder('#editor/editor_icons.gen.h', icon_sources)])
|
env.Alias('editor_icons', [env.MakeEditorIconsBuilder('#editor/editor_icons.gen.h', icon_sources)])
|
||||||
|
|
|
@ -1554,7 +1554,11 @@ bool Main::start() {
|
||||||
print_line("Loading docs...");
|
print_line("Loading docs...");
|
||||||
|
|
||||||
for (int i = 0; i < _doc_data_class_path_count; i++) {
|
for (int i = 0; i < _doc_data_class_path_count; i++) {
|
||||||
String path = doc_tool.plus_file(_doc_data_class_paths[i].path);
|
// Custom modules are always located by absolute path.
|
||||||
|
String path = _doc_data_class_paths[i].path;
|
||||||
|
if (path.is_rel_path()) {
|
||||||
|
path = doc_tool.plus_file(path);
|
||||||
|
}
|
||||||
String name = _doc_data_class_paths[i].name;
|
String name = _doc_data_class_paths[i].name;
|
||||||
doc_data_classes[name] = path;
|
doc_data_classes[name] = path;
|
||||||
if (!checked_paths.has(path)) {
|
if (!checked_paths.has(path)) {
|
||||||
|
|
59
methods.py
59
methods.py
|
@ -130,31 +130,41 @@ def parse_cg_file(fname, uniforms, sizes, conditionals):
|
||||||
fs.close()
|
fs.close()
|
||||||
|
|
||||||
|
|
||||||
def detect_modules():
|
def detect_modules(at_path):
|
||||||
|
module_list = {} # name : path
|
||||||
|
|
||||||
module_list = []
|
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
|
||||||
|
|
||||||
|
for x in files:
|
||||||
|
if not is_module(x):
|
||||||
|
continue
|
||||||
|
name = os.path.basename(x)
|
||||||
|
path = x.replace("\\", "/") # win32
|
||||||
|
module_list[name] = path
|
||||||
|
|
||||||
|
return module_list
|
||||||
|
|
||||||
|
|
||||||
|
def is_module(path):
|
||||||
|
return os.path.isdir(path) and os.path.exists(path + "/config.py")
|
||||||
|
|
||||||
|
|
||||||
|
def write_modules(module_list):
|
||||||
includes_cpp = ""
|
includes_cpp = ""
|
||||||
register_cpp = ""
|
register_cpp = ""
|
||||||
unregister_cpp = ""
|
unregister_cpp = ""
|
||||||
|
|
||||||
files = glob.glob("modules/*")
|
for name, path in module_list.items():
|
||||||
files.sort() # so register_module_types does not change that often, and also plugins are registered in alphabetic order
|
|
||||||
for x in files:
|
|
||||||
if not os.path.isdir(x):
|
|
||||||
continue
|
|
||||||
if not os.path.exists(x + "/config.py"):
|
|
||||||
continue
|
|
||||||
x = x.replace("modules/", "") # rest of world
|
|
||||||
x = x.replace("modules\\", "") # win32
|
|
||||||
module_list.append(x)
|
|
||||||
try:
|
try:
|
||||||
with open("modules/" + x + "/register_types.h"):
|
with open(os.path.join(path, "register_types.h")):
|
||||||
includes_cpp += '#include "modules/' + x + '/register_types.h"\n'
|
includes_cpp += '#include "' + path + '/register_types.h"\n'
|
||||||
register_cpp += '#ifdef MODULE_' + x.upper() + '_ENABLED\n'
|
register_cpp += '#ifdef MODULE_' + name.upper() + '_ENABLED\n'
|
||||||
register_cpp += '\tregister_' + x + '_types();\n'
|
register_cpp += '\tregister_' + name + '_types();\n'
|
||||||
register_cpp += '#endif\n'
|
register_cpp += '#endif\n'
|
||||||
unregister_cpp += '#ifdef MODULE_' + x.upper() + '_ENABLED\n'
|
unregister_cpp += '#ifdef MODULE_' + name.upper() + '_ENABLED\n'
|
||||||
unregister_cpp += '\tunregister_' + x + '_types();\n'
|
unregister_cpp += '\tunregister_' + name + '_types();\n'
|
||||||
unregister_cpp += '#endif\n'
|
unregister_cpp += '#endif\n'
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
@ -178,7 +188,18 @@ void unregister_module_types() {
|
||||||
with open("modules/register_module_types.gen.cpp", "w") as f:
|
with open("modules/register_module_types.gen.cpp", "w") as f:
|
||||||
f.write(modules_cpp)
|
f.write(modules_cpp)
|
||||||
|
|
||||||
return module_list
|
|
||||||
|
def convert_custom_modules_path(path):
|
||||||
|
if not path:
|
||||||
|
return path
|
||||||
|
err_msg = "Build option 'custom_modules' must %s"
|
||||||
|
if not os.path.isdir(path):
|
||||||
|
raise ValueError(err_msg % "point to an existing directory.")
|
||||||
|
if os.path.realpath(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 os.path.realpath(os.path.expanduser(path))
|
||||||
|
|
||||||
|
|
||||||
def disable_module(self):
|
def disable_module(self):
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
Import('env')
|
Import("env")
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
env_modules = env.Clone()
|
env_modules = env.Clone()
|
||||||
|
|
||||||
|
@ -10,11 +12,15 @@ env.modules_sources = []
|
||||||
|
|
||||||
env_modules.add_source_files(env.modules_sources, "register_module_types.gen.cpp")
|
env_modules.add_source_files(env.modules_sources, "register_module_types.gen.cpp")
|
||||||
|
|
||||||
for x in env.module_list:
|
for name, path in env.module_list.items():
|
||||||
if (x in env.disabled_modules):
|
if (name in env.disabled_modules):
|
||||||
continue
|
continue
|
||||||
env_modules.Append(CPPDEFINES=["MODULE_" + x.upper() + "_ENABLED"])
|
|
||||||
SConscript(x + "/SCsub")
|
env_modules.Append(CPPDEFINES=["MODULE_" + name.upper() + "_ENABLED"])
|
||||||
|
if not os.path.isabs(path):
|
||||||
|
SConscript(name + "/SCsub") # Built-in.
|
||||||
|
else:
|
||||||
|
SConscript(path + "/SCsub") # Custom.
|
||||||
|
|
||||||
if env['split_libmodules']:
|
if env['split_libmodules']:
|
||||||
env.split_lib("modules", env_lib = env_modules)
|
env.split_lib("modules", env_lib = env_modules)
|
||||||
|
|
Loading…
Reference in New Issue