diff --git a/src/ibek/__main__.py b/src/ibek/__main__.py index bad3e937e..b2645ee83 100644 --- a/src/ibek/__main__.py +++ b/src/ibek/__main__.py @@ -4,6 +4,7 @@ from ruamel.yaml import YAML from ibek._version import __version__ +from ibek.dev_cmds.commands import dev_cli from ibek.globals import NaturalOrderGroup from ibek.ioc_cmds.commands import ioc_cli from ibek.runtime_cmds.commands import runtime_cli @@ -26,6 +27,11 @@ name="runtime", help="Commands for building IOC instance startup files at container runtime", ) +cli.add_typer( + dev_cli, + name="dev", + help="Commands for working inside Generic IOC development containers", +) yaml = YAML() diff --git a/src/ibek/dev_cmds/__init__.py b/src/ibek/dev_cmds/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/ibek/dev_cmds/commands.py b/src/ibek/dev_cmds/commands.py new file mode 100644 index 000000000..65f77dec8 --- /dev/null +++ b/src/ibek/dev_cmds/commands.py @@ -0,0 +1,70 @@ +import logging +import shutil +from pathlib import Path + +import typer + +from ibek.globals import CONFIG_DIR_NAME, IOC_DIR_NAME, IOC_FOLDER, NaturalOrderGroup +from ibek.ioc_cmds.assets import get_ioc_source + +log = logging.getLogger(__name__) +dev_cli = typer.Typer(cls=NaturalOrderGroup) + + +@dev_cli.command() +def instance( + instance: Path = typer.Argument( + ..., help="The filepath to the ioc instance entity file" + ), +): + """ + Symlink an IOC instance config folder into /epics/ioc/config. + + Used in the devcontainer to allow the IOC instance to be run using + /epics/ioc/starts.sh. Changes made to the config will be immediately + available and also under version control. + + e.g. if instance is /repos/bl38p/iocs/bl38p-mo-panda-01 then we need: + - /epics/ioc -> /epics/ioc-pandablocks/ioc + - /epics/ioc/config -> /repos/bl38p/iocs/bl38p-mo-panda-01/config + """ + # validate the instance folder has a config folder + + generic_root = get_ioc_source() + ioc_folder = generic_root / IOC_DIR_NAME + config_folder = ioc_folder / CONFIG_DIR_NAME + instance_config = instance / CONFIG_DIR_NAME + + if not instance_config.exists(): + log.error(f"Could not find config folder {instance_config}") + raise typer.Exit(1) + + # First make sure there is an ioc folder in the generic IOC source root + # and that it is symlinked from /epics/ioc, remove any existing config. + if not ioc_folder.exists(): + ioc_folder.mkdir() + + # remove any existing config folder and ioc symlink from /epics/ioc + for folder in [config_folder, IOC_FOLDER]: + if folder.is_symlink(): + folder.unlink() + if folder.exists(): + shutil.rmtree(folder) + + IOC_FOLDER.symlink_to(ioc_folder) + + # Now symlink the instance config folder into the generic IOC source root + config_folder.symlink_to(instance_config) + + +@dev_cli.command() +def support( + module: Path = typer.Argument( + ..., help="The filepath to the support module to work on" + ), +): + """ + enable a support module for development. + """ + + raise NotImplementedError diff --git a/src/ibek/globals.py b/src/ibek/globals.py index 735beca2e..5b05e8ab7 100644 --- a/src/ibek/globals.py +++ b/src/ibek/globals.py @@ -13,6 +13,8 @@ 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" +IOC_DIR_NAME = "ioc" # the global RELEASE file which lists all support modules RELEASE = SUPPORT / "configure/RELEASE" diff --git a/src/ibek/ioc_cmds/assets.py b/src/ibek/ioc_cmds/assets.py index 76c494fd9..0393d5821 100644 --- a/src/ibek/ioc_cmds/assets.py +++ b/src/ibek/ioc_cmds/assets.py @@ -1,3 +1,4 @@ +import logging import os import shutil import subprocess @@ -8,6 +9,8 @@ from ibek.globals import EPICS_ROOT, IBEK_DEFS, IOC_FOLDER, PVI_DEFS +log = logging.getLogger(__name__) + def get_ioc_source() -> Path: """ @@ -19,17 +22,22 @@ def get_ioc_source() -> Path: Functions that use this should provide an override variable that allows the ibek caller to specify the location. """ - try: - ibek_support = ( - list(EPICS_ROOT.glob("*/ibek-support")) - or list(Path("..").glob("/**/ibek-support")) - )[0] - except IndexError: - raise RuntimeError( - "Could NOT find a suitable location for the IOC source folder. " + ibek_supports = list(EPICS_ROOT.glob("*/ibek-support")) + + if len(ibek_supports) > 1: + log.error( + "Found more than one ibek-support folder. " + "ibek must be run in a container a single generic IOC source folder" + ) + raise typer.Exit(1) + elif len(ibek_supports) == 0: + log.error( + "Could NOT find a generic IOC source folder containing ibek-support. " "ibek must be run in a container with the generic IOC source folder" ) - return (ibek_support / "..").resolve() + raise typer.Exit(1) + + return (ibek_supports[0] / "..").resolve() def move_file(src: Path, dest: Path, binary: List[str]): diff --git a/src/ibek/ioc_cmds/commands.py b/src/ibek/ioc_cmds/commands.py index 8e8331c27..853af9515 100644 --- a/src/ibek/ioc_cmds/commands.py +++ b/src/ibek/ioc_cmds/commands.py @@ -124,10 +124,14 @@ def make_source_template( if destination is None: destination = get_ioc_source() / "ioc" - if not destination.exists(): + # use a known file to check if the destination is already an IOC + # because an empty folder can be created by 'ibek dev instance' + dest_release = destination / "configure" / "RELEASE" + + if not dest_release.exists(): if destination.is_symlink(): destination.unlink() - shutil.copytree(TEMPLATES / "ioc", destination) + shutil.copytree(TEMPLATES / "ioc", destination, dirs_exist_ok=True) # make a symlink to the ioc folder in the root of the epics folder # this becomes the standard place for code to look for the IOC