Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

EPICS Configuration Environment (EPICS-env)

EPICS-env builds one Experimental Physics and Industrial Control System (EPICS) base and a pinned set of 32 EPICS modules from source, and installs them as one tree per release, operating system (OS), and base version. The installed tree links its libraries relative to its own location, carries a shell environment script, and holds a set of common iocsh fragments for input/output controllers (IOCs).

A make target drives every action: fetching the sources, applying carried upstream patches, configuring, building, installing, linking, and checking the result. The three verification gates are make targets as well: check.module-deps checks the module dependencies before the build, and check.deps and check.env check the library search paths and the environment script after the install.

Supported operating systems

The environment builds on six Linux OSs, each with one continuous integration (CI) workflow: Debian 12, Debian 13, Rocky Linux 8, Rocky Linux 10, Ubuntu 24.04, and Ubuntu 26.04. Every workflow builds and installs the environment, then runs check.env and check.deps on the installed tree. Every workflow also runs check.module-deps after configuration and before the build. Debian 12, Debian 13, and Rocky 8 run it through github.check; Rocky 10 and both Ubuntu workflows call it as a separate stage. Supported platforms and CI lists the container image and the build sequence of each workflow.

How to read this book

The pages follow the order in which you meet the system:

  • The tutorial, Build and use your first EPICS environment, takes you from a fresh clone to a running IOC.
  • The concept pages explain how the build pipeline, the module set, the installed tree, the verification gates, the patch carry, and the iocsh fragments work.
  • The procedure pages each complete one task, such as a module bump or an upstream fix carry.
  • The reference pages list exact values: make targets, variables, module pins, fragment macros, tools, and platforms.
  • The glossary of EPICS-env terms defines each term the pages use.

Topics outside this book

The cross build of EPICS base and modules for a Libera target is outside this book. It consists of scripts/build_base_libera.bash, scripts/build_modules_libera.bash, the conf.modules.libera target, and configure/os/CONFIG_SITE.linux-x86_64.linux-arm.

Build and use your first EPICS environment

In this lesson you build Experimental Physics and Industrial Control System (EPICS) base and its modules from source, and open a shell on the installed tree. You then start a soft input/output controller (IOC) that serves one process variable (PV). Last, you read and write that PV over Channel Access (CA) and pvAccess (PVA).

What you build in this lesson

At the end of the lesson you have:

  • An installed tree at <install_location>/1.4.0/debian-13/7.0.10, with EPICS base, every module, and the uldaq and open62541 vendor libraries.
  • A shell whose PATH and LD_LIBRARY_PATH point at that tree.
  • A running IOC that serves the PV tutorial:value, and a second shell that reads and writes it.

The lesson runs on a Debian 13 host. It needs:

  • The host packages for EPICS base and the modules. The continuous integration (CI) workflows install them with https://github.com/jeonghanlee/pkg_automation; see Package setup in the OS workflows.
  • Access to GitHub, and about 3.5 GB of free disk space for the sources, the build products, and the installed tree.
  • An empty directory that you own, as the working directory of the lesson. The lesson starts there and keeps one shell open until the IOC stage.

The whole lesson takes about 12 to 15 minutes on a 20-core host; make build is the longest step.

Fetch EPICS-env and set the install location

  1. Clone EPICS-env and enter the clone:

    git clone https://github.com/jeonghanlee/EPICS-env
    cd EPICS-env
    
  2. Set the install location in configure/CONFIG_SITE.local:

    echo "INSTALL_LOCATION=<install_location>" > configure/CONFIG_SITE.local
    

    <install_location> is an absolute path that you can write to, such as /home/<user>/epics-lesson. Set it before running an action such as make init or make build, because actions try to create this directory. Query-only invocations, such as make print-INSTALL_LOCATION_EPICS, create no installation directory or generated configuration file. The default install location is ${HOME}/epics.

  3. Print the path of the installed tree:

    make print-INSTALL_LOCATION_EPICS
    

    The output is:

    <install_location>/1.4.0/debian-13/7.0.10
    

    The path adds the EPICS-env release 1.4.0, the operating system debian-13, and the EPICS base version 7.0.10 under your location. The stages below create this tree and fill it.

Build the vendor libraries

The measComp module links the uldaq library and the opcua module links the open62541 library. In this lesson both libraries go into the vendor directory of the installed tree.

  1. Save the vendor path in a shell variable:

    VENDOR_PATH="$(make print-INSTALL_LOCATION_EPICS)/vendor"
    
  2. Point the two modules at that directory in configure/RELEASE.local:

    echo "VENDOR_ULDAQ_PATH=${VENDOR_PATH}" > configure/RELEASE.local
    echo 'OPEN62541_PATH=\$$\$$\(\_OPEN62541_CONFIG_OPCUA\)/../../../vendor' >> configure/RELEASE.local
    

    Type the second line exactly as shown. Its escapes let the installed opcua configuration find the vendor directory relative to its own location.

  3. Look at the file:

    cat configure/RELEASE.local
    

    The file holds two lines:

    VENDOR_ULDAQ_PATH=<install_location>/1.4.0/debian-13/7.0.10/vendor
    OPEN62541_PATH=\$$\$$\(\_OPEN62541_CONFIG_OPCUA\)/../../../vendor
    
  4. Build uldaq into the vendor directory:

    git clone https://github.com/jeonghanlee/uldaq-env ../uldaq-env
    echo "INSTALL_LOCATION=${VENDOR_PATH}" > ../uldaq-env/configure/CONFIG_SITE.local
    make -C ../uldaq-env init conf build install
    
  5. Build open62541 into the vendor directory:

    git clone https://github.com/jeonghanlee/open62541-env ../open62541-env
    echo "INSTALL_LOCATION=${VENDOR_PATH}" > ../open62541-env/configure/CONFIG_SITE.local
    make -C ../open62541-env init conf build install
    
  6. List the libraries:

    ls "${VENDOR_PATH}/lib"
    

    The output lists the libraries of both packages:

    cmake
    libopen62541.so
    libopen62541.so.1
    libopen62541.so.1.3.15
    libuldaq.a
    libuldaq.la
    libuldaq.so
    libuldaq.so.1
    libuldaq.so.1.2.1
    pkgconfig
    

    The installed tree exists and holds its first directory, vendor.

Fetch and patch the sources

  1. Clone EPICS base and every module at its pinned tag or commit:

    make init
    
  2. List the source trees:

    ls -d *-src
    

    The output lists 33 source trees:

    asyn-src
    autosave-src
    busy-src
    calc-src
    caPutLog-src
    epics-base-src
    ether_ip-src
    feed-core-src
    iocStats-src
    linStat-src
    lua-src
    mca-src
    MCoreUtils-src
    measComp-src
    modbus-src
    motorMotorSim-src
    motor-src
    opcua-src
    pcas-src
    pmac-src
    pscdrv-src
    pvxs-src
    pyDevSup-src
    QPC-src
    recsync-src
    retools-src
    rgamv2-src
    scaler-src
    sequencer-src
    snmp-src
    sscan-src
    std-src
    StreamDevice-src
    

    EPICS base and 32 modules sit next to each other at the repository top. Module pins and dependencies lists the pin of each one. The list above follows the sort order of the en_US.UTF-8 locale; under another locale, such as C, ls orders the names differently.

  3. Apply the upstream fixes that EPICS-env carries:

    make patch
    

    The output starts with the first two patches of EPICS base:

    
    Patching epics-base-src with the file : <clone>/patch/7.0.10-01-b2d2758-putnotify-type-check.p0.patch
    patching file modules/database/src/ioc/db/dbPutNotifyBlocker.cpp
    
    Patching epics-base-src with the file : <clone>/patch/7.0.10-pr0817-mbbi-cosv-aftc.p0.patch
    patching file modules/database/src/std/rec/mbbiRecord.c
    

    <clone> is the absolute path of your EPICS-env clone. Each Patching line names one patch file from the patch directory; the run applies 37 of them.

Configure, build, and install

  1. Write the site configuration of base and every module:

    make conf
    

    The command prints one empty line. It writes, among other files, RELEASE.local at the repository top, which tells every module where the installed base is:

    cat RELEASE.local
    

    The file holds two lines:

    EPICS_BASE:=<install_location>/1.4.0/debian-13/7.0.10/base
    SUPPORT=
    
  2. Build base and every module:

    make build
    

    This is the long step. Base installs into the tree as it builds, and each module installs when its build completes.

  3. Install the setup and reset scripts, the commonIocsh fragments, and the version file, and complete the base and module installs:

    make install
    
  4. Create the unversioned module links:

    make symlinks
    

    The environment script finds the pvxs tools through the link pvxs, so this step is required for the next stage.

  5. Look at the top level of the installed tree:

    LC_ALL=C make exist LEVEL=1
    

    The output is:

    <install_location>/1.4.0/debian-13/7.0.10
    |-- .versions
    |-- base
    |-- modules
    |-- resetEpicsEnv.bash
    |-- setEpicsEnv.bash
    `-- vendor
    
    4 directories, 3 files
    

    LC_ALL=C makes tree draw its lines with American Standard Code for Information Interchange (ASCII) characters, as shown above. Repeating installation also leaves backups of replaced setup and reset scripts; those files add entries to the listing.

Open a shell on the installed tree

  1. Source the environment script from the installed tree:

    source <install_location>/1.4.0/debian-13/7.0.10/setEpicsEnv.bash
    

    The script prints a summary that starts with these lines:

    
    Set the EPICS Environment as follows:
    THIS Source NAME    : setEpicsEnv.bash
    THIS Source PATH    : <install_location>/1.4.0/debian-13/7.0.10
    EPICS_BASE          : <install_location>/1.4.0/debian-13/7.0.10/base
    EPICS_HOST_ARCH     : linux-x86_64
    EPICS_MODULES       : <install_location>/1.4.0/debian-13/7.0.10/modules
    
  2. Find the IOC program that the lesson uses:

    command -v softIocPVX
    

    The output is:

    <install_location>/1.4.0/debian-13/7.0.10/modules/pvxs/bin/linux-x86_64/softIocPVX
    

    softIocPVX comes from the pvxs module. It serves its records over both CA and PVA; the softIoc program of base serves CA only.

  3. Keep CA and PVA traffic of this lesson on the loopback interface:

    export EPICS_CA_AUTO_ADDR_LIST=NO EPICS_CA_ADDR_LIST=127.0.0.1 EPICS_CAS_INTF_ADDR_LIST=127.0.0.1
    export EPICS_PVA_AUTO_ADDR_LIST=NO EPICS_PVA_ADDR_LIST=127.0.0.1 EPICS_PVAS_INTF_ADDR_LIST=127.0.0.1
    

    Without these settings, a client on a host with more than one network interface can receive replies from the same IOC over several addresses and print a warning for each one.

Start a soft IOC with one record

  1. Create a directory for the IOC and enter it:

    mkdir ../first-ioc
    cd ../first-ioc
    
  2. Create the file first.db with one analog output record:

    record(ao, "tutorial:value") {
        field(VAL, "42")
        field(PINI, "YES")
    }
    

    PINI processes the record once at start, so the PV holds 42 with a valid time stamp.

  3. Start the IOC with the database:

    softIocPVX -d first.db
    

    The IOC prints a banner and waits at its prompt:

    INFO: PVXS QSRV2 is loaded, permitted, and ENABLED.
    Starting iocInit
    ############################################################################
    ## EPICS R7.0.10-github.com/jeonghanlee/EPICS-env
    ## Rev. R7.0.10-dirty
    ## Rev. Date Git: 2025-12-15 17:11:22 -0600
    ############################################################################
    iocRun: All initialization complete
    7.0.10 >
    

    The banner names the EPICS-env site version. dirty marks the base source tree that make patch changed. The prompt 7.0.10 > waits for IOC shell commands. The output above is from a terminal; when the IOC output goes to a pipe, the same lines can appear in a different order.

  4. At the IOC prompt, list the records:

    dbl
    

    The IOC prints the name of its one record:

    tutorial:value
    

Read and write the PV from a second shell

  1. Open a second terminal in the working directory of the lesson and set up the same environment without the summary:

    source <install_location>/1.4.0/debian-13/7.0.10/setEpicsEnv.bash disable
    export EPICS_CA_AUTO_ADDR_LIST=NO EPICS_CA_ADDR_LIST=127.0.0.1 EPICS_CAS_INTF_ADDR_LIST=127.0.0.1
    export EPICS_PVA_AUTO_ADDR_LIST=NO EPICS_PVA_ADDR_LIST=127.0.0.1 EPICS_PVAS_INTF_ADDR_LIST=127.0.0.1
    
  2. Read the PV over CA:

    caget tutorial:value
    

    The output is:

    tutorial:value                 42
    
  3. Read the PV over PVA:

    pvget tutorial:value
    

    The output shows the time stamp and the value:

    tutorial:value 2026-09-26 23:16:46.167  42
    

    PVA also carries the time stamp that PINI set. Your output shows the time at which your IOC processed the record.

  4. Write the value 7 over CA:

    caput tutorial:value 7
    

    The output shows the value before and after the write:

    Old : tutorial:value                 42
    New : tutorial:value                 7
    
  5. Read the value again:

    caget tutorial:value
    

    The output is:

    tutorial:value                 7
    
  6. To stop the IOC, type exit at the IOC prompt in the first terminal.

You built an installed tree from source, set up a shell on it, and exchanged a value with a running IOC over both protocols.

Pages to read after this lesson

Build pipeline stages

EPICS-env turns a checkout into an installed Experimental Physics and Industrial Control System (EPICS) tree in six stages: init, patch, conf, build, install, and symlinks. Each stage is a make target at the repository top, and each one reads files that an earlier stage or the configuration wrote. Make targets by purpose lists every target each stage runs, and Build and install the environment runs the stages in order.

What each stage reads and writes

StageReadsWrites
initThe pins in configure/RELEASE and the generated configure/MODULESGEN.mkepics-base-src and one <name>-src source tree per module, checked out at the pin
patchThe .p0.patch files under patch/The cloned source trees
confThe configuration variables: install location, pins, and vendor library pathsSite files in epics-base-src/configure, RELEASE.local and CONFIG_SITE.local at the repository top, and site files under each module’s configure directory
buildThe patched source trees and the files conf wroteEPICS base and every module in the installed tree
installThe build products, scripts/setEpicsEnv.bash, scripts/resetEpicsEnv.bash, commonIocsh/iocsh/*.iocsh, and the EPICS-env commitsetEpicsEnv.bash, resetEpicsEnv.bash, .versions, and modules/commonIocsh/iocsh in the installed tree
symlinksThe versioned directories in the installed treeOne unversioned link per module under modules

init skips a source tree that already exists, so it never replaces a checkout that patch has changed. Upstream patch carry explains the patch files, and Module set and dependencies explains the module site files that conf writes. Installed tree and relocation describes the tree that build, install, and symlinks produce.

Why configuration precedes the build

The build target runs conf.base, build.base, conf.modules, and build.modules, in that order. Every build therefore reads site files written from the variables in effect for that make run.

The configuration decides where the build puts its output and how it links:

  • conf.base sets INSTALL_LOCATION for EPICS base to the base directory of the installed tree. It also selects linking with library search paths relative to $ORIGIN, the directory of the loaded file.
  • conf.modules points every module at the installed EPICS base and sets each module’s INSTALL_LOCATION to its versioned directory under modules.
  • The module configuration targets write the install directory of each dependency into the module’s RELEASE.local, so a module compiles against the dependencies already installed.

An EPICS build installs its products into INSTALL_LOCATION as part of the build. A module build therefore reads EPICS base, and every module it depends on, from the installed tree rather than from a source tree. Without the configuration, EPICS base and each module would install into their own source trees.

Most configuration targets rewrite their files from the first line, so a later make conf or make build replaces the earlier content. Some targets change files in other ways:

  • conf.base.site adds its two lines to the EPICS base file configure/os/CONFIG_SITE.linux-x86_64.linux-x86_64 only when each line is absent.
  • conf.calc, conf.lua, and conf.StreamDevice edit module files in place with sed, and conf.StreamDevice and conf.pmac remove module files.
  • The conf.gz.* targets append compression flags to files that other targets wrote.

On Ubuntu 26, each of the ten modules listed in Source configuration targets writes its C17 compiler flag during its own configuration. The Ubuntu 26 condition is defined before the automatic module rules are generated, so conf.iocStats and the custom configuration targets use the same condition.

Serial execution of every target

configure/RULES_VARS declares .NOTPARALLEL. GNU Make then runs the prerequisites of every target one at a time, in the order the target lists them, even when you pass -j.

Several aggregates depend on that order:

  • patch.revert lists the patch targets in the exact reverse order of patch.
  • github.check runs check.module-deps after conf and before build.

Parallel compilation happens inside a single build. build.base runs the EPICS base build with four parallel jobs; each module build runs make without a -j option.

Module builds in dependency order

build.modules has one build.<module> prerequisite per module, followed by install.modules. Each build.<module> target has the prerequisites that the <module>_DEPS variable in configure/CONFIG_MODS_DEPS lists, such as null.base build.sequencer build.sscan build.calc for asyn.

Make runs each build.<module> target once per make run. A module therefore starts to build only after every module it depends on has built and installed. The order among modules with no dependency relation between them carries no meaning.

After the last module builds, install.modules runs make install in every module source tree. Module set and dependencies describes the <module>_DEPS graph.

The build stage alone leaves the installed tree without its top-level files. install adds them:

  • install.base runs the EPICS base install and copies scripts/setEpicsEnv.bash and scripts/resetEpicsEnv.bash to the top of the installed tree with mode 0644, backing up existing files.
  • install.modules runs the install of every module.
  • install.commoniocsh copies the common iocsh fragments to modules/commonIocsh/iocsh.
  • src_version writes the time it runs and the EPICS-env commit to .versions and copies that file to the top of the installed tree.

symlinks creates an unversioned link, such as modules/asyn, for each module. On Linux it then deletes every dangling link under modules. setEpicsEnv.bash reaches the pvxs and pmac executables through these links.

Continuous integration aggregates

Two aggregates run the pipeline for continuous integration (CI):

AggregateRuns, in order
githubinit, patch, vars, conf, build, symlinks
github.checkinit, patch, vars, conf, check.module-deps, build, symlinks

vars prints the configuration variables into the job log. Neither aggregate runs install; a CI workflow that uses github.check runs make install after it.

github.check places check.module-deps after patch and conf because that audit reads the patched module sources and the RELEASE.local files that conf writes. The installed-tree checks check.deps and check.env need an installed tree, so neither aggregate runs them. Build and install verification gates explains each check, and Supported platforms and CI lists which workflow runs which aggregate.

Module set and dependencies

The module set is the list of Experimental Physics and Industrial Control System (EPICS) modules that EPICS-env clones, configures, builds, and installs beside EPICS base. The configuration files define it: configure/RELEASE names each module and its pin, configure/CONFIG_MODS sets where each module comes from, configure/CONFIG_MODS_TYPES declares each configuration type, and configure/CONFIG_MODS_DEPS records its build dependencies and optional configuration settings. Module pins and dependencies lists the values for every module, and Add or bump a module changes them.

Module declaration by RELEASE triples

Each module has a module key, an upper-case name such as ASYN or SNCSEQ. configure/RELEASE defines three variables per key:

VariableMeaningExample for ASYN
SRC_NAME_<MODULE_KEY>Repository name; also the source directory name without -srcasyn
SRC_TAG_<MODULE_KEY>Tag or commit to check outtags/R4-46
SRC_VER_<MODULE_KEY>Version in the install directory name4.46.0

The set holds every key whose SRC_NAME_<MODULE_KEY> variable a file defines; SRC_NAME_BASE names EPICS base and stays outside the set. A SRC_NAME_<MODULE_KEY> value from the environment or the make command line does not add a module. A RELEASE.local file can override a triple; Configuration variables and override files gives the read order.

From the triple, make derives three names for each module:

  • The source tree <name>-src at the repository top, such as asyn-src. recsync builds from recsync-src/client.
  • The install directory modules/<name>-<version> in the installed tree, such as modules/asyn-4.46.0.
  • The target suffix <name>, as in build.asyn and install.asyn.

Generated module variables in MODULESGEN.mk

configure/CONFIG_MODS derives module variables from the effective pins. Actions include configure/MODULESGEN.mk, a generated file Git ignores. Query-only invocations derive the same variables in memory without reading or writing that cache. For each module key, the derivation supplies three variables:

VariableValue
SRC_GITURL_<MODULE_KEY>The value of SRC_URL_EPICSMODULES, expanded when make writes the file, followed by /<name>, such as https://github.com/epics-modules/asyn
INSTALL_LOCATION_<MODULE_KEY>$(INSTALL_LOCATION_MODS)/<name>-<version>; $(INSTALL_LOCATION_MODS)/seq-<version> for the sequencer
SRC_PATH_<MODULE_KEY><name>-src

An action invocation generates the file when it is absent, or when configure/RELEASE or configure/CONFIG_SITE carries a later modification time. The file also records the effective module triples used to generate it. Make regenerates it when those values change, including after creating, editing, or removing configure/RELEASE.local or ../RELEASE.local. Make reads the regenerated file in the same run, so the install directories follow the effective pins. An unchanged action invocation preserves the cache. Query-only invocations use current settings even when the cache is absent or stale; they preserve its bytes and modification time.

make show.genmk displays the existing cache, including stale values. When the cache is absent, it prints the current generated configuration without creating a file. It also displays other existing configure/*.mk files.

make reconf.modules explicitly removes the generated files under configure/ and regenerates MODULESGEN.mk from the current settings.

Repository overrides in CONFIG_MODS

The generated repository address of every module points at https://github.com/epics-modules. After it derives or reads the module variables, configure/CONFIG_MODS replaces that address for the twelve modules hosted elsewhere:

Organization variableModules
SRC_URL_BASEpvxs
SRC_URL_CHANNELFINDERrecsync
SRC_URL_BRUNOSEIVAMretools
SRC_URL_PSIStreamDevice
SRC_URL_JEONGHANLEEsnmp, QPC, rgamv2
SRC_URL_MOTORmotorMotorSim
SRC_URL_PMACpmac
SRC_URL_MDpscdrv, linStat
SRC_URL_BERKELEYLABfeed-core

A module outside epics-modules therefore needs both its triple in configure/RELEASE and one override line in configure/CONFIG_MODS.

Build order from declared dependencies

configure/CONFIG_MODS_DEPS defines one <module>_DEPS variable per module. Its value is null.base, an empty target that stands for EPICS base, followed by one build.<module> entry for each module that must build first:

asyn_DEPS:=null.base build.sequencer build.sscan build.calc

Make turns each <module>_DEPS value into the prerequisites of build.<module>, which gives the dependency order of the build stage. The Depends on column of Module repositories and pins shows the same graph.

<module>_DEPS is also the declared dependency list that check.module-deps compares with the references it finds in each module’s source tree. Build and install verification gates describes that comparison.

Configuration types auto and custom

configure/CONFIG_MODS_TYPES declares each module’s <module>_CONF_TYPE, with the value auto or custom. The type decides where its conf.<module> target comes from.

An auto module gets a generated conf.<module> target. That target writes one line to the module’s configure/CONFIG_SITE.local: INSTALL_LOCATION set to the module’s install directory. Three optional variables extend it:

VariableEffectModule that sets it
<module>_CONF_RELEASE_LINESWrites the value to configure/RELEASE.localiocStats
<module>_CONF_SITE_LINESAppends the value to configure/CONFIG_SITE.localretools
<module>_CONF_PLATFORMRuns the target only when uname -s prints this valueMCoreUtils

The auto modules are MCoreUtils, autosave, caPutLog, ether_ip, iocStats, pcas, pscdrv, retools, and snmp. A custom module has a hand-written conf.<module> target in configure/RULES_MODS_CONFIG, which can write any site setting the module needs.

Make checks the declarations each time it reads the makefiles, before it runs any target:

  • Every module in the set must declare <module>_CONF_TYPE, and the value must be auto or custom.
  • Every auto module must have a source path, and exactly one install directory name in the module set must start with <module>-.

A failed check stops every make command, including distclean.modulesgen, with a message such as Missing foo_CONF_TYPE declaration. The message names the recovery for a stale generated file: remove configure/MODULESGEN.mk with rm and run make again.

Dependency paths through RELEASE.local

A module learns where EPICS base and its dependencies are installed from RELEASE.local files that the configuration writes.

conf.release.modules writes two files at the repository top, one directory above every module source tree:

  • RELEASE.local sets EPICS_BASE to the installed base directory and clears SUPPORT.
  • CONFIG_SITE.local sets CHECK_RELEASE = NO and adds -Wl,--enable-new-dtags to PROD_LDFLAGS.

A module whose configure/RELEASE reads $(TOP)/../RELEASE.local, and whose configure/CONFIG_SITE reads $(TOP)/../CONFIG_SITE.local, picks up both files; asyn and autosave read them this way.

Most custom targets for modules with dependencies write the install directory of each dependency into the module’s own configure/RELEASE.local. conf.asyn, for example, writes SNCSEQ, SSCAN, and CALC, each set to the absolute path of a versioned directory, such as /home/user/epics/1.4.0/debian-13/7.0.10/modules/seq-2.2.9. Two targets write other files: conf.motorMotorSim writes the module’s configure/RELEASE and configure/CONFIG_SITE, and conf.pmac writes its configure/CONFIG_SITE and configure/RELEASE.local.

The dependencies a custom target writes form a list separate from <module>_DEPS. check.module-deps reads the written RELEASE.local as evidence and reports where the two lists disagree.

These generated dependency paths name versioned directories, so a module links against the dependency version the module set pins.

QPC inherits ASYN=$(EPICS_BASE)/../modules/asyn from its own configure/RELEASE; conf.QPC writes only CONFIG_SITE.local. Its active digitelQpcApp tree installs data and IOC fragments and declares no library or executable. The installed gamma-pctrl.iocsh fragment uses the consuming IOC’s ASYN macro to locate asynRecord.db at startup. That runtime macro is separate from QPC’s inherited, unversioned build setting.

Sequencer installed as seq

The sequencer module carries several names:

UseName
Module keySNCSEQ
Repository and source treesequencer, sequencer-src
Build and install targetsbuild.sequencer, install.sequencer
Configuration targetconf.sncseq
Install directory and unversioned linkmodules/seq-<version>, modules/seq

configure/CONFIG_VARS maps sequencer to seq for the install directory and the link. The mapping ignores a SRC_NAME_SNCSEQ value from the environment or the command line. The dependency audit treats SNCSEQ, seq, and pv as references to sequencer.

MCoreUtils on Linux only

configure/RELEASE defines the MCOREUTILS triple only when uname -s prints Linux. On any other system, MCoreUtils is absent from the module set, so make generates no clone, build, or install target for it. Its auto configuration target also carries Linux as its platform.

Installed tree and relocation

The installed tree is the directory that holds one built Experimental Physics and Industrial Control System (EPICS) base, every module in the module set, and the files that set up a shell for them. Its binaries find their shared libraries by position relative to themselves, so the whole directory works after you move or copy it. Choose the install location and release sets where the tree goes, and Set up a shell with the environment uses it.

Directory layout by release, system, and base version

EPICS-env installs each tree at this path:

$(INSTALL_LOCATION)/$(ENV_RELEASE_VERS)/<os_id>-<os_version>/$(SRC_VER_BASE)

<os_id> and <os_version> are the ID and VERSION_ID values from /etc/os-release. The make variable INSTALL_LOCATION_EPICS holds the whole path, such as /home/user/epics/1.4.0/debian-13/7.0.10, so trees for several releases, operating systems, and EPICS base versions can share one INSTALL_LOCATION. Install location and release lists the variables.

One tree has this layout, shown for two of its modules:

<install_location_epics>/
|-- setEpicsEnv.bash
|-- resetEpicsEnv.bash
|-- .versions
|-- base/
|   |-- bin/linux-x86_64/
|   `-- lib/linux-x86_64/
|-- modules/
|   |-- asyn-4.46.0/
|   |-- asyn -> ./asyn-4.46.0
|   |-- seq-2.2.9/
|   |-- seq -> ./seq-2.2.9
|   `-- commonIocsh/
|       `-- iocsh/
`-- vendor/
    `-- lib/

