diff --git a/bin/optional-modules.py b/bin/optional-modules.py new file mode 100644 index 0000000000..443a608084 --- /dev/null +++ b/bin/optional-modules.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# trunk-ignore-all(ruff/F821) +# trunk-ignore-all(flake8/F821): For SConstruct imports +# +# Registers optional modules dropped into src/modules/optional/. +# +# A module is a directory / holding .h, which declares `void setup();`. This +# writes $BUILD_DIR/OptionalModules.h with an include and a setup call for each one found; +# Modules.cpp picks that header up through __has_include and calls OPTIONAL_MODULES_SETUP(). +# +# src/modules/optional/ does not exist in a stock checkout, so a stock build generates a header that +# defines nothing and registers nothing. Sources under the directory are compiled by the default +# recursive build_src_filter, so dropping a module in needs no platformio.ini edit. +import os +import re + +Import("env") + +# The directory name becomes a C++ call, so it has to be a usable identifier. +identifier = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") + +optionalDir = os.path.join(env["PROJECT_DIR"], "src", "modules", "optional") + +names = [] +if os.path.isdir(optionalDir): + for entry in sorted(os.listdir(optionalDir)): + entryDir = os.path.join(optionalDir, entry) + if not os.path.isdir(entryDir): + continue + if not identifier.match(entry): + print(f"optional-modules: skipping {entry}/, setup{entry}() is not a valid identifier") + elif os.path.isfile(os.path.join(entryDir, entry + ".h")): + names.append(entry) + else: + print(f"optional-modules: skipping {entry}/, no {entry}.h") + +lines = ["// Generated by bin/optional-modules.py. Do not edit.", "#pragma once", ""] +for name in names: + lines.append(f'#include "modules/optional/{name}/{name}.h"') +if names: + lines.append("") + lines.append("#define OPTIONAL_MODULES_SETUP() \\") + lines.append(" do { \\") + for name in names: + lines.append(f" setup{name}(); \\") + lines.append(" } while (0)") +lines.append("") +content = "\n".join(lines) + +buildDir = env.subst("$BUILD_DIR") +os.makedirs(buildDir, exist_ok=True) +header = os.path.join(buildDir, "OptionalModules.h") + +# Rewrite only on a change, so an unchanged set of modules does not keep rebuilding Modules.cpp. +previous = None +if os.path.isfile(header): + with open(header, encoding="utf-8") as f: + previous = f.read() +if previous != content: + with open(header, "w", encoding="utf-8") as f: + f.write(content) + +env.Append(CPPPATH=[buildDir]) + +if names: + print("optional-modules: " + ", ".join(names)) diff --git a/platformio.ini b/platformio.ini index c819994102..64d0306aef 100644 --- a/platformio.ini +++ b/platformio.ini @@ -25,6 +25,7 @@ build_flags = test_build_src = true extra_scripts = pre:bin/platformio-pre.py + pre:bin/optional-modules.py bin/platformio-custom.py post:extra_scripts/nrf54l15_linker.py ; note: we add src to our include search path so that lmic_project_config can override diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index d05ac35a34..1c32a0ab2e 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -34,6 +34,11 @@ #if !MESHTASTIC_EXCLUDE_BEACON #include "modules/MeshBeaconModule.h" #endif +// Generated by bin/optional-modules.py from whatever sits in src/modules/optional/. Absent from a +// stock checkout, and empty unless a module has been dropped in. +#if __has_include("OptionalModules.h") +#include "OptionalModules.h" +#endif #if !MESHTASTIC_EXCLUDE_GPS #include "modules/PositionModule.h" #endif @@ -283,6 +288,11 @@ void setupModules() #endif #if defined(HAS_HARDWARE_WATCHDOG) watchdogThread = new WatchdogThread(); +#endif + // Anything dropped into src/modules/optional/. Undefined, so compiled away, unless a module is + // actually present. +#ifdef OPTIONAL_MODULES_SETUP + OPTIONAL_MODULES_SETUP(); #endif // NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra // acks