""" Acroname **on-device Reflex** (embedded language on BrainStem modules). This module does **not** run Reflex on the hub; it helps you **compile** ``.reflex`` sources to ``.map`` files using the BrainStem Development Kit **arc** compiler. Load maps with Acroname **ReflexLoader** or **HubTool** (see their docs). The pip ``brainstem`` package is the host Python API; **arc** ships with the separate dev kit. """ from __future__ import annotations import os import shutil import subprocess import sys from pathlib import Path # Primary language reference (path may move; search Acroname “Reflex language” if stale). REFLEX_DOC_URL = "https://acroname.com/reference/brainstem/reflex/" # Full SDK tarball includes ``bin/arc``, Reflex API ``*.reflex`` headers, HubTool, etc. BRAINSTEM_SDK_DOWNLOAD_URL = "https://acroname.com/software/brainstem-development-kit" def _dedupe_paths(paths: list[Path]) -> list[Path]: seen: set[str] = set() out: list[Path] = [] for p in paths: try: key = str(p.resolve()) except OSError: key = str(p) if key not in seen: seen.add(key) out.append(p) return out def _env_path_list(key: str) -> list[Path]: raw = os.environ.get(key, "").strip() if not raw: return [] return [Path(p).expanduser() for p in raw.split(os.pathsep) if p.strip()] def _fiwi_repo_brainstem_sdk_roots() -> list[Path]: """ Optional checkout next to this repo: ``FiWiManager/brainstem_sdk`` (and nested ``BrainStem2`` after extracting the official ``.tgz``). """ root = Path(__file__).resolve().parent.parent out: list[Path] = [] b = root / "brainstem_sdk" if b.is_dir(): out.append(b) inner = b / "BrainStem2" if inner.is_dir(): out.append(inner) return out def brainstem_dev_roots() -> list[Path]: """Likely BrainStem2 checkout or SDK install roots (for ``bin/arc`` and reflex API includes).""" roots: list[Path] = [] for key in ( "BRAINSTEM2_DEV_ROOT", "BRAINSTEM_DEV_ROOT", "ACRONAME_BRAINSTEM_ROOT", "BRAINSTEM_ROOT", "REFLEX_SDK_ROOT", "FIWI_BRAINSTEM_SDK", ): roots.extend(_env_path_list(key)) roots.extend(_fiwi_repo_brainstem_sdk_roots()) return _dedupe_paths(roots) def _likely_sdk_roots_under_home() -> list[Path]: """Common extract locations when the user has not set env vars (Linux/macOS).""" home = Path.home() names = ( "BrainStem2", "brainstem2", "BrainStemDevelopment", "brainstem-development-kit", "acroname-brainstem", ) out: list[Path] = [] for name in names: p = home / name if p.is_dir(): out.append(p) # Nested layout: ~/Downloads/BrainStem2-2.12.2-Linux/BrainStem2 try: for child in home.iterdir(): if not child.is_dir(): continue if "BrainStem" in child.name or "brainstem" in child.name.lower(): nested = child / "BrainStem2" if nested.is_dir(): out.append(nested) out.append(child) except OSError: pass return _dedupe_paths(out) def default_reflex_include_dirs() -> list[Path]: """ ``-I`` paths for ``#include ``-style API headers. Set ``FIWI_REFLEX_INCLUDE`` to ``os.pathsep``-separated directories (required if arc is not configured elsewhere). Optionally set ``BRAINSTEM2_DEV_ROOT`` to a dev kit tree containing ``api/lib/BrainStem2`` (or similar) with ``*.reflex`` API files. """ inc: list[Path] = [] inc.extend(_env_path_list("FIWI_REFLEX_INCLUDE")) for root in brainstem_dev_roots(): for sub in ( "api/lib/BrainStem2", "lib/BrainStem2", "BrainStem2/api/lib/BrainStem2", "include/reflex", ): p = root / sub if p.is_dir(): inc.append(p) return _dedupe_paths(inc) def find_arc() -> str | None: """Return executable path for **arc**, or None.""" explicit = os.environ.get("FIWI_REFLEX_ARC", "").strip() if explicit: p = Path(explicit).expanduser() if p.is_file() and os.access(p, os.X_OK): return str(p) return None w = shutil.which("arc") if w: return w roots = _dedupe_paths(brainstem_dev_roots() + _likely_sdk_roots_under_home()) for root in roots: cand = root / "bin" / "arc" if cand.is_file() and os.access(cand, os.X_OK): return str(cand) return None def print_arc_missing_help() -> None: print( "The Reflex compiler **arc** is not on PATH and was not found under common install paths.\n" "\n" "It is **not** installed by ``pip install brainstem``. Get the BrainStem **Development Kit** " "(Linux .tgz), extract it, then either:\n" "\n" f" • Add the kit’s ``bin`` to PATH (contains ``arc``, ``ReflexLoader``, ``HubTool``), or\n" " • export BRAINSTEM2_DEV_ROOT=/path/to/extracted/BrainStem2\n" " (FiWi searches $BRAINSTEM2_DEV_ROOT/bin/arc and api/lib/BrainStem2 for includes), or\n" " • export FIWI_REFLEX_ARC=/full/path/to/arc\n" "\n" f"Download: {BRAINSTEM_SDK_DOWNLOAD_URL}\n" "\n" "If you keep the SDK under ``FiWiManager/brainstem_sdk``, extract the Linux ``.tgz`` there " "so you get ``brainstem_sdk/BrainStem2/bin/arc`` (the ``api/`` tree alone does not ship " "``arc``).\n" "\n" "Check: python3 fiwi.py reflex which\n", file=sys.stderr, flush=True, ) def reflex_compile( source: Path, output_map: Path | None, *, extra_argv: list[str] | None = None, verbose: bool = False, ) -> int: """ Run **arc** to compile ``source`` → ``output_map`` (default: same basename, ``.map``). ``extra_argv`` are appended before the output/source (for unusual arc builds). Returns subprocess exit code. """ arc = find_arc() if not arc: print("fiwi reflex: no 'arc' executable.", file=sys.stderr, flush=True) print_arc_missing_help() return 1 src = source.expanduser().resolve() if not src.is_file(): print(f"fiwi reflex: source not found: {src}", file=sys.stderr, flush=True) return 1 out = ( output_map.expanduser().resolve() if output_map is not None else src.with_suffix(".map") ) includes = default_reflex_include_dirs() argv: list[str] = [arc] for d in includes: argv.extend(["-I", str(d.resolve())]) if extra_argv: argv.extend(extra_argv) argv.extend(["-o", str(out), str(src)]) if verbose: print(subprocess.list2cmdline(argv), flush=True) return subprocess.run(argv, cwd=str(src.parent)).returncode def print_reflex_cli_help() -> None: print( "On-device Reflex (Acroname) — compile only; load .map with ReflexLoader / HubTool.\n" "\n" "Usage:\n" " fiwi.py reflex help | doc | which\n" " fiwi.py reflex compile [output.map] [-- ]\n" "\n" "Environment:\n" " FIWI_REFLEX_ARC Full path to arc (if not on PATH)\n" " FIWI_REFLEX_INCLUDE Include dirs for aUSBHub*.reflex APIs (os.pathsep-separated)\n" " BRAINSTEM2_DEV_ROOT BrainStem Development Kit root (finds bin/arc, api/lib/BrainStem2)\n" " REFLEX_SDK_ROOT, FIWI_BRAINSTEM_SDK — same idea as BRAINSTEM2_DEV_ROOT\n" "\n" f"SDK (includes arc): {BRAINSTEM_SDK_DOWNLOAD_URL}\n" "\n" "Example:\n" " export BRAINSTEM2_DEV_ROOT=~/BrainStem2\n" " fiwi.py reflex compile reflex/inrush.reflex reflex/inrush.map\n" " ./scripts/compile_inrush_reflex.sh\n" f"\nLanguage reference: {REFLEX_DOC_URL}\n" ) def cli_main(argv: list[str]) -> int: """Entry for ``fiwi.py reflex …`` (invoked before BrainStem Python load).""" if not argv or argv[0].lower() in ("help", "-h", "--help"): print_reflex_cli_help() return 0 cmd = argv[0].lower() if cmd == "doc": print(REFLEX_DOC_URL, flush=True) return 0 if cmd == "which": arc = find_arc() print(f"arc: {arc or '(not found)'}", flush=True) incs = default_reflex_include_dirs() if incs: print("include (-I):", flush=True) for d in incs: print(f" {d}", flush=True) else: print("include (-I): (none — set FIWI_REFLEX_INCLUDE or BRAINSTEM2_DEV_ROOT)", flush=True) return 0 if cmd != "compile": print(f"fiwi reflex: unknown subcommand {argv[0]!r}", file=sys.stderr, flush=True) print("Try: fiwi.py reflex help", file=sys.stderr, flush=True) return 2 rest = argv[1:] if not rest: print("fiwi reflex: compile requires ", file=sys.stderr, flush=True) return 2 extra: list[str] = [] for i, a in enumerate(rest): if a == "--": extra = rest[i + 1 :] rest = rest[:i] break src_s = rest[0] out_s = rest[1] if len(rest) >= 2 else None verbose = os.environ.get("FIWI_REFLEX_VERBOSE", "").strip() in ("1", "yes", "true") return reflex_compile(Path(src_s), Path(out_s) if out_s else None, extra_argv=extra or None, verbose=verbose)