Each module installs into modules/<name>-<version>, where <version> is the SRC_VER_<MODULE_KEY> value of the module, such as asyn-4.46.0 or calc-4217e83. The sequencer installs as seq-<version>.

make symlinks adds an unversioned link for each module, such as modules/asyn, that points to the versioned directory by a relative path. The link gives a path that stays the same when a pin changes.

The build itself uses only versioned directories. Every module RELEASE.local and every library search path in a binary names a versioned directory, so removing or replacing the links changes no binary. setEpicsEnv.bash uses the pvxs and pmac links to put their executables on PATH.

Environment script and version record

install.base copies scripts/setEpicsEnv.bash and scripts/resetEpicsEnv.bash to the top of the tree with mode 0644. It backs up files it replaces. You source setup in a Bash shell. The script finds the tree from its own location, and follows a symbolic link to the script when there is one. It then sets the shell environment in four ways:

  • It sets EPICS_PATH to the tree, EPICS_BASE to its base directory, and EPICS_MODULES to its modules directory.
  • It sets EPICS_HOST_ARCH from the EpicsHostArch.pl script of EPICS base, which perl runs, or else from base/startup/EpicsHostArch, which sh runs. When perl or all three scripts are absent, it uses its own fallback architecture argument. Its interface is [<fallback_arch>] [disable]; disable controls only the summary.
  • It adds base/bin/<arch>, modules/pvxs/bin/<arch>, and modules/pmac/bin/<arch> to the front of PATH.
  • It adds base/lib/<arch> to the front of LD_LIBRARY_PATH.

When EPICS_BASE is already set, the script first removes the entries of the earlier tree from PATH and LD_LIBRARY_PATH after resolving the selected tree and architecture. Complete-field comparison preserves unrelated entries and empty fields; repeated setup adds managed directories once. Directly sourcing another tree’s setup selects that tree. Resolution failure preserves the existing managed environment.

Reset removes the known executable and base library entries and unsets EPICS_PATH, EPICS_BASE, EPICS_MODULES, EPICS_HOST_ARCH, and legacy EPICS_EXTENSIONS, including when base is absent. Both scripts preserve independently configured CA settings and operate with Bash nounset enabled.

src_version writes .versions at the top of the tree. It records when the install ran and which EPICS-env commit it used. The .versions file of one tree reads:

Timestamps : 20260926-225114/YYYYMMDD-HHMMSS
git version :4601e4049ac3f846806ec59a7cf462f4f9459393

Common iocsh fragment directory

install.commoniocsh copies commonIocsh/iocsh/*.iocsh from the repository to modules/commonIocsh/iocsh. That directory has no version in its name and no unversioned link. An input/output controller (IOC) sets IOCSH_TOP to its parent, modules/commonIocsh, and loads each fragment as $(IOCSH_TOP)/iocsh/<fragment>.iocsh. Common iocsh fragments explains the fragments, and Load common iocsh fragments in an IOC uses them.

Vendor library directory

vendor/ holds third-party libraries that two modules link: uldaq for measComp and open62541 for opcua. No EPICS-env make target creates it. The continuous integration (CI) workflows and tools/prep-vendors.bash install both libraries there, and point VENDOR_ULDAQ_PATH and OPEN62541_PATH at it in configure/RELEASE.local. With the default value /usr/local of both variables, the modules link the libraries from that system location instead. Vendor library locations lists the two variables.

vendor/ lies inside the tree, so make uninstall removes it with the rest of the tree.

Library search paths relative to ORIGIN

A shared library or executable in the Executable and Linkable Format (ELF) carries a list of directories that the dynamic loader searches for the libraries it needs. EPICS-env makes every entry for a library inside the tree relative to $ORIGIN, which the loader replaces with the directory of the file being loaded.

Two EPICS base build variables produce the relative entries:

  • LINKER_USE_RPATH is ORIGIN. conf.base.site writes it to the EPICS base site file, and every build receives it on the make command line.
  • LINKER_ORIGIN_ROOT names the root of the relocatable tree. Every EPICS base and module build receives it on the make command line, set to INSTALL_LOCATION_EPICS, the top of the tree.

EPICS base records a library directory under that root as a path relative to $ORIGIN, and a directory outside the root as an absolute path.

The linker option -Wl,--enable-new-dtags stores the list as a RUNPATH entry, the run-time library search path, rather than an RPATH entry. check.deps fails on any RPATH entry, and the configuration passes the option to every kind of link:

Where the configuration sets itVariableLinks
EPICS base configure/CONFIG_SITE.localPROD_LDFLAGS_DEFAULTExecutables of EPICS base and of every module, because EPICS base installs this file and its configure/CONFIG_SITE reads it
EPICS base configure/os/CONFIG_SITE.linux-x86_64.linux-x86_64SHRLIB_LDFLAGS, LOADABLE_SHRLIB_LDFLAGSShared libraries of EPICS base and of every module, because EPICS base installs this file
CONFIG_SITE.local at the repository topPROD_LDFLAGSExecutables of the modules that read this file

From the top of an installed tree, this command prints the libraries that the asyn library needs and its search path:

readelf -d modules/asyn-4.46.0/lib/linux-x86_64/libasyn.so | grep -E 'NEEDED|RUNPATH'

The output is:

 0x0000000000000001 (NEEDED)             Shared library: [libdbCore.so.3.25.0]
 0x0000000000000001 (NEEDED)             Shared library: [libCom.so.3.25.0]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000001d (RUNPATH)            Library runpath: [$ORIGIN/../../../../base/lib/linux-x86_64:$ORIGIN/.]

The first entry leads from modules/asyn-4.46.0/lib/linux-x86_64 up four levels to the top of the tree and down to the EPICS base libraries. No entry names the directory the tree was built in.

Why the tree can be moved

Two properties make the tree independent of the directory it was built in:

  • Every library search path to a library inside the tree is relative to $ORIGIN, so the loader finds EPICS base, module, and vendor libraries at the same relative positions after a move.
  • setEpicsEnv.bash computes every path it sets from its own location.

check.deps guards the first property. It fails when an installed binary carries an RPATH entry or an absolute path outside the system library directories. It also fails on a shared library that needs a library of the tree but has no $ORIGIN entry. Build and install verification gates describes it.

A search path can also name a system library directory, such as /usr/lib/x86_64-linux-gnu in the pmac and pyDevSup libraries. Such a path does not depend on where the tree lies.

Relocation covers the loader and the environment script, not every text file. These installed files keep the absolute path of the original tree:

  • The EPICS base configure/CONFIG_SITE.local.
  • The configure/RELEASE.local file that 16 of the installed modules carry, except the one of iocStats, which sets only MAKE_TEST_IOC_APP=NO.
  • The S99caRepeater, S99logServer, and caRepeater.service files in base/bin/linux-x86_64.
  • The epics-base.pc and epics-base-linux-x86_64.pc files in base/lib/pkgconfig.
  • The libuldaq.la and pkgconfig/open62541.pc files under vendor/lib.

The tree path names one operating system and version, and the tree links the system libraries of that system. A moved tree therefore belongs on a host that runs the same operating system and version.

make uninstall removes the tree at the path that the current configuration computes. Uninstall and clean covers removal.

Build and install verification gates

EPICS-env checks the Experimental Physics and Industrial Control System (EPICS) build at two points: before compilation, it compares module dependencies against the sources, and after installation, it inspects the installed tree. Each check is a make target in two forms. The report form prints findings and exits 0 on them, and the strict form exits with a failure code on a finding. Both forms exit with a failure code on a usage error or a missing tool. The strict form is the gate. Run the verification gates runs them, and Verification and inspection targets lists the targets.

Gates and the stage each one guards

GateReport formStageWhat it readsScript
check.module-depsaudit.module-depsAfter conf, before buildModule source treestools/audit_module_deps.bash
check.depsaudit.depsAfter installInstalled binaries and shared librariestools/check_deps.bash
check.envaudit.envAfter installInstalled setEpicsEnv.bashtools/check_env.bash

check.module-deps is static: it reads text and compiles nothing. check.deps and check.env read the installed tree, so they belong after make install. Neither aggregate github nor github.check runs them.

Static module dependency audit

check.module-deps compares, for each module, the dependencies the module declares with the dependencies its source tree shows. The declared list is the <module>_DEPS variable in configure/CONFIG_MODS_DEPS. Module set and dependencies describes that variable.

The audit reads these files in each module source tree and records every reference to another module:

SourceReferenceStrength
configure/RELEASE.local, which conf writesA macro that names a module, such as ASYNRequired
Makefile filesA database definition (.dbd) file in a DBD lineRequired
Makefile filesA library in a _LIBS lineProbable
Database definition, database, and protocol filesA referenced .dbd, database, or .proto fileRequired
Database filesA record typeProbable
Startup scripts (st.cmd, *.cmd, *.iocsh)A file that dbLoadRecords, dbLoadTemplate, or dbLoadDatabase loadsRequired
C and C++ sources and headersAn included headerProbable

The location of a file can weaken a reference to optional. A reference under a test, example, demonstration, or iocBoot directory is optional, and so is one inside a conditional block of a Makefile. Documentation directories are ignored. Build output directories are ignored too, except under a test directory, where their files count as optional. A reference to a known external library, such as ftdi, appears as external and never fails. References under os/Linux, os/posix, and os/default stay required only when the PLATFORM variable is Linux, the default on a Linux host. Only required references can produce a failing finding; probable and optional references appear in the report.

The audit reports three kinds of finding:

FindingMeaningFails the strict form
undeclared-observedA required reference names a module that <module>_DEPS does not listYes
unknownA required reference matches no module, EPICS base library, or known external libraryYes
declared-unobserved<module>_DEPS lists a module that no required reference namesNo

The audit maps several spellings to one module, such as SNCSEQ, seq, and pv to sequencer. configure/CONFIG_MODS_AUDIT holds the spellings, the EPICS base libraries and record types, and the external libraries such as ssl and z.

Because the audit reads patched sources and the RELEASE.local files that conf writes, github.check runs it after patch and conf. The feed-core and QPC patches remove source references to modules those builds do not use; the strict audit passes for both modules with or without those patches.

Installed runpath scan

check.deps inspects the Executable and Linkable Format (ELF) files of the installed tree with readelf -d. It scans the executables under base/bin/linux-x86_64 and modules/*/bin/linux-x86_64, and the shared libraries under base/lib/linux-x86_64, each module’s lib/linux-x86_64, and vendor/lib.

Executable paths are resolved to canonical paths before analysis. A versioned module directory and its unversioned link therefore contribute each executable only once to the scan and its counts.

It counts three defects:

  • An RPATH entry in any scanned file. A RUNPATH entry, the run-time library search path, belongs there instead.
  • An absolute directory in a library search path, other than a system library directory such as /usr/lib or /usr/lib/x86_64-linux-gnu.
  • A shared library whose search path lacks $ORIGIN but that needs a library found in the tree. The scan calls this a lost runpath.

A system library directory in a search path prints a note and is not a defect. Installed tree and relocation explains why these defects break relocation.

An empty installed-tree path, a path that is not a directory, or a failed make lookup of INSTALL_LOCATION_EPICS exits 2 before scanning. The diagnostic directs the caller to set INSTALL_LOCATION_EPICS or pass a valid <installed_tree>. These input errors also fail with --report-only. Run the check after make install; an existing directory alone does not prove that every required component has been installed.

Environment script library path check

check.env sources the installed setEpicsEnv.bash in a child Bash shell started with an empty environment and a PATH of /usr/bin:/bin. It then reads the LD_LIBRARY_PATH that the script produced. The empty environment keeps the caller’s own LD_LIBRARY_PATH, PATH, and EPICS_* variables out of the result.

The check reports each LD_LIBRARY_PATH entry that contains a pvxs/bundle path component. The build links the system libevent library, so that bundle directory never exists, and the dynamic loader skips a missing directory without an error. The check compares normalized entries, so doubled slashes and . segments do not hide such an entry.

Report and strict forms and their exit codes

TargetExit 0Exit 1Exit 2Exit 3
audit.module-depsAudit ranInvalid argument or no matching moduleNot usedNot used
check.module-depsNo failing findingInvalid argument or no matching moduleOne or more failing findingsNot used
audit.depsScan ranInvalid optionInvalid installed-tree input, unresolved executable path, or readelf not foundNot used
check.depsNo defectInvalid optionA defect, invalid installed-tree input, unresolved executable path, or readelf not foundNot used
audit.envCheck ran or skippedInvalid optionNot usedNot used
check.envNo findingInvalid optionOne or more findingsNo inspectable environment

check.env treats three states as no inspectable environment: no installed setEpicsEnv.bash, an empty EPICS_MODULES or EPICS_HOST_ARCH after the script runs, and an empty LD_LIBRARY_PATH. audit.env prints SKIP for the same states and exits 0.

When a script fails under a make target, make reports the script’s code in its error message, such as Error 3, and exits 2 itself.

Where continuous integration runs each gate

Every continuous integration (CI) workflow for an operating system ends with make exist, make check.env, and make check.deps, after make install. The workflows for Debian 12, Debian 13, and Rocky Linux 8 build through make github.check, so they also run check.module-deps before the build. The workflows for Rocky Linux 10, Ubuntu 24.04, and Ubuntu 26.04 run the stages one by one, with make check.module-deps immediately before make build, after patching and configuration. An audit failure stops the installation step before compilation. Supported platforms and CI lists the workflows.

Upstream patch carry

EPICS-env builds Experimental Physics and Industrial Control System (EPICS) base and each module from a pinned upstream tag or commit. When a pinned source needs a fix that the pin does not contain, EPICS-env carries the fix as a patch file under patch/ and applies it to the cloned source during the patch stage. The pin in configure/RELEASE stays unchanged; a carried patch and a version bump are separate changes. Carry an upstream fix as a patch adds a patch, Verify a fix against an installed tree tests one, and Upstream patch targets lists the targets.

Patch file families and names

The file name decides which target applies a patch and to which source tree:

FamilyFile nameApply targetSource tree
EPICS base carry, merged pull request<base_version>-pr<NNNN>-<slug>.p0.patchpatch.base.pr.applyepics-base-src
EPICS base carry, direct commit<base_version>-<NN>-<sha7>-<slug>.p0.patchpatch.base.pr.applyepics-base-src
pvxs carry<pvxs_version>-<NN>-<sha7>-<slug>.p0.patchpatch.pvxs.commit.applypvxs-src
EPICS base site patch<base_version>.base.p0.patchpatch.base.applyepics-base-src
Fixed module patch<module>-<slug>.p0.patchpatch.<name>.applyOne module

The placeholders in the names mean:

  • <base_version> and <pvxs_version> are the values of SRC_VER_BASE and SRC_VER_PVXS, such as 7.0.10 and 1.5.2.
  • <NNNN> is the number of the upstream pull request (PR) that merged the fix, written with four digits.
  • <NN> is a two-digit sequence number, and <sha7> is the first seven characters of the upstream commit.
  • <slug> is a short description of the fix.

For the pins in configure/RELEASE, patch/ holds 18 EPICS base carry patches for 7.0.10 and 12 pvxs carry patches for 1.5.2. No 7.0.10.base.p0.patch exists, so patch.base.apply does nothing.

Version-anchored names that drop on a bump

The carry targets select their files with a pattern that starts with the pinned version:

TargetPattern
patch.base.pr.applypatch/$(SRC_VER_BASE)-*.p0.patch
patch.pvxs.commit.applypatch/$(SRC_VER_PVXS)-*.p0.patch

