[Scons] Implement module dependency sorting.

Modules can now call:

env.module_add_dependencies(name: str, deps: list, optional: bool)

To add required or optional dependencies during the "can_build" step.

Required dependencies will be checked and the module will be not be
enabled when they are missing, printing a warning to notify the user.
This commit is contained in:
Fabio Alessandrelli 2022-08-04 19:11:01 +02:00
parent bed2482ce2
commit 951a1016d3
8 changed files with 57 additions and 23 deletions

View File

@ -138,6 +138,7 @@ env_base.__class__.CommandNoCache = methods.CommandNoCache
env_base.__class__.Run = methods.Run env_base.__class__.Run = methods.Run
env_base.__class__.disable_warnings = methods.disable_warnings env_base.__class__.disable_warnings = methods.disable_warnings
env_base.__class__.force_optimization_on_debug = methods.force_optimization_on_debug env_base.__class__.force_optimization_on_debug = methods.force_optimization_on_debug
env_base.__class__.module_add_dependencies = methods.module_add_dependencies
env_base.__class__.module_check_dependencies = methods.module_check_dependencies env_base.__class__.module_check_dependencies = methods.module_check_dependencies
env_base["x86_libtheora_opt_gcc"] = False env_base["x86_libtheora_opt_gcc"] = False
@ -699,6 +700,7 @@ if selected_platform in platform_list:
sys.modules.pop("detect") sys.modules.pop("detect")
modules_enabled = OrderedDict() modules_enabled = OrderedDict()
env.module_dependencies = {}
env.module_icons_paths = [] env.module_icons_paths = []
env.doc_class_path = {} env.doc_class_path = {}
@ -710,6 +712,10 @@ if selected_platform in platform_list:
import config import config
if config.can_build(env, selected_platform): if config.can_build(env, selected_platform):
# Disable it if a required dependency is missing.
if not env.module_check_dependencies(name):
continue
config.configure(env) config.configure(env)
# Get doc classes paths (if present) # Get doc classes paths (if present)
try: try:
@ -732,6 +738,7 @@ if selected_platform in platform_list:
sys.modules.pop("config") sys.modules.pop("config")
env.module_list = modules_enabled env.module_list = modules_enabled
methods.sort_module_list(env)
methods.update_version(env.module_version_string) methods.update_version(env.module_version_string)
@ -794,15 +801,6 @@ if selected_platform in platform_list:
if env["minizip"]: if env["minizip"]:
env.Append(CPPDEFINES=["MINIZIP_ENABLED"]) env.Append(CPPDEFINES=["MINIZIP_ENABLED"])
editor_module_list = []
if env["tools"] and not env.module_check_dependencies("tools", editor_module_list):
print(
"Build option 'module_"
+ x
+ "_enabled=no' cannot be used with 'tools=yes' (editor), only with 'tools=no' (export template)."
)
Exit(255)
if not env["verbose"]: if not env["verbose"]:
methods.no_verbose(sys, env) methods.no_verbose(sys, env)

View File

@ -1,5 +1,6 @@
import os import os
import re import re
import sys
import glob import glob
import subprocess import subprocess
from collections import OrderedDict from collections import OrderedDict
@ -336,7 +337,20 @@ def disable_module(self):
self.disabled_modules.append(self.current_module) self.disabled_modules.append(self.current_module)
def module_check_dependencies(self, module, dependencies, silent=False): def module_add_dependencies(self, module, dependencies, optional=False):
"""
Adds dependencies for a given module.
Meant to be used in module `can_build` methods.
"""
if module not in self.module_dependencies:
self.module_dependencies[module] = [[], []]
if optional:
self.module_dependencies[module][1].extend(dependencies)
else:
self.module_dependencies[module][0].extend(dependencies)
def module_check_dependencies(self, module):
""" """
Checks if module dependencies are enabled for a given module, Checks if module dependencies are enabled for a given module,
and prints a warning if they aren't. and prints a warning if they aren't.
@ -344,23 +358,41 @@ def module_check_dependencies(self, module, dependencies, silent=False):
Returns a boolean (True if dependencies are satisfied). Returns a boolean (True if dependencies are satisfied).
""" """
missing_deps = [] missing_deps = []
for dep in dependencies: required_deps = self.module_dependencies[module][0] if module in self.module_dependencies else []
for dep in required_deps:
opt = "module_{}_enabled".format(dep) opt = "module_{}_enabled".format(dep)
if not opt in self or not self[opt]: if not opt in self or not self[opt]:
missing_deps.append(dep) missing_deps.append(dep)
if missing_deps != []: if missing_deps != []:
if not silent: print(
print( "Disabling '{}' module as the following dependencies are not satisfied: {}".format(
"Disabling '{}' module as the following dependencies are not satisfied: {}".format( module, ", ".join(missing_deps)
module, ", ".join(missing_deps)
)
) )
)
return False return False
else: else:
return True return True
def sort_module_list(env):
out = OrderedDict()
deps = {k: v[0] + list(filter(lambda x: x in env.module_list, v[1])) for k, v in env.module_dependencies.items()}
frontier = list(env.module_list.keys())
explored = []
while len(frontier):
cur = frontier.pop()
deps_list = deps[cur] if cur in deps else []
if len(deps_list) and any([d not in explored for d in deps_list]):
# Will explore later, after its dependencies
frontier.insert(0, cur)
continue
explored.append(cur)
for k in explored:
env.module_list.move_to_end(k)
def use_windows_spawn_fix(self, platform=None): def use_windows_spawn_fix(self, platform=None):
if os.name != "nt": if os.name != "nt":

View File

@ -1,4 +1,5 @@
def can_build(env, platform): def can_build(env, platform):
env.module_add_dependencies("gdscript", ["jsonrpc", "websocket"], True)
return True return True

View File

@ -1,5 +1,6 @@
def can_build(env, platform): def can_build(env, platform):
return env.module_check_dependencies("msdfgen", ["freetype"]) env.module_add_dependencies("msdfgen", ["freetype"])
return True
def configure(env): def configure(env):

View File

@ -36,8 +36,8 @@ def make_icu_data(target, source, env):
# Thirdparty source files # Thirdparty source files
thirdparty_obj = [] thirdparty_obj = []
freetype_enabled = env.module_check_dependencies("text_server_adv", ["freetype"], True) freetype_enabled = "freetype" in env.module_list
msdfgen_enabled = env.module_check_dependencies("text_server_adv", ["msdfgen"], True) msdfgen_enabled = "msdfgen" in env.module_list
if env["builtin_harfbuzz"]: if env["builtin_harfbuzz"]:
env_harfbuzz = env_modules.Clone() env_harfbuzz = env_modules.Clone()

View File

@ -3,8 +3,8 @@
Import("env") Import("env")
Import("env_modules") Import("env_modules")
freetype_enabled = env.module_check_dependencies("text_server_fb", ["freetype"], True) freetype_enabled = "freetype" in env.module_list
msdfgen_enabled = env.module_check_dependencies("text_server_fb", ["msdfgen"], True) msdfgen_enabled = "msdfgen" in env.module_list
env_text_server_fb = env_modules.Clone() env_text_server_fb = env_modules.Clone()

View File

@ -1,7 +1,8 @@
def can_build(env, platform): def can_build(env, platform):
if env["arch"].startswith("rv"): if env["arch"].startswith("rv"):
return False return False
return env.module_check_dependencies("theora", ["ogg", "vorbis"]) env.module_add_dependencies("theora", ["ogg", "vorbis"])
return True
def configure(env): def configure(env):

View File

@ -1,5 +1,6 @@
def can_build(env, platform): def can_build(env, platform):
return env.module_check_dependencies("vorbis", ["ogg"]) env.module_add_dependencies("vorbis", ["ogg"])
return True
def configure(env): def configure(env):