Skip to content

Commit

Permalink
Merge pull request #36922 from Xrayez/modules-search-path
Browse files Browse the repository at this point in the history
Add `custom_modules` build option to compile external, user-defined C++ modules
  • Loading branch information
akien-mga committed May 25, 2020
2 parents fee9742 + a96f0e9 commit 78f554a
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 67 deletions.
68 changes: 45 additions & 23 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ for x in sorted(glob.glob("platform/*")):
sys.path.remove(tmppath)
sys.modules.pop("detect")

module_list = methods.detect_modules()

methods.save_active_platforms(active_platforms, active_platform_ids)

custom_tools = ["default"]
Expand Down Expand Up @@ -123,6 +121,7 @@ opts.Add(BoolVariable("use_precise_math_checks", "Math checks use very precise e
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.", "")

# Advanced options
opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False))
Expand Down Expand Up @@ -181,18 +180,41 @@ for k in platform_opts.keys():
for o in opt_list:
opts.Add(o)

for x in module_list:
module_enabled = True
tmppath = "./modules/" + x
sys.path.insert(0, tmppath)
# Detect modules.
modules_detected = {}
module_search_paths = ["modules"] # Built-in path.

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

enabled_attr = getattr(config, "is_enabled", None)
if callable(enabled_attr) and not config.is_enabled():
module_enabled = False
sys.path.remove(tmppath)
try:
enabled = config.is_enabled()
except AttributeError:
pass
sys.path.remove(path)
sys.modules.pop("config")
opts.Add(BoolVariable("module_" + x + "_enabled", "Enable module '%s'" % (x,), module_enabled))
opts.Add(BoolVariable("module_" + name + "_enabled", "Enable module '%s'" % (name,), enabled))

methods.write_modules(modules_detected)

opts.Update(env_base) # update environment
Help(opts.GenerateHelpText(env_base)) # generate help
Expand Down Expand Up @@ -501,41 +523,41 @@ if selected_platform in platform_list:
sys.path.remove(tmppath)
sys.modules.pop("detect")

env.module_list = []
modules_enabled = {}
env.module_icons_paths = []
env.doc_class_path = {}

for x in sorted(module_list):
if not env["module_" + x + "_enabled"]:
for name, path in sorted(modules_detected.items()):
if not env["module_" + name + "_enabled"]:
continue
tmppath = "./modules/" + x
sys.path.insert(0, tmppath)
env.current_module = x
sys.path.insert(0, path)
env.current_module = name
import config

if config.can_build(env, selected_platform):
config.configure(env)
env.module_list.append(x)

# Get doc classes paths (if present)
try:
doc_classes = config.get_doc_classes()
doc_path = config.get_doc_path()
for c in doc_classes:
env.doc_class_path[c] = "modules/" + x + "/" + doc_path
env.doc_class_path[c] = path + "/" + doc_path
except:
pass
# Get icon paths (if present)
try:
icons_path = config.get_icons_path()
env.module_icons_paths.append("modules/" + x + "/" + icons_path)
env.module_icons_paths.append(path + "/" + icons_path)
except:
# 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")

env.module_list = modules_enabled

methods.update_version(env.module_version_string)

env["PROGSUFFIX"] = suffix + env.module_version_string + env["PROGSUFFIX"]
Expand Down
26 changes: 13 additions & 13 deletions editor/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ env.editor_sources = []

import os
import os.path
import glob
from platform_methods import run_in_subprocess
import editor_builders

Expand Down Expand Up @@ -40,29 +41,28 @@ if env["tools"]:
f.write(reg_exporters_inc)
f.write(reg_exporters)

# API documentation
# Core API documentation.
docs = []
doc_dirs = ["doc/classes"]
docs += Glob("#doc/classes/*.xml")

for p in env.doc_class_path.values():
if p not in doc_dirs:
doc_dirs.append(p)
# Module API documentation.
module_dirs = []
for d in env.doc_class_path.values():
if d not in module_dirs:
module_dirs.append(d)

for d in doc_dirs:
try:
for f in os.listdir(os.path.join(env.Dir("#").abspath, d)):
docs.append("#" + os.path.join(d, f))
except OSError:
pass
for d in module_dirs:
if not os.path.isabs(d):
docs += Glob("#" + d + "/*.xml") # Built-in.
else:
docs += Glob(d + "/*.xml") # Custom.

