diff --git a/docs/developer/explanations/entities.rst b/docs/developer/explanations/entities.rst index ab99f17b2..2f748f4c3 100644 --- a/docs/developer/explanations/entities.rst +++ b/docs/developer/explanations/entities.rst @@ -158,7 +158,7 @@ Click the arrows to reveal the files.
st.cmd - .. include:: ../../../tests/samples/outputs/motorSim.st.cmd + .. include:: ../../../tests/samples/outputs/motorSim/st.cmd :literal: .. raw:: html diff --git a/pyproject.toml b/pyproject.toml index b3e8f1387..71ee96e4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "ruamel.yaml", "jinja2", "GitPython", - "pvi>=0.7.1", + "pvi~=0.8", # pvi currently tracks breaking changes with minor version ] # Add project dependencies here, e.g. ["click", "numpy"] dynamic = ["version"] license.file = "LICENSE" diff --git a/src/ibek/gen_scripts.py b/src/ibek/gen_scripts.py index 19fb0b384..8187b4fed 100644 --- a/src/ibek/gen_scripts.py +++ b/src/ibek/gen_scripts.py @@ -58,6 +58,10 @@ def ioc_deserialize(ioc_instance_yaml: Path, definition_yaml: List[Path]) -> IOC # extract the ioc instance yaml into a dict ioc_instance_dict = YAML(typ="safe").load(ioc_instance_yaml) + if ioc_instance_dict is None or "ioc_name" not in ioc_instance_dict: + raise RuntimeError( + f"Failed to load a valid ioc config from {ioc_instance_yaml}" + ) # extract the ioc name into UTILS for use in jinja renders name = UTILS.render({}, ioc_instance_dict["ioc_name"]) diff --git a/src/ibek/globals.py b/src/ibek/globals.py index 5afa6a04a..025112cb8 100644 --- a/src/ibek/globals.py +++ b/src/ibek/globals.py @@ -8,9 +8,36 @@ from pydantic import BaseModel, ConfigDict from typer.core import TyperGroup + +class _Globals: + """Helper class for accessing global constants.""" + + def __init__(self) -> None: + self.EPICS_ROOT = Path(os.getenv("EPICS_ROOT", "/epics/")) + """Root of epics directory tree. + + This can be overriden by defining an environment variable "EPICS_ROOT". + """ + + self.IBEK_DEFS = self.EPICS_ROOT / "ibek-defs" + """Directory containing ibek support yaml definitions.""" + + self.PVI_DEFS = self.EPICS_ROOT / "pvi-defs" + """Directory containing pvi device yaml definitions.""" + + self.RUNTIME_OUTPUT = self.EPICS_ROOT / "runtime" + """Directory containing runtime generated assets for IOC boot.""" + + self.OPI_OUTPUT = self.EPICS_ROOT / "opi" + """Directory containing runtime generated opis to serve over http.""" + + +GLOBALS = _Globals() + +# TODO: Include all constants in _Globals + # get the container paths from environment variables EPICS_BASE = Path(os.getenv("EPICS_BASE", "/epics/epics-base")) -EPICS_ROOT = Path(os.getenv("EPICS_ROOT", "/epics/")) IOC_FOLDER = Path(os.getenv("IOC", "/epics/ioc")) SUPPORT = Path(os.getenv("SUPPORT", "/epics/support")) CONFIG_DIR_NAME = "config" @@ -26,19 +53,11 @@ # Folder containing templates for IOC src etc. TEMPLATES = Path(__file__).parent / "templates" -# Definitions populated at container build time -IBEK_DEFS = EPICS_ROOT / "ibek-defs" -PVI_DEFS = EPICS_ROOT / "pvi-defs" - # Paths for ibek-support IBEK_GLOBALS = Path("_global") SUPPORT_YAML_PATTERN = "*ibek.support.yaml" PVI_YAML_PATTERN = "*pvi.device.yaml" -# Assets generated at runtime -RUNTIME_OUTPUT_PATH = EPICS_ROOT / "runtime" -OPI_OUTPUT_PATH = EPICS_ROOT / "opi" - IOC_DBDS = SUPPORT / "configure/dbd_list" IOC_LIBS = SUPPORT / "configure/lib_list" RUNTIME_DEBS = SUPPORT / "configure/runtime_debs" diff --git a/src/ibek/ioc_cmds/assets.py b/src/ibek/ioc_cmds/assets.py index 8f3eb6d36..ece8ec58a 100644 --- a/src/ibek/ioc_cmds/assets.py +++ b/src/ibek/ioc_cmds/assets.py @@ -7,7 +7,7 @@ import typer -from ibek.globals import IBEK_DEFS, IOC_FOLDER, PVI_DEFS +from ibek.globals import GLOBALS, IOC_FOLDER log = logging.getLogger(__name__) @@ -54,8 +54,8 @@ def extract_assets( if defaults: default_assets = [ source / "support" / "configure", - PVI_DEFS, - IBEK_DEFS, + GLOBALS.PVI_DEFS, + GLOBALS.IBEK_DEFS, IOC_FOLDER, Path("/venv"), ] diff --git a/src/ibek/ioc_cmds/commands.py b/src/ibek/ioc_cmds/commands.py index 58d6f9e91..283652914 100644 --- a/src/ibek/ioc_cmds/commands.py +++ b/src/ibek/ioc_cmds/commands.py @@ -7,7 +7,7 @@ from ibek.gen_scripts import ioc_create_model from ibek.globals import ( - IBEK_DEFS, + GLOBALS, SUPPORT_YAML_PATTERN, NaturalOrderGroup, ) @@ -57,7 +57,7 @@ def generate_schema( ), ] = None, ibek_defs: bool = typer.Option( - True, help=f"Include definitions in {IBEK_DEFS} in generated schema" + True, help=f"Include definitions in {GLOBALS.IBEK_DEFS} in generated schema" ), ): """ @@ -71,10 +71,10 @@ def generate_schema( if ibek_defs: # this allows us to use the definitions inside the container # which are in a known location after the container is built - definitions += IBEK_DEFS.glob(SUPPORT_YAML_PATTERN) + definitions += GLOBALS.IBEK_DEFS.glob(SUPPORT_YAML_PATTERN) if not definitions: - log.error(f"No `definitions` given and none found in {IBEK_DEFS}") + log.error(f"No `definitions` given and none found in {GLOBALS.IBEK_DEFS}") raise typer.Exit(1) ioc_model = ioc_create_model(definitions) diff --git a/src/ibek/render_db.py b/src/ibek/render_db.py index aebfc46a2..bdca11d6b 100644 --- a/src/ibek/render_db.py +++ b/src/ibek/render_db.py @@ -44,14 +44,8 @@ def add_row(self, filename: str, args: Mapping[str, Any], entity: Entity) -> Non columns=[0] * len(args), ) - # Add a new row of argument values. - # Note the case where no value was specified for the argument - # meaning that the name is to be rendered in Jinja - row = ["{{ %s }}" % k if v is None else v for k, v in args.items()] - - # render any Jinja fields in the arguments - for i, line in enumerate(row): - row[i] = UTILS.render(dict(entity), row[i]) + # add a new row of argument values, rendering any Jinja template fields + row = list(UTILS.render_map(dict(entity), args).values()) # save the new row self.render_templates[filename].rows.append(row) diff --git a/src/ibek/runtime_cmds/commands.py b/src/ibek/runtime_cmds/commands.py index 5b6e01996..5655b3b2d 100644 --- a/src/ibek/runtime_cmds/commands.py +++ b/src/ibek/runtime_cmds/commands.py @@ -9,20 +9,13 @@ from pvi.device import Device from ibek.gen_scripts import create_boot_script, create_db_script, ioc_deserialize -from ibek.globals import ( - OPI_OUTPUT_PATH, - PVI_DEFS, - RUNTIME_OUTPUT_PATH, - NaturalOrderGroup, -) +from ibek.globals import GLOBALS, NaturalOrderGroup from ibek.ioc import IOC, Entity from ibek.support import Database from ibek.utils import UTILS runtime_cli = typer.Typer(cls=NaturalOrderGroup) -PVI_PV_PREFIX = "${prefix}" - @runtime_cli.command() def generate( @@ -36,16 +29,6 @@ def generate( help="The filepath to a support module definition file", autocompletion=lambda: [], # Forces path autocompletion ), - out: Path = typer.Option( - default=RUNTIME_OUTPUT_PATH / "st.cmd", - help="Path to output startup script", - autocompletion=lambda: [], # Forces path autocompletion - ), - db_out: Path = typer.Option( - default=RUNTIME_OUTPUT_PATH / "ioc.subst", - help="Path to output database expansion shell script", - autocompletion=lambda: [], # Forces path autocompletion - ), ): """ Build a startup script for an IOC instance @@ -55,25 +38,24 @@ def generate( ioc_instance = ioc_deserialize(instance, definitions) - # Clear out generated files so developers know if something stop being generated - shutil.rmtree(RUNTIME_OUTPUT_PATH, ignore_errors=True) - RUNTIME_OUTPUT_PATH.mkdir(exist_ok=True) - shutil.rmtree(OPI_OUTPUT_PATH, ignore_errors=True) - OPI_OUTPUT_PATH.mkdir(exist_ok=True) + # Clear out generated files so developers know if something stops being generated + shutil.rmtree(GLOBALS.RUNTIME_OUTPUT, ignore_errors=True) + GLOBALS.RUNTIME_OUTPUT.mkdir(exist_ok=True) + shutil.rmtree(GLOBALS.OPI_OUTPUT, ignore_errors=True) + GLOBALS.OPI_OUTPUT.mkdir(exist_ok=True) pvi_index_entries, pvi_databases = generate_pvi(ioc_instance) generate_index(ioc_instance.ioc_name, pvi_index_entries) script_txt = create_boot_script(ioc_instance) - - out.parent.mkdir(parents=True, exist_ok=True) - - with out.open("w") as stream: + script_output = GLOBALS.RUNTIME_OUTPUT / "st.cmd" + script_output.parent.mkdir(parents=True, exist_ok=True) + with script_output.open("w") as stream: stream.write(script_txt) db_txt = create_db_script(ioc_instance, pvi_databases) - - with db_out.open("w") as stream: + db_output = GLOBALS.RUNTIME_OUTPUT / "ioc.subst" + with db_output.open("w") as stream: stream.write(db_txt) @@ -99,37 +81,42 @@ def generate_pvi(ioc: IOC) -> Tuple[List[IndexEntry], List[Tuple[Database, Entit if entity_pvi is None: continue - pvi_yaml = PVI_DEFS / entity_pvi.yaml_path + pvi_yaml = GLOBALS.PVI_DEFS / entity_pvi.yaml_path device_name = pvi_yaml.name.split(".")[0] - device_bob = OPI_OUTPUT_PATH / f"{device_name}.pvi.bob" + device_bob = GLOBALS.OPI_OUTPUT / f"{device_name}.pvi.bob" # Skip deserializing yaml if not needed - if entity_pvi.pva_template or device_name not in formatted_pvi_devices: + if entity_pvi.pv or device_name not in formatted_pvi_devices: device = Device.deserialize(pvi_yaml) - device.deserialize_parents([PVI_DEFS]) + device.deserialize_parents([GLOBALS.PVI_DEFS]) - # Render the prefix value for the device from the instance parameters - macros = {"prefix": UTILS.render(entity.model_dump(), entity_pvi.prefix)} - - if entity_pvi.pva_template: + if entity_pvi.pv: # Create a template with the V4 structure defining a PVI interface - output_template = RUNTIME_OUTPUT_PATH / f"{device_name}.pvi.template" - format_template(device, PVI_PV_PREFIX, output_template) + output_template = GLOBALS.RUNTIME_OUTPUT / f"{device_name}.pvi.template" + format_template(device, entity_pvi.pv_prefix, output_template) # Add to extra databases to be added into substitution file databases.append( - (Database(file=output_template.name, args=macros), entity) + ( + Database(file=output_template.name, args=entity_pvi.ui_macros), + entity, + ) ) if device_name not in formatted_pvi_devices: - formatter.format(device, PVI_PV_PREFIX, device_bob) + formatter.format(device, device_bob) # Don't format further instance of this device formatted_pvi_devices.append(device_name) - if entity_pvi.index: + if entity_pvi.ui_index: + macros = UTILS.render_map(dict(entity), entity_pvi.ui_macros) index_entries.append( - IndexEntry(label=device_name, ui=device_bob.name, macros=macros) + IndexEntry( + label=f"{device.label}", + ui=device_bob.name, + macros=macros, + ) ) return index_entries, databases @@ -143,4 +130,4 @@ def generate_index(title: str, index_entries: List[IndexEntry]): index_entries: List of entries to include as buttons on index UI """ - DLSFormatter().format_index(title, index_entries, OPI_OUTPUT_PATH / "index.bob") + DLSFormatter().format_index(title, index_entries, GLOBALS.OPI_OUTPUT / "index.bob") diff --git a/src/ibek/support.py b/src/ibek/support.py index 80037d65c..5dd593cf5 100644 --- a/src/ibek/support.py +++ b/src/ibek/support.py @@ -111,7 +111,8 @@ class Database(BaseSettings): args: Mapping[str, Optional[str]] = Field( description=( "Dictionary of args and values to pass through to database. " - "A value of None is equivalent to ARG: '{{ ARG }}'" + "A value of None is equivalent to ARG: '{{ ARG }}'. " + "See `UTILS.render_map` for more details." ) ) @@ -167,17 +168,25 @@ class EntityPVI(BaseSettings): yaml_path: str = Field( description="Path to .pvi.device.yaml - absolute or relative to PVI_DEFS" ) - index: bool = Field( - description="Whether to add generated UI to index for Entity", default=True + ui_index: bool = Field( + True, + description="Whether to add the UI to the IOC index.", ) - prefix: str = Field(description="PV prefix to pass as $(prefix) on index button") - pva_template: bool = Field( + ui_macros: dict[str, str | None] = Field( + None, description=( - "Whether to generate a database template with info tags that create a " - "PVAccess structure defining the PV interface (PVI) for this entity" + "Macros to launch the UI on the IOC index. " + "These must be args of the Entity this is attached to." ), - default=False, ) + pv: bool = Field( + False, + description=( + "Whether to generate a PVI PV. This adds a database template with info " + "tags that create a PVAccess PV representing the device structure." + ), + ) + pv_prefix: str = Field("", description='PV prefix for PVI PV - e.g. "$(P)"') class Definition(BaseSettings): diff --git a/src/ibek/support_cmds/commands.py b/src/ibek/support_cmds/commands.py index f29a52475..b59be8287 100644 --- a/src/ibek/support_cmds/commands.py +++ b/src/ibek/support_cmds/commands.py @@ -16,11 +16,10 @@ from typing_extensions import Annotated from ibek.globals import ( - IBEK_DEFS, + GLOBALS, IBEK_GLOBALS, IOC_DBDS, IOC_LIBS, - PVI_DEFS, PVI_YAML_PATTERN, RELEASE, RUNTIME_DEBS, @@ -260,10 +259,10 @@ def generate_links( """ support_globals = folder / ".." / IBEK_GLOBALS - symlink_files(folder, SUPPORT_YAML_PATTERN, IBEK_DEFS) + symlink_files(folder, SUPPORT_YAML_PATTERN, GLOBALS.IBEK_DEFS) if support_globals.exists(): - symlink_files(support_globals, SUPPORT_YAML_PATTERN, IBEK_DEFS) - symlink_files(folder, PVI_YAML_PATTERN, PVI_DEFS) + symlink_files(support_globals, SUPPORT_YAML_PATTERN, GLOBALS.IBEK_DEFS) + symlink_files(folder, PVI_YAML_PATTERN, GLOBALS.PVI_DEFS) @support_cli.command() diff --git a/src/ibek/support_cmds/files.py b/src/ibek/support_cmds/files.py index d69bdc2b0..8e8e81347 100644 --- a/src/ibek/support_cmds/files.py +++ b/src/ibek/support_cmds/files.py @@ -87,18 +87,23 @@ def add_text_once(file: Path, text: str): def symlink_files(source_directory: Path, file_pattern: str, target_directory: Path): - """Symlink files patching the given pattern in source directory to target directory. + """ + Symlink files matching the given pattern in source directory to target directory. Args: source_directory: Directory containing source files - file_patterm: Pattern of files in source directory to be symlinked + file_pattern: Pattern of files in source directory to be symlinked target_directory: Directory to create symlinks in """ + source_files = list(source_directory.glob(file_pattern)) + if not source_files: + return + typer.echo(f"Symlinking {file_pattern} files:") target_directory.mkdir(parents=True, exist_ok=True) - for yaml in source_directory.glob(file_pattern): - typer.echo(f" {target_directory / yaml.name} -> {yaml}") - target = target_directory / yaml.name + for source_file in source_files: + typer.echo(f" {target_directory / source_file.name} -> {source_file}") + target = target_directory / source_file.name target.unlink(missing_ok=True) - target.symlink_to(yaml) + target.symlink_to(source_file) diff --git a/src/ibek/utils.py b/src/ibek/utils.py index 0be485d4d..cb9c9d409 100644 --- a/src/ibek/utils.py +++ b/src/ibek/utils.py @@ -10,7 +10,7 @@ import os from dataclasses import dataclass from pathlib import Path -from typing import Any, Dict +from typing import Any, Dict, Mapping from jinja2 import Template @@ -113,6 +113,25 @@ def render(self, context: Any, template_text: str) -> str: ioc_name=self.ioc_name, ) + def render_map(self, context: Any, map: Mapping[str, str | None]) -> dict[str, str]: + """ + Render a map of jinja templates with values from the given context. + + If given a key with a value of `None`, the key itself will be used as a template + value, so ``{"P": None}`` is equivalent to ``{"P": "{{ P }}"}``. + + Args: + context: Context to extract template variables from + map: Map of macro to jinja template to render + + """ + return { + key: self.render( + context, template if template is not None else "{{ %s }}" % key + ) + for key, template in map.items() + } + # a singleton Utility object for sharing state across all Entity renders UTILS: Utils = Utils() diff --git a/tests/generate_samples.sh b/tests/generate_samples.sh index 5b1a874df..9303301f4 100755 --- a/tests/generate_samples.sh +++ b/tests/generate_samples.sh @@ -34,9 +34,9 @@ echo making an ioc schema using utils support yaml ibek ioc generate-schema --no-ibek-defs support/utils.ibek.support.yaml --output schemas/utils.ibek.ioc.schema.json echo making ioc based on ibek-mo-ioc-01.yaml -ibek runtime generate iocs/ibek-mo-ioc-01.yaml support/asyn.ibek.support.yaml support/motorSim.ibek.support.yaml --out outputs/motorSim.st.cmd --db-out outputs/motorSim.ioc.subst -cp ${EPICS_ROOT}/opi/* outputs/ +EPICS_ROOT=`pwd`/epics ibek runtime generate iocs/ibek-mo-ioc-01.yaml support/asyn.ibek.support.yaml support/motorSim.ibek.support.yaml +mv `pwd`/epics/{runtime,opi}/* `pwd`/outputs/motorSim echo making ioc based on utils support yaml -ibek runtime generate iocs/utils.ibek.ioc.yaml support/utils.ibek.support.yaml --out outputs/utils.st.cmd --db-out outputs/utils.ioc.subst - +EPICS_ROOT=`pwd`/epics ibek runtime generate iocs/utils.ibek.ioc.yaml support/utils.ibek.support.yaml +mv `pwd`/epics/{runtime,opi}/* `pwd`/outputs/utils diff --git a/tests/samples/epics/pvi-defs/simple.pvi.device.yaml b/tests/samples/epics/pvi-defs/simple.pvi.device.yaml index eacf3d918..dff0275b4 100644 --- a/tests/samples/epics/pvi-defs/simple.pvi.device.yaml +++ b/tests/samples/epics/pvi-defs/simple.pvi.device.yaml @@ -3,6 +3,6 @@ label: Simple Device children: - type: SignalR name: SimplePV - pv: Simple - widget: + read_pv: $(P)Simple + read_widget: type: TextRead diff --git a/tests/samples/outputs/index.bob b/tests/samples/outputs/motorSim/index.bob similarity index 89% rename from tests/samples/outputs/index.bob rename to tests/samples/outputs/motorSim/index.bob index b425043bc..47965f498 100644 --- a/tests/samples/outputs/index.bob +++ b/tests/samples/outputs/motorSim/index.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 388 55 4 @@ -9,7 +9,7 @@ Title TITLE - ibek-mo-ioc-01 - Index + ibek-mo-ioc-01 0 0 388 @@ -27,7 +27,7 @@ Label - Simple + Simple Device 23 30 150 @@ -41,11 +41,11 @@ tab Open Display - IBEK-MO-TST-01:: +