When the pin of EPICS base or pvxs moves to another version, its pattern matches no file, and the whole carry set for that source stops applying. Every carried fix then needs a review against the release that the pin names, and a carry patch never reaches a version it was not written for.

The hyphen after the version keeps the carry pattern apart from the site patch <base_version>.base.p0.patch. The pvxs pattern 1.5.2-* also never matches the file pvxs-1.3.1.p0.patch.

Sorted apply and exact reverse revert

A carry target applies its files in the sorted order of their names, and the matching revert target removes them in the exact reverse order. Patches that touch the same file then apply and revert against the source state each one expects.

Revert targets classify each whole patch with noninteractive dry-runs. They skip confirmed unapplied patches and reverse confirmed applied patches. Conflicts, partial application, required-input absence, and unresolved states stop the invocation; earlier completed reversals remain in effect.

Later carry patches can depend on context from earlier patches. Classification can prepare that context in a private copy of real source files, preserving the original tree. For supported single-hunk static C function patches, it also checks the named function to distinguish similar code in other functions. Private hunk search positions follow that function when independent source edits move it. Patch contents must match inside the function; an absent or duplicate function leaves the state unresolved.

Sorting compares names as text. Within the EPICS base set, the two-digit commit form sorts before the pull request form, because a digit sorts before p. The EPICS base set for 7.0.10 therefore applies 7.0.10-01-b2d2758-putnotify-type-check.p0.patch first, followed by the pull request patches in ascending number.

Each file goes through patch -d <source_tree> --ignore-whitespace -p0. The carry targets stop at the first file that fails, so a later success cannot hide an earlier failure. The pvxs targets also pass --no-backup-if-mismatch, so a patch that applies at an offset leaves no .orig backup file in the source tree. All revert targets suppress mismatch backups and preserve existing backups.

The patch aggregate applies every family in a fixed order, and patch.revert lists the same targets in the exact reverse order:

  1. patch.base.apply
  2. patch.base.pr.apply
  3. patch.mca.apply
  4. patch.measComp.apply
  5. patch.measComp.tc32.apply
  6. patch.opcua.apply
  7. patch.opcua.export.apply
  8. patch.feed-core.apply
  9. patch.QPC.apply
  10. patch.pvxs.commit.apply
  11. patch.StreamDevice.apply

Fixed per-module patches

A fixed patch has no version in its name, and one dedicated target pair applies and reverts it. It keeps applying after the module pin changes, until it fails to apply to the changed source and make patch stops.

FileTargetChange
measComp-CONFIG_MEASCOMP.p0.patchpatch.measCompInstalls cfg/CONFIG_MEASCOMP, so a module that names MEASCOMP inherits ULDAQ_DIR
measComp-tc32-chan-count.p0.patchpatch.measComp.tc32Halves the reported TC-32 thermocouple channel count when no expansion unit is present
opcua-CONFIG_OPCUA.p0.patchpatch.opcuaDerives the open62541 library and include directories from OPEN62541 in the installed CONFIG_OPCUA
opcua-anon-ns-export.p0.patchpatch.opcua.exportMoves exported registration blocks out of unnamed namespaces, which the GNU Compiler Collection (GCC) 15 needs to link them
feed-core-libonly.p0.patchpatch.feed-coreTrims the build to the library and drops the busy, asyn, and autosave references of the unused bundled application
QPC-dataonly.p0.patchpatch.QPCRemoves module references from the unbuilt example application Makefile
StreamDevice-no-vxi11.p0.patchpatch.StreamDeviceRemoves the vxi11 driver registration, which asyn builds only with DRV_VXI11=YES
mca-libnet.p0.patchpatch.mcaActs only on macOS; the target does nothing on Linux

The Target column names the prefix of the .apply, .revert, and .make targets.

The feed-core and QPC patches remove module references from Makefile files that the module dependency audit check.module-deps reads. On the current pins, the strict audit passes in all four combinations: both patches applied, only feed-core applied, only QPC applied, and neither applied. These patches define the selected build content; passing the audit does not require them. Build and install verification gates describes that audit.

Patch file format

Every patch file is a unified diff with no path prefix, the form that git diff --no-prefix writes. Paths in the file start at the top of the source tree, such as modules/database/src/std/rec/mbbiRecord.c for EPICS base, and the -p0 option applies them from there. The .p0.patch suffix marks this format.

The .make targets write a patch from the current changes in a source tree:

  • patch.<name>.make writes the fixed patch of one module from the current changes in its source tree, limited for most modules to the files that patch covers.
  • patch.base.make writes every change in epics-base-src to <base_version>.base.p0.patch.

No make target writes the EPICS base or pvxs carry files.

Inactive patch files

Some files under patch/ match no active target on a Linux build of the pinned versions:

FileWhy it does not apply
3.15.5.base.p0.patch, 7.0.5.base.p0.patch, 7.0.7.base.p0.patchTheir version differs from SRC_VER_BASE
pvxs-1.3.1.p0.patchNo active target names it, and the pvxs carry pattern does not match it
mca-libnet.p0.patchIts targets act only on macOS

Common iocsh fragments

The common iocsh fragments are shell script files that set up site services in an Experimental Physics and Industrial Control System (EPICS) input/output controller (IOC). Each fragment takes its settings as macros, so every IOC loads the same file with its own values. Load common iocsh fragments in an IOC uses them, Common iocsh fragment macros lists every macro, and Run the fragment verification suite checks them against running IOCs.

Fragment files and their services

The repository holds the fragments in commonIocsh/iocsh. Each service has one fragment, except linStat and serial setup, which have more than one:

FragmentServiceModule the IOC links
iocLog.iocshSends the IOC error log to a log serverEPICS base
caPutLog.iocshSends a record of each Channel Access (CA) put to a log servercaPutLog
autosave.iocshSaves record values and settings, and restores them at the next startautosave
reccaster.iocshPublishes the IOC’s record names to a recceiver servicerecsync
iocStatsAdmin.iocshLoads the IOC status records of the iocStats moduleiocStats
linStat.iocshLoads Linux host and process statistics through the four linStat*.iocsh sub-fragmentslinStat
serial.iocshLoads a serial configuration file that the IOC ownsnone
setSerialParams.iocshSets the baud rate, data bits, stop bits, and parity of one asyn serial portasyn

Installed location and the IOCSH_TOP macro

