""" Guard the environment suffix in Qt UI files. qt/ is rendered at deploy time (see config/environments.yml), so every EPICS device name in a .ui file must carry the suffix token. A name without it resolves to the production device in every environment -- which is how the dev Service Manager ended up driving prod IOCs. The rule is: every AGEBD- name is immediately followed by the suffix token. The one exception is systemd unit names, which genuinely have no environment suffix (the unit file is AGEBD-SERVICE-.service in both environments). This replaces an earlier check for the caQtDM macro '$(AGEBD_ENV_SUFFIX)', which was abandoned in favour of deploy-time rendering. That check had also stopped matching anything once placeholders appeared in the names. """ import re import sys from agebd.subs import SubstitutionsConfig from agebd.utils import get_git_root REPO_ROOT = get_git_root(__file__) QT_DIR = REPO_ROOT / "qt" SUFFIX_TOKEN_NAME = "agebd_env_suffix_upper" #: An EPICS device name belonging to us, e.g. AGEBD-ALH or AGEBD-CPCL-TUNE. AGEBD_NAME = re.compile(r"AGEBD-[0-9A-Z][0-9A-Z-]*") #: Systemd unit names are the same in every environment. EXEMPT_PREFIXES = ("AGEBD-SERVICE-",) def suffix_token(config): """ The token text these files must carry, taken from the central config so that renaming the token cannot leave this check enforcing a stale name. """ if SUFFIX_TOKEN_NAME not in config.tokens: raise AssertionError( f"Token '{SUFFIX_TOKEN_NAME}' is not declared in " f"{config.environments_file}, but qt/ is checked against it." ) return f"@@{SUFFIX_TOKEN_NAME}@@" def check_ui_file(text, token, filename): """ Report every AGEBD- device name in ``text`` that lacks ``token``. """ errors = [] for match in AGEBD_NAME.finditer(text): name = match.group(0) if name.startswith(EXEMPT_PREFIXES): continue if text[match.end() : match.end() + len(token)] == token: continue line = text.count("\n", 0, match.start()) + 1 errors.append( f"{filename}:{line}: '{name}' is missing '{token}', so it resolves to the " f"production device in every environment." ) return errors def check_ui_files(): """ Raise AssertionError listing every violation, or return silently. """ config = SubstitutionsConfig.load(REPO_ROOT / "config") token = suffix_token(config) errors = [] for path in sorted(QT_DIR.glob("*.ui")): errors.extend(check_ui_file(path.read_text(), token, path.relative_to(REPO_ROOT))) if errors: raise AssertionError( "Qt UI files missing the environment suffix:\n\n " + "\n ".join(errors) ) if __name__ == "__main__": try: check_ui_files() except AssertionError as e: print(e, file=sys.stderr) sys.exit(1) print("All AGEBD- device names in qt/ carry the environment suffix.")