_make_doc_data_class_path(os.path.join(env.Dir("#").abspath, "editor"))

docs = sorted(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))

import glob

path = env.Dir(".").abspath

# Editor translations
Expand Down
9 changes: 7 additions & 2 deletions editor/icons/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Import("env")

import os

from platform_methods import run_in_subprocess
import editor_icons_builders

Expand All @@ -15,7 +17,10 @@ env["BUILDERS"]["MakeEditorIconsBuilder"] = make_editor_icons_builder
icon_sources = Glob("*.svg")

# Module icons
for module_icons in env.module_icons_paths:
icon_sources += Glob("#" + module_icons + "/*.svg")
for path in env.module_icons_paths:
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)])
6 changes: 5 additions & 1 deletion main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1645,7 +1645,11 @@ bool Main::start() {
print_line("Loading docs...");

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;
doc_data_classes[name] = path;
if (!checked_paths.has(path)) {
Expand Down
69 changes: 45 additions & 24 deletions methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,37 +137,47 @@ def parse_cg_file(fname, uniforms, sizes, conditionals):
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 = ""
preregister_cpp = ""
register_cpp = ""
unregister_cpp = ""
preregister_cpp = ""

files = glob.glob("modules/*")
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)
for name, path in module_list.items():
try:
with open("modules/" + x + "/register_types.h"):
includes_cpp += '#include "modules/' + x + '/register_types.h"\n'
register_cpp += "#ifdef MODULE_" + x.upper() + "_ENABLED\n"
register_cpp += "\tregister_" + x + "_types();\n"
register_cpp += "#endif\n"
preregister_cpp += "#ifdef MODULE_" + x.upper() + "_ENABLED\n"
preregister_cpp += "#ifdef MODULE_" + x.upper() + "_HAS_PREREGISTER\n"
preregister_cpp += "\tpreregister_" + x + "_types();\n"
with open(os.path.join(path, "register_types.h")):
includes_cpp += '#include "' + path + '/register_types.h"\n'
preregister_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n"
preregister_cpp += "#ifdef MODULE_" + name.upper() + "_HAS_PREREGISTER\n"
preregister_cpp += "\tpreregister_" + name + "_types();\n"
preregister_cpp += "#endif\n"
preregister_cpp += "#endif\n"
unregister_cpp += "#ifdef MODULE_" + x.upper() + "_ENABLED\n"
unregister_cpp += "\tunregister_" + x + "_types();\n"
register_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n"
register_cpp += "\tregister_" + name + "_types();\n"
register_cpp += "#endif\n"
unregister_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n"
unregister_cpp += "\tunregister_" + name + "_types();\n"
unregister_cpp += "#endif\n"
except IOError:
pass
Expand Down Expand Up @@ -202,7 +212,18 @@ def detect_modules():
with open("modules/register_module_types.gen.cpp", "w") as f:
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):
Expand Down
13 changes: 9 additions & 4 deletions modules/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Import("env")

import modules_builders
import os

env_modules = env.Clone()

Expand All @@ -13,16 +14,20 @@ env.CommandNoCache("modules_enabled.gen.h", Value(env.module_list), modules_buil

vs_sources = []
# libmodule_<name>.a for each active module.
for module in env.module_list:
for name, path in env.module_list.items():
env.modules_sources = []
SConscript(module + "/SCsub")

if not os.path.isabs(path):
SConscript(name + "/SCsub") # Built-in.
else:
SConscript(path + "/SCsub") # Custom.

# Some modules are not linked automatically but can be enabled optionally
# on iOS, so we handle those specially.
if env["platform"] == "iphone" and module in ["arkit", "camera"]:
if env["platform"] == "iphone" and name in ["arkit", "camera"]:
continue

lib = env_modules.add_library("module_%s" % module, env.modules_sources)
lib = env_modules.add_library("module_%s" % name, env.modules_sources)
env.Prepend(LIBS=[lib])
if env["vsproj"]:
vs_sources += env.modules_sources
Expand Down

0 comments on commit 78f554a

Please sign in to comment.