IBEK-MO-TST-01:

- Simple + IBEK-MO-TST-01: 178 30 205 diff --git a/tests/samples/outputs/motorSim.ioc.subst b/tests/samples/outputs/motorSim/ioc.subst similarity index 97% rename from tests/samples/outputs/motorSim.ioc.subst rename to tests/samples/outputs/motorSim/ioc.subst index ae8927c8a..b56b1a843 100644 --- a/tests/samples/outputs/motorSim.ioc.subst +++ b/tests/samples/outputs/motorSim/ioc.subst @@ -26,6 +26,6 @@ pattern file "simple.pvi.template" { pattern - { "prefix" } - { "IBEK-MO-TST-01::" } + { "P" } + { "IBEK-MO-TST-01:" } } diff --git a/tests/samples/outputs/simple.pvi.bob b/tests/samples/outputs/motorSim/simple.pvi.bob similarity index 93% rename from tests/samples/outputs/simple.pvi.bob rename to tests/samples/outputs/motorSim/simple.pvi.bob index 545d4633f..663bfc3c1 100644 --- a/tests/samples/outputs/simple.pvi.bob +++ b/tests/samples/outputs/motorSim/simple.pvi.bob @@ -1,7 +1,7 @@ Display 0 - 0 + 0 388 55 4 @@ -9,7 +9,7 @@ Title TITLE - Simple Device - ${prefix} + Simple Device 0 0 388 @@ -35,7 +35,7 @@ TextUpdate - ${prefix}Simple + $(P)Simple 178 30 205 diff --git a/tests/samples/outputs/motorSim/simple.pvi.template b/tests/samples/outputs/motorSim/simple.pvi.template new file mode 100644 index 000000000..d0582cb80 --- /dev/null +++ b/tests/samples/outputs/motorSim/simple.pvi.template @@ -0,0 +1,15 @@ + +### PV Interface for Simple Device ### + +record("*", "$(P)Simple") { + info(Q:group, { + "$(P)PVI": { + "pvi.SimplePV.r": { + "+channel": "NAME", + "+type": "plain", + } + } + }) +} + +### End of PV Interface for Simple Device ### diff --git a/tests/samples/outputs/motorSim.st.cmd b/tests/samples/outputs/motorSim/st.cmd similarity index 100% rename from tests/samples/outputs/motorSim.st.cmd rename to tests/samples/outputs/motorSim/st.cmd diff --git a/tests/samples/outputs/utils/index.bob b/tests/samples/outputs/utils/index.bob new file mode 100644 index 000000000..b83f1ecee --- /dev/null +++ b/tests/samples/outputs/utils/index.bob @@ -0,0 +1,28 @@ + + Display + 0 + 0 + 10 + 35 + 4 + 4 + + Title + TITLE + counter + 0 + 0 + 10 + 25 + + + + + + + + + true + 1 + + diff --git a/tests/samples/outputs/utils.ioc.subst b/tests/samples/outputs/utils/ioc.subst similarity index 100% rename from tests/samples/outputs/utils.ioc.subst rename to tests/samples/outputs/utils/ioc.subst diff --git a/tests/samples/outputs/utils.st.cmd b/tests/samples/outputs/utils/st.cmd similarity index 100% rename from tests/samples/outputs/utils.st.cmd rename to tests/samples/outputs/utils/st.cmd diff --git a/tests/samples/schemas/ibek.support.schema.json b/tests/samples/schemas/ibek.support.schema.json index 747922cbe..a46e4506d 100644 --- a/tests/samples/schemas/ibek.support.schema.json +++ b/tests/samples/schemas/ibek.support.schema.json @@ -93,7 +93,7 @@ } ] }, - "description": "Dictionary of args and values to pass through to database. A value of None is equivalent to ARG: '{{ ARG }}'", + "description": "Dictionary of args and values to pass through to database. A value of None is equivalent to ARG: '{{ ARG }}'. See `UTILS.render_map` for more details.", "title": "Args", "type": "object" } @@ -235,27 +235,43 @@ "title": "Yaml Path", "type": "string" }, - "index": { + "ui_index": { "default": true, - "description": "Whether to add generated UI to index for Entity", - "title": "Index", + "description": "Whether to add the UI to the IOC index.", + "title": "Ui Index", "type": "boolean" }, - "prefix": { - "description": "PV prefix to pass as $(prefix) on index button", - "title": "Prefix", - "type": "string" + "ui_macros": { + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "default": null, + "description": "Macros to launch the UI on the IOC index. These must be args of the Entity this is attached to.", + "title": "Ui Macros", + "type": "object" }, - "pva_template": { + "pv": { "default": false, - "description": "Whether to generate a database template with info tags that create a PVAccess structure defining the PV interface (PVI) for this entity", - "title": "Pva Template", + "description": "Whether to generate a PVI PV. This adds a database template with info tags that create a PVAccess PV representing the device structure.", + "title": "Pv", "type": "boolean" + }, + "pv_prefix": { + "default": "", + "description": "PV prefix for PVI PV - e.g. \"$(P)\"", + "title": "Pv Prefix", + "type": "string" } }, "required": [ - "yaml_path", - "prefix" + "yaml_path" ], "title": "EntityPVI", "type": "object" diff --git a/tests/samples/support/motorSim.ibek.support.yaml b/tests/samples/support/motorSim.ibek.support.yaml index 92a0cbf57..3ea2df023 100644 --- a/tests/samples/support/motorSim.ibek.support.yaml +++ b/tests/samples/support/motorSim.ibek.support.yaml @@ -52,9 +52,10 @@ defs: pvi: yaml_path: simple.pvi.device.yaml - prefix: "{{P}}:" - pva_template: true - index: true + ui_macros: + P: + pv: true + pv_prefix: $(P) - name: simMotorAxis description: |- diff --git a/tests/samples/support/simple.pvi.device.yaml b/tests/samples/support/simple.pvi.device.yaml index eacf3d918..dff0275b4 100644 --- a/tests/samples/support/simple.pvi.device.yaml +++ b/tests/samples/support/simple.pvi.device.yaml @@ -3,6 +3,6 @@ label: Simple Device children: - type: SignalR name: SimplePV - pv: Simple - widget: + read_pv: $(P)Simple + read_widget: type: TextRead diff --git a/tests/test_cli.py b/tests/test_cli.py index b64851e35..97839261b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -11,8 +11,13 @@ from pytest_mock import MockerFixture from ibek import __version__ -from ibek.globals import IBEK_DEFS, PVI_DEFS, PVI_YAML_PATTERN, SUPPORT_YAML_PATTERN +from ibek.globals import ( + GLOBALS, + PVI_YAML_PATTERN, + SUPPORT_YAML_PATTERN, +) from ibek.ioc import clear_entity_model_ids +from ibek.runtime_cmds.commands import generate from ibek.support_cmds.commands import generate_links from tests.conftest import run_cli @@ -75,7 +80,7 @@ def test_motor_sim_schema(tmp_path: Path, samples: Path): assert expected == actual -def test_build_runtime_motorSim(tmp_path: Path, samples: Path): +def test_build_runtime_motorSim(mocker: MockerFixture, tmp_path: Path, samples: Path): """ build an ioc runtime script from an IOC instance entity file and multiple support module definition files @@ -87,65 +92,52 @@ def test_build_runtime_motorSim(tmp_path: Path, samples: Path): ioc_yaml = samples / "iocs" / "ibek-mo-ioc-01.yaml" support_yaml1 = samples / "support" / "asyn.ibek.support.yaml" support_yaml2 = samples / "support" / "motorSim.ibek.support.yaml" - out_file = tmp_path / "motorSim.st.cmd" - out_db = tmp_path / "motorSim.ioc.subst" + expected_outputs = samples / "outputs" / "motorSim" - run_cli( - "runtime", - "generate", - ioc_yaml, - support_yaml1, - support_yaml2, - "--out", - out_file, - "--db-out", - out_db, - ) + mocker.patch.object(GLOBALS, "RUNTIME_OUTPUT", tmp_path) + mocker.patch.object(GLOBALS, "OPI_OUTPUT", tmp_path) + + generate(ioc_yaml, [support_yaml1, support_yaml2]) - example_boot = (samples / "outputs" / "motorSim.st.cmd").read_text() - actual_boot = out_file.read_text() + example_boot = (expected_outputs / "st.cmd").read_text() + actual_boot = (tmp_path / "st.cmd").read_text() assert example_boot == actual_boot - example_db = (samples / "outputs" / "motorSim.ioc.subst").read_text() - actual_db = out_db.read_text() + example_db = (expected_outputs / "ioc.subst").read_text() + actual_db = (tmp_path / "ioc.subst").read_text() assert example_db == actual_db - example_index = (samples / "outputs" / "index.bob").read_text() - actual_index = (samples / "epics" / "opi" / "index.bob").read_text() + example_index = (expected_outputs / "index.bob").read_text() + actual_index = (tmp_path / "index.bob").read_text() assert example_index == actual_index - example_pvi = (samples / "outputs" / "simple.pvi.bob").read_text() - actual_pvi = (samples / "epics" / "opi" / "simple.pvi.bob").read_text() - assert example_pvi == actual_pvi + example_bob = (expected_outputs / "simple.pvi.bob").read_text() + actual_bob = (tmp_path / "simple.pvi.bob").read_text() + assert example_bob == actual_bob + example_template = (expected_outputs / "simple.pvi.template").read_text() + actual_template = (tmp_path / "simple.pvi.template").read_text() + assert example_template == actual_template -def test_build_utils_features(tmp_path: Path, samples: Path): + +def test_build_utils_features(mocker: MockerFixture, tmp_path: Path, samples: Path): """ build an ioc runtime script to verify utils features """ clear_entity_model_ids() ioc_yaml = samples / "iocs" / "utils.ibek.ioc.yaml" support_yaml = samples / "support" / "utils.ibek.support.yaml" - out_file = tmp_path / "st.cmd" - out_db = tmp_path / "ioc.subst" - run_cli( - "runtime", - "generate", - ioc_yaml, - support_yaml, - "--out", - out_file, - "--db-out", - out_db, - ) + mocker.patch.object(GLOBALS, "RUNTIME_OUTPUT", tmp_path) + + run_cli("runtime", "generate", ioc_yaml, support_yaml) - example_boot = (samples / "outputs" / "utils.st.cmd").read_text() - actual_boot = out_file.read_text() + example_boot = (samples / "outputs" / "utils" / "st.cmd").read_text() + actual_boot = (tmp_path / "st.cmd").read_text() assert example_boot == actual_boot - example_db = (samples / "outputs" / "utils.ioc.subst").read_text() - actual_db = out_db.read_text() + example_db = (samples / "outputs" / "utils" / "ioc.subst").read_text() + actual_db = (tmp_path / "ioc.subst").read_text() assert example_db == actual_db @@ -154,5 +146,9 @@ def test_generate_links_ibek(samples: Path, mocker: MockerFixture): generate_links(samples / "support") - symlink_mock.assert_any_call(samples / "support", PVI_YAML_PATTERN, PVI_DEFS) - symlink_mock.assert_any_call(samples / "support", SUPPORT_YAML_PATTERN, IBEK_DEFS) + symlink_mock.assert_any_call( + samples / "support", PVI_YAML_PATTERN, GLOBALS.PVI_DEFS + ) + symlink_mock.assert_any_call( + samples / "support", SUPPORT_YAML_PATTERN, GLOBALS.IBEK_DEFS + )