diff --git a/scripts/apply-patches.sh b/scripts/apply-patches.sh index 486bc44..57b0e8e 100755 --- a/scripts/apply-patches.sh +++ b/scripts/apply-patches.sh @@ -29,3 +29,5 @@ for patch in "${patches[@]}"; do done echo "Applied ${#patches[@]} MeshCore patch(es) to $(meshcore_commit)" + +python3 "${MESHCORE_FW_ROOT}/scripts/enable-bot-on-companion-envs.py" diff --git a/scripts/enable-bot-on-companion-envs.py b/scripts/enable-bot-on-companion-envs.py new file mode 100755 index 0000000..b6de340 --- /dev/null +++ b/scripts/enable-bot-on-companion-envs.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Inject ${cmesh_bot_production.build_flags} into every +*_companion_radio_(usb|ble) env across vendor/MeshCore/variants/. + +Run after scripts/apply-patches.sh. Idempotent: skips envs that already +include the flag. + +Upstream MeshCore only includes cmesh_bot_production in Heltec V3 and +RAK 4631 variants. For our release matrix (133 companion envs) we want +the bot enabled everywhere, and we don't want to maintain a patch +that has to be kept in sync with every new upstream board. +""" +from __future__ import annotations + +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] / "vendor" / "MeshCore" / "variants" +ENV_HEADER = re.compile(r"^\[env:[A-Za-z0-9_-]+_companion_radio_(?:usb|ble)\]\s*$") +NEW_SECTION = re.compile(r"^\[") +INJECT_LINE = " ${cmesh_bot_production.build_flags}" + + +def process_file(path: Path) -> int: + lines = path.read_text().splitlines() + out: list[str] = [] + i = 0 + injected = 0 + while i < len(lines): + line = lines[i] + if ENV_HEADER.match(line): + # Collect this env block until the next [section] header (or EOF). + j = i + 1 + while j < len(lines) and not NEW_SECTION.match(lines[j]): + j += 1 + block = lines[i:j] + + if "${cmesh_bot_production.build_flags}" in "\n".join(block): + out.extend(block) + else: + # Find the last indented continuation line of the build_flags block + # so we can append the injection after it. + in_flags = False + last_flag_idx = -1 + for k, ln in enumerate(block): + stripped = ln.lstrip() + if stripped.startswith("build_flags"): + in_flags = True + last_flag_idx = k + elif in_flags: + # A continuation line of build_flags must be indented + # with whitespace; comments and blanks don't count. + if (ln.startswith(" ") or ln.startswith("\t")) and not stripped.startswith(";"): + last_flag_idx = k + elif ln.startswith(" ") or ln.startswith("\t"): + # An indented comment - stays inside the block but doesn't + # change the insertion point. + pass + else: + # Hit a non-indented line (likely build_src_filter or lib_deps). + in_flags = False + if last_flag_idx >= 0: + block = block[: last_flag_idx + 1] + [INJECT_LINE] + block[last_flag_idx + 1 :] + injected += 1 + out.extend(block) + i = j + continue + out.append(line) + i += 1 + + if injected > 0: + path.write_text("\n".join(out) + "\n") + return injected + + +def main() -> int: + if not ROOT.is_dir(): + print(f"variants dir not found: {ROOT}", file=sys.stderr) + return 1 + + total = 0 + files_touched = 0 + for ini in sorted(ROOT.rglob("platformio.ini")): + n = process_file(ini) + if n: + print(f" + {ini.relative_to(ROOT.parent.parent.parent)}: injected into {n} env(s)") + files_touched += 1 + total += n + + if total == 0: + print("All companion_radio_(usb|ble) envs already include cmesh_bot_production.build_flags.") + else: + print( + f"Injected cmesh_bot_production.build_flags into {total} env(s) " + f"across {files_touched} variant file(s)." + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main())