The require module¶
Provides a method to start a soft IOC.
Provides a mechanism to dynamically load shared libraries for use within an IOC.
Provides a build process (more on this in Building and installing).
Note
Each of the above-mentioned features are linked to each other; the dynamic loading depends on the build process, and it also depends on how the soft IOC has been started.
IOC startup¶
IOC startup is run from the script iocsh
, which is installed in
${E3_REQUIRE_LOCATION}/bin/iocsh
. This script generates a temporary
startup script which is passed to softIocPVA
from EPICS base. This temporary
startup script:
Sets some environment variables, e.g.
${IOCSH_TOP}
and${REQUIRE_IOC}
Loads require
Initialises PVs to track which modules and versions are loaded
Note
At ESS, we have decided to only use EPICS base 7, and thus we only make use of
softIocPVA
(and not softIoc
).
There are number of option flags and arguments that iocsh
accepts, the
most common being:
iocsh st.cmd
—Run the commands inst.cmd
.iocsh -r module[,version]
—Load the given module/version upon startup. Equivalent to including the linerequire module[,version]
in your startup script.iocsh -c 'some command'
—Executes the commandsome command
in the IOC shell.
For a comprehensive list of flags and arguments, use iocsh -h
.
Note
If the command iocInit
is not explicitly called in st.cmd
, it will be
implicitly called by the end of the process (after running the contents of
st.cmd
).
Warning
The file st.cmd
above must have a newline at the end of the
file—otherwise the last line will be ignored.
There are also gdb and valgrind options if you would like to run an IOC with either of those utilities. More information can be found at Debugging e3.
Dynamic loading of modules¶
This is the most obviously visible part of require from the perspective of an
IOC developer; one must include the line require $MODULE[,$MODULE_VERSION]
in
the startup script (st.cmd
) in order to load a module in e3. If a version is
specified, require will try to load that version. If you leave version
blank, it will load the version with the highest numerical version available,
else the first test version it finds.
Versioning¶
Numerical versions¶
Versioning of modules mostly follows the semantic versioning (semver) scheme. A numerical version is specified in one of two ways:
MAJOR.MINOR.PATCH (e.g.
require asyn,4.41.0
)MAJOR.MINOR.PATCH+REVISION (e.g.
require sis8300llrf,3.17.1+1
)
If you do not specify a REVISION number, then require will load the version with the highest revision number. Otherwise, require will match the version exactly.
Note that 1.0.0 < 1.0.0+0 < 1.0.0+1 < ... < 1.0.1 < ...
.
Note
The syntax for revision numbers changed between require 3.3.0 and require 3.4.0.
Initially the separator was a -
, but in order to be more consistent with
semantic versioning, the e3 team decided to change it to
a +
.
Test versions¶
A test version is any version that does not conform to the above pattern. So
simonrose
is a test version, but so is 1.0.0-test
or even 1.0
.
Tip
As require will load the first test version it finds when there are no numeric versions, it is best practice to specify the exact version you would like to load when working with test versions.
Dependency resolution¶
If one module depends on another one, both of these will be loaded. For example, StreamDevice depends on asyn, so loading StreamDevice will automatically load asyn as well. Dependencies are version-specific; StreamDevice 2.8.22 in its current incarnation has been built against asyn 4.42.0—if you load that version of StreamDevice then it will try to load specifically version 4.42.0 of asyn, and if it cannot find that version, or if another version of asyn has already been loaded, then the IOC will exit with an error.
These dependencies are generated at build time and are stored in
$(module)/$(version)/lib/$(T_A)/$(module).dep
. For example, the dependencies
for StreamDevice 2.8.22 are (directly from the aforementioned file):
# Generated file. Do not edit.
asyn 4.42.0+0
calc 3.7.4+1
pcre 8.44.0+0
The reader should be aware that require is limited in the degree to which it can perform dependency resolution; all it can do is a simple check against existing loaded versions. This is why revision numbers are necessary. As an example, consider the following scenario.
Example scenario¶
The module sis8300llrf version 3.16.1 depends on the module scaling, and has been built against version 1.7.0. We update scaling to version 1.7.1. There is no new version of sis8300llrf, but an IOC integrator would like to use the new version of scaling. What should happen to the existing installed version of sis8300llrf?
We could uninstall it and rebuild/install it against the new version of scaling. However, this prevents anyone who needs that version combination for any reason from being able to use it. In general, we want to avoid removing any installed modules—we should only add new versions.
We could try to update the version of sis8300llrf to 3.16.2 despite the fact that no changes have been made. If this is an ESS module, then this is possible, but not ideal. It is particularly bad if it is a module that is not being developed in-house, as our version will be out of sync with the community module.
We could instead update the version to 3.16.1+1, i.e. add a revision number. This way, the existing version has not been modified. Moreover, you can use sis8300llrf version 3.16.1 with either version of scaling by specifying the revision number.