Files
firmware/bin/optional-modules.py
Thomas Göttgens 427ed0f1a0 Load optional modules dropped into src/modules/optional/ (#11673)
* Load optional modules dropped into src/modules/optional/

bin/optional-modules.py scans src/modules/optional/ for a directory <Name>/ holding <Name>.h and generates $BUILD_DIR/OptionalModules.h with an include and a setup<Name>() call for each, which Modules.cpp picks up through __has_include. The directory does not exist in a stock checkout, so a stock build generates a header that defines nothing, OPTIONAL_MODULES_SETUP compiles away, and nothing is registered. Sources under the directory are already covered by the default recursive build_src_filter, so dropping a module in needs no platformio.ini edit.

* Address review: skip a module directory that is not a usable identifier

The directory name becomes a setup<Name>() call, so foo-bar/ would have generated setupfoo-bar() and failed to compile with the error pointing at generated code rather than at the directory. Names that cannot form an identifier are now skipped with a message that names the directory.
2026-09-01 11:13:30 +00:00

67 lines
2.4 KiB
Python

#!/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 <Name>/ holding <Name>.h, which declares `void setup<Name>();`. 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))