Skip to content

Add site extension framework - #190

Open
Monarda wants to merge 1 commit into
epics-base:masterfrom
ISISNeutronMuon:add_site_extensions
Open

Monarda wants to merge 1 commit into
epics-base:masterfrom
ISISNeutronMuon:add_site_extensions

Conversation

@Monarda

@Monarda Monarda commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

As always with this, I have no idea if I'm following the correct etiquette. This seems too big to spring out of the blue (and it started so much smaller...). Should I have started this as an issue? A discussion via email or mailing list? Do let me know how to do these better!

Site Extension Framework for PVXS IOC

Adds a lightweight plugin system that lets facilities drop .cpp files into a site/ directory and have them compiled automatically into libpvxsIoc. This allows customisation without modifying any upstream source files. As a demonstration of the API, the Q:time:tag / utag feature is refactored out of the core and reimplemented as a site extension.

The primary development goal of the new extension system was to minimise changes to existing PVXS code. Most changes in the ioc/ directory aren't directly due to the new site extension system, but instead due to the example of moving Q:time:tag into the new framework. Unfortunately, necessary changes to the build system make this less obvious.

The primary development motivation for this framework is to support different alarm messages (see PR #153 ). The proposed site extension framework allows this without needing any alteration of PVXS base or any mention of alarms at all!

Changes

1. Site extension framework

Affected files: +ioc/sitehooks.h, +ioc/sitehooks.cpp, ioc/iochooks.cpp, ioc/iocsource.cpp

A new pvxs::ioc::site namespace provides a (PVXS_IOC_API) API of three registration functions, declared in ioc/sitehooks.h. For each registration function, multiple callbacks may be added and fire in registration order.

Function Purpose
addInitHookAtBeginning(fn) Add a void() callback fired at initHookAtBeginning. Used to reset per-IOC state (e.g. during testing) and to scan static database configuration such as info fields.
addInitHookAfterIocBuilt(fn) Add a void() callback fired at initHookAfterIocBuilt. Used to read field values or follow links that are only valid after full initialisation.
addNodePostProcessor(fn) Add a void(dbCommon*, Value&) callback fired at the end of every IOCSource::get() after all standard fields have been populated. Allows fields to be modified before a PVA response is sent to a client.

Adding a .cpp file to the new site/ directory automatically adds it as an extension into the IOC. site::registerHooks() is added to pvxsBaseRegistrar() in ioc/iochooks.cpp. It calls registerSiteExtensions() in site/siteregister.cpp, which is generated at build time by site/gen_siteregister.py from the list of site/*.cpp files (scanned by the Makefile or setup.py). For each file matching the naming convention (e.g. foo.cpp -> registerFoo()), a call to its registrar function is added to registerSiteExtensions().

ioc/iocsource.cpp calls site::postProcessNode() at the end of IOCSource::get(), giving registered post-processors the opportunity to read or overwrite any field in the PVA response node.

Build system and packaging

Affected files: Makefile, setup.py, MANIFEST.in, ioc/Makefile, site/Makefile, site/gen_siteregister.py

In the top-level project Makefile, site/ is added as a build directory before ioc/ so that gen_siteregister.py produces site/siteregister.cpp before the ioc/ build needs it. site/test/ is added as a test directory after ioc/.

The Python/pip build path (setup.py) is kept in sync with the Makefile changes. It invokes gen_siteregister.py at build time through a new generate_siteregister() call. ioc/sitehooks.cpp is added to the shared library sources, and globs site/*.cpp into the same list.

The ioc/Makefile is altered so that site/*.cpp files are compiled into libpvxsIoc.

MANIFEST.in is extended to include site/*.cpp, site/*.h, and site/gen_siteregister.py in the Python source distribution tarball.

Test organisation

Affected files: site/test/Makefile

Tests for site extensions live in site/test/, separate from the core test/ directory. The site/test/Makefile follows a naming convention to run test binaries automatically: for each <name>.cpp in site/, if a matching test<name>.cpp exists in site/test/, a test<name> binary is built from those two files plus the standard IOC driver registration stub. If a matching test<name>.db file also exists it is added to TESTFILES automatically. Extensions that need extra source files, extra libraries, or a non-standard binary name must be added manually below the auto-discovery block.

Each test binary links against pvxsIoc and therefore exercises the extension through the full hook registration path, the same as a real IOC. Tests use TestIOC and dbUnitTest.h, so they require EPICS Base >= 3.15; the site/test/ build is skipped on older bases via a BASE_3_15 guard.

2. Q:time:tag moved from core to site extension

Affected files: ioc/fieldconfig.h, ioc/typeutils.cpp, ioc/field.cpp, ioc/singlesrcsubscriptionctx.h, ioc/iocsource.cpp, site/timetag.cpp

The nsecMask field and updateNsecMask() method have been removed from MappingInfo. The Q:time:tag / nsec:lsb:N feature is now implemented in site/timetag.cpp using addNodePostProcessor, which has access to the same record pointer and PVA node.

This keeps the core IOCSource::get() path free of facility-specific logic and demonstrates that the post-processor hook is sufficient to modify PVA fields.

Test suite cleanup

Affected files: test/testqsingle.cpp, test/testqsingle.db

The test:nsec record and its associated Q:time:tag assertions have been removed from testqsingle, since that feature now lives in site/timetag.cpp and is tested in site/test/testtimetag.cpp.

3. Other minor changes

codespell.dic adds statics to the spell-check whitelist, due to a comment in sitehooks.cpp.

Site directory

The site/ directory includes the timetag extension but should work with no .cpp files present. Facilities may add their own .cpp files alongside or instead of timetag.cpp. A README.md describes the API and gives some guidance on how to add a new extension.

Comments and Questions

I've used the pvxs::ioc::site namespace to make it the join between the base and extension code as clear as possible.

It would be possible to update pvxinfo -D to include information about the extensions added (using an addition to the existing gen_siteregister.py automated generation step) so that it's possible to see that site extensions might be the source of reported issues.

Should sitehooks.cpp be using PVXS_EXPERT_API_ENABLED?

@mdavidsaver

Copy link
Copy Markdown
Member

This seems too big to spring out of the blue ...

A bit :)

A discussion via email or mailing list?

Either. The place to start is why? What are you trying to accomplish? Why your specific proposed change? And why are the existing plugin mechanisms in epics-base insufficient?

@mdavidsaver

Copy link
Copy Markdown
Member

Also, to give fair warning. I have strong views on the advisability of minimal and well defined interfaces. The idea of making the source tree of PVXS a plugin interface really does not appeal to me. This particular approach looks likely to be a very hard sell.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants