Files
HaloKeymind/scripts/client_acl_role.py

121 lines
6.2 KiB
Python

"""Derive feature-preserving role definitions from effective application sources.
Shared compiler definitions cover application and helper translation units.
Unknown/custom roles keep the complete layout; room and sensor session fields
are never removed from builds that use them. Companions omit only unbound
forwarding-region callbacks because that role has no RegionMap.
"""
import importlib.util
from pathlib import Path
import re
SCRIPT_DIR = Path(__file__).resolve().parent if "__file__" in globals() else Path("scripts").resolve()
SPEC = importlib.util.spec_from_file_location("client_acl_source_policy", SCRIPT_DIR / "nrf52_flash_trim.py")
SOURCE_POLICY = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(SOURCE_POLICY)
DEFINITION = "MESH_CLIENT_REPEATER_ONLY"
REPEATER_SOURCES = ("../examples/simple_repeater/main.cpp", "../examples/simple_repeater/MyMesh.cpp")
OTHER_ROLES = ("simple_room_server", "simple_sensor", "companion_radio", "simple_secure_chat",
"kiss_modem", "partition_expander", "esp32_partition_migrator", "esp32_partition_legacy_seed")
COMPANION_DEFINITION = "MESH_ROLE_COMPANION"
COMPANION_SOURCES = ("../examples/companion_radio/main.cpp", "../examples/companion_radio/MyMesh.cpp")
EMERGENCY_DEFINITION = "MESH_ENABLE_EMERGENCY_POWER"
def emergency_power_policy(src_filter, flags=(), cppdefines=()):
"""Enable scheduled battery sleep only on complete supported infrastructure roles."""
values = SOURCE_POLICY.flag_defines(flags, cppdefines)
if EMERGENCY_DEFINITION in values:
value = str(values[EMERGENCY_DEFINITION]).strip("\"'() ")
if value not in ("0", "1"):
raise ValueError(EMERGENCY_DEFINITION + " override must be 0 or 1")
return {}
if not any(name in values for name in ("ESP32_PLATFORM", "NRF52_PLATFORM")):
return {}
if str(values.get("MESH_ENABLE_BATTERY_FORWARDING", "1")).strip("\"'() ") == "0":
return {}
selected = SOURCE_POLICY.source_selected
text = src_filter if isinstance(src_filter, str) else " ".join(map(str, src_filter))
includes = re.findall(r"\+<\.\./examples/([^>]+)>", text.replace("\\", "/"))
for role, mesh_source in (("simple_repeater", "MyMesh.cpp"),
("simple_room_server", "MyMesh.cpp"),
("simple_sensor", "SensorMesh.cpp")):
if includes and all(path == role or path.startswith(role + "/") for path in includes) \
and all(selected(src_filter, "../examples/" + role + "/" + source)
for source in ("main.cpp", mesh_source)):
return {EMERGENCY_DEFINITION: 1}
return {}
def companion_role_policy(src_filter, flags=(), cppdefines=()):
"""Exclude unbound forwarding-scope callbacks, retaining all radio commands.
Companions have no RegionMap. Select the complete exclusive source role,
never an environment-name alias, so every translation unit shares its ABI.
Unknown and mixed roles retain the general callback implementation.
"""
values = SOURCE_POLICY.flag_defines(flags, cppdefines)
selected = SOURCE_POLICY.source_selected
other_roles = ("simple_repeater", *(role for role in OTHER_ROLES
if role != "companion_radio"))
filter_text = src_filter if isinstance(src_filter, str) else " ".join(map(str, src_filter))
example_includes = re.findall(r"\+<\.\./examples/([^>]+)>", filter_text.replace("\\", "/"))
known_companion_only = all(path == "companion_radio" or path.startswith("companion_radio/")
for path in example_includes)
qualified = (known_companion_only
and all(selected(src_filter, source) for source in COMPANION_SOURCES)
and not any(selected(src_filter, "../examples/" + role + "/" + source)
for role in other_roles
for source in ("main.cpp", "MyMesh.cpp", "SensorMesh.cpp")))
if COMPANION_DEFINITION in values:
value = values[COMPANION_DEFINITION]
text = "0" if value is None else str(value).strip("\"'() ")
if text not in ("0", "1"):
raise ValueError(COMPANION_DEFINITION + " override must be 0 or 1")
if text == "1" and not qualified:
raise ValueError(COMPANION_DEFINITION + "=1 requires only the Companion source role")
return {}
return {COMPANION_DEFINITION: 1} if qualified else {}
def role_policy(src_filter, flags=(), cppdefines=()):
values = SOURCE_POLICY.flag_defines(flags, cppdefines)
selected = SOURCE_POLICY.source_selected
qualified = (all(selected(src_filter, source) for source in REPEATER_SOURCES)
and not any(selected(src_filter, "../examples/" + role + "/main.cpp")
or selected(src_filter, "../examples/" + role + "/MyMesh.cpp")
or selected(src_filter, "../examples/" + role + "/SensorMesh.cpp")
for role in OTHER_ROLES)
and not any(name.startswith("COMPANION_") for name in values))
if DEFINITION in values:
value = values[DEFINITION]
text = "0" if value is None else str(value).strip("\"'() ")
if text not in ("0", "1"):
raise ValueError(DEFINITION + " override must be 0 or 1")
if text == "1" and not qualified:
raise ValueError(DEFINITION + "=1 requires only the repeater source role")
return {}
return {DEFINITION: 1} if qualified else {}
def install(build_env):
src_filter = build_env.get("SRC_FILTER")
if src_filter is None:
src_filter = build_env.GetProjectOption("build_src_filter", "")
definitions = role_policy(src_filter, build_env.get("BUILD_FLAGS", ()),
build_env.get("CPPDEFINES", ()))
definitions.update(companion_role_policy(src_filter, build_env.get("BUILD_FLAGS", ()),
build_env.get("CPPDEFINES", ())))
definitions.update(emergency_power_policy(src_filter, build_env.get("BUILD_FLAGS", ()),
build_env.get("CPPDEFINES", ())))
if definitions:
build_env.AppendUnique(CPPDEFINES=list(definitions.items()))
if "Import" in globals():
Import("env")
install(env)