make install runs install.commoniocsh, which copies every commonIocsh/iocsh/*.iocsh file to modules/commonIocsh/iocsh in the installed tree. The directory name carries no version, so one installed tree holds one fragment set. Installed tree and relocation shows where the directory sits in the tree.

An IOC names modules/commonIocsh, the directory that holds iocsh, in the macro IOCSH_TOP and loads a fragment with iocshLoad:

iocshLoad("$(IOCSH_TOP)/iocsh/<fragment>.iocsh", "<macros>")

<fragment> is the file name without .iocsh, and <macros> is a comma-separated list of NAME=value pairs. Neither the IOC build nor envPaths sets IOCSH_TOP; the startup script sets it with epicsEnvSet, or the IOC inherits it from the process environment.

linStat.iocsh relies on this form, because it loads its sub-fragments from $(IOCSH_TOP)/iocsh:

iocshLoad("$(IOCSH_TOP)/iocsh/linStatHost.iocsh", "IOC=$(IOC)")
iocshLoad("$(IOCSH_TOP)/iocsh/linStatProc.iocsh", "IOC=$(IOC)")

How a fragment receives its values

A fragment reads three kinds of values:

  • Module top macros. autosave.iocsh, reccaster.iocsh, iocStatsAdmin.iocsh, and the linStat fragments read the install directory of their module from AUTOSAVE, RECCASTER, devIocStats, and LINSTAT. The IOC build writes iocBoot/<ioc_name>/envPaths with one epicsEnvSet line for each configure/RELEASE macro that names an existing directory. The startup script reads it with < envPaths, so each module macro in configure/RELEASE must use exactly these names.
  • The IOC macro. Most fragments build record names and paths from IOC: record prefixes such as $(IOC):, the autosave directory $(AS_TOP)/$(IOC), and the log prefix proc=$(IOC). envPaths sets IOC to the name of the iocBoot/<ioc_name> subdirectory, and the startup script passes it on as IOC=$(IOC).
  • Service settings. Every other macro has a default written as $(NAME=default) in the fragment, or is required and has none.

linStat.iocsh and serial.iocsh have optional parts that an empty macro turns on. The macros NICENABLE, FSENABLE, and SERIAL_ENABLE default to #--, which turns the line that starts with them into an iocsh comment. Passing the macro with an empty value, such as NICENABLE=, leaves the line as a command:

$(NICENABLE=#--) iocshLoad("$(IOCSH_TOP)/iocsh/linStatNIC.iocsh", "IOC=$(IOC),NIC=$(NIC=)")

Load order in the startup script

The fragments depend on the IOC state at the point where the startup script loads them:

  • The fragments load after dbLoadDatabase and the application’s registerRecordDeviceDriver call. Most of them use commands and variables that the module database definition (DBD) files register, such as asynSetOption and reccastTimeout.
  • Every fragment loads before iocInit. dbLoadRecords must run before iocInit, autosave restores its first pass during iocInit, and afterIocRunning accepts commands only before iocInit.
  • iocLog.iocsh loads before any device setup. Its iocLogInit call registers the error log listener, and a message printed before that call does not reach the log server.
  • serial.iocsh loads after drvAsynSerialPortConfigure creates each port that the serial configuration file names.
  • caPutLog.iocsh needs an access security configuration file (ACF) that marks write access with TRAPWRITE. caPutLog receives puts only from that trap, and the IOC loads its own file with asSetFilename before iocInit. The fragment registers caPutLogInit with afterIocRunning, so the logger starts after iocInit completes.
  • autosave.iocsh creates its directories with the iocsh system command, which exists only when the IOC includes system.dbd from EPICS base.

This ACF gives every client read access and traps every write:

ASG(DEFAULT) {
    RULE(1, READ)
    RULE(1, WRITE, TRAPWRITE)
}

linStat entry fragment and sub-fragments

linStat.iocsh is the entry point for the linStat service. It always loads linStatHost.iocsh and linStatProc.iocsh, and loads one linStatNIC.iocsh and one linStatFS.iocsh when their enable macros are empty. Each sub-fragment loads one database of the linStat module:

Sub-fragmentStatisticsRecord names
linStatHost.iocshHost memory, processor, uptime, hardware sensors, and interrupts$(IOC):SYS_*, $(IOC):MEM_*, $(IOC):NET:HOST:*, and others under $(IOC):
linStatProc.iocshMemory, file descriptors, threads, CA clients, and records of this IOC processUnder $(IOC):
linStatNIC.iocshOne network interface$(IOC):NET:$(NIC):*
linStatFS.iocshOne mounted filesystem$(IOC):$(FSID):*

To monitor more than one network interface or filesystem, the startup script loads linStatNIC.iocsh or linStatFS.iocsh once more for each one, with a distinct NIC or FSID.

iocStatsAdmin and linStat record name collisions

The database that iocStatsAdmin.iocsh loads, iocAdminSoft.db, shares record names under $(IOC): with both linStat host and process databases. linStatHost.db, which linStatHost.iocsh loads, defines these names with a different record type:

  • $(IOC):CPU_CNT
  • $(IOC):MEM_FREE
  • $(IOC):MEM_MAX
  • $(IOC):MEM_USED
  • $(IOC):SYS_CPU_LOAD

linStatProc.db, which linStatProc.iocsh loads, defines more names with a different record type, such as $(IOC):FD_CNT, $(IOC):IOC_CPU_LOAD, and $(IOC):SYSRESET.

When both services load with the same IOC value and iocStatsAdmin.iocsh loads first, dbLoadRecords reports already exists for each name with a different record type. It then reports Failed to load for linStatHost.db and linStatProc.db. When linStat.iocsh loads first, the IOC reports the same already exists errors and then crashes while it loads iocAdminSoft.db. A name that both databases define with the same record type merges into one record without an error, such as $(IOC):HOSTNAME, $(IOC):KERNEL_VERS, and $(IOC):TOD of linStatHost.db. An IOC therefore loads either linStat or iocStatsAdmin under one prefix. The integrated check of the verification suite loads linStat and leaves out iocStatsAdmin.

IOC can hold at most 21 characters when iocStatsAdmin.iocsh loads. The fragment checks no length; the longest record name of iocAdminSoft.db adds 39 characters to $(IOC), and an EPICS record name holds at most 60 characters. With a longer IOC, dbLoadRecords reports Failed to load for iocAdminSoft.db.

Choose the install location and release

Set the install location before running an action in an EPICS-env clone. Actions try to create it; query-only invocations inspect settings without creating directories or generated files. The default is ${HOME}/epics.

Prerequisites

  • A clone of EPICS-env. Run every command from the repository top.
  • An absolute directory path that your user can create or write to.
  • python3 on PATH; make stops while reading its configuration when it is missing.
  1. In configure/CONFIG_SITE.local, set the install location:

    echo "INSTALL_LOCATION=<install_location>" > configure/CONFIG_SITE.local
    

    <install_location> is the absolute path of the directory that holds every installed tree, such as /opt/epics.

    The file is untracked, so the setting survives a git pull. A CONFIG_SITE.local in the directory that contains the repository applies to every clone in that directory; configure/CONFIG_SITE.local is read after it and wins.

  2. Optional: to install under a release number other than the default 1.4.0, add ENV_RELEASE_VERS to the same file:

    echo "ENV_RELEASE_VERS=<release>" >> configure/CONFIG_SITE.local
    

    <release> names the first directory level below <install_location>. Trees with different release numbers sit side by side under <install_location>.

  3. To see where the installed tree goes, print its path:

    make print-INSTALL_LOCATION_EPICS
    

    With the default release, the output is:

    <install_location>/1.4.0/debian-13/7.0.10
    

    With <release> set to 1.4.0-site, the output is:

    <install_location>/1.4.0-site/debian-13/7.0.10
    

    The path is <install_location>/<release>/<os_id>-<os_version>/<base_version>. <os_id> and <os_version> are the ID and VERSION_ID values from /etc/os-release, and <base_version> is the version of Experimental Physics and Industrial Control System (EPICS) base pinned in configure/RELEASE. The output above is from a Debian 13 host.

  4. To check whether the build uses sudo, print SUDO_INFO:

    make print-SUDO_INFO
    

    For a location your user can create, the output is:

    0
    

    A query reports 0 when <install_location> is an existing directory whose parent path you can traverse, even if you cannot access the final directory. For an absent path, it checks search and create access at the nearest existing ancestor. It reports 1 for a non-directory or blocked path. This inspection cannot predict quota, read-only mount, or concurrent changes.

    Actions run the actual directory-creation probe. When that probe returns 1, make runs the module build and install steps, the module links, and the commonIocsh install through sudo. The EPICS base build and install and the .versions install never use sudo, so a complete install needs a <install_location> that your user can write. To use a system directory such as /opt/epics, create it and give your user ownership of it before you build. A query result of 0 does not guarantee permission to install into an existing directory.

Verification

  • Print the value in effect and where it comes from:

    make PRINT.INSTALL_LOCATION
    

    The output names your path and the origin file:

    INSTALL_LOCATION = <install_location>
    INSTALL_LOCATION's origin is file
    

Configuration variables and override files lists the defaults and the read order of the override files. To build into the chosen location, see Build and install the environment.

Build and install the environment

Build the vendor libraries, then run the EPICS-env target sequence to clone, patch, configure, build, and install Experimental Physics and Industrial Control System (EPICS) base and every module into the installed tree.

Prerequisites

When the uldaq and open62541 libraries are installed under /usr/local, the default of VENDOR_ULDAQ_PATH and OPEN62541_PATH, skip steps 1 through 4.

  1. To place the vendor libraries inside the installed tree, set a shell variable to its vendor directory:

    VENDOR_PATH="$(make print-INSTALL_LOCATION_EPICS)/vendor"
    
  2. In configure/RELEASE.local, point the measComp and opcua modules at that directory:

    echo "VENDOR_ULDAQ_PATH=${VENDOR_PATH}" > configure/RELEASE.local
    echo 'OPEN62541_PATH=\$$\$$\(\_OPEN62541_CONFIG_OPCUA\)/../../../vendor' >> configure/RELEASE.local
    

    conf.measComp writes VENDOR_ULDAQ_PATH into the measComp configuration as the uldaq library and header directories. The escaped OPEN62541_PATH value reaches the installed opcua configuration file cfg/CONFIG_OPCUA as $(_OPEN62541_CONFIG_OPCUA)/../../../vendor, a path relative to that file’s own directory, so it keeps pointing at the vendor directory of the same installed tree.

  3. Build the uldaq library into the vendor directory:

    git clone https://github.com/jeonghanlee/uldaq-env ../uldaq-env
    echo "INSTALL_LOCATION=${VENDOR_PATH}" > ../uldaq-env/configure/CONFIG_SITE.local
    make -C ../uldaq-env init conf build install
    
  4. Build the open62541 library into the vendor directory:

    git clone https://github.com/jeonghanlee/open62541-env ../open62541-env
    echo "INSTALL_LOCATION=${VENDOR_PATH}" > ../open62541-env/configure/CONFIG_SITE.local
    make -C ../open62541-env init conf build install
    

    Both libraries are in the vendor directory:

    ls "${VENDOR_PATH}/lib"
    

    The output lists the libraries of both packages:

    cmake
    libopen62541.so
    libopen62541.so.1
    libopen62541.so.1.3.15
    libuldaq.a
    libuldaq.la
    libuldaq.so
    libuldaq.so.1
    libuldaq.so.1.2.1
    pkgconfig
    
  5. Clone EPICS base and every module at its pinned tag or commit:

    make init
    

    Each source tree lands in a <name>-src directory at the repository top. Module pins and dependencies lists the pins.

  6. Apply the carried upstream patches:

    make patch
    

    The output prints one Patching line per patch file.

  7. Write the site configuration of base and every module:

    make conf
    

    The output is one empty line.

  8. Build base and every module:

    make build
    

    Base installs into the installed tree as it builds, and each module installs after its build completes.

  9. Complete the install of base and the modules, and add the commonIocsh fragments, setEpicsEnv.bash, resetEpicsEnv.bash, and the .versions file:

    make install
    
  10. Create the unversioned module links, such as asyn for asyn-4.46.0:

    make symlinks
    

    setEpicsEnv.bash finds the pvxs and pmac tools through these links.

The Debian 12, Debian 13, and Rocky 8 CI workflows run make github.check and then make install. github.check runs steps 5 through 8, step 10, and the vars listing, with the module dependency gate between make conf and make build.

Verification

  1. List the top level of the installed tree:

    LC_ALL=C make exist LEVEL=1
    

    With the vendor libraries of steps 1 through 4, the tree holds base, the modules, the vendor libraries, both environment scripts, and the version file:

    <install_location>/1.4.0/debian-13/7.0.10
    |-- .versions
    |-- base
    |-- modules
    |-- resetEpicsEnv.bash
    |-- setEpicsEnv.bash
    `-- vendor
    
    4 directories, 3 files
    

    <install_location> is the value of INSTALL_LOCATION. When you skip steps 1 through 4 because the vendor libraries are under /usr/local, the tree has no vendor directory, and the listing has no vendor line.

    This listing shows a first install. Replacing existing setup or reset scripts also leaves backup files, which add entries to the listing.

    LC_ALL=C makes tree draw its lines with American Standard Code for Information Interchange (ASCII) characters. make exist uses tree when it is installed and find otherwise, so the drawing differs on a host without tree.

  2. Run the runpath and environment gates:

    make check.deps check.env > /dev/null 2>&1; echo $?
    

    The output is:

    0
    

    Run the verification gates explains each gate and its report.

Set up a shell with the environment

Source setEpicsEnv.bash from the top of an installed tree to point the current Bash shell at that tree’s Experimental Physics and Industrial Control System (EPICS) base, modules, and tools.

Prerequisites

  • An installed tree built with make install and make symlinks; see Build and install the environment. The script puts the pvxs and pmac tools on PATH through the unversioned module links that make symlinks creates.
  • perl on PATH for automatic architecture detection, or a fallback architecture supplied as described in step 3.
  1. Source the script of the installed tree:

    source <installed_tree>/setEpicsEnv.bash
    

    <installed_tree> is the path that make print-INSTALL_LOCATION_EPICS prints in the EPICS-env clone that built the tree.

    The script prints a summary of the values it set, followed by the full PATH and LD_LIBRARY_PATH and the line Enjoy Everlasting EPICS!. The summary starts with these lines:

    
    Set the EPICS Environment as follows:
    THIS Source NAME    : setEpicsEnv.bash
    THIS Source PATH    : <installed_tree>
    EPICS_BASE          : <installed_tree>/base
    EPICS_HOST_ARCH     : linux-x86_64
    EPICS_MODULES       : <installed_tree>/modules
    

    The script derives every path from its own location, so it needs no edit when the tree moves. To select another installed version, source that tree’s setup script. No location or release argument is required. It exports these variables:

    VariableValue
    EPICS_PATHThe directory that holds the script
    EPICS_BASE$EPICS_PATH/base
    EPICS_MODULES$EPICS_PATH/modules
    EPICS_HOST_ARCHThe detected architecture, such as linux-x86_64, or the supplied fallback
    PATHPrepends modules/pmac/bin/<arch>, modules/pvxs/bin/<arch>, and base/bin/<arch>
    LD_LIBRARY_PATHPrepends base/lib/<arch>

    <arch> is the value of EPICS_HOST_ARCH. When EPICS_BASE is set before you source the script, the script first prints EPICS_BASE is defined as with that value. It then removes the entries of that tree from PATH and LD_LIBRARY_PATH and adds the entries of the sourced tree. Sourcing a second tree therefore replaces the known base, pvxs, and pmac paths of the first. Removal compares complete colon-delimited entries. Unrelated entries retain their bytes and order, including duplicates and empty entries. Repeating setup adds each managed directory once.

    Setup preserves independent settings such as EPICS_CA_ADDR_LIST and EPICS_CA_AUTO_ADDR_LIST. It also works with Bash nounset enabled by set -u, and preserves the caller’s directory, shell options, and positional arguments.

  2. Optional: to set the same variables without the printed summary, pass disable:

    source <installed_tree>/setEpicsEnv.bash disable
    

    In the shell of step 1, EPICS_BASE is set, so the script prints the tree that it replaces, followed by two empty lines:

    
    EPICS_BASE is defined as <installed_tree>/base
    
    
    

    In a shell where EPICS_BASE is not set, the script prints one empty line.

  3. Optional: supply a fallback architecture when Perl or the base discovery scripts are unavailable:

    source <installed_tree>/setEpicsEnv.bash linux-x86_64 disable
    

    The interface is [<fallback_arch>] [disable]. Automatic detection takes priority over the fallback. disable controls only the summary. Invalid argument forms return status 2. If the script cannot determine its tree or architecture, it returns status 1 with a diagnostic and preserves the existing managed environment. A discovery command that fails returns status 1 before the script replaces that environment.

  4. Optional: remove the environment from the shell by sourcing the installed reset script:

    source <installed_tree>/resetEpicsEnv.bash
    

    The output names the tree it removes:

    
    EPICS_BASE is defined as <installed_tree>/base
    
    Reset ...
    

    The script removes the base, pvxs, and pmac entries from PATH, the base entry from LD_LIBRARY_PATH, and unsets EPICS_PATH, EPICS_BASE, EPICS_HOST_ARCH, and EPICS_MODULES. It also removes the known legacy extension executable entry and unsets EPICS_EXTENSIONS. Missing path components prevent removal of the corresponding entry; variable unsetting also runs when base is absent. Repeated reset succeeds.

    Reset preserves independent CA settings and other unmanaged EPICS variables. install.base installs both setup and reset with mode 0644.

Verification

  • Check that the shell finds the EPICS tools in the installed tree:

    command -v softIoc caget pvget pvxget
    

    Each tool resolves inside <installed_tree>:

    <installed_tree>/base/bin/linux-x86_64/softIoc
    <installed_tree>/base/bin/linux-x86_64/caget
    <installed_tree>/base/bin/linux-x86_64/pvget
    <installed_tree>/modules/pvxs/bin/linux-x86_64/pvxget
    

Add or bump a module

A module enters the Experimental Physics and Industrial Control System (EPICS) environment through a pin in configure/RELEASE, and make derives its clone, configuration, build, and install targets from that pin. This page changes the pin of one module, or adds a module, and builds only that module. The module set explains how the declarations fit together. Run every command from the top of the EPICS-env checkout. The example bumps caPutLog from dafb0b2 to 6f9eb3f.

Prerequisites

  • EPICS base is installed at the install location; see Build and install the environment.
  • make init and make conf have run in the checkout, so the source trees of the module’s dependencies and the top-level RELEASE.local exist.
  • The module key, the module name, and the current pin of an existing module are listed in Module pins and dependencies.
  1. Optional: To list the modules whose pin differs from the latest upstream tag or commit, survey the pins without changing any file:

    tools/update-release.bash check
    

    The survey reads configure/RELEASE, not the .local override files. The entry for caPutLog reads:

    CAPUTLOG       : UPDATE AVAILABLE
        Current: dafb0b2
        Latest:  6f9eb3f
        Date:    2026-02-26
        >> Diff Link: https://github.com/epics-modules/caPutLog/compare/dafb0b2...6f9eb3f
    

    The command exits 0 when every pin was surveyed, 1 when a repository was unreachable or a pin has no repository Uniform Resource Locator (URL) comment, and 2 when a repository has no release tag.

  2. Set the pin of the module.

    a. To bump a module, set SRC_TAG_<module_key> to the tag or commit to check out and SRC_VER_<module_key> to the version that names the install directory. In configure/RELEASE the change becomes the pin of the release; in configure/RELEASE.local it applies to this checkout only, because make reads configure/RELEASE.local after configure/RELEASE:

    SRC_TAG_CAPUTLOG:=6f9eb3f
    SRC_VER_CAPUTLOG:=6f9eb3f
    

    A tag pin takes the form tags/<tag>, such as tags/R4-46, or the bare tag name, such as 2-7-9 for pmac.

    b. To add a module, add a block before the two -include lines at the end of configure/RELEASE. The comment line holds the repository URL that tools/update-release.bash surveys:

    ## https://github.com/<organization>/<module>
    SRC_NAME_<module_key>:=<module>
    SRC_TAG_<module_key>:=<tag_or_commit>
    SRC_VER_<module_key>:=<version>
    

    <organization> is the GitHub organization or user that hosts the repository, such as epics-modules. <module_key> is an upper-case key of your choice, such as CAPUTLOG. <module> is the repository name; the source directory is <module>-src, and the module targets use <module>, such as build.<module>. <tag_or_commit> and <version> are the pin and the version as in sub-step a.

  3. If you are adding a module that is not hosted under https://github.com/epics-modules, set its repository URL in configure/CONFIG_MODS, next to the other SRC_GITURL_* lines:

    SRC_GITURL_<module_key>:=$(strip $(SRC_URL_<org_key>))/$(strip $(SRC_NAME_<module_key>))
    

    <org_key> is the upper-case key of an organization URL that configure/RELEASE defines. Most of these URLs are at the top of the file, such as SRC_URL_MD; a few are in the block of their module, such as SRC_URL_PMAC. When none matches, add a line such as SRC_URL_<org_key>:=https://github.com/<organization> to the block of the module.

  4. If you are adding a module, declare its build prerequisites and its configuration type. In configure/CONFIG_MODS_DEPS, declare the prerequisites:

    <module>_DEPS:=null.base build.<dependency>
    

    <module>_DEPS starts with null.base and lists, in build order, the build.<dependency> target of each module that must be built first; a module that needs only EPICS base uses null.base alone. In configure/CONFIG_MODS_TYPES, declare the configuration type:

    <module>_CONF_TYPE:=custom
    

    <module>_CONF_TYPE is auto or custom. Make stops every command with Missing <module>_CONF_TYPE declaration until this line exists.

  5. If you are adding a module, provide its configuration target.

    a. For an auto module, make generates conf.<module>, which writes INSTALL_LOCATION into the module’s configure/CONFIG_SITE.local. Choose auto only when the module’s own configure/CONFIG_SITE reads $(TOP)/configure/CONFIG_SITE.local and the module needs no dependency path; otherwise the module installs into its source tree. Three optional variables in configure/CONFIG_MODS_DEPS extend the generated target, as for iocStats, retools, and MCoreUtils:

    iocStats_CONF_RELEASE_LINES:=MAKE_TEST_IOC_APP=NO
    retools_CONF_SITE_LINES:=USR_CPPFLAGS += -DUSE_TYPED_RSET
    MCoreUtils_CONF_PLATFORM:=Linux
    

    The target writes <module>_CONF_RELEASE_LINES into the module’s configure/RELEASE.local and appends <module>_CONF_SITE_LINES to its configure/CONFIG_SITE.local. When <module>_CONF_PLATFORM is set, the target acts only on a host whose uname -s output matches it.

    b. For a custom module, add conf.<module> and conf.<module>.show to configure/RULES_MODS_CONFIG. The rule writes each dependency path from its INSTALL_LOCATION_<module_key> variable, as conf.modbus does:

    conf.modbus:
    	@echo "ASYN=$(INSTALL_LOCATION_ASYN)"                > $(TOP)/$(SRC_PATH_MODBUS)/configure/RELEASE.local
    	@echo "INSTALL_LOCATION:=$(INSTALL_LOCATION_MODBUS)" > $(TOP)/$(SRC_PATH_MODBUS)/configure/CONFIG_SITE.local
    
    conf.modbus.show: conf.release.modules.show
    	cat -b $(TOP)/$(SRC_PATH_MODBUS)/configure/RELEASE.local
    	cat -b $(TOP)/$(SRC_PATH_MODBUS)/configure/CONFIG_SITE.local
    

    Also list conf.<module>.show in QUERY_SHOW_TARGETS in configure/CONFIG_GOALS, so the target takes the query-only path.

    c. For a custom module, append conf.<module> to MODS_ZERO_CUSTOM_VARS when the module needs only EPICS base, or to MODS_ONE_VARS when it needs other modules, in configure/RULES_MODS_CONFIG. Do not edit MODS_ZERO_VARS: make builds it from MODS_ZERO_CUSTOM_VARS and the generated auto targets.

    These lists group configuration targets; <module>_DEPS controls build order. QPC and sscan belong to MODS_ONE_VARS because their effective configuration names ASYN and SNCSEQ, respectively.

  6. If another module’s configuration names the added module by its key, map the key to the module name in configure/CONFIG_MODS_AUDIT, so the dependency audit resolves it:

    AUDIT_MODULE_ALIASES+=<module_key>=<module>
    
  7. Optional: To force regeneration of configure/MODULESGEN.mk, which holds each module’s repository URL, source directory, and install directory, run:

    make reconf.modules
    

    Make regenerates this file automatically when configure/RELEASE or configure/CONFIG_SITE changes, or when the effective module triples change. Creating, editing, or removing a pin override in configure/RELEASE.local or ../RELEASE.local takes effect on the next action invocation. Variable queries use the effective pins immediately without generating or rewriting the cache. This optional command removes the generated files under configure/ and regenerates MODULESGEN.mk from the current settings.

  8. Print the install directory of the module:

    make print-INSTALL_LOCATION_CAPUTLOG
    

    The last path component carries the version from step 2:

    <install_location>/1.4.0/debian-13/7.0.10/modules/caPutLog-6f9eb3f
    

    <install_location> is the INSTALL_LOCATION of the checkout.

  9. If you are bumping a module, remove its source tree, because the clone target skips a directory that exists:

    rm -rf caPutLog-src
    
  10. Clone the module and check out its pin:

    make CAPUTLOG
    

    The target is the module key. The last line of the output names the checked-out commit:

    HEAD is now at 6f9eb3f Bumped compatibility to EPICS 3.15.9 in the documentation
    
  11. Write the configuration files of the module:

    make conf.caPutLog
    

    A few configuration targets differ from the module name, such as conf.sncseq for sequencer; see Source configuration targets.

  12. Check the declared dependencies against the module source:

    make check.module-deps MODULE=caPutLog
    

    The report ends with the findings of the module:

    Module: caPutLog
    Declared: null.base
    Observed:
    Findings:
      none
    

    The command exits 2 when the source uses a module that <module>_DEPS does not declare, or a token that no module name or alias matches.

  13. Build and install the module:

    make build.caPutLog
    

    The target builds the modules in <module>_DEPS first, and each module installs as it builds.

  14. Point the unversioned link of the module at the install directory from step 8:

    make symlink.caPutLog
    
  15. If other modules list build.<module> in their _DEPS, reconfigure and rebuild them, because their configuration names the install directory of the pin they were built with; see Build and install the environment.

Verification

List the installed module directory:

make ls.INSTALL_LOCATION_CAPUTLOG

The directory holds the installed module:

configure
dbd
include
lib

Show the link in the modules directory:

make ls.INSTALL_LOCATION_MODS LSOPTS=-l | grep caPutLog

<user> is your user and group. The link points at the directory of the pin from step 2. A bump does not remove the install directory of the replaced pin, so caPutLog-dafb0b2 stays in place:

lrwxrwxrwx  1 <user> <user>  18 Sep 26 23:49 caPutLog -> ./caPutLog-6f9eb3f
drwxrwxr-x  6 <user> <user> 120 Sep 26 23:49 caPutLog-6f9eb3f
drwxrwxr-x  6 <user> <user> 120 Sep 26 22:48 caPutLog-dafb0b2

Carry an upstream fix as a patch

EPICS-env carries upstream fixes that the pinned tag of the Experimental Physics and Industrial Control System (EPICS) base or of pvxs does not contain. Each fix is a patch file in patch/, which make patch applies after make init clones the sources. A patch file that follows the naming below joins the carry set without any Makefile change; a patch for any other module has its own targets, listed in Upstream patch targets. The example regenerates the pvxs carry 1.5.2-12-cc7bc72-synccancel-diag.p0.patch from upstream commit cc7bc72. Upstream patch carry explains the carry set. Run every command from the top of the EPICS-env checkout.

Prerequisites

  • make init and make patch have run, so epics-base-src and pvxs-src hold the pinned sources with the carry set applied.
  • The fix is merged upstream as one commit or as a contiguous range of commits.
  1. Print the pinned version, which is the first field of every carry file name of that source:

    make print-SRC_VER_PVXS
    

    The command prints the version:

    1.5.2
    

    For EPICS base, print SRC_VER_BASE instead.

  2. Revert the carry set, so the source tree returns to the pin:

    make patch.pvxs.commit.revert
    

    The target reverses each file in the exact reverse of the apply order and stops at the first file that does not revert. For EPICS base, run make patch.base.pr.revert.

  3. Check that the source tree matches the pin:

    git -C pvxs-src status --short
    

    The command prints nothing. In epics-base-src, the command lists only configure/CONFIG_SITE_ENV and configure/os/CONFIG_SITE.linux-x86_64.linux-x86_64, which make conf writes.

  4. Update the clone with the upstream commits:

    git -C pvxs-src fetch origin
    
  5. Choose the file name, so that the sorted file names give the apply order:

    SourceFile nameUnit
    EPICS base<base_version>-pr<pr_number>-<slug>.p0.patchA merged pull request
    EPICS base<base_version>-<sequence>-<sha7>-<slug>.p0.patchA commit merged without a pull request
    pvxs<pvxs_version>-<sequence>-<sha7>-<slug>.p0.patchA commit

    <base_version> and <pvxs_version> are the pinned versions from step 1. <pr_number> is the number of the pull request, zero-padded to four digits. <sequence> is a two-digit number that follows the upstream merge order. <sha7> is the first seven characters of the commit hash, and <slug> is a short lower-case description. Make applies the files in byte order, so every <sequence> file of EPICS base applies before every pr file. Make selects only the files whose name starts with the pinned version, so a pin bump leaves every other carry file out of the set.

  6. Write the change of the upstream commits as a patch without path prefixes, which patch -p0 applies from the top of the source tree:

    git -C pvxs-src diff --no-prefix cc7bc72^ cc7bc72 > patch/1.5.2-12-cc7bc72-synccancel-diag.p0.patch
    

    cc7bc72^ stands for <first_commit>^, the parent of the first commit of the fix, and cc7bc72 stands for <last_commit>, its last commit. For a fix of one commit, both name the same commit. For EPICS base, run the command against epics-base-src.

  7. Apply the carry set, including the file from step 6:

    make patch.pvxs.commit.apply
    

    The output ends with the file from step 6:

    Patching pvxs-src with the file : <checkout>/patch/1.5.2-12-cc7bc72-synccancel-diag.p0.patch
    patching file ioc/pvalink_channel.cpp
    patching file src/clientdiscover.cpp
    patching file src/clientget.cpp
    patching file src/clientintrospect.cpp
    patching file src/clientmon.cpp
    patching file src/evhelper.cpp
    patching file src/evhelper.h
    

    <checkout> is the path of the EPICS-env checkout. The target stops with a non-zero exit status at the first file that does not apply. If the fix does not apply on top of the files that sort before it, edit the fix against the pinned source before you carry it. For EPICS base, run make patch.base.pr.apply.

Verification

Revert the carry set, print the exit status of make, and check the source tree:

make patch.pvxs.commit.revert > /dev/null; echo $?
git -C pvxs-src status --short

The first command prints the exit status, and the status command prints nothing, as in step 3:

0

Apply the carry set again and print the exit status of make:

make patch.pvxs.commit.apply > /dev/null; echo $?

The command prints 0 when every carry file applies:

0

When a carry file does not apply, the target stops at that file, and make reports the error and exits 2:

make: *** [<checkout>/configure/RULES_PATCH:141: patch.pvxs.commit.apply] Error 1
2

For EPICS base, the same check uses make patch.base.pr.revert, epics-base-src, and make patch.base.pr.apply.

Verify a fix against an installed tree

Some module fixes show their effect only with the hardware that a running input/output controller (IOC) drives. To verify such a fix, you build the fixed module and a copy of that IOC beside the installed tree, and run the copy in place of the production IOC. The procedure reads the installed tree and never writes to it. tools/verify_fix_build.bash builds both copies and checks their links, and tools/pv_snapshot.bash compares process variable (PV) values before, during, and after the run. The example verifies a StreamDevice fix with a consumer IOC named sdemo.

Prerequisites

  • The installed tree <tree> holds base/, modules/, vendor/, and setEpicsEnv.bash; see the installed tree.
  • An EPICS-env checkout <env_checkout> at the release that <tree> was built from.
  • The fix as a patch with a/ and b/ path prefixes, such as the output of git diff in a fork of the module, and the commit <base_commit> of the module that the patch applies to.
  • The consumer IOC repository and the commit that production runs. The IOC configure/RELEASE names the module by its key, such as STREAM.
  • A PV list file with one PV name per line, holding the PVs whose values the fix must not change. Blank lines and # comments are ignored.
  • caget in PATH, and the authority to stop and restart the production IOC.
  1. In the checkout, clone the module source, which also generates configure/MODULESGEN.mk:

    make -C <env_checkout> STREAM
    

    <env_checkout> is the path of the EPICS-env checkout, and STREAM is the module key, the Key column of Module pins and dependencies.

  2. Write the configuration of the module in the checkout:

    make -C <env_checkout> conf.release.modules conf.StreamDevice
    

    The tool reads the module’s own settings, such as vendor paths, from this configuration.

    On Ubuntu 26, check the generated C17 setting before building:

    grep -Fxc 'USR_CFLAGS += -std=gnu17' <env_checkout>/StreamDevice-src/configure/CONFIG_SITE.local
    

    A checkout with per-module C17 configuration prints 1. Release 1.4.0 does not add this setting through conf.StreamDevice; the check prints 0 and exits 1. For that result, append the setting:

    printf '%s\n' 'USR_CFLAGS += -std=gnu17' >> <env_checkout>/StreamDevice-src/configure/CONFIG_SITE.local
    

    Repeat the check; it must print 1 before you continue. Resolve any read error or duplicate setting first. Repeat this check after rerunning the configuration command, because it rewrites the file. Skip this C17 check and append on other operating systems.

  3. Create the scratch directories, named after the installed module and the IOC binary:

    mkdir -p <scratch_dir>/StreamDevice <scratch_dir>/sdemo
    

    <scratch_dir> is a directory outside <tree> and outside any checkout. The module directory name must equal the directory name in <tree>/modules, and the IOC directory name must equal the IOC binary name.

  4. Export the module source at the commit the fix applies to:

    git -C <module_source> archive <base_commit> | tar -x -C <scratch_dir>/StreamDevice
    

    <module_source> is a clone of the module that holds <base_commit>, such as the fork of the fix or <env_checkout>/StreamDevice-src. <base_commit> must be the pin of the installed module, SRC_TAG_STREAM in the example; otherwise the copy carries changes unrelated to the fix.

  5. If the fix is not one of the patch files in <env_checkout>/patch, apply it:

    patch -d <scratch_dir>/StreamDevice -p1 < <fix_patch>
    

    <fix_patch> is the path of the fix patch.

  6. Apply each file from <env_checkout>/patch that the patch target applies to the module on this platform, in the order of that target. configure/RULES_SRC defines the patch target, and configure/RULES_PATCH names the file that each module target applies. For StreamDevice, the target applies one file:

    patch -d <scratch_dir>/StreamDevice --ignore-whitespace -p0 < <env_checkout>/patch/StreamDevice-no-vxi11.p0.patch
    

    The directory also holds files that no target applies on this platform, such as mca-libnet.p0.patch, which applies only on macOS. Upstream patch targets lists the patch target of each module and the name pattern of each carry set.

  7. Export the consumer IOC at its production commit:

    git -C <ioc_repository> archive <ioc_commit> | tar -x -C <scratch_dir>/sdemo
    

    <ioc_repository> is a clone of the IOC repository, and <ioc_commit> is the commit production runs.

  8. Load the environment of the installed tree:

    source <tree>/setEpicsEnv.bash
    

    The tool takes the tree from EPICS_BASE, which this script sets to <tree>/base.

  9. Build the module and the IOC copy against the tree:

    <env_checkout>/tools/verify_fix_build.bash <scratch_dir>/StreamDevice <scratch_dir>/sdemo
    

    The tool writes configure/RELEASE.local and configure/CONFIG_SITE.local in both copies. The module takes its dependencies from <tree>/modules/StreamDevice/configure/RELEASE.local and its own settings from conf.StreamDevice.show in the checkout, with vendor paths pointed at <tree>/vendor. The IOC takes the module from <scratch_dir>/StreamDevice through the module key.

    The tool then runs four checks. The copy must rebuild every library that the installed module provides, and each rebuilt library must resolve the same shared libraries. Runpaths must name only <tree>, $ORIGIN, and, for the IOC, the module copy. The IOC must link the module from the copy. Finally, the build must write no file under <tree>. In this output, <tree> and <scratch_dir> stand for the paths of the run:

    [verify_fix_build.bash] production tree: <tree>
    [verify_fix_build.bash] module: <scratch_dir>/StreamDevice
    [verify_fix_build.bash] IOC: <scratch_dir>/sdemo
    [verify_fix_build.bash] module dependencies from the production tree: 3 entries
    [verify_fix_build.bash] dependency cross-check: EPICS-env checkout matches the installed module
    [verify_fix_build.bash] module build: OK
    [verify_fix_build.bash] module libraries (libstream.so): runpath only the production tree
    [verify_fix_build.bash] module libraries resolve the same shared libraries as production
    [verify_fix_build.bash] IOC build: OK; links 1 module library from the module directory
    [verify_fix_build.bash] production tree untouched
    
    ----------------------------------------------------------------
    Steps 2-3 PASSED: StreamDevice and sdemo against <tree>
    Build logs: <scratch_dir>/module-build.log, <scratch_dir>/ioc-build.log
    ----------------------------------------------------------------
    Steps 4-5 (manual, one IOC at a time):
      1. tools/pv_snapshot.bash capture -l <pvlist> -o before.txt  (production running)
      2. stop the production IOC; start the copy from its iocBoot with the defect visible
      3. observe the defect check; capture after.txt
      4. stop the copy; restart production; capture restored.txt
      5. tools/pv_snapshot.bash compare -t <tolerance> before.txt after.txt
         tools/pv_snapshot.bash compare -t <tolerance> before.txt restored.txt
    

    The closing lines summarize steps 10 to 18 and the verification of this page. The tool exits 0 when every check passes or on --help, and 2 when it does not receive exactly two arguments. It exits 1 on any other failure, such as a missing directory, an unset EPICS_BASE, a failed build, or a failed check. When the checkout’s module dependencies differ from those of the installed module, the tool shows the difference and asks for confirmation on the terminal. The file <scratch_dir>/.started marks the start of the run.

  10. In the copy’s iocBoot/<instance>/st.cmd, remove any setting that hides the defect, such as an asynSetTraceMask call that silences errors. <instance> is the directory under iocBoot that holds the startup script of the IOC, such as iocsdemo.

  11. While the production IOC runs, capture the before snapshot:

    <env_checkout>/tools/pv_snapshot.bash capture -l <pv_list> -o before.txt
    

    <pv_list> is the PV list file. The command reports the count:

    pv_snapshot.bash: 3 PVs captured to before.txt, 0 not connected
    

    A PV that does not connect is written as __DISCONNECTED__. The option -w <seconds> sets the Channel Access (CA) timeout, 2.0 seconds by default.

  12. Stop the production IOC.

  13. Start the copy from <scratch_dir>/sdemo/iocBoot/<instance>, the directory of step 10.

  14. Observe the defect check with the copy running; the defect must not appear.

  15. While the copy runs, capture the after snapshot:

    <env_checkout>/tools/pv_snapshot.bash capture -l <pv_list> -o after.txt
    
  16. Stop the copy.

  17. Restart the production IOC.

  18. Capture the restored snapshot:

    <env_checkout>/tools/pv_snapshot.bash capture -l <pv_list> -o restored.txt
    

Verification

Compare the snapshot taken while the copy ran, and the snapshot taken after the restart, with the before snapshot:

<env_checkout>/tools/pv_snapshot.bash compare -t 0.001 before.txt after.txt
<env_checkout>/tools/pv_snapshot.bash compare -t 0.001 before.txt restored.txt

-t sets the largest numeric difference that counts as WITHIN; choose it before the run. Each command prints one line per PV and a summary:

SAME	VFD:Setpoint	1.5	1.5	
SAME	VFD:Mode	2	2	
SAME	VFD:Label	demo	demo	
# PVs 3: SAME 3, WITHIN 0, DIFF 0, MISSING 0, DISCONN 0; tolerance 0.001

Each command exits 0 when every PV is SAME or WITHIN, 1 when a PV is DIFF, MISSING, or DISCONN, and 2 on a usage or runtime error. Check that no file under the tree was written since the run started:

find <tree> -type f \( -newer <scratch_dir>/.started -o -cnewer <scratch_dir>/.started \)

The command prints nothing.

Run the verification gates

Run the three verification gates to confirm that the module dependencies match the sources, that the installed libraries find each other, and that the installed environment script adds no LD_LIBRARY_PATH entry under pvxs/bundle.

Prerequisites

  • An EPICS-env clone configured with make init, make patch, and make conf for the module dependency gate.
  • An installed tree built with make install and make symlinks for the runpath and environment gates; see Build and install the environment.
  • readelf on PATH for the runpath gate.
  1. To compare each module’s declared dependencies with the dependencies found in its sources, run the module dependency gate from the repository top:

    make check.module-deps
    

    The report prints one block per module. For each module, Declared lists the build targets it waits for, Observed lists the dependencies found in its RELEASE.local, makefiles, databases, and sources, and Findings lists the differences. An undeclared-observed or unknown finding makes the gate fail with exit status 2. A declared-unobserved finding is reported and does not fail the gate. Run this gate after make conf and before make build, the place it takes in make github.check.

  2. Optional: to audit one module, add MODULE:

    make check.module-deps MODULE=asyn
    

    On a configured clone before make build, the output is:

    Module Dependency Audit
    Strict: YES
    Source state: generated RELEASE.local files are used when present.
    Platform: Linux
    Strict policy: undeclared-observed and unknown findings fail.
    
    Module: asyn
    Declared: null.base build.sequencer build.sscan build.calc
    Observed:
      required sequencer        asyn-src/configure/RELEASE.local:1 SNCSEQ
      required sscan            asyn-src/configure/RELEASE.local:2 SSCAN
      required calc             asyn-src/configure/RELEASE.local:3 CALC
      optional sequencer        asyn-src/testIPServerApp/src/Makefile:20 seq
      optional sequencer        asyn-src/testIPServerApp/src/Makefile:20 pv
      optional sequencer        asyn-src/testIPServerApp/src/Makefile:31 seq
      optional sequencer        asyn-src/testIPServerApp/src/Makefile:31 pv
      optional calc             asyn-src/testEpicsApp/src/Makefile:24 calc
      optional sscan            asyn-src/testEpicsApp/src/Makefile:27 sscan
      optional sequencer        asyn-src/testEpicsApp/src/Makefile:30 seq
      optional sequencer        asyn-src/testEpicsApp/src/Makefile:30 pv
      optional calc             asyn-src/testEpicsApp/src/Makefile:44 calc
      optional sscan            asyn-src/testEpicsApp/src/Makefile:48 sscan
      optional sequencer        asyn-src/testEpicsApp/src/Makefile:51 seq
      optional sequencer        asyn-src/testEpicsApp/src/Makefile:51 pv
      optional iocStats         asyn-src/testApp/src/Makefile:15 test.dbd
      optional sequencer        asyn-src/makeSupport/app/_NAME_App/src/Makefile:31 seq
      optional sequencer        asyn-src/makeSupport/app/_NAME_App/src/Makefile:31 pv
      optional calc             asyn-src/asyn/Makefile:193 calc
      external ftdi1            asyn-src/asyn/Makefile:268 ftdi1
      external ftdi1            asyn-src/asyn/Makefile:270 ftdi1
      external ftdi             asyn-src/asyn/Makefile:275 ftdi
      external ftdi             asyn-src/asyn/Makefile:277 ftdi
      optional calc             asyn-src/testEpicsApp/Db/devOctetCalc.db:6 scalcout
    Findings:
      none
    

    The Observed lines follow the order in which the file system lists the files, so their order can differ. The audit also reads files that the build generates, so a built clone can list different Observed lines. make audit.module-deps prints the same report and exits 0 whatever it finds.

  3. To scan the installed executables and shared libraries for runpath defects, run the runpath gate:

    make check.deps
    

    The scan ends with a summary of counts. For the verified Debian 13 installed tree, the summary is:

    --------------------------------------------------------
     >> BIN: Total Files with   RPATH / ALL:   0 /  85
     >>  SO: Total Files with   RPATH / ALL:   0 /  69
     >> BIN: Total Files with ABSPATH / ALL:   0 /  85
     >>  SO: Total Files with ABSPATH / ALL:   0 /  69
     >>  SO: Total Files with LOSTORG / ALL:   0 /  69
    --------------------------------------------------------
    

    The block shows the text of the summary. The tool wraps each defect count in terminal color codes, which a terminal shows as color and a file or pipe keeps as escape sequences.

    Each canonical executable is counted once, including executables reached through both versioned module directories and unversioned links. File counts depend on the installed module set. An empty tree path or a path that is not a directory exits 2 and directs the caller to set INSTALL_LOCATION_EPICS or pass a valid <installed_tree>.

    The gate reads the dynamic section of the executables in bin/linux-x86_64 and the shared libraries in lib/linux-x86_64 of base and every module, and of the shared libraries in vendor/lib. Each row counts one defect:

    • RPATH: the file carries a run-time search path (RPATH), which the loader searches before LD_LIBRARY_PATH, instead of a run-time library search path (RUNPATH).
    • ABSPATH: the search path holds an absolute directory.
    • LOSTORG: the shared library needs a library of the installed tree, and its search path lacks $ORIGIN, the loader token for the directory of the file itself.

    Any nonzero count makes the gate exit with status 2. A NOTE line above the summary marks a search path that holds a standard system directory such as /usr/lib; it does not count. make audit.deps prints the same scan and exits 0.

    An existing tree directory can still contain no files in the scan locations. Check that the ALL counts match the components you installed; a zero-defect result alone does not prove a complete installation.

  4. To check the library paths that the installed setEpicsEnv.bash adds, run the environment gate:

    make check.env
    

    The output is:

    Inspecting <installed_tree>/setEpicsEnv.bash
      EPICS_MODULES    <installed_tree>/modules
      EPICS_HOST_ARCH  linux-x86_64
    
    OK: no pvxs/bundle path in LD_LIBRARY_PATH
    
    findings: 0
    

    <installed_tree> is the path that make print-INSTALL_LOCATION_EPICS prints. The gate sources the script in a clean child shell and reports each LD_LIBRARY_PATH entry that points at a pvxs/bundle directory, which the build never creates. The tool exits with status 2 on a finding and with status 3 when it cannot inspect the tree, such as before make install; make then reports Error 2 or Error 3 and exits with status 2. make audit.env prints the same report, exits 0 on a finding, and skips the check when the tree is absent.

Verification

  • Run all three gates and print the exit status:

    make check.module-deps check.deps check.env > /dev/null 2>&1; echo $?
    

    The output is:

    0
    

    make stops at the first gate that fails, so 0 means that all three passed.

Make targets by purpose lists every verification target, and Variables set on the command line lists MODULE, FORMAT, and PLATFORM.

Load common iocsh fragments in an IOC

Add the site services of the common iocsh fragments to an Experimental Physics and Industrial Control System (EPICS) input/output controller (IOC) built against an installed tree. The IOC application in these steps is named demo. It loads the iocLog, serial, caPutLog, autosave, reccaster, and linStat fragments, and leaves out iocStatsAdmin, whose records collide with linStat; see Common iocsh fragments.

Prerequisites

  • An installed tree built with make install, which holds the fragments in modules/commonIocsh/iocsh; see Build and install the environment.
  • An IOC application whose iocBoot/<ioc_name> directory has a Makefile that writes envPaths, such as one that makeBaseApp.pl -i creates.
  • For iocLog.iocsh and caPutLog.iocsh: a log server, such as iocLogServer from EPICS base, listening on Transmission Control Protocol (TCP) port 7004 of <log_host>.
  • For setSerialParams.iocsh: a serial device that the IOC can open.
  1. In the IOC’s configure/RELEASE, or the configure/RELEASE.local file it includes, name EPICS base and each module with the macro that the fragments read:

    MODULES = <installed_tree>/modules
    AUTOSAVE = $(MODULES)/autosave
    RECCASTER = $(MODULES)/recsync
    LINSTAT = $(MODULES)/linStat
    CAPUTLOG = $(MODULES)/caPutLog
    ASYN = $(MODULES)/asyn
    EPICS_BASE = <installed_tree>/base
    

    <installed_tree> is the path that make print-INSTALL_LOCATION_EPICS prints in the EPICS-env clone that built the tree. List only the modules whose fragments the IOC loads. Common iocsh fragment macros names the module each fragment needs.

  2. In the application’s src/Makefile, add the database definition (DBD) files and libraries of those modules before the line demo_LIBS += $(EPICS_BASE_IOC_LIBS):

    demo_DBD += caPutLog.dbd asSupport.dbd reccaster.dbd linStat.dbd system.dbd
    demo_DBD += asyn.dbd drvAsynSerialPort.dbd
    demo_LIBS += caPutLog autosave reccaster linStat asyn
    

    The makeBaseApp.pl template already adds base.dbd and $(EPICS_BASE_IOC_LIBS).

    system.dbd registers the iocsh system command, which autosave.iocsh uses to create its directories.

  3. To build the IOC against the installed modules, run this command from the top of the IOC application:

    make CHECK_RELEASE=NO
    

    The installed modules keep the configure/RELEASE files of their upstream sources, which name a different EPICS_BASE. With the release check on, the build stops at Definition of EPICS_BASE conflicts with CAPUTLOG support. The build writes iocBoot/<ioc_name>/envPaths, which sets IOC to <ioc_name> and sets each module macro of step 1.

  4. Optional: to configure serial ports, create a serial configuration file in iocBoot/<ioc_name>, such as serialPorts.cmd, with one setSerialParams.iocsh line for each port:

    iocshLoad("$(IOCSH_TOP)/iocsh/setSerialParams.iocsh", "PORT=S1,BAUD=19200,BITS=8,STOP=2,PARITY=odd")
    

    PORT is the asyn port name that the startup script gives to drvAsynSerialPortConfigure.

  5. To let caPutLog see puts, create an access security configuration file (ACF) in iocBoot/<ioc_name>, such as demo.acf, that marks writes with TRAPWRITE:

    ASG(DEFAULT) {
        RULE(1, READ)
        RULE(1, WRITE, TRAPWRITE)
    }
    
  6. In iocBoot/<ioc_name>/st.cmd, set IOCSH_TOP and load the fragments between the DBD registration and iocInit:

    #!../../bin/linux-x86_64/demo
    < envPaths
    epicsEnvSet("IOCSH_TOP", "<installed_tree>/modules/commonIocsh")
    dbLoadDatabase("$(TOP)/dbd/demo.dbd")
    demo_registerRecordDeviceDriver(pdbbase)
    iocshLoad("$(IOCSH_TOP)/iocsh/iocLog.iocsh", "IOC=$(IOC),LOG_INET=<log_host>")
    drvAsynSerialPortConfigure("S1", "<tty_device>", 0, 0, 0)
    iocshLoad("$(IOCSH_TOP)/iocsh/serial.iocsh", "SERIAL_ENABLE=,SERIAL_CONFIG=$(TOP)/iocBoot/$(IOC)/serialPorts.cmd")
    asSetFilename("$(TOP)/iocBoot/$(IOC)/demo.acf")
    iocshLoad("$(IOCSH_TOP)/iocsh/caPutLog.iocsh", "LOG_INET=<log_host>")
    iocshLoad("$(IOCSH_TOP)/iocsh/autosave.iocsh", "IOC=$(IOC),AS_TOP=<as_top>")
    iocshLoad("$(IOCSH_TOP)/iocsh/reccaster.iocsh", "IOC=$(IOC)")
    iocshLoad("$(IOCSH_TOP)/iocsh/linStat.iocsh", "IOC=$(IOC)")
    iocInit()
    
    • <log_host> is the address of the log server.
    • <tty_device> is the serial device path, such as /dev/ttyS0.
    • <as_top> is a writable directory for the autosave files.

    IOCSH_TOP names the directory that holds iocsh, because linStat.iocsh loads its host and process sub-fragments from $(IOCSH_TOP)/iocsh. iocLog.iocsh loads first so that the log server receives the messages of the device setup that follows. serial.iocsh loads after the port it configures exists. Leave out the lines of any service the IOC does not use; to skip serial setup, leave out SERIAL_ENABLE=.

Verification

With the log server of the prerequisites listening, boot the IOC of steps 1 to 6 and check its output:

  1. Change to the boot directory of the IOC:

    cd <ioc_top>/iocBoot/<ioc_name>
    

    <ioc_top> is the top directory of the IOC application.

  2. Start the IOC with st.cmd, write its output to boot.log, and let it exit at the end of input:

    ../../bin/linux-x86_64/demo st.cmd < /dev/null > boot.log 2>&1
    
  3. Show the log client, serial, caPutLog, and autosave lines of the boot:

    grep -E 'log client|iocRun|caPutLog:|asynSetOption|afterIocRunning' boot.log
    

    The output of the IOC iocdemo is:

    log client: connected to log server at '<log_host>:7004'
    asynSetOption("S1", -1, "baud",   "19200")
    asynSetOption("S1", -1, "bits",   "8")
    asynSetOption("S1", -1, "stop",   "2")
    asynSetOption("S1", -1, "parity", "odd")
    afterIocRunning("caPutLogInit('<log_host>:7004', 0)")
    afterIocRunning("makeAutosaveFileFromDbInfo('<as_top>/iocdemo/req/settings.req','autosaveFields')")
    afterIocRunning("makeAutosaveFileFromDbInfo('<as_top>/iocdemo/req/values_pass0.req','autosaveFields_pass0')")
    afterIocRunning("makeAutosaveFileFromDbInfo('<as_top>/iocdemo/req/values_pass1.req','autosaveFields_pass1')")
    afterIocRunning("create_monitor_set('settings.req','5')")
    afterIocRunning("create_monitor_set('values_pass0.req','5')")
    afterIocRunning("create_monitor_set('values_pass1.req','10')")
    iocRun: All initialization complete
    sevr=info caPutLog: successfully initialized
    log client: connected to log server at '<log_host>:7004'
    afterIocRunning: caPutLogInit('<log_host>:7004', 0)
    afterIocRunning: makeAutosaveFileFromDbInfo('<as_top>/iocdemo/req/settings.req','autosaveFields')
    afterIocRunning: makeAutosaveFileFromDbInfo('<as_top>/iocdemo/req/values_pass0.req','autosaveFields_pass0')
    afterIocRunning: makeAutosaveFileFromDbInfo('<as_top>/iocdemo/req/values_pass1.req','autosaveFields_pass1')
    afterIocRunning: create_monitor_set('settings.req','5')
    afterIocRunning: create_monitor_set('values_pass0.req','5')
    afterIocRunning: create_monitor_set('values_pass1.req','10')
    7.0.10 > caPutLog: disabled
    

    The two log client: connected lines show that iocLog and caPutLog each reach the log server. The asynSetOption lines show that serial.iocsh ran the serial configuration file. The lines with afterIocRunning( are the commands that caPutLog and autosave register before iocInit, and the lines with afterIocRunning: show each one running after iocInit. caPutLog: successfully initialized shows that the logger started, and caPutLog: disabled shows it stopping when the IOC exits. The IOC writes to both standard output and standard error, so boot.log does not keep the order in which these lines run. Some of these lines carry terminal color codes, which the output above leaves out.

Run the fragment verification suite

Check the common iocsh fragments of an installed tree against running Experimental Physics and Industrial Control System (EPICS) input/output controllers (IOCs). The suite has two parts in examples/commonIocsh: verify_caputlog.py checks caPutLog.iocsh through the example IOC, and the scripts in tests/ check every fragment through an external test IOC named tc32sim. Common iocsh fragments explains what each fragment does.

Prerequisites

  • An installed tree built with make install; see Build and install the environment.
  • An EPICS-env clone, which holds the example IOC in examples/commonIocsh.
  • Python 3 and stdbuf on PATH.
  • Transmission Control Protocol (TCP) port 7004 free on the local host. verify_caputlog.py fails a case that finds another process on that port.
  • For the optional tests/ scripts in step 5: a checkout of https://github.com/jeonghanlee/tc32sim, socat, ss, and free TCP ports 7011 and 7013.
  1. In <example_dir>/configure/RELEASE.local, set the installed EPICS base and caPutLog module:

    EPICS_BASE = <installed_tree>/base
    CAPUTLOG = <installed_tree>/modules/caPutLog
    
    • <example_dir> is the examples/commonIocsh directory of the EPICS-env clone.
    • <installed_tree> is the path that make print-INSTALL_LOCATION_EPICS prints in the EPICS-env clone that built the tree.
  2. Build the example IOC:

    make -C <example_dir> CHECK_RELEASE=NO
    

    The installed caPutLog module keeps the configure/RELEASE file of its upstream source, which names a different EPICS_BASE. With the release check on, the build stops at Definition of EPICS_BASE conflicts with CAPUTLOG support. The build writes <example_dir>/bin/linux-x86_64/commonIocshExample.

  3. Change to the installed tree, so that the script arguments stay short:

    cd <installed_tree>
    
  4. Run the caPutLog checks against the installed fragment:

    python3 <example_dir>/verify_caputlog.py --base base --iocsh-top modules/commonIocsh --output <evidence_dir>
    
    • --base names the EPICS base directory that provides caput and iocLogServer.
    • --iocsh-top names the commonIocsh directory that holds the iocsh directory of the fragments, and the script reads iocsh/caPutLog.iocsh below it. The default is commonIocsh of the clone that holds the script.
    • <evidence_dir> is the absolute path of a directory that does not exist; the script creates it and refuses an existing one.

    Two more options have defaults: --arch sets the architecture directory of the IOC and base binaries, linux-x86_64, and --timeout sets the seconds to wait for each expected event, 30.

    The script prints one line per case and the evidence directory:

    [ PASS ] defaults: observed expected behavior
    [ PASS ] all_puts: observed expected behavior
    [ PASS ] unfiltered: observed expected behavior
    [ PASS ] disabled: observed expected behavior
    [ PASS ] invalid_option: observed expected behavior
    [ PASS ] missing_host: observed expected behavior
    Evidence: <evidence_dir>
    

    For each case, the script starts iocLogServer from EPICS base and the example IOC, which loads iocsh/caPutLog.iocsh below --iocsh-top. It then writes the test record with caput from EPICS base and reads what the log server received. It removes every inherited EPICS_CA_*, EPICS_CAS_*, and EPICS_IOC_LOG_* variable and gives each case its own Channel Access (CA) port on 127.0.0.1. The cases are:

    CaseMacros passed to caPutLog.iocshExpected behavior
    defaultsLOG_INET=127.0.0.1Port 7004, option 0: puts of 17, 17, and 29 give new=17 old=0 and new=29 old=17, and the repeated 17 adds no line
    all_putsLOG_INET, a free LOG_INET_PORT, OPTION=1The repeated 17 adds a line
    unfilteredLOG_INET, a free LOG_INET_PORT, OPTION=2The repeated 17 adds a line
    disabledLOG_INET, a free LOG_INET_PORT, OPTION=-1The IOC prints caPutLogInit config: Disabled, and the log server receives no line
    invalid_optionLOG_INET, a free LOG_INET_PORT, OPTION=9The IOC prints caPutLogInit config: Unknown (must be -1, 0, 1, or 2), and the log server receives no line
    missing_hostnoneThe IOC prints macLib: macro LOG_INET is undefined, and the log server receives no line

    In the cases that log, the script also checks that caPutLogInit runs after iocRun: All initialization complete. A case that waits for a result fails after 30 seconds; a case that expects no line watches for 12 seconds.

    The evidence directory holds summary.json and one directory per case. summary.json names the base, the IOC binary, the fragment path, the Secure Hash Algorithm 256-bit (SHA-256) digest of the fragment, and each case result. Each case directory holds inputs.json, ioc.log, server.log, received.log, and clients.log. The script exits with 0 when every case passes and 1 when a case fails.

  5. Optional: to check every fragment through the tc32sim test IOC, run run_all.sh from the top of the EPICS-env clone whose example IOC steps 1 and 2 built:

    export DIST_TOP=<installed_tree> TC32SIM=<tc32sim_dir>
    export COMMONIOCSH=<installed_tree>/modules/commonIocsh
    bash examples/commonIocsh/tests/run_all.sh
    
    • DIST_TOP names the installed tree that provides base, the modules, iocLogServer, and caput.
    • <tc32sim_dir> is the tc32sim checkout.
    • COMMONIOCSH names the commonIocsh directory that holds the iocsh directory of the fragments. The scripts set IOCSH_TOP to it and load $(IOCSH_TOP)/iocsh/<fragment>.iocsh.

    The defaults of these three variables are paths on the developer’s host, so set all three. The scripts read them, and these optional variables, through tests/common.sh:

    VariableDefaultMeaning
    ARCHlinux-x86_64Architecture directory of the binaries
    SKIP_REBUILD01 leaves configure/RELEASE.local of tc32sim unchanged and uses its built binary
    KEEP_WORKSPACE01 keeps the temporary directory of verify_caputlog.sh and verify_integrated.sh

    The scripts use the tc32sim checkout as follows:

    • They run <tc32sim_dir>/bin/<ARCH>/tc32sim from <tc32sim_dir>/iocBoot/ioctestlab-tc32sim, whose envPaths sets IOC to ioctestlab-tc32sim.
    • Unless SKIP_REBUILD=1, each script except verify_caputlog.sh overwrites <tc32sim_dir>/configure/RELEASE.local with EPICS_BASE and the module macros it needs, then runs make clean and make in the checkout.
    • The tc32sim IOC must include system.dbd and asyn serial support, and verify_autosave.sh and verify_integrated.sh load its iocsh/tc32sim.iocsh device setup.
    • verify_caputlog.sh uses the example IOC of steps 1 and 2 instead of tc32sim.

    run_all.sh runs eight scripts in turn. Each script prints its PASS: and FAIL: lines and a PASS=<n> FAIL=<m> line, and run_all.sh prints a RESULT line after each script and an OVERALL line at the end:

    ==== verify_linstat.sh ====
    linStat: enabling module and rebuilding
    PASS: linStat host records present (ioctestlab-tc32sim:SYS_*)
    PASS: linStat host-net records present (ioctestlab-tc32sim:NET:HOST*)
    PASS: linStat proc records present (ioctestlab-tc32sim:IOC_*)
    PASS: linStat nic records present (ioctestlab-tc32sim:NET:lo*)
    PASS: linStat fs records present (ioctestlab-tc32sim:ROOT:*)
    --------------------
    PASS=5 FAIL=0
    RESULT verify_linstat.sh: PASS
    
    ==== verify_reccaster.sh ====
    reccaster: enabling module and rebuilding
    PASS: reccaster record present (ioctestlab-tc32sim:State-Sts)
    PASS: reccaster record present (ioctestlab-tc32sim:Msg-I)
    --------------------
    PASS=2 FAIL=0
    RESULT verify_reccaster.sh: PASS
    
    ==== verify_iocstatsadmin.sh ====
    iocStatsAdmin: enabling devIocStats and rebuilding
    PASS: iocStatsAdmin record present (ioctestlab-tc32sim:ACCESS)
    PASS: iocStatsAdmin record present (ioctestlab-tc32sim:HEARTBEAT)
    PASS: iocStatsAdmin record present (ioctestlab-tc32sim:STARTTOD)
    PASS: iocStatsAdmin record present (ioctestlab-tc32sim:UPTIME)
    --------------------
    PASS=4 FAIL=0
    RESULT verify_iocstatsadmin.sh: PASS
    
    ==== verify_autosave.sh ====
    autosave: enabling module and rebuilding
    PASS: autosave pass1 retained across restart (all lines identical)
    PASS: autosave settings retained across restart (all lines identical)
    --------------------
    PASS=2 FAIL=0
    RESULT verify_autosave.sh: PASS
    
    ==== verify_ioclog.sh ====
    iocLog: rebuilding (Base feature, no module macro)
    PASS: iocLog boot errlog received at server (proc=ioctestlab-tc32sim)
    --------------------
    PASS=1 FAIL=0
    RESULT verify_ioclog.sh: PASS
    
    ==== verify_serial.sh ====
    serial: rebuilding and starting virtual PTYs
    PASS: serial params applied via config (baud 19200 on S1)
    PASS: serial skipped when SERIAL_ENABLE unset
    PASS: serial unreadable config reports error
    PASS: serial multiple ports get independent settings (S1 19200, S2 115200)
    --------------------
    PASS=4 FAIL=0
    RESULT verify_serial.sh: PASS
    
    ==== verify_caputlog.sh ====
    caPutLog: orchestrating example IOC on isolated CA port
    PASS: caPutLog logged value change 0 -> 17 (new=17 old=0)
    PASS: caPutLog logged value change 17 -> 29 (new=29 old=17)
    PASS: caPutLog OPTION 0 suppressed the unchanged put (only the 29 change logged)
    --------------------
    PASS=3 FAIL=0
    RESULT verify_caputlog.sh: PASS
    
    ==== verify_integrated.sh ====
    integrated: enabling all service modules and rebuilding
    Case A: aggregate boot of all services
    PASS: aggregate linStat host records present (:SYS_*)
    PASS: aggregate linStat proc records present (:IOC_*)
    PASS: aggregate linStat nic records present (:NET:lo*)
    PASS: aggregate linStat fs records present (:ROOT:*)
    PASS: aggregate reccaster records present (:State-Sts*)
    PASS: aggregate iocInit completed with all services loaded
    PASS: aggregate record names fully resolved (no macro cross-talk)
    PASS: aggregate loaded with no duplicate record collisions across services
    PASS: iocLog boot errlog reached server alongside other services
    PASS: caPutLog initialized in aggregate (coexists with autosave afterIocRunning)
    PASS: autosave afterIocRunning fired in aggregate (values_pass1.sav written)
    Case B: restart with autosave restore
    PASS: restart restored autosave values_pass1 set and reloaded all services
    Case C: minimal boot with optional services omitted
    PASS: minimal boot completed with optional NIC/FS/serial omitted
    --------------------
    PASS=13 FAIL=0
    RESULT verify_integrated.sh: PASS
    
    --------------------
    OVERALL: PASS
    

    verify_integrated.sh loads iocLog, serial, caPutLog, autosave, reccaster, and linStat in one IOC on port 7013 and leaves out iocStatsAdmin. run_all.sh exits with 0 only when every script passes.

Verification

  • Count the passing cases in the summary file of step 4:

    grep -c '"result": "Pass"' <evidence_dir>/summary.json
    

    All six cases pass:

    6
    

Uninstall and clean

Remove the installed tree and the cloned sources of one EPICS-env clone, and keep its .local settings files for the next build.

Prerequisites

  • The EPICS-env clone that built the tree, with the same configure/CONFIG_SITE.local. The targets compute the tree path from it; make print-INSTALL_LOCATION_EPICS prints the path they act on.
  • Write access to the installed tree. make uninstall removes the tree as your user and does not use sudo.
  1. Optional: to remove the installed files of one module, run its uninstall target before you remove the tree:

    make uninstall.<module>
    

    <module> is the module name without the version, such as asyn; the Module column of Module pins and dependencies lists every name. The target runs make uninstall in the module source tree, which reads the installed base, and leaves an empty module directory. For std, conf.std supplies the installed base path to the child IOC while retaining upstream cleanup recursion. Configure the module before cleanup if its child settings are absent or name another tree. make uninstall.modules uninstalls every module and then removes its installation directory, preserving the installed base and source trees.

  2. Optional: to remove the build products and the installed files of one module and keep its sources, run its clean target before you remove the tree:

    make distclean.<module>
    

    The target runs make distclean in the module source tree. The EPICS distclean target also uninstalls, so the target leaves an empty module directory in the installed tree, as make uninstall.<module> does. make clean.modules runs this target for every module. It preserves tracked sources and user local settings. Upstream motor cleanup removes its generated modules/RELEASE.<host_arch>.local; the motor Makefile recreates that file when needed.

  3. Remove the installed tree:

    make uninstall
    

    The output names the path that the target removes:

    Removing <install_location>/1.4.0/debian-13/7.0.10...
    

    <install_location> is the value of INSTALL_LOCATION. The target removes the whole tree for the current release, operating system, and base version, including the vendor directory, and leaves other trees under <install_location> in place.

  4. Remove the cloned sources:

    make distclean
    

    The target removes epics-base-src, every module source tree, and configure/MODULESGEN.mk. Make writes configure/MODULESGEN.mk again on the next action invocation. Query-only invocations leave it absent and derive current module variables in memory. To remove only part of this set, run make distclean.base, make distclean.modules, or make distclean.modulesgen.

  5. Remove the version file that make install wrote in the clone:

    make src_clean
    

    The target removes site-template/.versions.

The .local files stay: configure/CONFIG_SITE.local, configure/RELEASE.local, and the RELEASE.local and CONFIG_SITE.local that make conf wrote at the repository top. A later build reuses the first two, and make conf rewrites the other two.

Verification

  1. Check that the installed tree is gone:

    LC_ALL=C make exist
    

    The output is:

    No <install_location>/1.4.0/debian-13/7.0.10
    
  2. Check that no source tree is left:

    ls -d *-src
    

    The output is:

    ls: cannot access '*-src': No such file or directory
    

Make targets by purpose

The top-level Makefile exposes every EPICS-env action as a make target. EPICS-env builds the Experimental Physics and Industrial Control System (EPICS) base and modules. Run each target from the repository top as make <target>. A run with no target prints the configuration variables, because vars is the default goal.

Targets that name a module use one of three spellings. The Module and Key columns of Module pins and dependencies list every module name and key:

  • <MODULE_KEY> is the upper-case key from configure/RELEASE, such as ASYN or SNCSEQ.
  • <module> is the source directory name without its -src suffix, such as asyn, sequencer, or recsync.
  • conf.<module> targets use the names listed in Source configuration targets; a few differ from the source directory name, such as conf.sncseq for sequencer.

Some targets read a variable that you pass on the make command line. This command audits only the asyn module:

make audit.module-deps MODULE=asyn

Variables set on the command line lists every such variable and its default.

Pipeline aggregate targets

TargetRuns
initinit.base and init.modules: clones EPICS base and every module at its pinned tag or commit
patchEvery patch.*.apply target, in a fixed order
patch.revertEvery patch.*.revert target, in the exact reverse order of patch
confconf.base and conf.modules: writes the site files that point each source tree at its install location and dependencies
buildconf.base, build.base, conf.modules, and build.modules: builds base and every module; module builds install as they complete
installinstall.base, install.modules, install.commoniocsh, and src_version
symlinkssymlinks.modules: creates an unversioned link for each installed module
distcleandistclean.base, distclean.modules, and distclean.modulesgen: removes the cloned source trees and configure/MODULESGEN.mk

Source checkout targets

TargetEffect
init.base, clone.baseClones EPICS base into epics-base-src, checks out its pinned tag, and initializes its submodules; skips an existing directory
init.modules, clone.modulesRuns the clone target of every module
<MODULE_KEY>Clones one module and checks out its pinned tag or commit; skips an existing directory
reconf.modulesRemoves and regenerates configure/MODULESGEN.mk
remove.genmk, clean.genmkRemoves every configure/*.mk file
show.genmkPrints every existing configure/*.mk file; if MODULESGEN.mk is absent, also prints its current derived configuration without creating it

Upstream patch targets

TargetEffect
patch.base.pr.apply, patch.base.pr.revertApplies the EPICS base patches patch/<base_version>-*.p0.patch in sorted order, or reverts them in the reverse order
patch.pvxs.commit.apply, patch.pvxs.commit.revertApplies the pvxs patches patch/<pvxs_version>-*.p0.patch in sorted order, or reverts them in the reverse order
patch.base.apply, patch.base.revertApplies or reverts patch/<base_version>.base.p0.patch when that file exists
patch.<name>.apply, patch.<name>.revertApplies or reverts one fixed module patch; <name> is mca, measComp, measComp.tc32, opcua, opcua.export, feed-core, QPC, or StreamDevice. The mca targets act only on macOS and do nothing on Linux
patch.<name>.make, patch.base.makeWrites the current source changes of that module, or of EPICS base, as its patch file; patch.mca.make acts only on macOS

Revert targets skip only confirmed unapplied patches and stop on conflicts, partial application, missing required inputs, or unresolved states. Completed reversals remain in effect after a later error. Optional empty patch sets and platform-inactive targets succeed without changes.

Source configuration targets

TargetEffect
conf.baseconf.base.site and conf.base.env
conf.base.siteRemoves epics-base-src/configure/CONFIG_SITE_ENV, which conf.base.env writes again, adds two linker lines to configure/os/CONFIG_SITE.linux-x86_64.linux-x86_64 when absent, and writes epics-base-src/configure/CONFIG_SITE.local: install location, linking with a run-time library search path (RUNPATH) relative to $ORIGIN, site version, and PYTHON = python3
conf.base.envWrites epics-base-src/configure/CONFIG_SITE_ENV: time zone, Network Time Protocol (NTP) server, iocsh prompt and history, and input/output controller (IOC) log settings
conf.modulesconf.release.modules, conf.modules.zero, and conf.modules.one
conf.release.modulesWrites the RELEASE.local and CONFIG_SITE.local files that every module reads from the repository top
conf.modules.zeroRuns the configuration targets of MCoreUtils, autosave, caPutLog, ether_ip, iocStats, pcas, pscdrv, retools, snmp, recsync, sncseq, sscan, opcua, pvxs, linStat, feed-core, QPC, and pyDevSup
conf.modules.oneRuns the configuration targets of calc, asyn, modbus, lua, std, StreamDevice, busy, scaler, mca, measComp, motor, motorMotorSim, pmac, and rgamv2
conf.<module>Configures one module; <module> is one of MCoreUtils, autosave, caPutLog, ether_ip, iocStats, pcas, pscdrv, retools, snmp, recsync, sncseq, sscan, opcua, pvxs, linStat, feed-core, QPC, pyDevSup, calc, asyn, modbus, lua, std, StreamDevice, busy, scaler, mca, measComp, motor, motorMotorSim, pmac, or rgamv2
conf.show, conf.base.show, conf.modules.show, conf.<module>.showPrints the files the matching configuration target writes
conf.gz.base, conf.gz.modulesSame as conf.base and conf.modules, and appends -g0 -gz=zlib to USR_CFLAGS, USR_CXXFLAGS, and USR_LDFLAGS
user.confCopies configure_user/CONFIG_USER and configure_user/RULES_USER into ${HOME}/configure

On Ubuntu 26, the individual configuration targets for sncseq, iocStats, sscan, calc, busy, StreamDevice, lua, std, scaler, and mca write USR_CFLAGS += -std=gnu17 in the module’s configure/CONFIG_SITE.local. The same targets run under conf.modules and conf.gz.modules. Repeating a configuration target rewrites the file with one copy of the flag; other operating systems do not receive it.

conf.std also writes the installed base path into the std child IOC iocs/stdTestIOC/configure/RELEASE.local. It replaces only EPICS_BASE assignments in that file, preserves other settings, comments, and includes, and avoids duplicate assignments on repetition or installation-root changes.

Build and install targets

TargetEffect
build.baseBuilds EPICS base with four parallel jobs
build.modulesBuilds every module in dependency order, then runs install.modules
build.<module>Builds one module after the modules it depends on
build.gzSame as build, with the conf.gz.* configuration
install.baseInstalls EPICS base and copies scripts/setEpicsEnv.bash and scripts/resetEpicsEnv.bash to the top of the installed tree with mode 0644, backing up existing files
install.modules, install.<module>Installs every module, or one module
install.commoniocshCopies commonIocsh/iocsh/*.iocsh to modules/commonIocsh/iocsh in the installed tree
src_versionWrites the time it runs and the EPICS-env commit to .versions and installs that file at the top of the installed tree
symlinks.modulesCreates every module link, then deletes dangling links under modules on Linux
symlink.<module>, cleansymlink.<module>Creates or removes the unversioned link of one module

Verification and inspection targets

TargetEffect
audit.module-depsReports differences between each module’s declared and observed dependencies; reads MODULE, FORMAT, and PLATFORM
check.module-depsSame audit, and fails when an undeclared or unknown dependency exists
audit.depsReports runpath defects in the installed executables and shared libraries without failing
check.depsSame scan, and fails on any finding
audit.envReports each LD_LIBRARY_PATH entry under pvxs/bundle that the installed setEpicsEnv.bash adds
check.envSame check, and fails on a finding or when it cannot inspect the installed tree
readelf.base, ldd.base, chrpath.base, readelf.runpath.basePrints the dynamic section, resolved libraries, or runpath of the installed EPICS base files
readelf.modules, ldd.modules, chrpath.modulesSame inspection for every installed module
readelf.<module>, ldd.<module>, chrpath.<module>Same inspection for one installed module
existPrints the installed tree to depth LEVEL, default 2
exist.modulesPrints the installed modules directory to depth LEVEL plus 1

Clean and uninstall targets

TargetEffect
clean.baseRuns make clean in the EPICS base source tree
clean.modulesRuns make distclean in every module source tree, which also empties each installed module directory; preserves sources and user local settings, except the upstream motor-generated host RELEASE file
distclean.baseRemoves the EPICS base source tree
distclean.modulesRemoves every module source tree
distclean.modulesgenRemoves configure/MODULESGEN.mk
uninstallRemoves the whole installed tree for the current release, operating system, and base version
uninstall.modulesRuns make uninstall in every module, then removes every module install directory; preserves source trees and the installed base
uninstall.<module>Runs make uninstall in one module
src_cleanRemoves site-template/.versions

Variable printing targets

An invocation is query-only when every selected goal is print-%, PRINT.%, vars, env, default, ls.%, tree.%, cat.%, exist, exist.modules, show.genmk, or a declared conf.*.show target, including conf.show. Pattern query names require a nonempty variable name. These invocations create no installation directory or generated file, including with -n. File-display targets read their actual files and can fail when those files are missing.

With no explicit goals, make classifies the effective .DEFAULT_GOAL; the shipped value is vars. Command-line default overrides retain their target behavior, and explicit goals take precedence. A mixed query/action list or an unknown goal takes the action path, which probes the installation root and can regenerate the cache. -n does not suppress those action-path effects.

TargetEffect
vars, env, defaultPrints the configuration variables; FILTER=<prefix> limits the list to names that start with <prefix>
print-<VARIABLE>Prints the value of one variable
PRINT.<VARIABLE>Prints the value and the origin of one variable
ls.<VARIABLE>, tree.<VARIABLE>, cat.<VARIABLE>Runs ls, tree, or cat on the path a variable holds

Continuous integration (CI) targets

TargetRuns
githubinit, patch, vars, conf, build, and symlinks
github.checkinit, patch, vars, conf, check.module-deps, build, and symlinks

Configuration variables and override files

EPICS-env reads its settings from make variables when it builds the Experimental Physics and Industrial Control System (EPICS) base and modules. The defaults live in tracked files under configure/; a site changes them in untracked .local files, so the tracked files stay unchanged.

Override files and read order

configure/CONFIG reads configure/RELEASE first and configure/CONFIG_SITE second. Each file then reads its two override files, and a later file wins:

Tracked fileOverride files, in the order read
configure/RELEASE../RELEASE.local (next to the repository), then configure/RELEASE.local
configure/CONFIG_SITE../CONFIG_SITE.local (next to the repository), then configure/CONFIG_SITE.local

Creating, editing, or removing either RELEASE.local override regenerates configure/MODULESGEN.mk on the next action invocation when the effective module triples change. Generated install directories follow the effective SRC_VER_<MODULE_KEY> values without a manual reconf.modules step. Query-only invocations use those effective values in memory and leave the persistent cache unchanged, including when it is absent or stale.

The variables that configure/CONFIG_BASE defines with ?= accept a value set in a CONFIG_SITE.local file, in the environment, or on the make command line. LINKER_USE_RPATH uses :=, so only a value on the make command line replaces it. Every EPICS base and module build receives LINKER_ORIGIN_ROOT set to INSTALL_LOCATION_EPICS on its own command line, so no value set for LINKER_ORIGIN_ROOT reaches a build.

conf.release.modules writes RELEASE.local and CONFIG_SITE.local at the repository top for the modules to read. EPICS-env itself does not read those two files, and each make conf overwrites them.

Install location and release

VariableDefaultSet inMeaning
INSTALL_LOCATION${HOME}/epicsCONFIG_SITE.localRoot of every installed tree
ENV_RELEASE_VERS1.4.0CONFIG_SITE.localEPICS-env release number; the second level of the installed tree

The installed tree for one host is $(INSTALL_LOCATION)/$(ENV_RELEASE_VERS)/<os_id>-<os_version>/$(SRC_VER_BASE). <os_id> and <os_version> are the operating system ID and VERSION_ID from /etc/os-release. This command prints that path for the current host:

make print-INSTALL_LOCATION_EPICS

Actions probe INSTALL_LOCATION with mkdir -p. When the probe returns 1, make runs the module build and install steps, the module links, and the commonIocsh install through sudo. The EPICS base build and install and the .versions install do not use sudo, so INSTALL_LOCATION must be writable by the current user for a complete install.

A query computes SUDO_INFO without creating the directory. An existing directory yields 0 when its parent path permits identifying it, even with mode 000 on the final directory. An absent path requires search and create access at the nearest existing ancestor; a blocked path or non-directory yields 1. The result does not guarantee write access inside an existing directory or predict quota, mount, or concurrent filesystem failures.

Source repositories and pins

VariableDefaultSet inMeaning
SRC_URL_BASEhttps://github.com/epics-baseRELEASE.localOrganization that hosts EPICS base
SRC_URL_EPICSMODULEShttps://github.com/epics-modulesRELEASE.localDefault organization for modules
SRC_URL_<ORG>One GitHub organization eachRELEASE.localOrganizations for modules hosted elsewhere: CHANNELFINDER, JEONGHANLEE, BRUNOSEIVAM, PSI, MOTOR, MD, BERKELEYLAB, PMAC
SRC_NAME_BASEepics-baseRELEASE.localEPICS base repository name
SRC_TAG_BASEtags/R7.0.10RELEASE.localEPICS base tag to check out
SRC_VER_BASE7.0.10RELEASE.localEPICS base version; the last level of the installed tree
SRC_NAME_<MODULE_KEY>Per moduleRELEASE.localModule repository name
SRC_TAG_<MODULE_KEY>Per moduleRELEASE.localModule tag or commit to check out
SRC_VER_<MODULE_KEY>Per moduleRELEASE.localModule version; part of the module install directory name

Module pins and dependencies lists the value of every <MODULE_KEY>.

Vendor library locations

VariableDefaultSet inMeaning
VENDOR_ULDAQ_PATH/usr/localRELEASE.localInstall prefix of the uldaq library that measComp links
OPEN62541_PATH/usr/localRELEASE.localInstall prefix of the open62541 library that opcua links

EPICS base site settings

conf.base.env writes these values into epics-base-src/configure/CONFIG_SITE_ENV, so they become the defaults of every input/output controller (IOC) built against the installed base.

EPICS base compiles CONFIG_SITE_ENV into its libCom library, so a changed value takes effect only after make conf.base, make build.base, and make install.base run again.

VariableDefaultMeaning
EPICS_TZ"PST8PDT,M3.2.0/2,M11.1.0/2"Time zone rule
EPICS_TS_NTP_INETtime.google.comNetwork Time Protocol (NTP) server
IOCSH_PS1"<base_version> > "iocsh prompt
IOCSH_HISTSIZE50Lines of iocsh command history
IOCSH_HISTEDIT_DISABLEemptyDisables line editing in iocsh when set
EPICS_IOC_LOG_INETemptyIOC log server address
EPICS_IOC_LOG_FILE_NAMEemptyIOC log file path
EPICS_IOC_LOG_FILE_COMMANDemptyCommand that returns a log file path after SIGHUP
EPICS_IOC_LOG_FILE_LIMIT1000000IOC log file size limit

conf.base.site writes these values into epics-base-src/configure/CONFIG_SITE.local:

VariableDefaultMeaning
CROSS_COMPILER_TARGET_ARCHSemptyCross target architectures for EPICS base
EPICS_SITE_VERSION"github.com/jeonghanlee/EPICS-env"Site version string built into EPICS base
EPICS_VCS_VERSION"EPICS-env-<base_version>-<git_describe>"Version string written as GENVERSIONDEFAULT

Variables set on the command line

VariableDefaultUsed byMeaning
VERBOSEunsetEvery targetPrints recipe commands when set
DEBUG_SHELLunsetEvery targetRuns recipes under /bin/sh -x when set
FILTER1 (no filter)varsPrints only variables whose names start with the given prefix
LEVEL2exist, exist.modules, tree.<VARIABLE>Tree depth
LSOPTSunsetls.<VARIABLE>Options passed to ls
MODULEempty (all)audit.module-deps, check.module-depsAudits one module
FORMATtextaudit.module-deps, check.module-depsReport format: text or json
PLATFORMoutput of uname -saudit.module-deps, check.module-depsWith Linux, dependencies found under a module’s os/Linux, os/posix, and os/default directories count as required; with any other value they count as optional

Module pins and dependencies

configure/RELEASE pins the Experimental Physics and Industrial Control System (EPICS) base and every module to one tag or commit. The values below are the ones make computes from the tracked configure/RELEASE and configure/CONFIG_MODS, with no override file. When a RELEASE.local file overrides a pin, this command prints the value in effect for one module in your checkout:

make print-SRC_TAG_<MODULE_KEY>

<MODULE_KEY> is the Key column below, such as ASYN.

EPICS base repository pin

RepositoryPinVersionInstall directory
https://github.com/epics-base/epics-basetags/R7.0.107.0.10base

Module repositories and pins

Module is the name the module’s make targets use, such as build.asyn. Key is the upper-case name of its SRC_* variables in configure/RELEASE. Install directory is the directory under modules/ in the installed tree; make symlinks adds a link without the version suffix. Depends on lists the modules that build first. MCoreUtils is part of the set only on Linux.

ModuleKeyRepositoryPinVersionInstall directoryDepends on
asynASYNhttps://github.com/epics-modules/asyntags/R4-464.46.0asyn-4.46.0calc, sequencer, sscan
autosaveAUTOSAVEhttps://github.com/epics-modules/autosavetags/R6-06.0.0autosave-6.0.0EPICS base only
busyBUSYhttps://github.com/epics-modules/busy2dfe92d2dfe92dbusy-2dfe92dasyn, autosave
calcCALChttps://github.com/epics-modules/calc4217e834217e83calc-4217e83sequencer, sscan
caPutLogCAPUTLOGhttps://github.com/epics-modules/caPutLogdafb0b2dafb0b2caPutLog-dafb0b2EPICS base only
ether_ipETHERIPhttps://github.com/epics-modules/ether_iptags/ether_ip-3-103.10.0ether_ip-3.10.0EPICS base only
feed-coreFEEDCOREhttps://github.com/BerkeleyLab/feed-core0472d880472d88feed-core-0472d88EPICS base only
iocStatsIOCSTATShttps://github.com/epics-modules/iocStatstags/4.0.14.0.1iocStats-4.0.1EPICS base only
linStatLINSTAThttps://github.com/mdavidsaver/linStattags/1.2.11.2.1linStat-1.2.1EPICS base only
luaLUAhttps://github.com/epics-modules/lua17475b517475b5lua-17475b5asyn
mcaMCAhttps://github.com/epics-modules/mca687d563687d563mca-687d563asyn, autosave, busy, calc, scaler, sequencer, sscan, std
MCoreUtilsMCOREUTILShttps://github.com/epics-modules/MCoreUtilsa86e5eda86e5edMCoreUtils-a86e5edEPICS base only
measCompMEASCOMPhttps://github.com/epics-modules/measCompc38974ec38974emeasComp-c38974easyn, autosave, busy, calc, mca, scaler, sequencer, sscan, std
modbusMODBUShttps://github.com/epics-modules/modbustags/R3-43.4.0modbus-3.4.0asyn
motorMOTORhttps://github.com/epics-modules/motor285f44d285f44dmotor-285f44dasyn, busy, lua, modbus, sequencer
motorMotorSimMOTORSIMhttps://github.com/epics-motor/motorMotorSimtags/R1-31.3.0motorMotorSim-1.3.0asyn, motor
opcuaOPCUAhttps://github.com/epics-modules/opcuatags/v0.11.20.11.2opcua-0.11.2EPICS base only
pcasPCAShttps://github.com/epics-modules/pcase075fd4e075fd4pcas-e075fd4EPICS base only
pmacPMAChttps://github.com/DiamondLightSource/pmac2-7-92.7.9pmac-2.7.9asyn, busy, calc, motor
pscdrvPSCDRVhttps://github.com/mdavidsaver/pscdrv276daca276dacapscdrv-276dacaEPICS base only
pvxsPVXShttps://github.com/epics-base/pvxstags/1.5.21.5.2pvxs-1.5.2EPICS base only
pyDevSupPYDEVSUPhttps://github.com/epics-modules/pyDevSup4527ed04527ed0pyDevSup-4527ed0EPICS base only
QPCQPChttps://github.com/jeonghanlee/QPC913fad4913fad4QPC-913fad4asyn
recsyncRECSYNChttps://github.com/ChannelFinder/recsync9834b949834b94recsync-9834b94EPICS base only
retoolsRETOOLShttps://github.com/brunoseivam/retools5ada1e15ada1e1retools-5ada1e1EPICS base only
rgamv2RGAMV2https://github.com/jeonghanlee/rgamv227fc63327fc633rgamv2-27fc633asyn
scalerSCALERhttps://github.com/epics-modules/scalerbeb5521beb5521scaler-beb5521asyn, autosave
sequencerSNCSEQhttps://github.com/epics-modules/sequencertags/R2-2-92.2.9seq-2.2.9EPICS base only
snmpSNMPhttps://github.com/jeonghanlee/snmptags/v1.1.0.4ja1.1.0.4jasnmp-1.1.0.4jaEPICS base only
sscanSSCANhttps://github.com/epics-modules/sscane13699ee13699esscan-e13699esequencer
stdSTDhttps://github.com/epics-modules/std5f2e4425f2e442std-5f2e442asyn, sequencer
StreamDeviceSTREAMhttps://github.com/paulscherrerinstitute/StreamDevicetags/2.8.262.8.26StreamDevice-2.8.26asyn, calc

Common iocsh fragment macros

Each common iocsh fragment in modules/commonIocsh/iocsh takes its settings as macros in the second argument of iocshLoad. An input/output controller (IOC) of the Experimental Physics and Industrial Control System (EPICS) sets IOCSH_TOP to modules/commonIocsh and loads a fragment as iocshLoad("$(IOCSH_TOP)/iocsh/<fragment>.iocsh", "<macros>"). A default in the tables below applies when the IOC does not pass the macro. Common iocsh fragments explains the macro sources and the load order.

Module and database definition requirements

Each fragment needs support that the IOC links and registers. Module top macro is the configure/RELEASE macro that the fragment reads through envPaths. DBD files are the database definition (DBD) files that the IOC includes, and Library is the library it links. The libCom library of EPICS base registers the iocsh commands that iocLog.iocsh calls, so that fragment needs no DBD file:

FragmentModule top macroDBD filesLibrary
autosave.iocshAUTOSAVEasSupport.dbd, system.dbdautosave
caPutLog.iocshnonecaPutLog.dbdcaPutLog
iocLog.iocshnonenoneCom of EPICS base
iocStatsAdmin.iocshdevIocStatsdevIocStats.dbddevIocStats
linStat.iocsh and linStat*.iocshLINSTATlinStat.dbdlinStat
reccaster.iocshRECCASTERreccaster.dbdreccaster
serial.iocshnonenonenone
setSerialParams.iocshnoneasyn.dbd, drvAsynSerialPort.dbdasyn

Macros for autosave.iocsh

autosave.iocsh sets the save and request file paths to $(AS_TOP)/$(IOC)/save and $(AS_TOP)/$(IOC)/req and creates both directories. It loads $(AUTOSAVE)/db/save_restoreStatus.db with the prefix $(IOC)-as:. The first restore pass reads the settings and pass 0 files, and the second reads the settings and pass 1 files. After iocInit, it writes a request file for each group from the autosaveFields, autosaveFields_pass0, and autosaveFields_pass1 info tags and starts a monitor set for each.

MacroRequired or defaultMeaning
IOCRequiredStatus record prefix $(IOC)-as: and the per-IOC directory under AS_TOP
AS_TOPRequiredWritable root directory of the save and request files
AUTOSAVERequired; from envPathsInstall directory of the autosave module
INCOMPLETE1Restores and saves a group even when some of its values are missing
CA_RECONNECT1Retries the connection to a process variable whose first connection failed
DATED_BACKUP1Writes dated backup files
NUM_SEQ3Number of sequenced backup files
SEQ_PERIOD300Seconds between sequenced backups
SETTINGS_FILESsettingsBase name of the settings group files
VALUES_FILES_PASS0values_pass0Base name of the pass 0 value group files
VALUES_FILES_PASS1values_pass1Base name of the pass 1 value group files
SETTINGS_PERIOD5Seconds between saves of the settings group
VALUES_PASS0_PERIOD5Seconds between saves of the pass 0 value group
VALUES_PASS1_PERIOD10Seconds between saves of the pass 1 value group
DEAD_SECONDS5DEAD_SECONDS value of save_restoreStatus.db

Macros for caPutLog.iocsh

caPutLog.iocsh registers caPutLogInit('$(LOG_INET):$(LOG_INET_PORT)', $(OPTION)) with afterIocRunning, so the logger starts after iocInit completes. The IOC supplies an access security configuration file that marks writes with TRAPWRITE.

MacroRequired or defaultMeaning
LOG_INETRequiredAddress of the log server that receives put records
LOG_INET_PORT7004Transmission Control Protocol (TCP) port of the log server
OPTION0-1 disables logging; 0 logs value changes; 1 logs every put, including an unchanged value; 2 logs every put without combining bursts on one field

Macros for iocLog.iocsh

iocLog.iocsh sets the environment variables EPICS_IOC_LOG_INET, EPICS_IOC_LOG_PORT, and iocLogDisable. It then sets the message prefix to fac=$(FACNAME) proc=$(IOC) and calls iocLogInit.

MacroRequired or defaultMeaning
IOCRequiredName after proc= in each message
LOG_INETRequiredAddress of the log server; written to EPICS_IOC_LOG_INET
LOG_INET_PORT7004TCP port of the log server; written to EPICS_IOC_LOG_PORT
LOGDISABLE0Written to the environment variable iocLogDisable; EPICS base reads its iocLogDisable program variable instead, so this value does not turn logging off
FACNAMEemptyFacility name after fac= in each message

Macros for iocStatsAdmin.iocsh

iocStatsAdmin.iocsh loads $(devIocStats)/db/iocAdminSoft.db with IOC=$(IOC). It creates records such as $(IOC):ACCESS, $(IOC):HEARTBEAT, $(IOC):STARTTOD, and $(IOC):UPTIME.

MacroRequired or defaultMeaning
IOCRequiredRecord name prefix; at most 21 characters
devIocStatsRequired; from envPathsInstall directory of the iocStats module

Macros for linStat.iocsh

linStat.iocsh loads linStatHost.iocsh and linStatProc.iocsh from $(IOCSH_TOP)/iocsh with IOC=$(IOC). It also loads linStatNIC.iocsh when NICENABLE is empty, and linStatFS.iocsh when FSENABLE is empty.

MacroRequired or defaultMeaning
IOCRequiredRecord name prefix, passed to every sub-fragment
LINSTATRequired; from envPathsInstall directory of the linStat module
IOCSH_TOPRequiredDirectory that holds the iocsh directory of the fragments, such as <installed_tree>/modules/commonIocsh; the sub-fragments load from $(IOCSH_TOP)/iocsh
NICENABLE#--Set to empty to load linStatNIC.iocsh for one network interface
NICemptyNetwork interface name, such as eth0; required when NICENABLE is empty
FSENABLE#--Set to empty to load linStatFS.iocsh for one filesystem
FSIDemptyUnique tag of the filesystem, such as ROOT; required when FSENABLE is empty
DIRemptyMount path of the filesystem, such as /; required when FSENABLE is empty

Macros for linStatHost.iocsh

linStatHost.iocsh loads $(LINSTAT)/db/linStatHost.db with IOC=$(IOC).

MacroRequired or defaultMeaning
IOCRequiredRecord name prefix
LINSTATRequired; from envPathsInstall directory of the linStat module

Macros for linStatProc.iocsh

linStatProc.iocsh loads $(LINSTAT)/db/linStatProc.db with IOC=$(IOC).

MacroRequired or defaultMeaning
IOCRequiredRecord name prefix
LINSTATRequired; from envPathsInstall directory of the linStat module

Macros for linStatNIC.iocsh

linStatNIC.iocsh loads $(LINSTAT)/db/linStatNIC.db with IOC=$(IOC) and NIC=$(NIC). The records are named $(IOC):NET:$(NIC):*.

MacroRequired or defaultMeaning
IOCRequiredRecord name prefix
NICRequiredNetwork interface name, such as eth0
LINSTATRequired; from envPathsInstall directory of the linStat module

Macros for linStatFS.iocsh

linStatFS.iocsh loads $(LINSTAT)/db/linStatFS.db with P=$(IOC):$(FSID), DIR=$(DIR), and PERIOD=$(PERIOD). The records are named $(IOC):$(FSID):*.

MacroRequired or defaultMeaning
IOCRequiredRecord name prefix
FSIDRequiredUnique tag of the filesystem, such as ROOT
DIRRequiredMount path of the filesystem, such as /
PERIOD10Scan period in seconds
LINSTATRequired; from envPathsInstall directory of the linStat module

Macros for reccaster.iocsh

reccaster.iocsh sets the reccaster variables reccastTimeout and reccastMaxHoldoff. It loads $(RECCASTER)/db/reccaster.db with P=$(IOC):, which creates $(IOC):State-Sts and $(IOC):Msg-I.

MacroRequired or defaultMeaning
IOCRequiredRecord name prefix
RECCASTERRequired; from envPathsInstall directory of the recsync module
RECCAST_TIMEOUT20.0Client timeout in seconds; written to reccastTimeout
RECCAST_MAX_HOLDOFF10.0Maximum holdoff in seconds; written to reccastMaxHoldoff

Macros for serial.iocsh

serial.iocsh loads the file named in SERIAL_CONFIG with iocshLoad when SERIAL_ENABLE is empty. That file belongs to the IOC and loads setSerialParams.iocsh once for each serial port. When SERIAL_ENABLE is not passed, the line is a comment, and iocsh prints that SERIAL_CONFIG is undefined. A SERIAL_CONFIG path that cannot be read makes iocsh print Can't open <path>, and the startup script continues.

MacroRequired or defaultMeaning
SERIAL_ENABLE#--Set to empty to load the serial configuration file
SERIAL_CONFIGRequired when SERIAL_ENABLE is emptyPath of the serial configuration file

Macros for setSerialParams.iocsh

setSerialParams.iocsh calls asynSetOption for baud, bits, stop, and parity on one asyn port, with address -1. It does not set hardware or software flow control.

MacroRequired or defaultMeaning
PORTRequiredasyn port name that drvAsynSerialPortConfigure created
BAUD9600Baud rate
BITS8Data bits: 5, 6, 7, or 8
STOP1Stop bits: 1 or 2
PARITYnoneParity: none, odd, or even

Tools and scripts reference

EPICS-env builds the Experimental Physics and Industrial Control System (EPICS) base and modules with make. The tools/ and scripts/ directories hold the programs around that build: checks of the installed tree, dependency and pin surveys, process variable (PV) helpers, and scripts that set up a shell for an installed tree. You run each Bash tool with bash. This command prints the usage of one tool:

bash tools/check_env.bash --help

audit_module_deps.bash without --top and check_deps.bash without an installed tree argument read make variables in the current directory; run them from the repository top. gen_dep_graph.bash, prep-vendors.bash, update-release.bash, and verify_fix_build.bash locate the checkout from their own path.

Programs in the tools directory

Placeholders in the Interface column:

  • <installed_tree> is the installed tree for one host, the value of make print-INSTALL_LOCATION_EPICS.
  • <repo> is the top directory of an EPICS-env checkout.
  • <name> after --module is the lower-case module name, such as asyn.
  • <pv_list> is a file with one PV name per line.
ToolPurposeInterfaceExit statusCalled by
audit_module_deps.bashCompares each module’s declared dependencies (<module>_DEPS) with the dependencies found in its source tree: configure/RELEASE.local, Makefiles, .dbd, database, startup, and C or C++ source files--top <repo>, --module <name>, --format text or json, --platform <name>, --strict, --help0 report printed with no strict failure; 1 argument error or no module matched; 2 with --strict when an undeclared-observed or unknown finding existsaudit.module-deps, check.module-deps, and every OS workflow: through github.check on Debian 12, Debian 13, and Rocky 8, and directly on Rocky 10 and both Ubuntu workflows
check_deps.bashScans each canonical installed executable once and scans the shared libraries of base, modules, and vendor/lib. It reports each file that has a run-time search path (RPATH) entry, each absolute RPATH or run-time library search path (RUNPATH) entry outside system library directories, and each shared library that needs a tree library without $ORIGIN in its RPATH or RUNPATH-v or --verbose, --report-only, then an optional <installed_tree>; without a positional argument the tool reads INSTALL_LOCATION_EPICS from make in the current directory0 no finding, or any finding with --report-only; 1 unknown option; 2 any finding without --report-only, invalid installed-tree input, unresolved executable path, or readelf not foundaudit.deps (with --report-only), check.deps, every operating system (OS) workflow, and prep-vendors.bash check-deps
check_env.bashSources the installed setEpicsEnv.bash in a shell started with env -i, and reports each LD_LIBRARY_PATH entry that contains a pvxs/bundle path component--epics <installed_tree> (required), --strict, --require-run, --help0 no finding, a finding without --strict, or check skipped; 1 argument error; 2 with --strict when a finding exists; 3 with --require-run when the script is absent, EPICS_MODULES or EPICS_HOST_ARCH stays empty, or LD_LIBRARY_PATH stays emptyaudit.env, check.env (with --strict --require-run), and every OS workflow
gen_dep_graph.bashWrites a Graphviz image of the module dependency graph from the <module>_DEPS lines, labeled with the commit and its date; needs the dot command-f <file> (default configure/CONFIG_MODS_DEPS), -o <file> (default epics_deps.png; the extension sets the image format), -v, -h0 image written or help; 1 rendering error, missing input file or Graphviz, or unknown option; 2 a missing option value, with a diagnosticNo target or workflow
prep-vendors.bashClones uldaq-env and open62541-env into ${HOME}/.vendor_temp_folder, builds and installs both into <installed_tree>/vendor, and builds EPICS-env against themOne command: init, prep-uldaq, prep-open62541, prep-vendors, epics-env, epics-build <make_target>, show-env, check-deps followed by check_deps.bash options, all, OS, or help0 success or help; 1 unknown command, missing argument, backup failure, refused replacement, or EOF at confirmation; otherwise the status of the failed stepNo target or workflow
pv_snapshot.bashCaptures PV values with caget into a snapshot file, and compares two snapshots PV by PV as SAME, WITHIN, DIFF, MISSING, or DISCONNcapture -l <pv_list> -o <snapshot> [-w <seconds>]; compare [-t <tolerance>] <before> <after>; --help0 no difference beyond the tolerance; 1 a DIFF, MISSING, or DISCONN PV; 2 usage or run-time errors, including a missing option value with a diagnosticNo target or workflow; verify_fix_build.bash names it in the manual steps it prints
pvs_gets.bashReads every PV of a list, sorted, with caget or pvget, once or in a loop-l <pv_list> (required), -f <regex>, -r <field>, -w <seconds>, -c, -n, -70 run finished; 1 usage error or no -l; 2 the selected client is missing or not executable, in single and watch modes; -w runs until interrupted when the client is availableNo target or workflow
revert_patch.bashClassifies a whole patch, reverses a confirmed applied patch, and skips a confirmed unapplied patch[--no-backup-if-mismatch] [--prerequisites] <source> <patch> [<ordered_patches>...]; the ordered list includes the selected patch0 reversed or confirmed unapplied; 2 invalid or unresolved inputs; otherwise a failed command statusEvery active patch.*.revert target
tc32-expansion-query.cppC++ source, built separately, of a program that reports whether a measComp TC-32 or E-TC32 has the EXP-32 expansion attached, through the installed uldaq libraryOne argument: the unique ID that the input/output controller (IOC) passes to the measComp driver0 query succeeded; 2 usage; 3 no device or more than one device matches; 4 a uldaq call failedNo target or workflow
update-release.bashSurveys every module pin in configure/RELEASE against its remote: tag pins against the latest release tag, commit pins against the branch head[-v] check, [-v] update, or help; reads GITHUB_TOKEN when setcheck: 0 survey complete, 1 a module unreachable or without a repository URL, 2 a module has no release tag; update: 0 run finished or choice 4 taken, 1 configure/RELEASE not found; help: 0; usage error: 2No target or workflow
verify_fix_build.bashBuilds a fixed module copy and a consumer IOC copy against the installed tree named by EPICS_BASE, then checks their RPATH or RUNPATH entries, their resolved libraries, and that nothing under the tree was written<module_dir> <ioc_dir>, after setEpicsEnv.bash of the tree is sourced; --help0 all checks passed; 1 a precondition, build, or check failed, or a dependency confirmation was refused or had no terminal; 2 wrong argument countNo target or workflow

Behavior details of the tools

  • audit_module_deps.bash reads the module list, each <module>_DEPS, and the AUDIT_* token lists through make print-<VARIABLE> in the directory that --top names. --platform defaults to the output of uname -s.

  • check_deps.bash looks only in bin/linux-x86_64 and lib/linux-x86_64 under base and each module, and in vendor/lib. An RPATH or RUNPATH entry that starts with /usr/lib, such as /usr/lib64 or /usr/lib/x86_64-linux-gnu, prints a note and does not count as a finding.

  • check_deps.bash resolves executable paths with realpath -e and removes duplicate canonical paths before calling readelf. Empty or non-directory tree paths and failed make lookups exit 2 with guidance to set INSTALL_LOCATION_EPICS or pass a valid <installed_tree>, including with --report-only.

  • revert_patch.bash runs noninteractive GNU patch dry-runs before changing sources. For supported single-file, single-hunk patches, it checks that changed lines match the uniquely named static C function in the hunk header. Conflicting, partial, missing-input, and unresolved states fail with the source and patch identified. Both directions remaining valid is unresolved.

  • Revert suppresses mismatch backups by default; --no-backup-if-mismatch accepts the same behavior explicitly. Existing target backups are preserved.

  • Revert classification can copy real patch-owned source files into a private workspace. With --prerequisites, it supplies context from earlier patches there. For supported static C functions, a private patch adjusts only hunk search positions; GNU patch must match the contents inside the named function. Classification leaves the original source and shipped patches unchanged. Successful classification removes that workspace; failure retains it and prints its path. Fixed module targets pass no prerequisite list.

  • pvs_gets.bash treats -f as a regular expression. -r appends .<field> to each PV name. -7 reads with pvget instead of caget. -c sets EPICS_CA_ADDR_LIST to the host’s Internet Protocol version 4 (IPv4) address and EPICS_CA_AUTO_ADDR_LIST to YES, or to NO together with -n.

  • update-release.bash update offers four choices for each module whose pin differs from the latest release tag or branch head: keep the pin (the default), take the latest, enter a value, or exit. Choice 4 removes configure/RELEASE.new and exits 0 without writing configure/RELEASE.

  • After the last module, update-release.bash update shows the difference and asks before it writes configure/RELEASE. It copies the replaced file to configure/RELEASE.bak.

  • verify_fix_build.bash needs configure/MODULESGEN.mk in the checkout that holds it. It writes configure/RELEASE.local and configure/CONFIG_SITE.local in both copies. It keeps module-build.log, ioc-build.log, and the empty marker file .started in the parent directory of <module_dir>. It writes .started before the builds and fails when a file under the installed tree changes after that point.

  • When the module dependencies of the checkout differ from those of the installed module, verify_fix_build.bash prints the difference and asks for confirmation on /dev/tty. Without a terminal, or on any answer other than y or Y, it exits 1.

  • tc32-expansion-query.cpp has no make target. From the repository top, these commands build it against the uldaq library of an installed tree:

    VENDOR=<installed_tree>/vendor
    g++ -c -I"${VENDOR}/include" tools/tc32-expansion-query.cpp
    g++ -o tc32-expansion-query tc32-expansion-query.o -L"${VENDOR}/lib" -Wl,-rpath,"${VENDOR}/lib" -luldaq
    

    <installed_tree> is the installed tree, as in the table above. The second command compiles the source, and the third links the program in the current directory. The -Wl,-rpath option records the vendor library directory in the program as its RUNPATH, so it finds libuldaq.so at run time. The program and tc32-expansion-query.o stay in the repository top, and Git does not ignore them.

  • prep-vendors.bash init empties ${HOME}/.vendor_temp_folder and writes configure/CONFIG_SITE.local. Vendor preparation writes that file in each vendor checkout. epics-env and epics-build rewrite configure/RELEASE.local with vendor paths and use the Network Time Protocol (NTP) default, time.google.com, from CONFIG_BASE.

  • Before replacing an existing local configuration, prep-vendors.bash asks for confirmation when stdin is a terminal. It proceeds automatically when stdin is not a terminal. Both modes preserve a byte-identical backup as <file>.bak.<YYYYMMDDTHHMMSSZ>, using UTC and a numeric suffix on collision. Earlier backups remain unchanged. Backup failure, refusal, or EOF preserves the original and stops the invocation with status 1 before later build steps.

  • The vendor builds use conf.rocky10 on Rocky 10, conf.rocky8 on Rocky 8, Red Hat Enterprise Linux (RHEL), CentOS, and Fedora, and conf elsewhere. all runs init, prep-vendors, and epics-env without a version argument.

Shell scripts in the scripts directory

ScriptUseEffectInstalled
setEpicsEnv.bashSourced as [<fallback_arch>] [disable]Resolves its tree and architecture before replacing known environment paths, then exports EPICS_PATH, EPICS_BASE, EPICS_MODULES, and EPICS_HOST_ARCH. Prepends modules/pmac/bin/<arch>, modules/pvxs/bin/<arch>, and base/bin/<arch> to PATH, and base/lib/<arch> to LD_LIBRARY_PATH. Returns 0 on success, 2 for invalid arguments, or 1 when tree or architecture resolution failsYes: install.base copies it to the top of the installed tree with mode 0644
resetEpicsEnv.bashSourcedRemoves known base, pvxs, pmac, and legacy extension executable entries and the base library entry, then unsets EPICS_PATH, EPICS_BASE, EPICS_HOST_ARCH, EPICS_MODULES, and EPICS_EXTENSIONS. Returns 0, including when base is absent or reset is repeatedYes: install.base copies it to the top of the installed tree with mode 0644
selectEpicsEnv.bashSourced, with optional arguments <epics_top> and <base_version>Sources <epics_top>/epics/<os_id>/<os_version>/<base_version>/setEpicsEnv.bash; <epics_top> defaults to ${HOME} and <base_version> to 7.0.4.1No
build_epics.bashExecuted with bash, with an optional argument <prefix>Writes configure/CONFIG_SITE.local with INSTALL_LOCATION:=<prefix>/epics, so INSTALL_LOCATION becomes <prefix>/epics. Runs init, patch, conf, build, install, and symlinks, and creates <prefix>/epics/R<base_version> with a copy of setEpicsEnv.bash and base and modules links. It then clones EPICS-env-support into the current directory and builds it against the installed baseNo
install_apps.bashExecuted with bash, with an optional argument <prefix>Installs PMD 7.22.0 into <prefix>/apps/pmd; on Rocky it also builds splint and installs ShellCheck under <prefix>/apps. Writes <prefix>/setEnv, which sources <prefix>/epics/R<base_version>/setEpicsEnv.bash from build_epics.bashNo

<prefix> defaults to /usr/local. <base_version> is an EPICS base version, such as 7.0.10; build_epics.bash and install_apps.bash take it from make print-PATH_NAME_EPICSVERS. <os_id> and <os_version> are the ID and VERSION_ID values in /etc/os-release. <arch> is the value of EPICS_HOST_ARCH. setEpicsEnv.bash takes EPICS_HOST_ARCH from EpicsHostArch.pl under base/startup or base/lib/perl, or from base/startup/EpicsHostArch. It uses its argument as the architecture only when none of these files exists or perl is missing. A discovery command failure returns 1 before replacement. The argument disable suppresses the summary without becoming the architecture. Setup and reset are safe under set -u, compare complete path fields, and preserve independently configured CA settings. Select among installed versions by directly sourcing each chosen tree’s setup script. The selector’s legacy path differs from make’s <install_location>/<release>/<os_id>-<os_version>/<base_version> layout.

User configuration files for make

make user.conf copies the two files in configure_user/ into ${HOME}/configure, keeping a backup of any file it replaces. No EPICS-env make file reads the copies in ${HOME}/configure.

FileContent
CONFIG_USERVARS_EXCLUDES entries that hide shell, terminal, desktop, and tool variables from the variable listing
RULES_USERThe variable printing targets vars, env, print-<VARIABLE>, PRINT.<VARIABLE>, ls.<VARIABLE>, tree.<VARIABLE>, and cat.<VARIABLE>, the exist target, and the defaults QUIET=@, LEVEL=2, FILTER=1, and LSOPTS="-lta"

Build version record in site-template

make src_version writes site-template/.versions, holding the time it runs and the EPICS-env commit, and installs that file at the top of the installed tree. make src_clean removes site-template/.versions.

Supported platforms and CI

EPICS-env builds the Experimental Physics and Industrial Control System (EPICS) base and modules on six Linux operating systems (OS). Each OS has one continuous integration (CI) workflow in .github/workflows/ that builds and checks the whole environment in a container. Two more workflows lint the Bash sources and publish this book. Every workflow runs on a GitHub Actions ubuntu-latest runner.

Supported operating systems and containers

The supported OS list is the set of OS workflows. README.md shows one status badge for each of them and one for the Linter Run workflow.

OSWorkflow fileWorkflow nameContainer image
Debian 12debian12.ymlDebian 12debian:bookworm-slim
Debian 13debian13.ymlDebian 13debian:trixie-slim
Rocky Linux 8rocky8.ymlRocky 8rockylinux/rockylinux:8
Rocky Linux 10rocky10.ymlRocky 10rockylinux/rockylinux:10
Ubuntu 24.04ubuntu24.ymlUbuntu 24.04ubuntu:24.04
Ubuntu 26.04ubuntu26.ymlUbuntu 26.04ubuntu:26.04

Package setup in the OS workflows

Each OS workflow prepares its container in the Install required packages step:

  • It installs git, make, sudo, bash, wget, and unzip with apt or dnf. The Rocky 8 workflow runs dnf update first and also installs tree.
  • It clones pkg_automation from https://github.com/jeonghanlee and runs pkg_automation.bash -y to install the build dependencies.
  • The Rocky 8 workflow then installs python3-pip and libusbx-devel, and installs numpy and nose with pip3.
  • The Ubuntu workflows set the time zone to America/Los_Angeles and export LC_CTYPE and LC_ALL as C.UTF-8.

Vendor library build in the OS workflows

Each OS workflow builds the uldaq and open62541 libraries before EPICS-env. The vendor directory is the vendor directory of the installed tree, the value of make print-INSTALL_LOCATION_EPICS followed by /vendor. The workflow first writes configure/RELEASE.local with VENDOR_ULDAQ_PATH set to that directory and OPEN62541_PATH set to a path relative to the opcua configuration. It then clones uldaq-env and open62541-env from https://github.com/jeonghanlee and installs both into the vendor directory.

WorkflowsClone depthBuild targets in each vendor repository
Debian 12, Debian 13, Ubuntu 24.04, Ubuntu 26.04Full historygithub
Rocky 8--depth 1init, conf.rocky8, build, and install
Rocky 10--depth 1init, conf.rocky10, build, and install

Both vendor repositories provide conf.rocky10 as an alias of the existing conf.rocky8 configuration recipe. Both names preserve the Red Hat linker settings and installation prefix. Rocky 10 prints each vendor checkout’s commit immediately after cloning it to identify the consumed source.

Build sequence in each OS workflow

The EPICS installation step runs these make targets in order. github.check runs init, patch, vars, conf, check.module-deps, build, and symlinks.

WorkflowsMake targets in order
Debian 12, Debian 13, Rocky 8github.check, install
Rocky 10init, patch, conf, check.module-deps, build, install, symlinks
Ubuntu 24.04, Ubuntu 26.04init, patch, conf, vars, check.module-deps, build, install, symlinks

All six OS workflows run the module dependency gate check.module-deps after patching and configuration and before compilation. A failing audit stops the build.

Final checks in each OS workflow

The EPICS Environment Check step runs the same three targets in every OS workflow, after the install:

  1. make exist prints the installed tree to depth 2.
  2. make check.env fails the job when the installed setEpicsEnv.bash adds a pvxs/bundle path to LD_LIBRARY_PATH, or when it cannot be inspected.
  3. make check.deps fails the job on any runpath finding in the installed executables and shared libraries.

Tools and scripts reference lists the exit status of each check.

Workflow triggers and path filters

A paths-ignore filter skips a push only when every changed file matches one of its patterns. A push that changes at least one other file runs the workflow. A pull request runs every workflow that has a pull_request trigger, with no path filter. The push triggers of the OS workflows and Linter Run have no branch or tag filter, so a tag push also runs them; GitHub Actions does not apply path filters to tag pushes.

WorkflowPush triggerPull request triggerManual run
Each OS workflowAny branch or tag; paths-ignore on branch pushes: **.md, docs/**, site-template/**, LICENSE, linter.yml, docs.yml, and the five other OS workflow filesPull requests to masterNo
Linter Run (linter.yml)Any branch or tag; paths-ignore on branch pushes: docs/** and ChangeLog.mdPull requests to masterNo
Deploy Docs (docs.yml)master only; paths: docs/src/**, docs/book.toml, and .github/workflows/docs.ymlNoneYes, through workflow_dispatch

The workflow file names in the OS filters are paths under .github/workflows/. An OS workflow does not ignore its own file, so a push that changes only one OS workflow file runs that OS workflow and Linter Run.

Linter Run workflow steps

The Linter Run workflow has one job, Lint Code Base:

  1. It checks out the full history with fetch-depth: 0 and without stored credentials.
  2. It runs super-linter/super-linter@v8.7.0 with VALIDATE_BASH set to true.

The workflow grants no permissions at the top level. The job receives contents: read, packages: read, and statuses: write.

Deploy Docs workflow steps

The Deploy Docs workflow builds this book with mdBook and publishes it to GitHub Pages. It runs in the concurrency group pages and does not cancel a run in progress. It holds contents: read, pages: write, and id-token: write.

JobContainerSteps
buildjeonghanlee/mdbookChecks out the repository, prints mdbook --version, runs mdbook build docs, registers the checkout as a Git safe directory, fails when git status fails or reports a change under docs/src after the build, sets up Pages with actions/configure-pages, and uploads docs/book as the Pages artifact
deployNoneRuns after build and deploys the artifact to the github-pages environment

Glossary of EPICS-env terms

This page defines each term the book uses with a specific meaning. The pages use each term only in the meaning given here.

Abbreviations used in this book

AbbreviationMeaning
ACFAccess security configuration file that an input/output controller (IOC) loads with asSetFilename
ASCIIAmerican Standard Code for Information Interchange
CAChannel Access, the EPICS network protocol that caget and caput use
CIContinuous integration: the GitHub Actions workflows under .github/workflows/
DBDDatabase definition file
ELFExecutable and Linkable Format, the Linux binary format that readelf reads
EPICSExperimental Physics and Industrial Control System
GCCGNU Compiler Collection
IOCInput/output controller: a process that serves EPICS records
IPv4Internet Protocol version 4
NTPNetwork Time Protocol
OSOperating system
PRPull request
PVProcess variable: a named record field that clients read and write
PVApvAccess, the EPICS network protocol that pvget and pvxget use
RHELRed Hat Enterprise Linux
RPATHRun-time search path entry in an ELF file; the link option --enable-new-dtags writes a RUNPATH entry instead
RUNPATHRun-time library search path entry in an ELF file, written when the link uses --enable-new-dtags
SHA-256Secure Hash Algorithm 256-bit
TCPTransmission Control Protocol

Build and installation terms

TermMeaning
$ORIGINThe directory of the loaded ELF file; a RUNPATH entry that starts with it is relative to that file
configuration typeThe <module>_CONF_TYPE value of a module: auto for a generated conf.<module> rule, custom for a hand-written one
declared dependenciesThe <module>_DEPS list: the build.<module> targets that build before the module
installed treeThe directory $(INSTALL_LOCATION)/<release>/<os_id>-<os_version>/<base_version>, which make print-INSTALL_LOCATION_EPICS prints
module keyThe upper-case name of a module’s SRC_* variables in configure/RELEASE, such as ASYN
module setThe EPICS modules that configure/RELEASE declares, each with a pin
MODULESGEN.mkThe generated file configure/MODULESGEN.mk that holds each module’s repository URL, install directory, and source directory
null.baseThe empty target that stands for EPICS base as the root of every declared dependency list
pinThe tag or commit, SRC_TAG_<module_key>, that a module source checks out
release tripleThe three variables SRC_NAME_<module_key>, SRC_TAG_<module_key>, and SRC_VER_<module_key> that declare one module
repository overrideA SRC_GITURL_<module_key> line in configure/CONFIG_MODS for a module hosted outside epics-modules
unversioned linkThe link modules/<module> that make symlinks creates to the versioned directory; modules/seq for the sequencer
vendor directoryThe directory vendor/ in the installed tree that holds the uldaq and open62541 libraries
versioned directoryThe install directory modules/<module>-<version> of one module; modules/seq-<version> for the sequencer

Verification gate terms

TermMeaning
gateThe strict form of a check that stops a build or a CI run on a finding
lost runpathA shared library in the installed tree that needs another library of the tree but has no $ORIGIN RUNPATH entry to find it
report formThe form of a check that prints its findings and exits 0
strict formThe form of a check that exits with a failure code on a finding

Patch carry terms

TermMeaning
carry patchA patch under patch/ that carries an upstream fix onto the pinned EPICS base or pvxs source
carry setEvery carry patch whose name starts with the pinned version of its source
fixed module patchA patch under patch/ with a fixed name for one module, applied by its own patch.<name>.apply target
version-anchored nameA carry patch name that starts with the source version, so a version bump drops the whole carry set

Common iocsh fragment terms

TermMeaning
common iocsh fragmentOne of the *.iocsh files that make install copies to modules/commonIocsh/iocsh in the installed tree
enable macroA fragment macro that defaults to #--, which comments out an optional part; an empty value enables that part
evidence directoryThe directory that verify_caputlog.py creates with --output; it must not exist beforehand
IOCSH_TOPThe IOC macro that names the installed modules/commonIocsh directory; an IOC loads a fragment as $(IOCSH_TOP)/iocsh/<fragment>.iocsh
serial configuration fileAn iocsh file that an IOC owns and that calls setSerialParams.iocsh once per serial port
soft IOCA prebuilt IOC program that loads records from a database file, such as softIoc of EPICS base or softIocPVX of the pvxs module